JTAG基础入门原理解析
JTAG(IEEE 1149.1)作为现代电子设计与测试的核心接口,通过四线(TDI/TDO/TMS/TCK)实现复杂电路板的边界扫描测试。JTAG是解决系统崩溃、驱动调试的终极工具,配合OpenOCD和GDB可实现对芯片的精准控制。文章还包含FPGA的BSDL文件解析和边界扫描寄存器操作实例,完整呈现了JTAG在硬件测试和嵌入式开发中的关键价值。
仅作为学习记录
作为BSP开发工程师,是需要软硬件都会的。芯片引脚密密麻麻,板子连线成千上万,怎么测通断、烧程序、调代码?JTAG接口就是干这些的。JTAG(IEEE 1149.1)作为现代电子设计与测试的核心接口,其边界扫描技术是解决复杂电路板互连测试、芯片调试及程序烧录的关键。四根线(TDI, TDO, TMS, TCK)咋就能把芯片管脚‘摸’个遍?
1、JTAG是什么?
JTAG是20世纪80年代开发的IEEE标准(1149.1),用来解决电路板的生产制造检修问题。现在JTAG还可以用来烧程序、调试以及检测端口状态。
测试电路板硬件: 生产时,用 JTAG 命令让芯片 A 的某个引脚输出 1,然后用 JTAG 命令去读芯片 B 对应连接的引脚,看它收到的是不是 1。如果不是,说明这条线有问题(断了/短路了)。这是它最初也是最重要的使命!不用拿万用表一根根戳。
烧录程序: 很多芯片(如微控制器/单片机/MCU)的程序(固件)可以通过 JTAG 接口直接灌进去(写入 Flash 存储器)。比串口快,也更底层可靠。
调试程序: 这是 BSP/底层开发的核心!
探测引脚状态: 在芯片运行时,偷偷看看某个引脚的电平状态,或者强行改变它。
在 BSP/底层开发中怎么用?
想象你在开发一个嵌入式系统(比如基于 ARM Cortex-M 的 MCU)的板级支持包(BSP)或底层驱动。系统刚启动(Bootloader),或者驱动有问题,系统直接挂死(死机),连串口都没输出!这时候 JTAG 就是你的救命稻草。
连接与初始化:
用 JTAG 调试器(硬件设备,如 J-Link, ST-Link, DAPLink)一头插电脑 USB,另一头通过 JTAG/SWD 接口连接到目标板的 JTAG 引脚。
调试器通过 JTAG 协议与目标芯片通信。
核心调试功能:
停止/暂停: 让正在狂奔的 CPU 立刻暂停(Halt)。这是调试的基础!系统卡死了?按个“暂停键”。
看内存/寄存器: CPU 停了之后,你可以通过调试器:
查看 CPU 内部所有寄存器的值(比如 PC 指针指向哪里?SP 栈指针在哪?R0-R15 是什么值?)。
查看 内存任意位置的内容(代码段、数据段、堆、栈、外设寄存器映射区域)。
修改寄存器和内存的值(试试改个值看能否恢复?)。
单步执行: 让 CPU 执行 一条指令,然后马上又暂停。你可以一步步看着程序跑,观察每步后寄存器和内存的变化。精准定位哪条指令出了问题。
设置断点: 在代码的某一行(对应某个内存地址)设置个「陷阱」。当 CPU 执行到这里时,自动暂停。比如你在怀疑出问题的函数入口设个断点。
读写 Flash: 烧写新的程序固件到芯片的 Flash 里。
如何用?
通常你会配合一个 调试器软件(如 OpenOCD)和一个 调试客户端(如 GDB 命令行,或基于 GDB 的图形界面 IDE 如 Keil, IAR, VSCode + Cortex-Debug)。
OpenOCD 负责与硬件调试器(J-Link 等)通信,理解 JTAG/SWD 协议,控制目标芯片。
GDB/IDE 负责和你交互,接收你的调试命令(断点、单步、查看变量),然后通过 TCP/IP 或其他方式告诉 OpenOCD 具体要做什么操作(如读哪个寄存器、设哪个地址的断点),OpenOCD 再翻译成 JTAG/SWD 命令发给目标芯片执行。
总结:在 BSP/底层开发中,JTAG 是当系统崩溃、代码行为诡异、需要深入芯片内部窥探和精准控制时的终极调试工具。没有它,调试底层问题会极其困难。
1.1边界扫描
如图1所示,在一个电路板上有两个芯片元件,一个CPU和FPGA。

图1
每个芯片都会有很多引脚,那么芯片之间的互联就会有很多连线,图2示意图仅仅画了4条连接线。

图2
正常情况下,对于芯片厂商,一次制作成千上万个PCB板子,每个班子上都有许许多多连接线,厂家需要如何保证每根芯片连接线都是正常的呢?这么大的工作量也不可能通过手工来每一根线进行检测。因此JTAG就应运而生了。

图3
JTAG可以控制芯片的每个引脚,图3中,我们可以通过JTAG使得所有的CPU引脚发送数据,而所有的FPGA引脚接收数据,然后根据FPGA中是否收到准确的数据来判断所有的芯片连接是否正常。
实际上JTAG的连接包括4根信号线,分别是TDI、TDO、TMS和TCK。从电脑主机的角度来看,TDI、TMS、TCK为输出,TDO为输入,如果从待测试的芯片角度来看则相反。

图4
JTAG的四根信号线有特定的连接方式,如图5所示,TMS和TCK是并联在所有待测芯片上的。

图5
TDI和TDO信号线则是串联在一起形成一个闭环链条。在JTAG的技术手册中,这种方式也叫JTAG链。

图6
因此,每个JTAG链上的芯片都会有四根线连接,其中三个输入,一个输出。在技术手册中,还会有一个可选的信号线TRST作为第五根信号线。一般而言,JTAG的四个引脚都是专用引脚。
现在所有的JTAG应用越来越普遍,基本上所有多引脚的芯片都会包含JTAG边界扫描功能。此外正如我们开头所说,CPU和FPGA厂商还用JTAG接口进行调试,对于可编程硬件FPGA和CPLD,还可以用JTAG接口继续配置和烧录程序。
2、JTAG如何起作用?
上一章我们知道了JTAG是如何连接芯片,现在学习具体工作原理以及如何通过PC端来控制器运行。
2.1PC控制JTAG
一般我们用JTAG连接线来连接PC和JTAG端口,电脑端口有并行端口(也叫打印机端口db25)、USB端口以及网线端口。对于数据量不大的情况下推荐并行端口,操作简单。对于大数据量推荐USB端口和网口,其速度快但是操作复杂一些。

图7 并行端口DB25
2.2、并行端口
电脑主机的并行端口12根线为输出,5根线为输入。对于JTAG而言,只用到了3个输出和一个输入(从PC角度来看输入输出)。因此,中间需要用到一些缓存器(电平转换与缓冲驱动电路),如赛灵思的parallel-III cable。
从软件代码的角度来看,并行端口由于简单是最理想的JTAG端口。例如,阿尔特拉的ByteBlaster JTAG接口用C语言改变TCK信号代码如下:
#define lpt_addr 0x378 // 定义并行端口的基地址(数据端口)
#define TCK 0x01 // 定义 TCK 信号对应的数据位(D0,即数据端口的最低位)
void toggle_TCK() // 定义一个函数,功能是翻转一次 TCK 信号
{
outport(lpt_addr, 0); // 步骤1:向数据端口写入0 (0x00),将所有数据线(D0-D7)置为低电平(包括 TCK)
outport(lpt_addr, TCK); // 步骤2:向数据端口写入 TCK (0x01),将 D0 线(即 TCK)置为高电平,其他线保持低
outport(lpt_addr, 0); // 步骤3:再次向数据端口写入0 (0x00),将所有数据线(包括 TCK)置回低电平
}
2.3、JTAG TAP控制器
PC和芯片之间的JTAG连接方式如图6,下面介绍这四根信号线分别代表什么意思。
TCK
TCK是JTAG的时钟信号,另外三个信号TDI、TDO、TMS都是跟该时钟信号同步的。一般其他三根信号都是在TCK时钟的上升沿发生改变或者状态的切换。
TMS
在每个芯片的内部都有JTAG TAP控制器,图6中有两个CPU和FPGA两个芯片,那么就有两个TAP控制器。一般我们在数据手册上看到的状态控制器就是这个,它有16个状态,如图8所示。TMS就是个控制TAP控制器的信号,根据TMS的高低电平变化,TAP控制器进入这16个状态中的一种,又因为同一个PCB板子上TMS是并联所有芯片 ,因此所有芯片都会处于同一状态。

图8
上图中每个状态旁边的0和1代表的是TMS的低、高电平。比如如果TAP状态控制器处于Select DR-Scan状态,且TMS为0,那么当TCK时钟信号切换时,TAP的状态就会变化下面的Capture-DR。
这里再强调一遍,要想JTAG正常工作,所有的链上的TAP控制器必须处于同一状态。PCB板上电后,是如何保证所有芯片的TAP处于同一状态呢?仔细观察图8,不管TAP在哪个状态,如果TMS在5个时钟周期内都保持1,那么TAP都会变成Test-Logic-Reset状态,这便是用来同步TAP状态的方法。
TAP 控制器 = 芯片内部的调试开关板(有 16 个档位)。
TMS = 遥控器按钮(0 或 1),控制开关板换档。
TCK = 节拍器,换档动作必须跟着节拍走。
状态图 = 开关板的档位切换说明书(按 0 切到哪,按 1 切到哪)。
所有芯片状态同步:因为共用同一个遥控器(TMS 并联)。
上电同步:用“强制复位大招”(连续 5 个 TMS=1)把所有开关板都拉回起点。
来看下面的代码,如何将TAP控制器切换到Shift-IR状态。
// first sync everybody to the test-logic-reset state
for(i=0; i<5; i++) JTAG_clock(TMS);
// now that everybody is in a known and identical state, we can move together to another state
// let's go to Shift-IR
JTAG_clock(0);
JTAG_clock(TMS);
JTAG_clock(TMS);
JTAG_clock(0);
JTAG_clock(0);
TDI和TDO
现在我们已经知道了如何切换TAP状态了,下面介绍JTAG最重要的两个状态Shift-DR和Shift-IR。

图9
Shift-DR和Shift-IR必须结合TDI和TDO信号线才能起作用,首先介绍Shift-IR。
每个芯片的TAP控制器中都有一个IR寄存器,也叫做指令寄存器。你可以把相关指令写入这个寄存器,然后TAP控制器会根据IR寄存器的指令进行相关操作。
每个IR寄存器都有一定的长度,我们假设CPU的IR寄存器是5位,FPGA的寄存器是10位,那么通过TDI和TDO的信号线连接方式,CPU和FPGA的IR寄存器其实是串联的,如图10所示。
图10
我们从PC主机的角度来看,整个链的IR寄存器是15位的,5位CPU和5位FPGA。
要想将IR寄存器写入数据,我们需要将TAP控制器的状态切换成Shift-IR,然后PC通过TDI信号线写入15位数据。前10位数据写入的是FPGA的IR寄存器,后5位数据写入的是CPU的IR寄存器。如果PC写入的数据多于15位,那么溢出的数据就会通过TDO信号线再被PC端给接收,只不过延时了15个时钟周期。
例如,我们想吧数值00100写入CPU的IR寄存器,而0000000010写入FPGA的IR寄存器,C语言代码如下:
// Because the bits are shifted through in a chain, we must start sending the data for the device that is at the end of the chain
// so we send the 10 FPGA IR bits first
JTAG_clock(0);
JTAG_clock(1);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(0);
// then send the 5 CPU IR bits
JTAG_clock(0);
JTAG_clock(0);
JTAG_clock(1);
JTAG_clock(0);
JTAG_clock(0 | TMS); // last bit needs to have TMS active (to exit shift-IR)
在我们的假设中,CPU的IR是5位(可以表示数值0~31)。那么CPU的IR寄存器可以支持32条JTAG指令。实际上,一个CPU可能只会有5~10条指令,剩下的IR寄存器数值都没有用。
同样的对于FPGA,它的IR寄存器是10位,那么它可以支持1024条JTAG指令(大部分也是没用的)。
但是JTAG有几条强制的指令必须都有:
- BYPASS
- EXTEST
- SAMPLE/PRELOAD
- IDCODE(这个不是强制的,但是非常常见)
每个芯片的都有IR数值的指令集,从芯片手册上都可以查到。
每个芯片的TAP控制器都只有一个IR寄存器,但是会有很多DR寄存器。我们知道IR寄存器数据切换是通过TAP的Shift-IR状态,类似的,DR寄存器的数据切换也是这样,只不过状态时TAP的Shift-DR状态。
每一个IR寄存器的数值都会对应一个不同的DR寄存器,在我们的假设中IR寄存器为5位,那么就有32个IR数值,因此就有32个DR寄存器(如果32个IR数值都被当做指令的话)。
IR寄存器 - “模式选择开关”:
每个芯片只有一个IR寄存器。
这个开关决定你当前想用工具箱干什么。比如:
开关拨到位置
BYPASS:意思是“跳过这个芯片,让它别碍事”。开关拨到位置
IDCODE:意思是“告诉我这个芯片的身份证号”。开关拨到位置
SAMPLE:意思是“让我看看芯片各个管脚当前的状态”。开关拨到位置
EXTEST:意思是“让我能控制芯片管脚输出电平来测试电路板连线”。开关的不同位置,就对应着不同的指令(模式)。
这个开关有多少个可能的位置呢?取决于它有多少位(比如5位,就有2^5=32种可能的拨法,虽然很多位置可能是预留的或者无效的)。
DR寄存器 - “工具抽屉”:
每个芯片有很多个不同的DR寄存器。
每个DR寄存器就像工具箱里一个特定功能的抽屉:
一个抽屉专门用来存/取芯片的身份证号(IDCODE)。
一个抽屉专门用来读取或控制芯片管脚的状态(边界扫描寄存器)。
一个抽屉是个空跑通道(BYPASS寄存器,里面就一个位)。
可能还有别的抽屉(寄存器)用来做调试、烧录程序等等。
每个抽屉(DR寄存器)都很大,能存很多位信息(比如边界扫描寄存器可能有几百位)。
IR 和 DR 的关系 - “开关选择抽屉”:
-
关键点:你转动“模式选择开关”(IR)到一个特定位置(指令),实际上就是在选择你要打开哪个“工具抽屉”(DR寄存器)!
-
当你把开关拨到
IDCODE位置时,工具箱就知道:“哦,主人现在要用那个存身份证号的抽屉了”。之后所有通过TDI输入的数据都会进入 IDCODE抽屉(DR寄存器),从TDO读出的数据也是来自 IDCODE抽屉(DR寄存器)。 -
当你把开关拨到
SAMPLE位置时,工具箱就知道:“主人要看管脚状态”。之后所有通过TDI输入的数据都会进入 边界扫描寄存器抽屉(DR寄存器),从TDO读出的数据也是来自 边界扫描寄存器抽屉(DR寄存器)。 -
当你把开关拨到
BYPASS位置时,工具箱就知道:“主人想跳过这个芯片”。之后数据会快速流过那个只有一个位的空跑抽屉(BYPASS寄存器),几乎不延迟。 -
所以,IR的值(开关的位置)决定了当前哪个DR寄存器(哪个抽屉)是“激活的”,是参与数据交换的。 有多少种不同的开关位置(即使有些没用上),理论上就对应着多少个不同的抽屉可以被选择。
2.4、计算JTAG链中元件个数
IR寄存器的指令不同芯片有所区别,但是有一个指令是一样的,那就是BYPASS指令。它的IR寄存器所有位都是1。对于CPU是11111,对于FPGA的IR寄存器,其数值是1111111111。在BYPASS指令模式下,TAP控制器对应的DR寄存器是个单触发器,只是将TDI的输入数据延时一个时钟周期然后通过TDO输出。
根据这个特性,我们可以用BYPASS指令来计算JTAG链上有多少个芯片。在此指令下,每个芯片的DR寄存器会延时一个时钟周期,那么我们发送一个数据后,检查延时多少周期收到数据,即可知道JTAG链上芯片的数量。
具体实现的C语言代码如下:
// 步骤1:进入复位状态(Test-Logic-Reset)
for(i=0; i<5; i++) JTAG_clock(TMS); // 连续5个TCK周期保持TMS=1,强制所有芯片TAP控制器复位
// 步骤2:切换到Shift-IR状态(准备设置IR寄存器)
JTAG_clock(0); // TMS=0:从复位状态进入Run-Test/Idle
JTAG_clock(TMS); // TMS=1:进入Select-DR-Scan
JTAG_clock(TMS); // TMS=1:进入Select-IR-Scan
JTAG_clock(0); // TMS=0:进入Capture-IR
JTAG_clock(0); // TMS=0:进入Shift-IR(现在可以移位IR数据了)
// 步骤3:设置所有芯片为BYPASS模式
for(i=0; i<999; i++) JTAG_clock(1); // 向IR寄存器发送连续999个"1"(确保所有芯片的IR都设为全1)
JTAG_clock(1 | TMS); // 发送最后一位IR数据(1)并设置TMS=1,退出Shift-IR状态
// 步骤4:切换到Shift-DR状态(准备操作DR寄存器)
JTAG_clock(TMS); // TMS=1:进入Exit1-IR
JTAG_clock(TMS); // TMS=1:进入Update-IR(指令生效,所有芯片进入BYPASS模式)
JTAG_clock(0); // TMS=0:进入Select-DR-Scan
JTAG_clock(0); // TMS=0:进入Capture-DR → Shift-DR(现在可以移位DR数据了)
// 步骤5:清空DR链上的残留数据
for(i=0; i<1000; i++) JTAG_clock(0); // 发送1000个"0"冲洗DR链(确保后续检测准确)
// 步骤6:检测芯片数量(关键步骤)
for(i=0; i<1000; i++)
if(JTAG_clock(1)) // 持续发送"1",同时检测TDO
break; // 当TDO返回"1"时跳出循环
// 结果处理:循环次数i即芯片数量
nbDevices = i; // i值 = 数据经过的芯片数量(每个芯片延迟1个时钟周期)
printf("There are %d device(s) in the JTAG chain\n", nbDevices);
2.5、获得JTAG链上芯片的ID
大部分的芯片JTAG模块都支持IDCODE指令,这个指令对应的DR寄存器是32位,具体数值代表者不同芯片的ID。
不同于BYPASS指令,INCODE指令的IR寄存器数值不是标准的,我们可以通过器件手册来查询。还有一种方法,当TAO控制器的状态处于Test-Logic-Reset时,它都会将INCODE数据写入DR寄存器中,我们可以据此读出DR寄存器的内容,C语言代码如下:
// 步骤1:强制进入复位状态(Test-Logic-Reset)
// 这个状态会自动将所有芯片的IDCODE加载到DR寄存器
for(i=0; i<5; i++) JTAG_clock(TMS); // 连续5个TMS=1,强制所有芯片进入复位状态
// 步骤2:切换到Shift-DR状态(准备读取数据寄存器)
JTAG_clock(0); // TMS=0:复位状态 → 运行/空闲状态
JTAG_clock(TMS); // TMS=1:运行/空闲状态 → 选择DR扫描状态
JTAG_clock(0); // TMS=0:选择DR扫描状态 → 捕获DR状态(芯片准备数据)
JTAG_clock(0); // TMS=0:捕获DR状态 → 移位DR状态(可以开始读取数据)
// 步骤3:读取每个芯片的IDCODE
for(i=0; i < nbDevices; i++) // nbDevices是之前计算出的芯片数量
{
// 从TDO读取32位数据(即芯片的ID身份证号)
// 注意:读取顺序是从链的末端开始(最后1个芯片→第1个芯片)
printf("IDCODE for device %d is %08X\n", i+1, JTAG_read(32));
}
3、边界扫描
本章节讲解JTAG的边界扫描。当TAP控制器进入“boundary-scan”的状态,其实也就是IR寄存器存入SAMPLE指令、EXTEST指令等,此时对应的DR寄存器就是边界扫描寄存器,这个寄存器将每个I/O单元连接在一起并且可以控制每个引脚。

图11
当芯片正常过程当中也是可以进行边界扫描的,例如对正常运行中的FPGA进行边界扫描,它可以将每个管脚的状态显示出来。
3.1、SAMPLE
现在我们尝试读取管脚的值,对应的IR寄存器的指令是SAMPLE。每个芯片的具体指令数值不同,查找数据手册或者芯片的BSDL文件来获取具体的指令。BSDL全称是boundary scan description language,它是硬件描述语言(VHDL)的一个子集。
一个BSDL文件其实就是一个描述边界链的VHDL文件。下面是阿尔特拉的BSDL文件(Cyclone EP1C3 in TQFP 100 pins package):
// 芯片型号:Altera Cyclone EP1C3 (100脚TQFP封装)
attribute INSTRUCTION_LENGTH of EP1C3T100 : entity is 10;
// IR寄存器长度 = 10位 (设置指令需发送10位二进制数)
attribute INSTRUCTION_OPCODE of EP1C3T100 : entity is
"BYPASS (1111111111), "& // 全1:跳过芯片指令
"EXTEST (0000000000), "& // 全0:外部电路测试指令
"SAMPLE (0000000101), "& // 读引脚状态指令:二进制0000000101
"IDCODE (0000000110), "& // 读芯片ID指令:二进制0000000110
"USERCODE (0000000111), "& // 读用户代码指令
"CLAMP (0000001010), "& // 钳位输出指令
"HIGHZ (0000001011), "& // 高阻态输出指令
"CONFIG_IO (0000001101)"; // 配置IO指令
attribute INSTRUCTION_CAPTURE of EP1C3T100 : entity is "0101010101";
// 芯片启动时IR寄存器的默认值:0101010101 (测试用)
attribute IDCODE_REGISTER of EP1C3T100 : entity is
"0000"& // 4位版本号 (Version)
"0010000010000001"& // 16位部件号 (Part Number = 0x2081)
"00001101110"& // 11位制造商ID (Altera编码 = 0x036)
"1"; // 固定最低位必须为1 (IEEE标准要求)
attribute BOUNDARY_LENGTH of EP1C3T100 : entity is 339;
// 边界扫描链总长度 = 339位 (包含所有引脚的控制/状态位)
从上面这个文件我们可以知道:
- IR寄存器的长度是10位;
- IR指令寄存器的指令清单,比如SAMPLE的是0000000101,也就是0x005;
- 该器件的IDCODE,00001101110是厂商的代号(阿尔特拉);
- 边界扫描链的长度是339位。
边界扫描寄存器有339位,并不意味着有339个管脚。
每一个管脚都有一个IO pad(芯片管脚处理模块),IO pad用1~3位寄存器(取决于该管脚是输入、三态输出或是输入输出均可)。当然一些IO pad包含的寄存器不一定包含在边界扫描链中。这就解释了为什么这个100管脚的芯片有339位的边界扫描寄存器。
接着看BSDL文件:
// 边界扫描寄存器定义(每个引脚对应3个扫描单元)
attribute BOUNDARY_REGISTER of EP1C3T100 : entity is
// --- 引脚100的扫描单元组(组0) ---
"0 (BC_1, IO100, input, X)," & // [索引0] 输入单元:采样IO100引脚电平
"1 (BC_1, *, control, 1)," & // [索引1] 控制单元:控制输出使能(1=禁用输出)
"2 (BC_1, IO100, output3, X, 1, 1, Z)," & // [索引2] 输出单元:当控制单元=1时输出高阻态(Z)
// --- 引脚99的扫描单元组(组1) ---
"3 (BC_1, IO99, input, X)," & // [索引3] 输入单元:采样IO99引脚电平
"4 (BC_1, *, control, 1)," & // [索引4] 控制单元(控制引脚99输出使能)
"5 (BC_1, IO99, output3, X, 4, 1, Z)," & // [索引5] 输出单元:受索引4控制
... // 其他引脚定义(省略)...
// --- 引脚1的扫描单元组(组112) ---
"336 (BC_1, IO1, input, X)," & // [索引336] 输入单元:采样IO1引脚电平
"337 (BC_1, *, control, 1)," & // [索引337] 控制单元(控制引脚1输出使能)
"338 (BC_1, IO1, output3, X, 337, 1, Z)"; // [索引338] 输出单元:受索引337控制
这一段罗列了边界扫描寄存器的339位的用途。
例如,处于第4位(其实是位3,从0开始计算的)保存的是管脚99的值。
现在读取边界扫描寄存器,并且将管脚99的值打印出来:
// 步骤1:强制进入复位状态(Test-Logic-Reset)
for(i=0; i<5; i++) JTAG_clock(TMS); // 连续5个TMS=1,重置所有芯片状态
// 步骤2:切换到Shift-IR状态(准备设置指令寄存器)
JTAG_clock(0); // TMS=0:复位状态 → 运行/空闲状态
JTAG_clock(TMS); // TMS=1:运行/空闲状态 → 选择DR扫描状态
JTAG_clock(TMS); // TMS=1:选择DR扫描状态 → 选择IR扫描状态
JTAG_clock(0); // TMS=0:选择IR扫描状态 → 捕获IR状态
JTAG_clock(0); // TMS=0:捕获IR状态 → 移位IR状态(开始发送指令)
// 步骤3:发送SAMPLE指令(0000000101)
// 注意:发送顺序是LSB先发(从右向左)
JTAG_clock(1); // 第1位(LSB):1
JTAG_clock(0); // 第2位:0
JTAG_clock(1); // 第3位:1
JTAG_clock(0); // 第4位:0
JTAG_clock(0); // 第5位:0
JTAG_clock(0); // 第6位:0
JTAG_clock(0); // 第7位:0
JTAG_clock(0); // 第8位:0
JTAG_clock(0); // 第9位:0
JTAG_clock(0 | TMS); // 第10位(MSB):0 + TMS=1(退出移位状态)
// 步骤4:切换到Shift-DR状态(准备读取引脚数据)
JTAG_clock(TMS); // TMS=1:退出IR状态 → 更新IR状态(指令生效)
JTAG_clock(TMS); // TMS=1:更新IR状态 → 选择DR扫描状态
JTAG_clock(0); // TMS=0:选择DR扫描状态 → 捕获DR状态(准备数据)
JTAG_clock(0); // TMS=0:捕获DR状态 → 移位DR状态(开始读取)
// 步骤5:读取边界扫描链数据(339位)
uint8_t BSB[339]; // 存储边界扫描数据的数组
JTAG_read(BSB, 339); // 从TDO读取339位数据到BSB数组
// 步骤6:提取引脚99的状态(根据BSDL文件,IO99在索引3)
printf("Status of pin 99 = %d\n", BSB[3]);
// BSB[3]对应引脚99的输入单元值(0=低电平,1=高电平)
3.2、边界扫描寄存器
下图是阿尔特拉数据手册中TAP状态寄存器、IR寄存器、DR寄存器结构图,各层级关系比较一目了然。

图12
从图12中我们可以得出以下信息:
- IR指令寄存器(Instruction Register)的值决定采用什么指令以及选择对应的DR寄存器(Data Register);
- Bypass Register只有一位,其指令BYPASS我们上文已经讲过,其提供从TDI到TDO最短路径;
- 边界扫描寄存器是个移位寄存器,由芯片上所有的管脚BSC组成。

阿尔特拉的BSC全称是Booundary scan cell,它可以将信号施加到管脚,或者获得管脚上的数据和内部逻辑信号。我们JTAG测试的数据也是串行输入到BSC单元中,捕获到的数据也是串行从BSC输出,进而判断测试结果。根据我的理解,此处的BSC就是我们上文提到的IO pad芯片管脚处理模块。

3.3、JTAG还可以做什么?
- 控制芯片的引脚状态,对应的IR指令为EXTEST,表示外部测试,可以让输出管脚输出高低逻辑电平,根据输入接受到的电平信号检测JTAG链中任何设备管脚处的开路和短路情况;
- 用于FPGA和CPLD的配置;
- JTAG接口可以作为调试端口。
对Jtag的基础学习就到这里
图13
更多推荐



所有评论(0)