4. SWD核心概念

JTAG 接口的核心,说白了就是通过 TDI 往指令寄存器(IR)里写指令,然后数据寄存器(DR)根据这条指令干活。指令寄存器通常 4~5 位宽,具体看芯片设计。我刚开始接触 JTAG 时,总觉得指令就是一堆 0 和 1,后来调了块板子才发现——选错指令,整个调试链路都跑不通。

这一节我们重点看几条标准指令:BYPASS、EXTEST、SAMPLE/PRELOAD、IDCODE,以及用户自定义指令怎么玩。

4.1 标准指令概览

IEEE 1149.1 标准定义了几条强制指令和可选指令。我整理了一张表,方便你对照着看:

指令名称 指令编码(4位示例) 类型 选中的数据寄存器 主要用途
BYPASS 1111 强制 1 位 Bypass 寄存器 旁路当前器件,缩短扫描链
EXTEST 0000 强制 边界扫描寄存器 板级互连测试
SAMPLE/PRELOAD 0001 强制 边界扫描寄存器 采样引脚状态 / 预加载数据
IDCODE 0010 可选 32 位 ID 寄存器 读取器件标识码
USERCODE 0011 可选 32 位 User 寄存器 读取用户自定义标识
HIGHZ 0100 可选 Bypass 寄存器 将所有输出置为高阻
我的习惯:调试新板子时,第一件事就是发 IDCODE 指令。如果读回来的 ID 和 datasheet 对不上,先查 JTAG 链路,别急着跑测试。

4.2 BYPASS 指令

这条指令最简单,也最常用。当你在一条 JTAG 链上挂了多个器件,只想测其中一个时,其他器件必须设成 BYPASS。

BYPASS 指令选中 1 位的 Bypass 寄存器。TDI 进来的数据,经过一个 TCK 周期就从 TDO 出去了。说白了就是个直通移位寄存器。

// 假设链上有 3 个器件,只想访问器件 2
// 器件 1 和器件 3 的指令寄存器写入 BYPASS(0xF)
// 器件 2 写入目标指令(如 IDCODE)
// 此时扫描链总长度 = 1 + 32 + 1 = 34 位

我曾经遇到过一个问题:链上 8 个器件,其中一个 BYPASS 没生效,结果整个扫描链长度算错了,TDO 回来的数据全是乱的。排查了半天才发现是那个器件的指令寄存器没锁存住。

注意:BYPASS 指令写入后,必须经过 Update-IR 状态才能生效。只走 Shift-IR 是不够的,很多人在这里翻车。

4.3 EXTEST 指令

EXTEST 是板级测试的核心指令。它选中边界扫描寄存器,把 TDI 输入的数据加载到输出引脚上,同时捕获输入引脚的状态。

说白了,就是通过 JTAG 接口控制芯片的引脚电平,检测板级焊接有没有短路、断路。

EXTEST 的执行流程:

  1. PRELOAD 阶段:先用 SAMPLE/PRELOAD 指令把安全数据加载到边界扫描寄存器
  2. 切换指令:写入 EXTEST 指令
  3. 驱动输出:边界扫描寄存器中的数据驱动到输出引脚
  4. 捕获输入:相邻器件的输入引脚捕获电平
  5. 移位读出:通过 Shift-DR 把捕获结果移出来
避坑指南:我曾经在调试一块 FPGA 板时,直接切到 EXTEST 没做 PRELOAD,结果输出引脚瞬间变成未知状态,把后级的一个运放烧了。从那以后,我每次切 EXTEST 前一定先做 PRELOAD,把输出设成安全电平。

4.4 SAMPLE/PRELOAD 指令

这条指令有两个功能,名字已经说清楚了:

  • SAMPLE:在不影响芯片正常工作的情况下,采样引脚当前状态
  • PRELOAD:在切到 EXTEST 之前,预加载边界扫描寄存器的数据

我个人的习惯是,在系统运行时先用 SAMPLE 抓一抓关键引脚的状态,看看时序对不对。这比用示波器去点方便多了,尤其是 BGA 封装的芯片,引脚根本没法直接测。

// SAMPLE 操作示例
// 1. 进入 Shift-IR,写入 SAMPLE/PRELOAD 指令(0x1)
// 2. 进入 Run-Test/Idle
// 3. 进入 Shift-DR,移出边界扫描数据
// 4. 移出的数据就是当前引脚状态的快照

4.5 IDCODE 指令

IDCODE 是调试时最常用的指令之一。它返回一个 32 位的器件标识码,包含:

  • 位 [31:28]:版本号
  • 位 [27:12]:器件编号(16 位)
  • 位 [11:1]:制造商编号(11 位)
  • 位 [0]:固定为 1

你想想看,如果 JTAG 链上挂了不同厂家的芯片,通过 IDCODE 就能自动识别每个器件的位置和型号。很多调试工具(比如 OpenOCD)启动时都会自动扫描 IDCODE 来建立器件列表。

小技巧:如果读 IDCODE 时 TDO 一直输出 0xFFFFFFFF 或 0x00000000,大概率是 TCK 或 TMS 信号有问题。先查硬件连接,别急着怀疑芯片坏了。

4.6 用户自定义指令

标准指令之外,芯片厂商可以定义自己的指令。比如 ARM 的 Cortex-M 系列就用自定义指令来实现 SWD 切换、调试寄存器访问等功能。

用户自定义指令的编码范围是 0x08~0x3F(4 位指令寄存器时),具体看芯片手册。我见过一些 FPGA 厂商用自定义指令来配置 PLL、读取温度传感器、甚至控制内部逻辑分析仪。

// 以某款 MCU 为例的自定义指令
// 0x0A: 读取芯片内部温度
// 0x0B: 写入调试锁定寄存器
// 0x0C: 触发软件复位

// 使用流程
// 1. Shift-IR: 写入 0x0A
// 2. Update-IR: 锁存指令
// 3. Shift-DR: 移出 16 位温度数据

嗯,这里要注意:自定义指令的编码不能和标准指令冲突。有些芯片会把 0x0~0x7 保留给标准指令,0x8~0xF 给自定义。但不同厂商的分配方式不一样,一定要看 datasheet。

4.7 指令寄存器操作时序

指令寄存器的操作和状态机绑定。我画了一张流程图,帮你理清指令写入的完整路径:

JTAG 指令寄存器操作流程图 JTAG 指令寄存器操作流程 Test-Logic-Reset Run-Test/Idle Select-DR-Scan Select-IR-Scan Capture-IR Shift-IR(写指令) Exit1-IR Update-IR(锁存指令) 关键:指令在 Shift-IR 写入,在 Update-IR 锁存生效

从流程图可以看到,指令写入的关键路径是:Select-IR-Scan → Capture-IR → Shift-IR → Exit1-IR → Update-IR。Capture-IR 阶段会把当前指令寄存器的值捕获到移位链中,Shift-IR 阶段逐位写入新指令,最后 Update-IR 锁存生效。

我曾经踩过的坑:写指令时忘了走 Update-IR,以为 Shift-IR 完指令就生效了。结果 TDO 一直输出乱码,查了三个小时才发现是状态机没走到 Update-IR。记住:Shift-IR 只是把数据移进去,Update-IR 才是真正生效的那一步。

4.8 指令寄存器的实际调试技巧

最后分享几个我在项目中积累的经验:

  • 读 IDCODE 确认链路:上电后第一件事,发 IDCODE 指令,确认每个器件都能正常响应
  • BYPASS 缩短扫描链:链上器件多时,把不操作的都设成 BYPASS,减少移位时间
  • PRELOAD 保护电路:切 EXTEST 前一定做 PRELOAD,避免输出引脚出现意外电平
  • 自定义指令先读手册:不同厂商的自定义指令编码可能冲突,务必确认
  • 状态机走完整:指令写入必须走到 Update-IR,别偷懒

指令寄存器这部分,说白了就是 JTAG 的「遥控器」。你按哪个键,芯片就执行哪个功能。理解每条指令的用途和操作流程,后面调试时才能得心应手。