SWD协议底层实现完全详解

一、SWD协议基础回顾

SWD物理接口

cpp 复制代码
两根线:
1. SWCLK: 时钟线,主机驱动,上升沿采样
2. SWDIO: 双向数据线,半双工通信

SWD传输帧结构

cpp 复制代码
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│Start│APnDP│ RnW │ A2  │ A3  │Parity│Stop │Park │Trn  │ACK  │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
   0     1     2     3     4     5     6     7   8-9  10-12

二、底层宏定义详解

1. 时钟周期生成宏

cpp 复制代码
#define SW_CLOCK_CYCLE()                \
  PIN_SWCLK_CLR();                      \  // 下降沿
  PIN_DELAY();                          \  // 延迟
  PIN_SWCLK_SET();                      \  // 上升沿
  PIN_DELAY()                           // 延迟

时序图:

cpp 复制代码
SWCLK:  ______/¯¯¯¯¯¯¯¯\______
        下降沿   上升沿
        采样点    切换点

关键点:

  • SWD在上升沿采样数据

  • 下降沿到上升沿之间是稳定时间

  • 上升沿到下降沿之间是传播时间

2. 位写入宏

cpp 复制代码
#define SW_WRITE_BIT(bit)               \
  PIN_SWDIO_OUT(bit);                   \  // 设置数据
  PIN_SWCLK_CLR();                      \  // 下降沿
  PIN_DELAY();                          \  // 稳定时间
  PIN_SWCLK_SET();                      \  // 上升沿(目标采样)
  PIN_DELAY()                           // 保持时间

工作流程:

  1. 在时钟下降沿之后设置数据

  2. 在下一个上升沿之前确保数据稳定

  3. 目标在上升沿采样数据

3. 位读取宏

cpp 复制代码
#define SW_READ_BIT(bit)                \
  PIN_SWCLK_CLR();                      \  // 下降沿
  PIN_DELAY();                          \  // 建立时间
  bit = PIN_SWDIO_IN();                 \  // 采样数据
  PIN_SWCLK_SET();                      \  // 上升沿
  PIN_DELAY()                           // 保持时间

关键时序:

cpp 复制代码
目标驱动SWDIO → 主机下降沿 → 等待建立时间 → 主机采样 → 主机上升沿

三、SWJ_Sequence函数详解

函数功能:发送任意位序列

cpp 复制代码
void SWJ_Sequence(uint32_t count, const uint8_t *data)

使用场景:

  1. SWD线复位(51个1)

  2. JTAG到SWD切换序列(0xE79E)

  3. 自定义测试序列

LSB优先发送实现

cpp 复制代码
while (count--) {
  if (n == 0U) {           // 当前字节已发完
    val = *data++;         // 读取新字节
    n = 8U;                // 重置位计数器
  }
  if (val & 1U) {          // 检查LSB
    PIN_SWDIO_TMS_SET();   // 发送1
  } else {
    PIN_SWDIO_TMS_CLR();   // 发送0
  }
  SW_CLOCK_CYCLE();        // 产生时钟
  val >>= 1;               // 右移,准备下一位
  n--;                     // 位计数器减1
}

位顺序示例(发送字节0xA5=10100101b):

cpp 复制代码
原始字节: 0xA5 = 1 0 1 0 0 1 0 1
           MSB←        →LSB
LSB优先发送顺序: 1 0 1 0 0 1 0 1
实际发送: 1(bit0)→0(bit1)→1(bit2)→0(bit3)→0(bit4)→1(bit5)→0(bit6)→1(bit7)

四、SWD_TransferFunction核心实现

这是整个SWD协议的核心,让我们逐段详细分析。

1. 函数定义和参数

cpp 复制代码
static uint8_t SWD_Transfer##speed (uint32_t request, uint32_t *data)
  • request: 8位请求值,包含APnDP、RnW、A2、A3

  • data: 指向32位数据的指针

    • 写操作:提供要写入的数据

    • 读操作:接收读取的数据

  • 返回值: 3位ACK响应

2. 请求包发送(8位)

位0:起始位(Start Bit)
cpp 复制代码
SW_WRITE_BIT(1U);  // 固定为1
  • 每个SWD传输都以逻辑1开始

  • 用于同步

位1:APnDP位
cpp 复制代码
bit = request >> 0;  // 从request参数提取bit0
SW_WRITE_BIT(bit);
parity += bit;
  • 0:访问DP(调试端口)

  • 1:访问AP(访问端口)

位2:RnW位
cpp 复制代码
bit = request >> 1;  // 提取bit1
SW_WRITE_BIT(bit);
parity += bit;
  • 0:写操作

  • 1:读操作

位3-4:地址位A2、A3
cpp 复制代码
bit = request >> 2;  // A2
SW_WRITE_BIT(bit);
parity += bit;

bit = request >> 3;  // A3
SW_WRITE_BIT(bit);
parity += bit;
  • 组合指定寄存器地址:

    • A3=0,A2=0 → 地址0x0 (DP-IDCODE/ABORT)

    • A3=0,A2=1 → 地址0x4 (DP-CTRL/STAT)

    • A3=1,A2=0 → 地址0x8 (DP-SELECT)

    • A3=1,A2=1 → 地址0xC (DP-RDBUFF)

位5:奇偶校验位
cpp 复制代码
SW_WRITE_BIT(parity);
  • 计算前5位的奇偶性

  • 奇校验:1的个数为奇数时校验位=1

  • 用于检测传输错误

位6-7:停止位和空闲位
cpp 复制代码
SW_WRITE_BIT(0U);  // 停止位(固定0)
SW_WRITE_BIT(1U);  // 停车位(固定1)
  • 停止位:表示请求包结束

  • 停车位:主机释放总线前的最后一个1

3. 方向切换(Turnaround)

cpp 复制代码
PIN_SWDIO_OUT_DISABLE();  // 释放SWDIO,改为输入
for (n = DAP_Data.swd_conf.turnaround; n; n--) {
  SW_CLOCK_CYCLE();
}

关键点:

  • SWDIO从输出模式 切换到输入模式

  • 需要至少1个时钟周期的方向切换时间

  • 避免总线冲突

时序:

cpp 复制代码
主机: SWDIO输出 → 释放总线 → SWDIO输入
目标: 准备驱动ACK响应

4. ACK响应接收(3位)

cpp 复制代码
SW_READ_BIT(bit);
ack  = bit << 0;  // ACK[0]
SW_READ_BIT(bit);
ack |= bit << 1;  // ACK[1]
SW_READ_BIT(bit);
ack |= bit << 2;  // ACK[2]

ACK响应码:

cpp 复制代码
0b001 (1): OK - 传输成功
0b010 (2): WAIT - 重试
0b100 (4): FAULT - 错误
0b000 (0): 无响应(超时)
0b111 (7): 协议错误

5. 数据阶段(32位数据 + 1位奇偶)

情况A:读操作(ACK=OK)
cpp 复制代码
if (ack == DAP_TRANSFER_OK) {
  if (request & DAP_TRANSFER_RnW) {  // 读操作
    val = 0U;
    parity = 0U;
    for (n = 32U; n; n--) {  // 读取32位数据
      SW_READ_BIT(bit);
      parity += bit;
      val >>= 1;
      val  |= bit << 31;  // LSB先接收,需要移位
    }
    SW_READ_BIT(bit);  // 读取奇偶位
    if ((parity ^ bit) & 1U) {  // 奇偶校验
      ack = DAP_TRANSFER_ERROR;
    }
    if (data) { *data = val; }  // 存储读取的数据

读取32位数据的技巧:

  • LSB先接收,但需要存储为MSB在前的格式

  • 每次接收1位,右移并放入最高位

  • 最终得到正确的32位值

示例:读取0x12345678

cpp 复制代码
接收顺序: LSB先 → 0,0,0,1,0,1,1,1,0,1,1,1,0,1,0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0,1,0
实际值: 0x12345678
情况B:写操作(ACK=OK)
cpp 复制代码
} else {  // 写操作
  // 先完成方向切换
  for (n = DAP_Data.swd_conf.turnaround; n; n--) {
    SW_CLOCK_CYCLE();
  }
  PIN_SWDIO_OUT_ENABLE();  // 切换回输出模式
  
  val = *data;  // 获取要写入的数据
  parity = 0U;
  for (n = 32U; n; n--) {  // 写入32位数据
    SW_WRITE_BIT(val);  // 写入LSB
    parity += val;
    val >>= 1;  // 准备下一位
  }
  SW_WRITE_BIT(parity);  // 写入奇偶位
}

写数据的关键:

  1. 方向切换:输入→输出

  2. LSB先发送

  3. 计算并发送奇偶校验位

6. 错误处理(WAIT/FAULT响应)

WAIT响应处理(目标忙)
cpp 复制代码
if (ack == DAP_TRANSFER_WAIT) {
  if (DAP_Data.swd_conf.data_phase && (request & DAP_TRANSFER_RnW)) {
    // 如果是读操作,需要跳过数据阶段
    for (n = 32U+1U; n; n--) {
      SW_CLOCK_CYCLE();  // 产生时钟但不采样
    }
  }
  // 方向切换
  for (n = DAP_Data.swd_conf.turnaround; n; n--) {
    SW_CLOCK_CYCLE();
  }
  PIN_SWDIO_OUT_ENABLE();
  
  if (DAP_Data.swd_conf.data_phase && !(request & DAP_TRANSFER_RnW)) {
    // 如果是写操作,需要跳过数据阶段
    PIN_SWDIO_OUT(0U);
    for (n = 32U+1U; n; n--) {
      SW_CLOCK_CYCLE();  // 产生时钟但不驱动数据
    }
  }
  PIN_SWDIO_OUT(1U);
  return ((uint8_t)ack);
}

WAIT响应的意义:

  • 目标暂时无法处理请求

  • 主机需要稍后重试

  • 必须正确跳过数据阶段,保持协议同步

7. 协议错误恢复

cpp 复制代码
// 协议错误(无效ACK)
for (n = DAP_Data.swd_conf.turnaround + 32U + 1U; n; n--) {
  SW_CLOCK_CYCLE();  // 产生足够时钟跳过剩余传输
}
PIN_SWDIO_OUT_ENABLE();
PIN_SWDIO_OUT(1U);
return ((uint8_t)ack);

协议错误场景:

  • ACK=0b000(无响应)

  • ACK=0b111(协议错误)

  • ACK=0b011等(保留值)


五、快速和慢速模式实现

1. 快速模式(Fast)

cpp 复制代码
#undef  PIN_DELAY
#define PIN_DELAY() PIN_DELAY_FAST()
SWD_TransferFunction(Fast)
  • 使用最小延迟

  • 用于高时钟频率(>1MHz)

  • 依赖硬件GPIO速度

2. 慢速模式(Slow)

cpp 复制代码
#undef  PIN_DELAY
#define PIN_DELAY() PIN_DELAY_SLOW(DAP_Data.clock_delay)
SWD_TransferFunction(Slow)
  • 可配置延迟

  • 用于低时钟频率或长线缆

  • 更稳定的时序

3. 模式选择

cpp 复制代码
uint8_t SWD_Transfer(uint32_t request, uint32_t *data) {
  if (DAP_Data.fast_clock) {
    return SWD_TransferFast(request, data);
  } else {
    return SWD_TransferSlow(request, data);
  }
}

切换条件:

  • 初始化阶段使用慢速模式

  • 识别目标后可能切换到快速模式

  • 连接不稳定时切回慢速模式


六、完整SWD传输时序图

读操作成功时序

cpp 复制代码
时钟周期   操作                    SWDIO状态
--------------------------------------------------
0-7        发送请求包              主机驱动
           [1][APnDP][RnW=1][A2][A3][P][0][1]
8-9        方向切换                高阻态
10-12      接收ACK响应             目标驱动
           [ACK0][ACK1][ACK2]=001(OK)
13-44      接收32位数据            目标驱动
           LSB先,每时钟1位
45        接收奇偶位              目标驱动
46-47      方向切换                高阻态
48-?       空闲周期                主机驱动高电平

写操作成功时序

cpp 复制代码
时钟周期   操作                    SWDIO状态
--------------------------------------------------
0-7        发送请求包              主机驱动
           [1][APnDP][RnW=0][A2][A3][P][0][1]
8-9        方向切换                高阻态
10-12      接收ACK响应             目标驱动
           [ACK0][ACK1][ACK2]=001(OK)
13-14      方向切换                高阻态
15-46      发送32位数据            主机驱动
           LSB先,每时钟1位
47        发送奇偶位              主机驱动
48-?       空闲周期                主机驱动高电平

七、关键配置参数

1. 时钟延迟(clock_delay)

cpp 复制代码
DAP_Data.clock_delay
  • 控制每个时钟边沿的延迟

  • 值越大,时钟频率越低

  • 计算公式:频率 = 1 / (2 × 延迟 × 时钟周期时间)

2. 方向切换周期(turnaround)

cpp 复制代码
DAP_Data.swd_conf.turnaround
  • 通常为1-2个时钟周期

  • 确保总线方向切换稳定

  • 长线缆需要更多周期

3. 空闲周期(idle_cycles)

cpp 复制代码
DAP_Data.transfer.idle_cycles
  • 传输间的空闲时钟数

  • 给目标芯片处理时间

  • 通常0-255个周期

4. 数据相位(data_phase)

cpp 复制代码
DAP_Data.swd_conf.data_phase
  • 控制WAIT/FAULT时是否处理数据阶段

  • 0:忽略数据阶段

  • 1:必须发送/接收数据阶段


八、实际传输示例分析

示例1:读取DP IDCODE(地址0x0)

请求字节计算:

cpp 复制代码
More=0, RnW=1(读), APnDP=0(DP), A2=0, A3=0
请求字节 = 0b00000101 = 0x05

调用:

cpp 复制代码
uint32_t idcode;
uint8_t ack = SWD_Transfer(0x05, &idcode);

传输过程:

  1. 发送请求包:1 0 1 0 0 0 0 1

  2. 接收ACK:0 0 1(OK)

  3. 接收32位IDCODE:LSB先

  4. 接收奇偶位

  5. 返回ACK=OK,idcode=0x1BA01477

示例2:写AP CSW寄存器(地址0x0)

请求字节计算:

cpp 复制代码
More=0, RnW=0(写), APnDP=1(AP), A2=0, A3=0
请求字节 = 0b00000010 = 0x02

**数据:**​ 0x23000012

调用:

cpp 复制代码
uint32_t data = 0x23000012;
uint8_t ack = SWD_Transfer(0x02, &data);

九、性能优化技巧

1. 批量传输优化

cpp 复制代码
// 批量读取时减少方向切换
for (i = 0; i < count; i++) {
  SWD_Transfer(read_request, &data[i]);
  // 只有最后一次需要完整的方向切换
}

2. 延迟调优

cpp 复制代码
// 自适应延迟调整
if (error_count > threshold) {
  DAP_Data.clock_delay++;  // 增加延迟,提高稳定性
} else if (stable_count > threshold2) {
  DAP_Data.clock_delay--;  // 减少延迟,提高速度
}

3. 预取优化

cpp 复制代码
// 提前发送下一个读请求
SWD_Transfer(read_req1, NULL);  // 延迟读
// 处理其他任务...
SWD_Transfer(read_req2, &data1);  // 获取数据1并发起新请求

十、调试和问题排查

1. 常见问题:无响应(ACK=0)

可能原因:

  • 目标未上电

  • SWD线路连接错误

  • 时钟频率过高

  • 目标处于复位状态

排查步骤:

  1. 检查电源和地线

  2. 降低时钟频率

  3. 发送SWD复位序列(51个1)

  4. 检查nRESET引脚状态

2. 常见问题:WAIT响应

可能原因:

  • 目标处理器忙

  • 访问被保护区域

  • 时钟不匹配

解决方法:

  1. 增加重试次数

  2. 检查目标状态

  3. 调整时钟延迟

3. 常见问题:奇偶校验错误

可能原因:

  • 噪声干扰

  • 时序不稳定

  • 目标驱动能力不足

解决方法:

  1. 增加方向切换周期

  2. 降低时钟频率

  3. 检查PCB布线


十一、SWD协议状态机

主机状态机

cpp 复制代码
IDLE → 发送请求 → 方向切换 → 接收ACK → 
  ↓           ↓           ↓
  OK? → 数据阶段 → 方向切换 → IDLE
  ↓           ↓
 WAIT? → 重试 → 发送请求
  ↓
FAULT? → 错误处理 → IDLE

目标状态机

cpp 复制代码
IDLE → 接收请求 → 解析 → 处理 → 
  ↓           ↓           ↓
  OK? → 准备数据 → 发送ACK → 数据阶段
  ↓           ↓
 忙? → 发送WAIT → 等待
  ↓
错误? → 发送FAULT → IDLE

总结

这个SWD_Transfer实现展示了:

  1. 精确的比特级控制:每个时钟周期都精确控制

  2. 完整的协议处理:请求、响应、数据、错误处理

  3. 灵活的配置:支持不同速度和时序要求

  4. 高效的实现:使用宏和内联优化性能

理解这个实现对于:

  • 开发自定义调试工具

  • 调试SWD连接问题

  • 优化调试性能

  • 移植到新硬件平台

都至关重要。这是现代ARM调试生态系统的基石之一。

相关推荐
夜月yeyue2 小时前
Linux 文件设备类型分析
linux·运维·网络·单片机
somi72 小时前
51单片机-03-串行通信
单片机·嵌入式硬件·51单片机
国科安芯2 小时前
星载电源遥测模块抗辐照RISC-V MCU的性能适配与应用
单片机·嵌入式硬件·无人机·cocos2d·risc-v
F133168929572 小时前
33V降5V,25V降12V,12A,WD5030A
人工智能·单片机·嵌入式硬件·汽车·智能家居
羽获飞3 小时前
从零开始学嵌入式之STM32——26.STM32的通用定时器-生成PWM方波
stm32·单片机·嵌入式硬件
12.=0.3 小时前
【stm32_1】集成开发环境的搭建 + KEIL5使用STM32标准固件库源码建立M4工程模板
stm32·单片机·嵌入式硬件
我不是程序猿儿4 小时前
【嵌入式】中断(Interrupt)和外部中断(External Interrupt)
单片机·嵌入式硬件
姓刘的哦4 小时前
RK3568之pinctrl子系统和GPIO子系统
单片机·嵌入式硬件
我不是程序猿儿4 小时前
【嵌入式】MCU常用外设模块介绍
stm32·单片机·嵌入式硬件