ARTICLE DETAIL

资讯详情

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

绕过IDE直接读写STM32寄存器:SWD协议从零实现与波形分析

绕过IDE直接读写STM32寄存器:SWD协议从零实现与波形分析 1. 为什么我要绕过IDE直接读写STM32寄存器第一次接触SWD协议是在做一个离线烧录器的项目。客户要求产线上不能装Keil也不能用STM32CubeProgrammer只能用一个自己做的上位机配合一个廉价下载器完成固件烧录和寄存器校验。当时我第一反应是找现成库结果翻了一圈发现能用的要么是封装好的动态库要么是命令行工具真正能让我控制到每一个SWD时序的代码几乎没有。于是只能硬着头皮从协议层啃起。SWD全称Serial Wire Debug是ARM CoreSight调试架构下的一种两线调试接口只需要SWCLK和SWDIO两根线就能完成对Cortex-M系列芯片的调试访问。相比JTAG的五线制SWD在引脚紧张的小型MCU上优势明显。STM32全系Cortex-M内核都支持SWD而且大部分型号出厂默认就是SWD使能、JTAG禁用的状态这也是为什么你用ST-Link连STM32时几乎不需要额外配置。这篇文章要讲的事情很具体不依赖任何现成的烧录软件自己实现SWD协议栈完成对STM32寄存器的读写。适合两类人看一类是想搞明白调试器底层到底在干什么的嵌入式工程师另一类是需要做产线工具、离线烧录器、自动化测试工装的开发者。读完之后你应该能自己写出一个最小可用的SWD驱动并且能看懂逻辑分析仪上抓到的波形到底对应协议里的哪一步。我当时的硬件配置是这样的一块STM32F103C8T6最小系统板作为目标芯片一个自己画的FT2232D模块作为SWD主机逻辑分析仪用的是某宝上几十块钱的8通道24M采样率版本。这个配置不算专业但足够把协议跑通。后面讲波形分析时用的就是这套设备抓的数据。2. SWD协议的核心机制拆解2.1 两层结构物理层包和事务层很多人看SWD协议文档时会被各种缩写搞晕其实把结构理清楚就简单了。SWD协议分两层物理层负责一根线上的双向数据传输事务层负责组织读写请求和响应。物理层的关键在于SWDIO是双向线主机和从机分时驱动。每个bit的传输都有一个方向切换的过程主机在SWCLK上升沿驱动SWDIO从机在SWCLK上升沿采样。读操作时主机先发请求然后释放SWDIO从机在接下来的周期里驱动数据回来。这个切换时机如果搞错读回来的数据就会全是1或者全是0。事务层定义了两种基本操作写请求和读请求。每个事务由三个阶段组成——请求阶段、应答阶段、数据阶段。请求阶段主机发8个bit包含APnDP位、RnW位、地址位和奇偶校验位。应答阶段从机回3个bit表示OK、WAIT还是FAULT。数据阶段根据方向传输32位数据加1位奇偶校验。2.2 请求包的8个bit到底怎么排请求包的8个bit顺序是固定的从LSB到MSB依次是Start、APnDP、RnW、A[2]、A[3]、Parity、Stop、Park。Start位固定为1表示一个事务的开始。APnDP位选择访问的是DP还是AP寄存器0选DP1选AP。RnW位表示读还是写0是写1是读。A[2]和A[3]是地址的低两位注意这里只传两位因为DP和AP的寄存器地址空间都是4字节对齐的低两位就够了。Parity是前面这几位Start到A[3]的奇偶校验采用偶校验。Stop位固定为0Park位固定为1。我一开始最困惑的是为什么地址只传两位。后来想明白了DP和AP各自只有4个寄存器用两位地址刚好能索引到。比如DP的CTRL/STAT寄存器地址是0x00SELECT是0x08RDBUFF是0x0C那A[3:2]就分别是00、10、11。这个设计很巧妙用最少的bit完成了寻址。2.3 应答阶段和WAIT状态的处理应答阶段的3个bit是直接从机驱动的主机在发完请求包后要立即把SWDIO切为输入模式。这3个bit的含义分别是OK响应0b001、WAIT响应0b010、FAULT响应0b100。WAIT响应是最容易被忽略的。STM32在Flash编程或者某些系统寄存器访问时如果内部总线忙会返回WAIT。这时候主机不能直接报错而是要重试。我踩过的坑是在写Flash控制寄存器时没有处理WAIT结果偶尔写入失败查了两天才发现是重试逻辑没写。正确的做法是收到WAIT后保持当前请求不变重新发起同一个事务直到收到OK或者超过重试次数。FAULT响应通常意味着访问了不存在的地址或者权限不够。比如你试图通过AP访问一个没有映射的寄存器就会收到FAULT。这时候要检查SELECT寄存器里的AP bank选择是否正确。2.4 数据阶段的奇偶校验数据阶段的32位数据后面跟1位奇偶校验同样是偶校验。计算方法是统计32位数据中1的个数如果1的个数是奇数校验位就是1使总1个数为偶数如果1的个数是偶数校验位就是0。这个校验看起来简单但实际写代码时很容易搞错。我建议单独写一个函数来计算奇偶校验不要内联在发送逻辑里。因为读和写都需要校验而且请求包的校验和数据包的校验算法一样只是位数不同。写成一个通用函数传入数据和位数返回校验位这样不容易出错。3. 从零实现SWD驱动的完整过程3.1 硬件连接与GPIO配置先说要准备什么。目标板一块STM32随便什么型号都行我用的F103。SWD主机我用的是FT2232D模块因为它支持MPSSE模式可以很方便地产生SWCLK和SWDIO时序。如果你手头没有FT2232用树莓派的GPIO或者STM32自己模拟也可以只是速度会慢一些。接线很简单SWCLK接目标板的SWCLKPA14SWDIO接目标板的SWDIOPA13GND共地目标板正常供电。注意SWDIO线上最好串一个100欧姆左右的电阻防止两边同时驱动时电流过大。我一开始没加这个电阻调试时偶尔会出现SWDIO被拉死的情况加了之后就没再出现过。GPIO配置方面如果用的是FT2232的MPSSE模式SWCLK和SWDIO都是它自动控制的你只需要通过USB发命令就行。如果用普通GPIO模拟SWCLK配置为推挽输出SWDIO在发送时配置为推挽输出接收时配置为浮空输入。切换方向时要先写GPIO的方向寄存器再操作输出寄存器顺序不能反。3.2 复位目标芯片并进入SWD模式STM32上电后默认就是SWD模式但如果你之前跑的程序把SWD引脚复用了就需要先复位。复位有两种方式硬件复位和软件复位。硬件复位就是拉低NRST引脚软件复位是通过写AIRCR寄存器的SYSRESETREQ位。我一般用硬件复位因为更可靠。具体操作是拉低NRST至少20微秒然后拉高等待10毫秒让芯片完成启动。启动完成后主机需要发送至少50个SWCLK周期同时保持SWDIO为高这是为了让目标芯片的SWD接口完成同步。这个步骤叫“线路复位”很多人会漏掉结果后面的请求全部没响应。线路复位之后主机要发送一个特殊的请求包0xE79E。这个包的8个bit是固定的用来让目标芯片的SWD接口从JTAG模式切换到SWD模式。发完之后如果收到OK响应说明切换成功。如果收到FAULT或者没响应检查接线和复位时序。3.3 读写DP寄存器的实操步骤进入SWD模式后第一步是读IDCODE寄存器地址0x00。请求包的8个bit计算如下Start1APnDP0RnW1A[2]0A[3]0Parity需要计算前4位的奇偶。前4位是1、0、1、01的个数是2偶数所以Parity0。Stop0Park1。合起来就是0b10010001即0x91。但实际发送时是LSB先发所以发送顺序是1、0、0、0、1、0、0、1。发完请求包后主机释放SWDIO读取3位应答。如果是0b001继续读32位数据加1位校验。读到的IDCODE对于STM32F103应该是0x1BA01477。这个值可以用来确认芯片型号和调试接口版本。写DP寄存器以CTRL/STAT为例地址0x04。请求包Start1APnDP0RnW0A[2]1A[3]0Parity计算前4位1、0、0、11的个数是2Parity0。合起来0b10010010即0x92。发送后读应答收到OK后发送32位数据加校验。CTRL/STAT的常用值是0x50000000表示使用SWD模式、开启调试。3.4 通过AP访问内存和寄存器DP只是调试端口真正要读写STM32的寄存器和内存需要通过AP。AP的访问要先配置SELECT寄存器选择当前的AP bank和寄存器地址。SELECT寄存器在DP地址0x08。写SELECT的值由四部分组成APBANKSEL[7:4]、AP[31:24]、DPBANKSEL[3:0]保留。对于STM32通常用AP0所以AP[31:24]0。APBANKSEL选择AP内部的寄存器组读写内存时用bank 0所以APBANKSEL0。合起来SELECT0x00000000。配置好SELECT后就可以通过AP地址读写内存了。AP的地址0x00是CSW控制/状态字0x04是TAR传输地址寄存器0x0C是DRW数据读写寄存器。要读一个内存地址步骤是写TAR为目标地址写CSW配置传输宽度和模式然后读DRW。注意读DRW时实际返回的是上一次传输的数据所以第一次读DRW是启动传输第二次读DRW才能拿到数据。这个“读两次”的机制是SWD协议里最容易搞错的地方。我当时的代码里读内存的函数长这样先写TAR再写CSW然后发一次读DRW的请求但忽略返回的数据再发一次读DRW的请求这次的数据才是有效的。写内存就简单一些写TAR写CSW写DRW数据直接生效。4. 波形分析用逻辑分析仪看懂SWD时序4.1 抓波形的硬件设置逻辑分析仪我用的是8通道24M采样率的版本接法是把通道0接SWCLK通道1接SWDIO通道2接NRST作为触发信号。采样率设成12M就够了因为SWD时钟我设的是1M12倍过采样能看清每个bit。触发条件设成NRST下降沿触发这样能抓到复位后的完整初始化过程。抓取长度设成2M采样点大概能覆盖160毫秒的数据足够看到线路复位、IDCODE读取和几次寄存器读写。这里有个小技巧如果逻辑分析仪的通道不够可以只接SWCLK和SWDIO用软件触发。但硬件触发更可靠尤其是抓复位后的时序时软件触发容易漏掉前面的同步周期。4.2 线路复位阶段的波形特征复位后的波形很好认。NRST拉高后SWCLK会连续出现至少50个周期同时SWDIO保持高电平。这段波形在逻辑分析仪上看起来就是SWCLK在跑SWDIO是一条直线。这50个周期的作用是让目标芯片的SWD状态机完成同步。STM32的SWD接口在复位后处于不确定状态需要这段同步序列来建立通信。如果这段波形缺失或者周期数不够后面的请求包会全部无响应。紧接着是0xE79E的切换包。这个包的波形特征是SWDIO上会出现一串特定的0和1对应0xE79E的LSB先发顺序。具体来说0xE79E的二进制是1110011110011110LSB先发就是0、1、1、1、1、0、0、1、1、1、1、0、0、1、1、1。在波形上能看到SWDIO在SWCLK的节拍下变化。4.3 请求包和应答包的波形对照以读IDCODE为例请求包0x91的LSB先发顺序是1、0、0、0、1、0、0、1。在波形上SWCLK的每个上升沿对应SWDIO的一个bit。前8个周期SWDIO依次是1、0、0、0、1、0、0、1。第9个周期开始SWDIO变成高阻态由从机驱动。从机在第9、10、11个周期分别输出0、0、1对应OK响应。注意从机的驱动时机是在SWCLK的上升沿之后所以逻辑分析仪上会看到SWDIO在第9个周期的上升沿之后才变化。应答之后是32位数据。IDCODE的值0x1BA01477LSB先发就是1、1、1、0、1、1、1、0、0、0、1、0、1、0、0、0、0、0、1、0、1、1、1、0、1、0、0、0、0、0、0、0。在波形上能看到SWDIO在32个周期内按照这个序列变化最后还有一个校验位。4.4 读写内存时的波形差异写内存和读内存在波形上的区别很明显。写操作时请求包之后主机继续驱动SWDIO发送32位数据所以SWDIO在整个事务期间都是主机驱动的。读操作时请求包之后主机释放SWDIO从机驱动应答和数据所以SWDIO在应答阶段会有一个方向切换的瞬间。具体来说读DRW的波形里你会看到请求包发完后SWDIO有一个短暂的高阻态然后从机拉低或拉高输出应答。这个高阻态在逻辑分析仪上可能表现为一个中间电平或者毛刺取决于总线上有没有上拉电阻。我在SWDIO上加了10K上拉所以高阻态时波形会被拉高看起来比较干净。还有一个细节读DRW时第一次读返回的是上一次的数据所以波形上第一次读DRW的数据段和第二次读DRW的数据段内容不同。如果你在波形上看到两次读DRW第一次的数据是之前某次写操作的值第二次才是目标地址的值这说明你的时序是对的。5. 常见问题与排查技巧实录5.1 请求包发出后没有应答怎么办这是最常见的问题。现象是主机发完8位请求包后读回来的3位应答全是1或者全是0。排查顺序如下先检查线路复位有没有做。用逻辑分析仪看SWCLK在复位后有没有至少50个周期SWDIO有没有保持高。如果没有补上这段代码。再检查0xE79E切换包有没有发。这个包是进入SWD模式的钥匙漏掉的话目标芯片还在JTAG模式不会响应SWD请求。然后检查SWCLK频率。STM32的SWD接口最高支持10MHz左右但如果你用GPIO模拟频率可能不稳定。先把SWCLK降到100K试试如果通了再逐步提高。最后检查硬件连接。SWDIO和SWCLK有没有接反GND有没有共地目标板有没有正常供电。我遇到过好几次是杜邦线接触不良换了线就好了。5.2 读回来的数据全是0xFF或0x00数据全0xFF通常意味着从机没有驱动SWDIO总线被上拉电阻拉高了。原因可能是请求包的地址算错了从机收到无效请求后不响应。检查A[2]和A[3]的计算以及奇偶校验位。数据全0x00则可能是SWDIO被拉低或者从机返回了FAULT响应但主机没正确处理。检查SELECT寄存器有没有配置AP bank选择对不对。还有一种情况是读DRW时只读了一次。前面说过读DRW要读两次第一次是启动传输第二次才是数据。如果只读一次拿到的就是上一次的残留值看起来像是错的。5.3 WAIT响应导致写入失败写Flash控制寄存器或者系统寄存器时STM32可能返回WAIT。如果代码里没有重试逻辑这次写入就丢了。表现是偶尔写入成功、偶尔失败很难复现。解决办法是在收到WAIT后重新发起同一个事务。重试次数设成100次左右每次重试之间不需要延时因为WAIT通常只持续几个时钟周期。如果重试100次还是WAIT那可能是目标芯片真的忙不过来需要检查是不是有其他调试器在占用SWD接口。我当时的代码里所有SWD事务都包了一层重试逻辑。收到OK就返回收到WAIT就重试收到FAULT就报错。这个结构虽然简单但很有效。5.4 波形分析时看不到方向切换如果你在逻辑分析仪上看不到SWDIO的方向切换可能是采样率不够。方向切换发生在半个SWCLK周期内如果采样率只有SWCLK的2倍很容易漏掉。把采样率提高到SWCLK的10倍以上就能看清了。另一个可能是逻辑分析仪的通道配置问题。有些逻辑分析仪的通道有固定的方向只能输入不能输出这种就不适合抓双向总线。选那种支持双向的通道或者用两个通道分别抓SWDIO的输入和输出方向。5.5 常见问题速查表现象可能原因排查方法无应答线路复位缺失检查复位后50个SWCLK周期无应答未发0xE79E检查切换包是否发送无应答SWCLK频率过高降到100K测试数据全0xFF地址或校验错误重新计算请求包数据全0x00SELECT未配置检查AP bank选择数据错误读DRW只读一次改为读两次写入偶发失败未处理WAIT添加重试逻辑波形看不清采样率不足提高到10倍SWCLK6. 几个容易被忽略的实操细节6.1 空闲周期的插入SWD协议规定两个事务之间需要至少插入一个空闲周期也就是SWCLK跑一个周期但SWDIO不驱动。这个空闲周期是为了让总线上的信号稳定也给从机时间准备下一个事务。我一开始没加空闲周期在低速时没问题但SWCLK提到2M以上就开始出现偶发错误。加了空闲周期后稳定性明显提升。空闲周期的实现很简单就是在每个事务结束后手动发一个SWCLK脉冲SWDIO保持高阻。6.2 奇偶校验的验证奇偶校验错误是隐蔽性很强的问题。因为校验位算错时从机可能返回FAULT也可能直接忽略这个事务表现和地址错误很像。我的做法是在代码里加一个校验验证函数每次发送请求包之前先本地计算一遍校验位和实际发送的对比。如果不等直接报错。这个检查在调试阶段帮我省了很多时间。数据阶段的校验也一样。读回来的32位数据本地算一遍校验和从机返回的校验位对比。如果不一致说明传输过程中有bit翻转需要重读。6.3 多AP和多bank的切换STM32通常只用AP0但如果你要访问CoreSight的其他组件比如ETM或者ITM就需要切换到其他AP。切换AP时要重新写SELECT寄存器而且切换后第一个事务可能返回WAIT需要重试。AP内部的bank切换也要注意。比如访问AP的CSW寄存器在bank 0但访问某些配置寄存器可能在bank 1或bank 2。切换bank也是通过写SELECT寄存器实现。每次切换bank后建议先读一次DP的RDBUFF清空流水线再开始新的事务。6.4 复位后的延迟等待STM32复位后内部Flash和SRAM需要一定时间初始化。如果复位后立即发SWD请求可能收到WAIT或者无响应。我一般复位后延时10毫秒再开始SWD通信这个时间对STM32F103足够了。如果是其他型号查数据手册里的复位启动时间留够余量。这个延时在产线烧录器上特别重要。产线节拍很快操作员可能复位后立刻触发烧录如果延时不够第一包数据就会丢。我在产线工具里把延时设成20毫秒牺牲一点速度换稳定性。6.5 信号完整性对高速SWD的影响SWCLK超过4M后信号完整性问题开始显现。杜邦线的寄生电容会导致SWCLK边沿变缓SWDIO上出现振铃。表现是偶发的校验错误或者WAIT响应。解决办法有几个缩短接线长度最好在10厘米以内用排线代替杜邦线排线的阻抗更稳定在SWCLK和SWDIO上串33欧姆电阻抑制振铃降低SWCLK频率如果应用对速度要求不高1M到2M就够用了。我在产线工具上最终用的是2M SWCLK接线长度控制在15厘米串了33欧姆电阻连续跑8小时没有出现一次错误。7. 从协议理解到工具落地的个人体会这套SWD驱动我前后写了三版。第一版是用GPIO模拟的跑在STM32上SWCLK只有100K烧一个64K的固件要十几秒。第二版换到FT2232的MPSSE模式SWCLK提到1M烧录时间降到2秒左右。第三版加了重试逻辑和校验验证稳定性大幅提升最终用在产线上跑了两年多。回头看最难的不是写代码而是理解协议里的那些“为什么”。比如为什么请求包只传两位地址为什么读DRW要读两次为什么WAIT要重试。这些问题在文档里都有答案但如果不亲手抓一次波形很难真正理解。逻辑分析仪在这件事上帮了大忙。很多问题看代码看不出来但一看波形就明白了。比如方向切换的时机、空闲周期的位置、校验位的变化这些在波形上都是一目了然的。如果你也在搞SWD协议强烈建议配一个逻辑分析仪哪怕是最便宜的那种也比盲猜强。最后分享一个调试技巧当你怀疑某个事务有问题时先把它单独抓出来用逻辑分析仪看完整的请求、应答、数据三个阶段。然后对照协议文档一个bit一个bit地核对。这个方法看起来很笨但能解决90%以上的问题。剩下的10%通常是硬件连接或者信号完整性问题换根线、加个电阻就能解决。
返回列表