简介:一份围绕 JTAG 标准的完整学习资料包,适合刚接触硬件调试的初学者,也适合需要快速查阅 TAP、边界扫描和在线编程细节的嵌入式工程师。内容涵盖 JTAG 协议原理、TAP 状态机、边界扫描、器件内测试、在线编程及故障诊断等核心主题,并且提供了 Verilog 探针工程、Python 辅助脚本、Markdown 笔记和 C 示例,帮助读者从概念到代码建立完整认识。压缩包共 47 个文件,主要包括 14 个 v 源码、7 个 PDF 文档、8 个 md 笔记、4 个 py 脚本,另有 SVG 电路图、Makefile、配置说明等,整体约 2.94MB,轻量且目录清晰,便于按模块学习。PDF 文档部分收录了 Xilinx、ARM、Altera 等厂商的 JTAG 应用笔记和经典案例分析,能帮助理解不同硬件平台下的实现差异;Verilog 工程与脚本则适合直接参考或二次修改。目前已有 697 人学习浏览,是一份兼顾原理讲解和动手实践的入门教程,也可作为日常调试时的速查手册。
1. JTAG 不是四根线,是一台状态机
调试器报出 SWD/JTAG Communication Failure,万用表量了电源正常,复位键也按了,换一个调试器依旧连不上。这种情况十有八九不是芯片坏了,而是 JTAG 链路在某个看不见的环节断了:可能是 VTref 没接,可能是 TMS 被某个外设拉低,也可能只是 20-pin 排线虚焊。JTAG 的特别之处在于它不是简单地传几根信号线,而是四根信号线加一台最小状态机:TDI 进、TDO 出、TCK 给节拍、TMS 决定状态跳转。这套机制让它成为边界扫描标准,也让它的故障往往表现为整条链“读不到 IDCODE”而不是个别引脚没波形。这篇文章把状态机、引脚定义、常见报错和自检方法串起来,适合正在被接线、禁口、报错折磨的人。
2. JTAG 协议的最小单元:TAP 状态机与 IR/DR 指令路径
2.1 看懂 TMS 的 1 和 0:TAP 状态机的 16 个状态
JTAG 的核心是一个叫作 TAP(Test Access Port)的状态机。无论目标芯片是 ARM、FPGA 还是专用 ASIC,所有操作都先转换成 TAP 的状态跳转,再由 TCK 的边沿把数据一位一位移入移出。这也是为什么很多工程师把 JTAG 调不通归咎于“接线问题”,但真正原因经常是 TAP 没有走到目标状态。
TAP 一共 16 个状态,DR 分支最常用的 8 个如下表所示:
| 状态 | TMS=0 时下一状态 | TMS=1 时下一状态 |
|---|---|---|
| Test-Logic-Reset | Run-Test/Idle | Test-Logic-Reset |
| Run-Test/Idle | Run-Test/Idle | Select-DR-Scan |
| Select-DR-Scan | Capture-DR | Select-IR-Scan |
| Capture-DR | Shift-DR | Exit1-DR |
| Shift-DR | Shift-DR | Exit1-DR |
| Exit1-DR | Pause-DR | Update-DR |
| Pause-DR | Pause-DR | Exit2-DR |
| Exit2-DR | Shift-DR | Update-DR |
| Update-DR | Run-Test/Idle | Select-DR-Scan |
注意一个细节:TMS 连续 5 个 1,无论当前在哪个状态,都会回到 Test-Logic-Reset。调试器在做“复位 JTAG 链路”时,就是先连续输出 5 个 TCK 且 TMS 保持高电平,然后才开始真正的操作。如果链路噪声把其中一位的 TMS 拉低了,状态机就停在错误分支,后续所有移位序列都会错位。
用一个简单的 Python 脚本可以模拟从复位走到 Shift-DR 的位序列,便于直观理解 TMS 的编码过程:
# tap_sm.py —— 按 IEEE 1149.1 状态转移表模拟 TAP 跳转 NEXT = { "Test-Logic-Reset": ("Run-Test/Idle", "Test-Logic-Reset"), "Run-Test/Idle": ("Run-Test/Idle", "Select-DR-Scan"), "Select-DR-Scan": ("Capture-DR", "Select-IR-Scan"), "Capture-DR": ("Shift-DR", "Exit1-DR"), "Shift-DR": ("Shift-DR", "Exit1-DR"), "Exit1-DR": ("Pause-DR", "Update-DR"), "Pause-DR": ("Pause-DR", "Exit2-DR"), "Exit2-DR": ("Shift-DR", "Update-DR"), "Update-DR": ("Run-Test/Idle", "Select-DR-Scan"), } def run(tms_bits, start="Test-Logic-Reset"): state = start trace = [state] for b in tms_bits: state = NEXT[state][int(b)] trace.append(state) return trace # 从 Reset 进 Shift-DR:TMS=0,1,0,0 print(" -> ".join(run("0100"))) # 从 Reset 进 Shift-IR:TMS=0,1,1,0,0 print(" -> ".join(run("01100")))这段代码按 IEEE 1149.1 的转移表逐位推进状态。run("0100")输出Test-Logic-Reset -> Run-Test/Idle -> Select-DR-Scan -> Capture-DR -> Shift-DR,而run("01100")会进入 Select-IR-Scan 再落到 Shift-IR。两者只差一个位,但后续移入的数据去向完全不同:前者进数据寄存器,后者进指令寄存器。理解这一点,再看调试器报“JTAG 链访问失败”时,就能明白它在哪个阶段卡住了。
2.2 IR 和 DR:为什么 JTAG 能同时做下载和调试
TAP 状态机每次只能选择一条路径:要操作指令寄存器就进 IR 分支,要操作数据寄存器就进 DR 分支。IR 决定“接下来 DR 用哪个寄存器”,DR 再根据 IR 内容决定数据从 TDI 移到哪里。所以一次完整的 JTAG 操作至少包含两段移位:先移指令,再移数据。
IEEE 1149.1 定义了几条基本指令,各家芯片基于此扩展:
| 指令 | 常见 IR 编码 | 作用 |
|---|---|---|
| EXTEST | 0x0 | 边界扫描测试,强制引脚输出 |
| IDCODE | 0x1 | 读出器件身份码 32 位 |
| SAMPLE/PRELOAD | 0x2 | 采样引脚状态或预加载边界寄存器 |
| BYPASS | 全 1 | 数据从 TDI 直接到 TDO,缩短链路时延 |
实际 IR 位数由芯片设计决定,常见 4 位或 5 位。这里要提醒一句:上面表格里的编码是多数芯片的默认值,但不是所有芯片都如此。板级调试时,以该器件的 BSDL 文件或数据手册 IR 描述为准。很多初学者拿通用 IDCODE 指令去读一颗冷门芯片,读回来的数据完全不对,最后发现是 IR 位宽不同。
读 IDCODE 的动作本身是:走 Shift-IR 移入 IDCODE 指令,Update-IR 生效后,再走 Shift-DR 移 32 个 TCK,同时把 TDO 上输出的 32 位移出来。IDCODE 的位字段也有固定结构:bit 0 固定为 1,bit 10 到 bit 1 是 JEDEC 厂商号,bit 31 到 bit 28 是版本号。如果读回的值全为 1,大概率 TDO 悬空或虚焊;全为 0 则可能是 TDO 被拉死或芯片未进入测试逻辑。
2.3 时序:TCK 边沿、TDO 延迟与线长的关系
JTAG 的时序比想象中严格:TDI 和 TMS 在 TCK 上升沿被采样,TDO 在 TCK 下降沿之后更新。也就是说,调试器在下降沿把数据放到 TDO 上,目标芯片在下一个上升沿把它采回。这个“半周期延迟”给信号完整性留了余量,但余量很有限。
常见调试器默认频率在 4MHz 左右,排线超过 20 厘米、没有共地或者地线过细时,反射和串扰会把有效信号窗口吃光。此时最有效的验证手段不是换线,而是把频率降下来:OpenOCD 里用adapter_khz 500,J-Link 的 RTT 配置里把 Speed 调到 100kHz 再试。同一块板子,4MHz 失败、100kHz 成功,基本可以断定是时序余量问题而不是链路断路。如果降频后依旧失败,再回头查供电、复位和引脚电平,别在频率上反复折腾。
3. JTAG 引脚定义与接线:从 20-pin 到 SWD 的复用关系
3.1 JTAG 20-pin 信号定义与上拉/下拉设计要求
标准的 ARM 20-pin JTAG 接口有明确的信号分布,但实际板子上能见到的更多是 10-pin 或 4-pin 的简化版本。不管针数怎么变,核心信号只有几个:
| 信号 | 方向 | 作用与常见坑 |
|---|---|---|
| VTref | 输入 | 目标板参考电压,用于电平匹配,不能替代目标板上电 |
| GND | - | 必须与调试器共地,地线越短越好 |
| TCK | 调试器到目标 | JTAG 时钟,频率可调,默认降低一个量级排查 |
| TMS | 调试器到目标 | 状态机选择,最敏感的信号,不应浮空 |
| TDI | 调试器到目标 | 数据输入,链式结构中上级接到下级 |
| TDO | 目标到调试器 | 数据输出,目标板通常加串联电阻防过冲 |
| nTRST | 调试器到目标 | 可选的 TAP 复位,板上无此信号时保持释放 |
| nSRST | 双向 | 系统复位,也是 connect under reset 的关键 |
VTref 是很多人接线的第一个坑。它只负责告诉调试器“目标板的电平是多少”,不是给目标板供电的。JTAG 引脚的电平转换全靠这个参考电压,VTref 悬空或实测为 0V 时,调试器会直接报告无法识别目标。另一个高频坑是 TMS 没有上拉:TMS 在芯片侧一般有内部上拉,但如果板上有外设把 TMS 复用到普通 IO,且默认输出低电平,JTAG 链路就会被卡死在某个状态。
3.2 JLink 有 JTAG 怎么接:常用接口的引脚映射
日常遇到“JLink 有 JTAG 怎么接”的问题,根源是 J-Link 一端是 20-pin 排针,目标板一端可能是 10-pin 或更小的连接器。常见映射如下:
| J-Link 20-pin | 目标板 10-pin | 信号 |
|---|---|---|
| 1 VTref | 1 VCC | 参考电平 |
| 7 TMS | 2 SWDIO/TMS | 模式选择 |
| 9 TCK | 4 SWCLK/TCK | 时钟 |
| 5 TDI | 8 TDI | 数据输入 |
| 13 TDO | 6 TDO | 数据输出 |
| 15 nRESET | 10 RESET | 系统复位 |
| 2/4/6/8/10/12/14/16/18/20 GND | 3/5/9 GND | 地 |
接线时先确认目标板的丝印,不要只看针数就对齐插。10-pin 连接器常见有两种定义,一种带 ETM 跟踪信号,一种纯 JTAG/SWD 复用,两者的 TDO、TDI 位置可能不同。最稳妥的接线顺序是:先接地和 VTref,再接 TCK、TMS,最后接 TDI、TDO。上电后先量 VTref 与 GND 之间的电压,再接调试器,可以避免插反烧坏调试器接口。
3.3 STM32 的关闭 JTAG 操作:释放 PB3/PB4/PA15 的代价
STM32 的 JTAG 和相关热搜词里的“关闭 jtag”关系很深。F1 系列默认把 PA13/PA14/PA15/PB3/PB4 分配给 SWJ 调试口,其中 PA13/PA14 是 SWD 的 SWDIO/SWCLK,PA15/PB3/PB4 是 JTAG 的 JTDI/JTDO/JNTRST。如果这些引脚要被用作普通 GPIO,必须先在代码里关闭 JTAG,保留 SWD。标准库写法如下:
// 标准库方式:关闭 JTAG,保留 SWD 功能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);HAL 库对应的方法是__HAL_AFIO_REMAP_SWJ_JTAG_DISABLE(),使用前同样要先开启 AFIO 时钟。这段代码的逻辑是:SWJ 默认全功能开启,映射配置改成只保留 SWD,释放 JTAG 占用的三个引脚。参数名字中的JTAGDisable只关 JTAG 不关 SWD,PA13/PA14 仍可用于调试。
需要特别提示:关闭 JTAG 后,PA15/PB3/PB4 作为 GPIO 使用没有问题,但如果固件里再次把 SWJ 全部关闭(GPIO_Remap_SWJ_Disable),PA13/PA14 也会失去调试功能。此时再想用 J-Link 连 SWD 是连不上的,恢复办法是把 BOOT0 拉高重新上电进系统存储器,用串口 ISP 擦除固件,或者用调试器的 connect under reset 抢在用户代码执行前抓取芯片。这个操作属于高风险的恢复流程,量产板尽量保留一个串口下载通道作为后手。
4. JTAG 链路报错定位:从 209040 到 Communication Failure
4.1 Quartus 报错 209040/209053:先查链,再查芯片
热搜词里的 error (209040): can't access jtag chain 和 error (209053): unexpected error in,主要来自 Intel/Altera 的 Quartus Programmer。前者表示 JTAG 链上的器件无法访问,后者通常是 JTAG server 在底层握手时出现异常。两行报错经常同时出现,处理顺序应该一致:先确认链路物理状态,再怀疑软件环境。
| 现象 | 优先排查方向 |
|---|---|
| 209040 且之前能连、现在连不上 | 目标板供电、VTref、排线接触 |
| 链上有多个 FPGA/CPLD | 检查链上各器件 TDO->TDI 顺序与 BSDL 版本 |
| 209053 伴随 USB-Blaster 异常 | 换 USB 口、重装驱动、回退 Quartus 版本 |
| 低温或手碰线缆就报错 | 连接器虚焊或排线太长,降频验证 |
多片器件菊花链时,Quartus 会先枚举全链的 IDCODE,如果中间有一片处于三态或 TDO 无上拉,整条链都枚举失败,报的往往是 209040 而不是“第 3 片有问题”。这是 JTAG 链式结构的特性:TDI 到 TDO 串行贯通,任何一环断路,后面的器件全部不可见。排查时先把链上其他器件摘掉或旁路,只留一片目标器件,确认单点能连上再逐步加回。
4.2 MDK 的 SWD/JTAG Communication Failure:复位窗口是关键
MDK 的 Debug 弹窗报 SWD/JTAG Communication Failure,是所有嵌入式工程师都见过的经典问题。这个报错覆盖的场景很广:芯片跑进了低功耗模式、时钟没起来、ALE 引脚冲突、甚至板子只是上电慢了一拍。最有效的处理办法是改变连接时序:
- 目标板完全断电 10 秒以上,让滤波电容放完电。
- 重新插拔调试排线,确认两端的连接器都插到位。
- 打开 Options for Target -> Debug -> Settings,Connect 改成 under Reset,Reset 改成 Hardware Reset。
- 把调试时钟频率降到 1MHz 或更低。
- 按住目标板复位键,点击下载,看到进度条后再松手。
这里的核心原理是:芯片上电后用户代码可能立刻重配置引脚,甚至把 JTAG/SWD 引脚改成普通 IO,调试器还没来得及接管,链路就被用户程序抢占了。Connect under Reset 模式下,调试器先用 nSRST 把芯片按住,在复位期间完成 JTAG 初始化和 IDCODE 读取,再释放复位。这也是为什么“普通连接失败,under Reset 能成功”这一现象如此普遍。
4.3 降低 TCK 频率验证线缆与连接器
OpenOCD 是排查 JTAG 链路最直接的工具,因为它把链路扫描和调试动作分开,报错信息比 IDE 更精确。用 J-Link 接一块 STM32 的开发板,最小验证命令如下:
openocd -f interface/jlink.cfg -f target/stm32f1x.cfg \ -c "adapter_khz 1000" \ -c "scan_chain" \ -c "runtest 10; exit"adapter_khz 1000把 TCK 强制设为 1MHz;scan_chain枚举链上所有器件的 IDCODE;runtest 10让 TAP 在 Run-Test/Idle 状态停留 10 个 TCK,确保链路空闲后再退出。如果这条命令能正确列出目标芯片的 IDCODE,说明链路物理上没有问题。把频率逐步从 4000 调到 1000 再调到 500,观察是否在某个频率点突然失败,就能判断是排线过长还是连接器接触不良。
OpenOCD 输出中的 IDCODE 应与目标芯片数据手册一致。如果读回的 IDCODE 是 0x00000000 或 0xFFFFFFFF,优先复查 TDO 的焊接和上拉电阻,而不是继续调软件参数。
5. 回环法自检 JTAG 链路:验证 TDI/TDO 与信号时机的技巧
JTAG 链路故障里,TDI 或 TDO 断路的比例很高,但它们在软件层面的表现和 TMS 被拉低非常相似。一个不依赖仿真器自带诊断、能直接区分“线断了”和“状态机没走对”的方法是回环法:把连接器上的 TDI 与 TDO 用杜邦线或跳线帽短接,再通过调试器发送一段已知数据,看读回的数据是否一致。
openocd -f interface/jlink.cfg -f target/stm32f1x.cfg \ -c "adapter_khz 1000" \ -c "init" \ -c "irscan 0 0x1; drscan 0 32 0xA5A5A5A5" \ -c "exit"irscan 0 0x1是给链上第一个器件发 IDCODE 指令,drscan 0 32 0xA5A5A5A5是把 32 位数据从 TDI 串入并从 TDO 读回。TDI 与 TDO 短接后,读到的数据应当与发出的A5A5A5A5完全一致;如果读回全 1,说明 TDO 到调试器之间存在断路;读回全 0,则大概率 TDO 被拉低短路。这个测试绕过了目标芯片内部逻辑,只验证排线、连接器和调试器三方的物理通路,是板级调试里最干净的一招。
最后补充一个信号时机的检查方法:用逻辑分析仪同时抓 TCK、TMS、TDI、TDO 四根线,看 TMS 的电平变化是否发生在 TCK 上升沿之前。正常情况下,TMS 在上升沿前稳定、上升沿后保持,TDO 的数据在 TCK 下降沿之后才更新。如果抓到 TMS 在上升沿附近跳变,说明调试器输出驱动能力不足或线缆过长。此时把 TCK 频率降低,确认波形边沿余量恢复,链路时序问题就算定位清楚了。
本文还有配套的精品资源,点击获取