ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入SWD协议:从寄存器读写到Could not stop报错排查

深入SWD协议:从寄存器读写到Could not stop报错排查 以前一看到Could not stop Cortex-M device! Please check the JTAG cable.这种报错十有八九的人第一反应就是换线、重插、重启。我真正把这行报错彻底搞明白是在连续被同一个目标板折磨了两天之后——换过三根杜邦线、换过两个调试器、把复位按键都快按烂了问题原封不动。后来冷静下来拿起逻辑分析仪夹在 SWDIO 和 SWCLK 两根线上才看到真相协议层面根本没建立起来主机发的请求目标芯片压根没有 ACK。那时候我才意识到搞 ARM Cortex-M 调试光会点“Play”按钮是不够的SWD 协议才是所有调试动作的地基。这篇文章就是要把这块地基讲透。我用 SWD 协议读写过大量寄存器也抓过不下几十次波形这里把从协议帧结构、寄存器访问链路、波形分析到Could not stop这类经典报错排查的完整套路都整理出来。适合刚被 Keil 调试器“教育”过的新手也适合已经会用调试器、但对底层机理还一知半解的嵌入式工程师。看完你会明白调试器能读寄存器、能改寄存器靠的到底是怎样的一套“暗号”。1. 从“设备停不下来”的排查说起报错信息到底在说什么1.1 为什么报错里明明写着 JTAG却和 SWD 有关系很多同学第一次遇到Could not stop Cortex-M device! Please check the JTAG cable.这句报错会下意识认为是 JTAG 调试线松了。实际上这句话里的“JTAG cable”是历史遗留叫法现在的 ARM Cortex-M 调试器绝大多数走的是 SWD 协议只是提示语沿用了老传统。真正的问题是调试器发出 halt 请求后目标 Cortex-M 核没有在预期时间内响应调试器只能放弃并报错。这个“没有响应”背后可能的原因从物理层到协议层都有。我那次排查一开始也是按老套路来先量目标板供电3.3V 正常再量复位脚高电平正常把 SWCLK 从 4MHz 降到了 100kHz依然失败换了 ST-Link、J-Link现象不变。最后用逻辑分析仪抓 SWDIO 线上的实际波形才看到主机确实发了请求帧但 ACK 阶段总线完全是一片平静——目标芯片根本没参与对话。1.2 我的排查链路按 OSI 的思路拆 SWD 连接嵌入式调试连接其实可以模仿网络排查的思路分层来看。我从下往上梳理了一遍物理层SWDIO、SWCLK、GND 三根线有没有接对调试器的 VTREF 有没有连到目标板的 3.3V 电源上地线是不是只靠杜邦线里的那一根回路阻抗高不高电气层信号电平匹配吗目标板是 1.8V 还是 3.3V如果调试器不认识参考电压IO 电平就可能错位。时钟与复位目标芯片的复位脚有没有被外部电路强行拉低有些板子复位脚上接了电容上电瞬间复位时间过长调试器在复位后立刻去 halt目标还没来得及启动就会失败。协议层SWCLK 连续翻转时SWDIO 上能不能看到有效的请求帧目标是否回复了 ACK如果请求没发出去或者 ACK 一直不来那就是协议层问题。应用层目标内核是不是已经进入了低功耗模式或者 Option Bytes 里把调试端口关掉了这五层里绝大多数人会卡在第二、三层就放弃然后归咎于“板子坏了”。但我自己的经验是先把逻辑分析仪架起来看协议层往往能更快定位问题。1.3 为什么“读寄存器”是排查的第一步其实排查 SWD 连接是否正常有一个很高效的动作读 DPIDRDebug Port IDCODE Register。这是 SWD 协议里最基础、也最关键的读操作。主机上电后只要发送读 DPIDR 的请求目标芯片就会把厂商 ID、内核版本等信息回传回来。如果 IDCODE 能读出来就说明物理层、电气层、协议层的链路基本通了后面不管是 halt 还是读写寄存器都有基础。如果 IDCODE 都读不出来就没必要继续纠结Could not stop先把链路修好再说。这也是为什么我强烈建议每个搞 Cortex-M 开发的人都学会手动发一条读 IDCODE 的 SWD 命令。2. SWD 协议核心两根线如何完成寄存器读写2.1 为什么是 SWDIO SWCLK 两根线SWD 全称 Serial Wire Debug是 ARM 为 Cortex-M 系列设计的两线调试接口。对比传统 JTAG 最少需要 TMS、TCK、TDI、TDO 四根信号线SWD 把双向数据合并到一根 SWDIO 上再加上时钟 SWCLK 就够用了。再加上 GND、VTREF、nRESET一个完整的最小调试接口只需要 5 个引脚。这里有个很容易忽略的点SWDIO 是半双工。同一根线上主机发送请求帧时是主机在驱动目标回复 ACK 和返回数据时是目标在驱动两者必须严格分时不能同时输出否则就是总线冲突。所以协议里规定了一个“切换时间”窗口主机发完请求帧后要主动释放 SWDIO把它变成高阻输入等待目标接管总线。很多自制调试器或者杜邦线连接不稳的问题本质就是切换时刻不对、总线竞争导致波形乱掉。2.2 请求帧、ACK、数据一个完整事务的三段结构一条完整的 SWD 事务在链路层上分为三个阶段主机请求阶段Host Request主机在 SWCLK 驱动下向 SWDIO 上按位发送 8 bit 的请求包。这 8 bit 依次是位含义说明起始位 Start1表示一次传输开始通常表现为总线由空闲态跳到低电平的下降沿APnDP0/10 表示访问 DP1 表示访问 APRnW0/10 表示写1 表示读A[2:0]地址位DP 或 AP 寄存器地址的低 3 位奇偶校验 Parity计算对前面 6 个数据位做奇偶校验停止位 Stop0传输结束标志Park 位1保证总线停在确定状态比如读取 DPIDR 时请求包大致是0xA5或者0x85这样的形态具体值取决于上面几个标志位的组合。知道这个结构比死记硬背字节值重要得多。目标响应阶段ACK目标芯片收到请求后会在接下来的 3 个时钟周期里驱动 SWDIO回复 3 bit 的 ACK 信号。最常见的值是001表示OK。如果回复010表示目标忙回复100表示协议错误。如果这 3 个 bit 全是高电平说明目标根本没理你总线没人驱动那就是悬空态。数据传输阶段Data Transfer根据是读还是写方向不同。读操作时目标在 ACK 之后立刻发送 32 bit 数据按 LSB-first 顺序数据后再跟 1 bit 奇偶校验。写操作时主机在 ACK 后接管总线发送 32 bit 数据加 1 bit 校验。这里同样存在切换窗口方向上必须是“发送方先释放总线等待一个 turn-around 周期再由接收方接管”。2.3 DP 与 AP调试端口的“两级寻址”模型SWD 协议里有两个核心概念DPDebug Port和 APAccess Port。打个比方DP 是“总机”AP 是“分机”。你要访问芯片内部的存储器和寄存器不能直接跨过 DP必须一级一级来。DP 寄存器负责调试端口本身的管理。常用的有DPIDRID 寄存器读它能拿到芯片身份信息。CTRL/STAT控制与状态寄存器控制是否 halt、是否上电调试域、清除错误标志等。SELECT选择当前要访问哪个 AP、哪个 AP Bank。RDBUFF读缓冲用于连续读操作时避免额外等待。ABORT放弃当前操作、清错误标志。AP 寄存器AP 才是真正通往“存储器系统”的大门。比如 ARM 的 AHB-APAccess Port for AHB提供了对内存映射地址的访问能力而 AP 的寄存器地址是挂在 DP 下面的。要访问 AP 的寄存器得先往 DP 的SELECT寄存器里写入 APSEL 和 APBANKSEL之后 DP 才会把后续的 AP 访问路由到正确的 AP 寄存器上。整个寻址链路就是主机 → 写 DP SELECT → 写 AP 寄存器或者先通过 AP 寄存器的地址字段发起读写→ 再读 DP RDBUFF 拿到结果。这和我们在调试器里“直接输入 0x40010800 读寄存器”的体验天差地别那些地址都是调试器靠 SWD 协议逐级翻译出来的。2.4 一个读操作的时序实例以读取 DPIDR 为例完整时序大致是主机发送 8 bit 请求帧APnDP 0RnW 1A[2:0] 0b000并带校验位。目标回复 3 bit ACK001。目标驱动 32 bit 数据IDCODE 值比如 STM32F1 系列常见值形如0x1BA01477或0x2BA01477后跟 1 bit 校验。总线回到空闲态主机准备下一次请求。如果是写操作比如写 AP 的某个寄存器时序变为主机发送请求帧APnDP 1RnW 0A[2:0] 指向 AP 寄存器带校验。目标回复 ACK 001。主机接管总线发送 32 bit 要写入的数据外加校验位。这里我不建议死记每一条命令的十六进制字节因为 AP 地址、DP 地址不同请求帧是不同的。更实用的方式是直接用逻辑分析仪的 SWD 解码插件或者用支持协议解析的调试工具让工具替你完成字节到语义的转换但是你必须能看懂波形上的三段结构才能判断问题出在哪一段。3. 波形分析用逻辑分析仪把协议“看”出来3.1 抓取前的准备采样率、通道与触发设置想把 SWD 波形抓出来一台采样率不低于 24MHz 的逻辑分析仪就够了。SWD 的时钟一般在 1MHz4MHz 之间理论上采样率只要大于信号最高频率的 4 倍就能看出基本轮廓但实际抓波形我建议至少 8 倍于 SWCLK。比如你用 4MHz 的 SWCLK那就把采样率设到 32MHz 以上不然数据位太密解码插件容易出错。接线很简单逻辑分析仪的 CH0 接 SWCLKCH1 接 SWDIOGND 必须和目标板共地。别只靠 USB 口的“地”去共地那样噪声很大。最好是分析仪的地线夹直接夹在目标板的 GND 测试点上。触发设置这里有个小技巧。不要用上升沿触发SWD 请求帧是以一个下降沿开始的你用 SWDIO 的下降沿触发能稳稳地抓到事务开头。如果你用某个更复杂的调试器工具也可以直接把触发条件设为“SWD 解码有效”那会更省事但前提是你的逻辑分析仪软件带协议解码功能。3.2 从波形读出一次完整事务以我抓过的一段读取DPIDR的波形为例你在逻辑分析仪屏幕上会看到一开始 SWDIO 是空闲的高电平。突然一个低脉冲这就是 Start 位。接着 SWDIO 上出现一串有规律的翻转对应 8 bit 请求帧。这串翻转的节奏完全跟着 SWCLK 走始终是 SWCLK 下降沿后变化数据上升沿采样。请求帧结束的地方你会注意到 SWDIO 有短暂变成高阻态的迹象波形上表现为不再是干脆的电平而可能出现一点毛刺或者浮空电平。这个位置就是 turn-around 周期主机把总线交还给目标。紧接着你能看到 3 个清楚的 bit 被拉低或拉高——这是 ACK001。ACK 之后如果目标是读操作SWDIO 上会出现连续 32 个数据位最后再来一个校验位。整个段落的波形是稳定的因为目标芯片在驱动总线不像主机驱动时那样有切换毛刺。这些细节用逻辑分析仪的“协议解码”功能可以自动标出来但我还是建议你至少手动数一次 bit。我第一次手动数完才真正理解“LSB first”是什么意思——低位在前数据看起来是“反着”的比如数值0x02BA01477中的低位字节在线上先出现的是它的 LSB跟你在 Keil 寄存器窗口里看到的顺序完全不一样。3.3 常见异常波形的判读抓波形多了你会发现绝大部分调试连接问题在波形上都有明显的特征模式波形特征含义常见原因请求帧正常但 ACK 位置总线一直高目标没有响应目标芯片没上电、SWDIO 连接错误、芯片调试端口被禁用请求帧完全没有只有杂乱的时钟主机没有发出有效请求调试器驱动问题、接线方向错误、SWCLK 没有接到目标ACK 偶尔是010忙目标忙于内部操作目标在复位后立刻被访问、时钟太快读到数据全是0xFFFFFFFF单线读回悬空电平SWDIO 连接断开、目标电压没建立起来、目标处于复位状态ACK 正常但数据校验错误频繁信号质量差杜邦线过长、地线回路大、EMI 干扰、SWCLK 过快还有一个高频现象有些芯片在调试时某个外设寄存器“怎么也读不对”比如热词里提到的“0x247 寄存器一直读出 0x80”。这种固定值往往不是 SWD 链路问题而是你访问的寄存器处于错误的状态域。最常见的是外设时钟没使能你读到的是总线上的“复位默认值”或者是受保护的寄存器必须先在解锁寄存器里写入特定密钥才能读再不然就是访问了未实现的保留地址系统返回了固定值。这种问题靠抓 SWD 波形用处不大得回到芯片手册的数据手册里去查寄存器访问属性和时钟树。4. 手把手实操三套方法读写目标寄存器4.1 用 OpenOCD 命令行直接读写OpenOCD 是嵌入式调试的瑞士军刀它把 SWD 协议封装成了简单的命令。我以 STM32F1 系列目标板 ST-Link 调试器为例演示如何通过命令行读、写一个外设寄存器。连接好硬件后在终端里输入openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c init -c reset haltreset halt很关键它让目标芯片停在复位向量的入口保证后续读写不受应用代码干扰。进入 OpenOCD 命令行后可以用mdwmemory display word按 32 位读和mwwmemory write word按 32 位写来操作内存映射的寄存器。比如我想操作 GPIO 的端口输出寄存器先把 GPIOC 对应的 APB2 外设时钟打开。以 STM32F103 为例APB2 外设时钟使能寄存器的地址是0x40021018其中位 4 是 IOPCEN mww 0x40021018 0x00000014这里写0x14是把 AFIOEN位 0和 IOPCEN位 4一起打开严谨来说需要先读再改但这里直接写已知状态也可以。接着配置 GPIOC 的 CRH 寄存器地址0x40011004把 PC13 设为通用推挽输出速度 2MHz。CRH 每 4 位对应一个引脚PC13 对应 CRH 的位 [23:20]模式值0010输出 2MHz mdw 0x40011004 mww 0x40011004 0x44144444最后往 GPIOC 的 ODR 寄存器地址0x4001100C写0x00002000把 PC13 拉高就能看到板载 LED 被点亮 mww 0x4001100C 0x00002000整个过程OpenOCD 在背后做的就是通过 SWD 协议访问 AHB-AP把 CPU 对内存的访问转换成调试访问请求。我会建议你执行每条命令前后都用mdw把寄存器内容读回来确认这能帮你建立对“读改写”最直观的认知。4.2 用 Keil 调试器的寄存器窗口实时观察如果你不想敲命令Keil 的调试界面就是最直观的寄存器读写工具。进入 Debug 模式后打开 Peripherals 菜单里的对应外设窗口就能看到每个寄存器的实时值。在 Watch 窗口添加变量可以直接看到内核寄存器 R0R15、xPSR、MSP、PSP 的值。但 Keil 里最干货的功能是 System Viewer。它不仅能显示寄存器值还能把每个 bit 的含义一一展开。比如你看 GPIOA 的 CRH它会告诉你每一位对应的模式是输入还是输出速度是多快。这个体验比裸读十六进制强太多。有一点要注意Keil 在 Debug 模式下之所以能“实时刷新”这些寄存器靠的是不断地通过 SWD 发起读请求。如果目标芯片进入了低功耗模式或者调试时钟不够稳定你在 System Viewer 里看到的数值会不变或者瞬间归零不要误以为是外设坏了很可能是调试连接进入了重试状态。我一般在怀疑寄存器显示异常时会先把 SWCLK 降速或者在Options for Target - Debug - Settings里把 Port 选成 SW再重新连接。4.3 自己写一个最小 SWD 主机理解协议的好方法如果你真的想彻底搞懂 SWD我强烈建议你用一个带 GPIO 的 MCU 试着去“模拟”SWD 主机去读目标芯片的 IDCODE。这个做法非常“自残”但是效果拔群。关键代码逻辑大概是这样的伪代码void swd_write_bit(uint32_t bit) { SWDIO_OUT(bit); SWCLK_LOW(); SWCLK_HIGH(); // 目标在上升沿采样 } void swd_send_request(uint8_t req) { for (int i 0; i 8; i) { swd_write_bit((req i) 1); } // 释放 SWDIO进入 turn-around SWDIO_IN(); SWCLK_TOGGLE(); // 空出一个时钟周期 }发送完请求后把你的 MCU 切到输入模式去采样 ACK 和数据位。这里最容易出错的就是时序SWCLK 的下降沿要变化数据、上升沿采样数据顺序反了目标可能完全收不到请求。我第一版代码就是搞反了采样沿结果逻辑分析仪上看到的波形请求帧是有的但目标一直不 ACK。排查了很久才发现目标在上沿才采样我的数据却在上沿才变化数据完全错位。这个练习做完你回头看 Keil 那种“一键调试”的便利会多一份敬畏那背后是大量状态机在帮你处理字节序、校验位、总线切换窗口和重试逻辑。5. 进阶排障与稳定性技巧让 SWD 从“能用”到“可靠”5.1 “Could not stop”终极排查清单最后把最常用的排查动作串成一张时间顺序执行清单。下次你再遇到Could not stop Cortex-M device按这个顺序过一遍比盲目换线高效得多检查目标板电源用万用表量 3.3V 是否真的存在电压有没有跌落。很多调试失败其实是目标板压根没上电。拉低复位再连接很多调试器支持“Connect under Reset”也就是让 MCU 一直保持复位状态调试器利用复位窗口去建立连接。这能绕过目标应用代码里一上电就把调试口关掉的恶意行为。降低 SWCLK 频率在调试器设置里把 SWCLK 从 4MHz 降到 100kHz 甚至更低。低速连接对线缆质量、接触电阻的容忍度会高很多。用逻辑分析仪看 SWDIO 上有没有有效请求如果连请求都没有把注意力放回调试器和接线如果有请求无 ACK重点查目标芯片的复位状态、时钟源和调试端口使能位。确认目标芯片的 Option Bytes 里调试端口没有被关闭有些芯片出厂默认开启但如果之前有人改过 Option Bytes把 DBG 相关位关掉了SWD 就可能连不上。这时需要用带复位时序的调试器在复位矢量执行前抢占总线。检查目标是否进入深度睡眠Cortex-M 在睡眠或停止模式下调试端口不一定会自动掉电但如果你想在低功耗模式下调试需要设置 DBGMCU 里关于低功耗模式下保持调试时钟的寄存器位。否则连上后一运行立刻就和目标失联。5.2 读寄存器“固定值”的排查思路回到热词里那个“0x247 寄存器一直读出 0x80”的场景。这其实是调试时最容易被带偏的一类问题。看到读寄存器得到一个固定值先别急着怀疑 SWD 链路按以下顺序排查查外设时钟是否使能绝大多数 Cortex-M 的外设寄存器在时钟没开启时读出来的都是“复位值”这个复位值往往是个固定数。你需要先确认对应的 RCC 外设时钟使能位已经置 1。查寄存器是否只读或只写有些寄存器写入后读回永远是 0 或者默认值比如很多状态寄存器SR在读取后会 self-clear。查总线位宽和数据对齐如果你用 32 位读去读一个 8 位外设寄存器读到的可能包含相邻字节的内容。查是否访问了保留地址不要对着地址猜去 data sheet 的 memory map 表里查这个地址是不是真实存在的外设寄存器。热词里那个 0x247大概率就是访问了一个保留区读回全是默认值。查读到的数据是不是 DMA 或硬件外设在写比如一个带 FIFO 的 SPI 外设你读它的数据寄存器每次读到的都是 FIFO 当前值如果 FIFO 一直是空或者满那读出来自然也是固定值。5.3 提高 SWD 连接稳定性的几个实操心得我在实践里最实用的一条经验是给调试器一个明确的“硬件复位”实现。很多调试连接失败并不是 SWD 协议本身有问题而是目标芯片处于一个调试器“不熟悉”的状态。如果在调试器配置里启用硬件复位并在连接时选择复位后暂停成功率会大幅提升。接线方面也有讲究。SWDIO 和 SWCLK 这两根线尽量短最好在 10cm 以内而且要避免和目标板上的电源线、大电流线路平行走线。杜邦线之间的间距近了会在高频时产生串扰。我遇到过 SWCLK 频率上到 4MHz 就不稳定降到 1MHz 就稳如老狗的情况后来发现就是杜邦线太长寄生电容把信号边沿磨圆了。另外强烈建议 SWDIO、SWCLK 各自接一个 10kΩ 左右的上拉电阻到目标板的 3.3V。这在芯片数据手册里通常会标推荐电路很多人会偷懒省略。但调试器连接时目标芯片可能处于未完全上电的状态总线悬空会导致调试端口误判启动模式。加了上拉电阻之后很多“偶发连不上”的问题会直接消失。最后提一个相对冷门的技巧在看波形时多留意“turn-around 周期”的位置。SWD 的双向切换是最容易出现总线冲突的地方一旦主机或目标有一个没按协议释放总线你会在波形上看到 SWDIO 上出现“两个驱动源打架”的现象——一段很短的时间里电平既不高也不低或者出现毛刺。这种问题往往和调试器固件版本有关升级调试器固件或者换一个调试器有时候就能解决。SWD 协议说到底并不复杂难的是在真实的物理世界里保持它的时序完整性。我现在每次遇到调试连接问题第一反应是抓波形第二反应是看时序第三步才是查寄存器配置。这套习惯帮我省下了大量“盲调”的时间。希望这篇文章也能帮你少走这些弯路。
返回列表