1、JTAG协议基础:JTAG历史、IEEE 1149.1标准、TAP控制器状态机详解
各位同学,咱们今天正式开讲JTAG。说实话,这玩意儿在嵌入式领域就像空气一样——你天天在用,但很少有人真正停下来琢磨它到底是怎么工作的。
我记得刚入行那会儿,拿着一个JLINK给板子烧程序,插上就能用,从来没想过背后是什么协议在支撑。直到有一次,一块新板子死活连不上调试器,我才被迫去啃JTAG的spec。嗯,从那以后,我再也不敢小看这5根线了。
1.1 JTAG的诞生:一段不得不提的历史
JTAG的全称是Joint Test Action Group,中文叫联合测试行动组。这名字听起来像某个特工组织,其实它是一群工程师在1985年凑在一起搞出来的标准。
当时PCB越来越密,芯片引脚越来越多,传统的飞针测试和床板测试已经搞不定了。你想啊,一个BGA封装的芯片,几百个引脚全藏在肚子底下,测试探针根本碰不到。怎么办?这帮人想了个绝妙的点子:干脆在芯片内部做一套测试电路,通过串行方式访问每个引脚的状态。
1990年,这个方案被IEEE采纳,成了IEEE 1149.1标准,也叫Standard Test Access Port and Boundary-Scan Architecture。说白了,就是边界扫描标准。
核心思想:在芯片的每个I/O引脚和内部逻辑之间,插入一个移位寄存器单元(边界扫描单元)。这些单元串联成一条链,通过一个专用的测试访问端口(TAP)来控制。这样,即使物理上够不到引脚,也能通过串行方式读取或设置每个引脚的状态。
1.2 IEEE 1149.1标准:到底规定了什么?
这个标准其实就干了三件事:
- 定义了TAP(Test Access Port)的接口信号——就是那5根线
- 定义了TAP控制器的状态机——16个状态,来回跳
- 定义了指令寄存器和数据寄存器的操作方式——你想让芯片干啥,就往指令寄存器里写啥
咱们先看接口信号。标准规定了5根必备线,外加1根可选的:
| 信号名 | 方向 | 功能描述 |
|---|---|---|
| TCK | 输入 | 测试时钟。所有操作都在这个时钟的上升沿或下降沿采样 |
| TMS | 输入 | 测试模式选择。控制TAP状态机的跳转 |
| TDI | 输入 | 测试数据输入。串行数据从这里进入芯片 |
| TDO | 输出 | 测试数据输出。串行数据从这里流出芯片 |
| TRST | 输入 | 测试复位(可选)。异步复位TAP控制器 |
个人经验:我建议你在设计板子时,一定要把TRST拉出来,哪怕只用个GPIO模拟。有一次我在调试一块量产板,TAP控制器莫名其妙卡死在未知状态,TRST拉一下立刻恢复,省了我半天查线的时间。
1.3 TAP控制器状态机详解
好,重头戏来了。TAP控制器状态机是整个JTAG的核心。你想想看,5根线里只有TMS是控制信号,怎么实现那么多复杂操作?答案就是——靠状态机。
这个状态机一共有16个状态,每个状态在TCK的上升沿根据TMS的电平决定下一个状态跳到哪里。说白了,你只要在TCK的每个上升沿之前,把TMS拉高或拉低,就能让状态机走到你想要的任何位置。
我画了一张图,把状态机的全貌展示出来:
这张图看着复杂,其实规律很简单。我总结了一个口诀:
状态机口诀:
- TMS=0:沿着当前路径往下走(Capture → Shift → Exit1 → Pause → Exit2)
- TMS=1:退出当前路径(回到Select或Update)
- 连续5个TMS=1:无论你在哪个状态,都会回到Test-Logic-Reset
为什么会设计成连续5个1就复位?我猜是为了容错。你想啊,如果调试器跟目标芯片的时钟不同步了,你只要连续拉高TMS 5个周期,状态机肯定回到已知状态。这个设计在实战中救过我很多次。
1.4 两条核心路径:DR扫描 vs IR扫描
状态机里其实只有两条核心路径:
- DR扫描路径:Select-DR-Scan → Capture-DR → Shift-DR → Exit1-DR → Update-DR
- IR扫描路径:Select-IR-Scan → Capture-IR → Shift-IR → Exit1-IR → Update-IR
两条路径完全对称,只是操作的对象不同。DR路径操作的是数据寄存器,IR路径操作的是指令寄存器。
我举个例子你就明白了。假设你想读取芯片的IDCODE:
- 先走IR路径,把指令"IDCODE"(比如二进制1001)移入指令寄存器
- 再走DR路径,把芯片ID移出来
就这么简单。每次操作都是先送指令,再送数据。
避坑指南:我曾经在一个项目里,发现读出来的IDCODE总是全0。查了半天,原来是TDO引脚虚焊了。JTAG的TDO是唯一的数据输出线,如果它断了,你读到的永远是0。所以调试时,先确认TDO有没有波形,这是最基本的排查步骤。
1.5 边界扫描寄存器:JTAG的"眼睛"
前面提到边界扫描单元,这里展开说一下。每个I/O引脚对应一个边界扫描单元(BSC),这些单元串联成边界扫描寄存器(BSR)。
每个BSC可以:
- 捕获(Capture):把引脚上的电平值锁存进来
- 移位(Shift):把数据沿着链传给下一个BSC
- 更新(Update):把数据输出到引脚上
说白了,BSR就是芯片的"眼睛"和"手"——能看引脚状态,也能控制引脚输出。
| 操作 | 对应状态 | 作用 |
|---|---|---|
| Capture | Capture-DR | 把引脚当前电平锁存到BSR |
| Shift | Shift-DR | 把BSR数据串行移出(同时移入新数据) |
| Update | Update-DR | 把BSR数据输出到引脚 |
1.6 常见的JTAG指令
IEEE 1149.1规定了几条强制性指令,所有兼容芯片都必须支持:
| 指令 | 指令码(示例) | 功能 |
|---|---|---|
| BYPASS | 1111 | 旁路模式,数据直接通过1位寄存器传到TDO |
| SAMPLE/PRELOAD | 0001 | 采样引脚状态或预加载数据到BSR |
| EXTEST | 0000 | 外部测试,通过BSR控制引脚输出,检测板级互联 |
| IDCODE | 1001(可选) | 读取芯片ID(可选,但几乎所有芯片都支持) |
注意:BYPASS指令在JTAG链有多颗芯片时特别有用。你想访问链上的某颗芯片,就把其他芯片都设成BYPASS,这样数据只经过它们1位寄存器就出来了,不会浪费时钟周期。
1.7 实战中的JTAG时序
说了这么多理论,咱们看一段实际的JTAG操作时序。假设我们要读取芯片的IDCODE:
// 伪代码:读取IDCODE
// 假设指令寄存器长度=4位,IDCODE指令=0b1001
// 第一步:进入Select-IR-Scan状态
// 从Run-Test/Idle开始,TMS=1,1,0
// 状态跳转:Run-Test/Idle → Select-DR-Scan → Select-IR-Scan
// 第二步:走IR路径,移入指令
// Select-IR-Scan → Capture-IR (TMS=0)
// Capture-IR → Shift-IR (TMS=0)
// Shift-IR: 移入4位指令 1001 (LSB first)
// TCK上升沿: TDI=1, TMS=0 (移入第1位)
// TCK上升沿: TDI=0, TMS=0 (移入第2位)
// TCK上升沿: TDI=0, TMS=0 (移入第3位)
// TCK上升沿: TDI=1, TMS=1 (移入第4位,同时退出)
// Shift-IR → Exit1-IR (TMS=1)
// Exit1-IR → Update-IR (TMS=1)
// Update-IR: 指令生效
// 第三步:进入Select-DR-Scan状态
// Update-IR → Select-DR-Scan (TMS=1)
// 第四步:走DR路径,读出ID
// Select-DR-Scan → Capture-DR (TMS=0)
// Capture-DR → Shift-DR (TMS=0)
// Shift-DR: 移出32位ID (LSB first)
// 连续32个TCK上升沿,TMS=0,从TDO读取数据
// 第32个TCK上升沿,TMS=1,退出
// Shift-DR → Exit1-DR (TMS=1)
// Exit1-DR → Update-DR (TMS=1)
// 第五步:回到Run-Test/Idle
// Update-DR → Select-DR-Scan (TMS=1)
// Select-DR-Scan → Run-Test/Idle (TMS=0)
这段代码看着长,其实核心就几步:送指令 → 送数据 → 读数据。我刚开始写JTAG驱动时,总是搞不清TMS应该在TCK上升沿之前还是之后设置。后来养成一个习惯:在TCK上升沿之前,先把TMS和TDI准备好,这样时序就不会乱。
关键时序要点:
- 所有状态跳转发生在TCK上升沿
- TMS和TDI在TCK上升沿之前建立稳定
- TDO在TCK下降沿之后采样(通常在下降沿读取)
- 数据移位是LSB first(最低位在前)
1.8 本章小结
好了,咱们把JTAG的基础捋了一遍。核心就三件事:
- 5根线:TCK、TMS、TDI、TDO、TRST(可选)
- 16个状态的状态机:两条路径(IR和DR),TMS控制跳转
- 边界扫描寄存器:芯片的"眼睛"和"手"