2. 嵌入式内存基础
做嵌入式调试这么多年,我越来越觉得硬件调试器就像医生的听诊器。没有它,你只能对着代码发呆,猜问题出在哪。有了它,你能直接看到芯片内部的状态——寄存器、内存、外设,一目了然。
这一章,咱们就聊聊调试器的核心:JTAG和SWD协议是怎么回事,调试器内部长什么样(以J-Link为例),以及怎么把它和目标板连起来。
JTAG协议:从边界扫描到调试
JTAG(Joint Test Action Group)最早不是为调试发明的。它诞生于上世纪80年代,目的是解决PCB板级测试问题——芯片引脚越来越多,探针够不着,怎么办?
JTAG的思路很巧妙:在芯片内部给每个引脚串联一个移位寄存器,形成一个“边界扫描链”。通过这条链,你可以把数据“推”进芯片,也能把芯片内部状态“拉”出来。
后来大家发现,这个机制不仅能测引脚,还能控制CPU内核——暂停、单步、读写寄存器。于是JTAG就成了嵌入式调试的事实标准。
JTAG的4根核心信号线:
- TMS(Test Mode Select)—— 模式选择,控制状态机跳转
- TCK(Test Clock)—— 时钟信号,所有操作同步于此
- TDI(Test Data In)—— 数据输入,串行喂给芯片
- TDO(Test Data Out)—— 数据输出,从芯片串行读出
JTAG的核心是一个16状态的有限状态机。你通过TMS引脚在TCK上升沿切换状态,最终进入“Shift-DR”或“Shift-IR”状态,开始读写数据。说白了,就是一条串行总线,但控制逻辑比SPI/I2C复杂得多。
我记得第一次用逻辑分析仪抓JTAG波形时,盯着那串TMS电平变化看了半天——每个状态跳转都有固定的序列,错一位就全乱套。后来我习惯在调试器初始化时先发一个“复位+跑到IDLE”的序列,确保状态机回到已知状态。
SWD协议:ARM的轻量级替代
JTAG什么都好,就是引脚多。对于Cortex-M这类小封装MCU,4根线太奢侈了。ARM在2004年推出了SWD(Serial Wire Debug),只用两根线:
- SWDIO —— 双向数据线,时分复用
- SWCLK —— 时钟线
SWD的协议栈比JTAG精简得多。它没有复杂的16状态机,而是用“包+确认”的方式通信。每个操作包含:
- 主机发送8位请求头(包含AP/DP选择、读写方向、寄存器地址)
- 目标返回3位确认(OK/WAIT/FAULT)
- 如果是读操作,目标返回33位数据(32位数据+1位奇偶校验)
- 如果是写操作,主机发送33位数据
我的经验:SWD的奇偶校验位经常被忽略。我曾经遇到一个项目,SWD偶尔通信失败,查了三天发现是PCB走线太长导致数据跳变,奇偶校验过不去。加上一个33Ω的串联电阻就好了。
JTAG和SWD怎么选?我一般这么判断:
| 对比项 | JTAG | SWD |
|---|---|---|
| 引脚数 | 4(不含GND) | 2(不含GND) |
| 速度上限 | 通常更高(并行优势) | 通常稍低(串行开销) |
| 多核调试 | 原生支持(JTAG链) | 需额外机制 |
| 协议复杂度 | 高(16状态机) | 低(包+确认) |
| 适用场景 | FPGA、多核SoC、边界扫描测试 | Cortex-M系列MCU |
说白了,做ARM Cortex-M开发,SWD就够了。JTAG更多用在FPGA配置、多核调试或者需要边界扫描的场景。
调试器硬件架构:以J-Link为例
J-Link是SEGGER公司的产品,也是我用过最稳定的调试器之一。它的内部架构其实不复杂,但设计得很讲究。
J-Link的核心是那颗协议桥接芯片。它内部跑着SEGGER的固件,负责把USB包解析成JTAG/SWD时序。说白了,PC端发一个“读内存0x20000000”的命令,USB传过来,桥接芯片把它变成一串TMS/TDI/TCK电平,目标芯片收到后返回数据,再打包回USB发给PC。
缓冲隔离层很多人不注意。我见过有人直接把调试器的TDO和TDI短接,结果烧了PC的USB口。J-Link的缓冲芯片能承受5V电平,还能隔离反向电流——这个保护很关键。
避坑指南:我曾经在调试一个3.3V的STM32F103板子时,误接了12V的电源到目标板。结果J-Link的缓冲芯片直接冒烟了。后来我学乖了:每次上电前先用万用表量目标板的VCC和GND,确认电压正常再连调试器。
调试器与目标板的连接方式
连接方式看似简单,但坑不少。我按经验总结了几种常见场景:
标准20pin JTAG连接器(ARM 20-pin)
这是最通用的接口,J-Link、ST-Link、ULINK都支持。引脚定义如下:
| 引脚 | 信号 | 方向 | 说明 |
|---|---|---|---|
| 1 | VTref | 输入 | 目标板参考电压(调试器检测用) |
| 2 | GND | - | 地 |
| 3 | nTRST | 输出 | JTAG复位(可选) |
| 4 | GND | - | 地 |
| 5 | TDI | 输出 | 数据输入 |
| 6 | GND | - | 地 |
| 7 | TMS/SWDIO | 双向 | JTAG模式选择 / SWD数据 |
| 8 | GND | - | 地 |
| 9 | TCK/SWCLK | 输出 | 时钟 |
| 10 | GND | - | 地 |
| 11 | nSRST | 双向 | 目标系统复位(可选) |
| 12 | GND | - | 地 |
| 13 | TDO | 输入 | 数据输出 |
| 14 | GND | - | 地 |
| 15-20 | GND/NC | - | 地或保留 |
我的习惯:VTref引脚必须接。调试器通过它检测目标板是否上电。如果不接,调试器会报“Target voltage not detected”。我一般直接从目标板的3.3V稳压输出飞一根线到VTref。
SWD的简化连接(4线制)
对于Cortex-M MCU,我通常只用4根线:
- SWDIO —— 数据线
- SWCLK —— 时钟线
- GND —— 地线
- VTref —— 参考电压(检测用)
nSRST(复位线)不是必须的,但我建议接上。为什么?因为有时候芯片进入了休眠模式或者看门狗复位了,SWD接口会锁死。拉一下复位线,芯片重新初始化,SWD就能连上了。
我记得有一次调试一个低功耗项目,芯片进入STOP模式后SWD完全失联。我试了各种方法——重新插拔、断电重启——都没用。最后接了nSRST线,在IDE里点了一下“Connect under Reset”,瞬间连上了。从那以后,我所有板子都预留了复位线。
连接注意事项
避坑指南:
- 线长控制:SWD信号线不要超过20cm。我曾经用了一根50cm的杜邦线,SWD时钟跑到4MHz就丢包了。降到1MHz才稳定。
- 上拉电阻:SWDIO需要上拉(通常4.7kΩ到VCC),SWCLK需要下拉(通常4.7kΩ到GND)。有些MCU内部已经集成了,但我不放心,习惯在板子上外加。
- 电平匹配:调试器通常支持1.2V-5V,但目标板电压不能超过调试器耐受范围。J-Link BASE最高支持5V,J-Link ULTRA+支持到5.5V。
- 热插拔:尽量别热插拔。虽然J-Link有ESD保护,但我见过有人热插拔时把目标板的SWDIO引脚打坏的。先断电,再插线。
小结
JTAG和SWD是嵌入式调试的基石。JTAG功能全、引脚多,适合复杂系统;SWD精简高效,是Cortex-M的首选。J-Link的架构——USB接口、协议桥接、缓冲隔离、目标接口——是主流调试器的通用模板。连接时注意线长、上拉、电平匹配,能省去很多排查时间。