MPSSE 协议开发 JTAG 完整指南
概述
本文档详细说明如何使用 FTDI MPSSE (Multi-Protocol Synchronous Serial Engine) 协议开发 JTAG 调试器。
适用场景:
- 基于 FT2232H/FT4232H/FT232H 的 JTAG 调试器
- 需要高性能 JTAG/SWD 通信
- 跨平台兼容性要求高
协议特点:
- FTDI 官方标准协议,由芯片固件直接解析
- 符合 IEEE 1149.1 JTAG 标准
- 支持 JTAG 和 SWD 协议
- 最高支持 30+ MHz 频率
目录
1. MPSSE 基础
1.1 硬件要求
支持的 FTDI 芯片:
| 芯片型号 | MPSSE 引擎数 | 最高频率 | 端口数 |
|---|---|---|---|
| FT2232C | 2 | 6 MHz | A/B |
| FT2232H | 2 | 30 MHz | A/B |
| FT4232H | 4 | 30 MHz | A/B/C/D |
| FT232H | 1 | 30 MHz | 1 |
1.2 软件依赖
# 必需库
libusb-1.0 >= 1.0.16
# 可选库
libftdi1 >= 1.5 # 高级 API(推荐)
# 编译工具
autoconf >= 2.69
automake >= 1.14
pkg-config >= 0.23
1.3 初始化流程
#include <libusb.h>
#include <stdio.h>
#define VID_FTDI 0x0403
#define PID_FT2232H 0x6010
// MPSSE 请求码
#define SIO_RESET_REQUEST 0x00
#define SIO_SET_LATENCY_TIMER_REQUEST 0x09
#define SIO_SET_BITMODE_REQUEST 0x0B
#define BITMODE_MPSSE 0x02
int mpsse_init(struct libusb_context **ctx, struct libusb_device_handle **dev) {
// 1. 初始化 libusb
if (libusb_init(ctx) < 0) {
fprintf(stderr, "Failed to init libusb\n");
return -1;
}
// 2. 打开设备
*dev = libusb_open_device_with_vid_pid(*ctx, VID_FTDI, PID_FT2232H);
if (!*dev) {
fprintf(stderr, "Device not found\n");
return -1;
}
// 3. 声明接口(通道 0)
if (libusb_claim_interface(*dev, 0) < 0) {
fprintf(stderr, "Failed to claim interface\n");
return -1;
}
// 4. 设置延迟定时器(降低 USB 延迟)
libusb_control_transfer(*dev,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
SIO_SET_LATENCY_TIMER_REQUEST,
0x02, // 2ms
0,
NULL, 0, 1000);
// 5. 复位设备
libusb_control_transfer(*dev,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
SIO_RESET_REQUEST,
SIO_RESET_PURGE_RX | SIO_RESET_PURGE_TX,
0,
NULL, 0, 1000);
// 6. 设置 MPSSE 模式
libusb_control_transfer(*dev,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE,
SIO_SET_BITMODE_REQUEST,
BITMODE_MPSSE, // 启用 MPSSE
0,
NULL, 0, 1000);
return 0;
}
1.4 Mode 标志定义
// src/jtag/drivers/mpsse.h:26-31
#define POS_EDGE_OUT 0x00 // TCK 上升沿输出 TDI
#define NEG_EDGE_OUT 0x01 // TCK 下降沿输出 TDI
#define POS_EDGE_IN 0x00 // TCK 上升沿采样 TDO
#define NEG_EDGE_IN 0x04 // TCK 下降沿采样 TDO
#define MSB_FIRST 0x00 // 高位优先
#define LSB_FIRST 0x08 // 低位优先
// JTAG 标准模式组合
// src/jtag/drivers/ftdi.c:98
#define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT)
// 解释:
// - LSB_FIRST: JTAG 标准使用 LSB 优先传输
// - POS_EDGE_IN: 在 TCK 上升沿采样 TDO
// - NEG_EDGE_OUT: 在 TCK 下降沿输出 TDI
// SWD 模式
#define SWD_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT)
2. 核心命令详解
2.1 时钟数据命令 (MPSSE 核心命令)
2.1.1 按位传输(< 8 位)
命令格式:
[0x02 | mode] [length-1] [data...]
参数说明:
0x02 | mode:命令码length-1:位数 - 1(范围 0-7,即 1-8 位)data:数据字节(低 length 位有效)
示例:发送 4 位数据 0b1010 (0xA)
uint8_t mode = JTAG_MODE;
uint8_t cmd[] = {
0x02 | mode | 0x10, // 0x10 = 发送标志
0x03, // 4 位 (length-1)
0x0A // 数据: 1010b
};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), &transferred, 1000);
2.1.2 按字节传输(≥ 8 位)
命令格式:
[0x10 | mode] [length-1 LSB] [length-1 MSB] [data...]
参数说明:
0x10 | mode:命令码length-1 LSB/MSB:字节数 - 1(16 位,最多 65535 字节)data:数据字节
示例:发送 32 位数据 0xDEADBEEF
uint8_t mode = JTAG_MODE | 0x10; // 0x10 = 发送标志
uint8_t cmd[] = {
0x10 | mode, // 命令码
0x03, 0x00, // 4 字节 (4-1=3)
0xEF, 0xBE, 0xAD, 0xDE // LSB 优先:0xEF 是最低字节
};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), &transferred, 1000);
注意:JTAG 使用 LSB 优先,所以 0xDEADBEEF 发送顺序为:
- 第 1 字节:0xEF (bit[7:0])
- 第 2 字节:0xBE (bit[15:8])
- 第 3 字节:0xAD (bit[23:16])
- 第 4 字节:0xDE (bit[31:24])
2.1.3 接收数据
命令格式:
[0x20 | mode] [length-1/length-1 MSB] // 仅接收,不发送
[0x30 | mode] [length-1/length-1 MSB] [data...] // 发送+接收
示例:接收 32 位数据
uint8_t mode = JTAG_MODE | 0x20; // 0x20 = 接收标志
uint8_t cmd[] = {
0x20 | mode,
0x03, 0x00, // 4 字节
};
// 发送命令
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), &transferred, 1000);
// 接收数据
uint8_t rx_data[4];
libusb_bulk_transfer(dev, in_ep, rx_data, sizeof(rx_data), &received, 1000);
2.2 带 TMS 控制的时钟命令
命令格式:
[0x42 | mode] [bits-1] [TMS/TDI byte]
TMS/TDI 字节格式:
| Bit 7 | Bit 6-0 |
|---|---|
| TDI 值 | TMS 序列(每 bit 对应一个 TCK) |
特点:
- 每次最多传输 7 位(受 MPSSE 限制)
- Bit 7 是 TDI 数据
- Bit 6-0 是 TMS 数据
示例:发送 5 个 TCK 周期,TMS=11111,TDI=0
uint8_t mode = JTAG_MODE | 0x42; // 0x42 = 带 TMS 控制
uint8_t tms_seq = 0x1F; // 00011111b
uint8_t tdi = 0;
uint8_t cmd[] = {
0x42 | mode,
0x04, // 5 个 TCK 周期 (5-1=4)
(tms_seq & 0x7F) | (tdi ? 0x80 : 0x00) // TMS + TDI(bit7)
};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), &transferred, 1000);
带接收的 TMS 命令:
uint8_t mode = JTAG_MODE | 0x42 | 0x20; // 0x20 = 接收标志
// 其余相同
2.3 其他 MPSSE 命令
| 命令码 | 功能 | 参数 |
|---|---|---|
| 0x84 | 设置低字节 GPIO | [data] [direction] |
| 0x85 | 设置高字节 GPIO | [data] [direction] |
| 0x86 | 读低字节 GPIO | - |
| 0x87 | SEND_IMMEDIATE | - |
| 0x8A | 禁用时钟分频 | [0x01] |
| 0x8B | 设置时钟分频 | [divisor LSB] [divisor MSB] |
| 0x9A | 调用延迟 | [length LSB] [length MSB] |
频率计算:
// FT2232H/FT4232H/FT232H
frequency = 60 MHz / (1 + divisor)
// 示例:30 MHz
divisor = 0x0001; // 60 / 2 = 30 MHz
// 示例:6 MHz
divisor = 0x0009; // 60 / 10 = 6 MHz
3. JTAG TAP 状态机
3.1 TAP 状态机图
┌─────┐
│TEST-LOGIC
│ RESET
└───┬─┘
│ TMS=1
▼
┌────────┴────────┐
│ RUN-TEST/IDLE
└────────┬───────┘
│ TMS=1
┌────────┴────────┐
│ SELECT-DR-SCAN
└────────┬───────┘
│ TMS=0 TMS=1
┌──────────────┴──┐ ┌───────┴────────┐
│ CAPTURE-DR │ │ SELECT-IR-SCAN│
└──────┬───────┘ └───────┬────────┘
│ TMS=0 │ TMS=0
┌──────┴──────┐ ┌──────┴──────┐
│ SHIFT-DR │ │ SHIFT-IR │
└──────┬──────┘ └──────┬──────┘
│ TMS=1 │ TMS=1
┌──────┴──────┐ ┌──────┴──────┐
│ EXIT1-DR │ │ EXIT1-IR │
└──────┬──────┘ └──────┬──────┘
│ TMS=1 │ TMS=1
┌──────┴──────┐ ┌──────┴──────┐
│ PAUSE-DR │ │ PAUSE-IR │
└──────┬──────┘ └──────┬──────┘
│ TMS=1 │ TMS=1
┌──────┴──────┐ ┌──────┴──────┐
│ EXIT2-DR │ │ EXIT2-IR │
└──────┬──────┘ └──────┬──────┘
│ TMS=0 │ TMS=0
┌──────┴──────┐ ┌──────┴──────┐
│ UPDATE-DR │ │ UPDATE-IR │
└──────┬──────┘ └──────┬──────┘
│ TMS=0 │ TMS=0
└────────┴─────────┘
│ RUN-TEST/IDLE
3.2 状态迁移表
| 当前状态 | TMS | 下一状态 |
|---|---|---|
| TEST-LOGIC-RESET | 0 | RUN-TEST/IDLE |
| RUN-TEST/IDLE | 1 | SELECT-DR-SCAN |
| SELECT-DR-SCAN | 0 | CAPTURE-DR |
| SELECT-DR-SCAN | 1 | SELECT-IR-SCAN |
| CAPTURE-DR | 0 | SHIFT-DR |
| SHIFT-DR | 0 | SHIFT-DR (继续移位) |
| SHIFT-DR | 1 | EXIT1-DR |
| EXIT1-DR | 1 | PAUSE-DR |
| PAUSE-DR | 1 | EXIT2-DR |
| EXIT2-DR | 0 | UPDATE-DR |
| UPDATE-DR | 0 | RUN-TEST/IDLE |
SELECT-IR-SCAN 到 UPDATE-IR 类似。
3.3 状态迁移代码示例
// 从 RUN-TEST/IDLE 切换到 SHIFT-IR
uint8_t tms_sequence_idle_to_shift_ir[] = {1, 1, 0, 0};
for (int i = 0; i < 4; i++) {
mpsse_clock_tms_cs_out(ctx, &tms_sequence_idle_to_shift_ir[i], 0, 1, 0, JTAG_MODE);
// 1 个 TCK 周期,TMS 值由数组元素决定
}
// 状态变化:
// IDLE --(1)--> SELECT-DR --(1)--> SELECT-IR --(0)--> CAPTURE-IR --(0)--> SHIFT-IR
4. 完整 JTAG 操作流程
4.1 IRSCAN 操作(写 IR 寄存器)
目标:写入 5 位 IR 值 0x04 (DMI 指令)
void jtag_irscan(uint8_t ir_value, unsigned ir_length) {
uint8_t mode = JTAG_MODE | 0x10 | 0x20; // 发送+接收
// 1. 从 IDLE 切换到 SHIFT-IR
uint8_t tms_to_irshift[] = {1, 1, 0, 0};
for (int i = 0; i < 4; i++) {
mpsse_clock_tms_cs_out(ctx, &tms_to_irshift[i], 0, 1, 0, JTAG_MODE);
}
// 2. 发送 IR 数据(ir_length 位)
unsigned bytes = (ir_length + 7) / 8;
uint8_t *ir_buffer = malloc(bytes);
ir_buffer[bytes - 1] = ir_value << (8 - (ir_length % 8));
// 发送前 ir_length-1 位
if (ir_length > 8) {
for (unsigned i = 0; i < bytes - 1; i++) {
uint8_t cmd[] = {0x10 | mode, 0x00, ir_buffer[i]};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), NULL, 1000);
}
}
// 发送最后 1 位 + TMS=1 (退出 SHIFT-IR)
uint8_t last_bit = ir_value & 0x01;
uint8_t cmd_final[] = {
0x02 | mode | 0x40, // 0x42 = TMS 控制
0x00, // 1 位
(last_bit ? 0x80 : 0x00) | 0x01 // TDI=last_bit, TMS=1
};
libusb_bulk_transfer(dev, out_ep, cmd_final, sizeof(cmd_final), NULL, 1000);
// 3. 从 EXIT1-IR 切换到 IDLE
uint8_t tms_to_idle[] = {1, 0};
for (int i = 0; i < 2; i++) {
mpsse_clock_tms_cs_out(ctx, &tms_to_idle[i], 0, 1, 0, JTAG_MODE);
}
free(ir_buffer);
}
4.2 DRSCAN 操作(写 DR 寄存器)
目标:写入 41 位 DR 值(示例)
void jtag_drscan(const uint8_t *dr_data, unsigned dr_length) {
uint8_t mode = JTAG_MODE | 0x10 | 0x20;
// 1. 从 IDLE 切换到 SHIFT-DR
uint8_t tms_to_drshift[] = {1, 0, 0};
for (int i = 0; i < 3; i++) {
mpsse_clock_tms_cs_out(ctx, &tms_to_drshift[i], 0, 1, 0, JTAG_MODE);
}
// 2. 发送 DR 数据
unsigned full_bytes = dr_length / 8;
unsigned remaining_bits = dr_length % 8;
// 发送完整字节
for (unsigned i = 0; i < full_bytes; i++) {
uint8_t cmd[] = {0x10 | mode, 0x00, dr_data[i]};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), NULL, 1000);
}
// 发送剩余位 + TMS=1
if (remaining_bits > 0) {
uint8_t last_byte = dr_data[full_bytes] >> (8 - remaining_bits);
uint8_t cmd[] = {
0x02 | mode | 0x40,
remaining_bits - 1,
last_byte | 0x01 // TMS=1
};
libusb_bulk_transfer(dev, out_ep, cmd, sizeof(cmd), NULL, 1000);
}
// 3. 切换到 IDLE
uint8_t tms_to_idle[] = {1, 0};
for (int i = 0; i < 2; i++) {
mpsse_clock_tms_cs_out(ctx, &tms_to_idle[i], 0, 1, 0, JTAG_MODE);
}
}
4.3 完整 DMI 写入示例
目标:写入 DMI 寄存器(地址 0x10,数据 0x12345678)
void dmi_write(uint32_t address, uint32_t data) {
// 1. IRSCAN: 写入 DMI 指令 (5 位)
uint8_t dmi_write_op = 0x02; // 写操作
jtag_irscan((dmi_write_op << 1) | 0, 5); // op 在 bit[1]
// 2. 构建 DR 数据(41 位)
uint8_t dr_buffer[6]; // 41 位 = 6 字节
dr_buffer[0] = address & 0xFF;
dr_buffer[1] = (address >> 8) & 0xFF;
dr_buffer[2] = (address >> 16) & 0xFF;
dr_buffer[3] = ((address >> 24) & 0x1F) | 0x00; // exec=0
dr_buffer[4] = data & 0xFF;
dr_buffer[5] = (data >> 8) & 0xFF;
dr_buffer[6] = (data >> 16) & 0xFF;
dr_buffer[7] = (data >> 24) & 0xFF;
jtag_drscan(dr_buffer, 41);
}
5. 代码示例
5.1 完整初始化代码
#include <libusb.h>
#include <stdio.h>
#include <stdlib.h>
#define VID_FTDI 0x0403
#define PID_FT2232H 0x6010
// MPSSE 模式
#define BITMODE_MPSSE 0x02
// Mode 标志
#define JTAG_MODE 0x44 // LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT
// USB 端点
#define EP_IN 0x81
#define EP_OUT 0x02
struct mpsse_device {
struct libusb_context *ctx;
struct libusb_device_handle *dev;
uint8_t mode;
};
int mpsse_init(struct mpsse_device *dev) {
// 1. 初始化 libusb
if (libusb_init(&dev->ctx) < 0) {
return -1;
}
// 2. 打开设备
dev->dev = libusb_open_device_with_vid_pid(dev->ctx, VID_FTDI, PID_FT2232H);
if (!dev->dev) {
return -2;
}
// 3. 声明接口
if (libusb_claim_interface(dev->dev, 0) < 0) {
return -3;
}
// 4. 设置 MPSSE 模式
uint8_t mode = BITMODE_MPSSE;
libusb_control_transfer(dev->dev,
0x40, // FTDI_REQUEST_TYPE
0x0B, // SIO_SET_BITMODE_REQUEST
mode, 0,
NULL, 0, 1000);
// 5. 设置频率(30 MHz)
uint16_t divisor = 0x0001; // 60 / 2 = 30 MHz
uint8_t freq_cmd[] = {0x86, divisor & 0xFF, divisor >> 8};
libusb_bulk_transfer(dev->dev, EP_OUT, freq_cmd, sizeof(freq_cmd), NULL, 1000);
dev->mode = JTAG_MODE;
return 0;
}
void mpsse_close(struct mpsse_device *dev) {
if (dev->dev) {
libusb_release_interface(dev->dev, 0);
libusb_close(dev->dev);
}
if (dev->ctx) {
libusb_exit(dev->ctx);
}
}
5.2 批量数据发送
int mpsse_send_bulk(struct mpsse_device *dev, const uint8_t *data, unsigned length) {
uint8_t mode = dev->mode | 0x10; // 发送标志
unsigned offset = 0;
while (offset < length) {
unsigned remaining = length - offset;
unsigned chunk = (remaining > 65535) ? 65535 : remaining;
// 构建命令
uint8_t cmd[3 + chunk];
cmd[0] = 0x10 | mode;
cmd[1] = (chunk - 1) & 0xFF;
cmd[2] = (chunk - 1) >> 8;
memcpy(&cmd[3], data + offset, chunk);
// 发送
int transferred;
if (libusb_bulk_transfer(dev->dev, EP_OUT, cmd, sizeof(cmd),
&transferred, 1000) < 0) {
return -1;
}
offset += chunk;
}
return 0;
}
5.3 TMS 序列发送
int mpsse_send_tms(struct mpsse_device *dev, const uint8_t *tms_seq, unsigned length) {
uint8_t mode = dev->mode | 0x42; // TMS 控制
unsigned offset = 0;
while (offset < length) {
unsigned chunk = (length - offset) > 7 ? 7 : (length - offset);
uint8_t cmd[] = {
mode,
chunk - 1,
tms_seq[offset]
};
int transferred;
if (libusb_bulk_transfer(dev->dev, EP_OUT, cmd, sizeof(cmd),
&transferred, 1000) < 0) {
return -1;
}
offset += chunk;
}
return 0;
}
6. 性能优化
6.1 批量传输优化
原理:累积多个 MPSSE 命令后一次性发送,减少 USB 往返次数
// 优化前:每个命令单独发送
mpsse_clock_tms_cs_out(ctx, tms, 0, 1, 0, mode); // 1 次 USB 传输
mpsse_clock_data(ctx, data, 0, NULL, 0, 32, mode); // 1 次 USB 传输
mpsse_clock_tms_cs_out(ctx, tms, 0, 1, 0, mode); // 1 次 USB 传输
// 总计:3 次 USB 传输
// 优化后:合并命令
uint8_t tx_buffer[256];
unsigned pos = 0;
// 添加命令 1
tx_buffer[pos++] = 0x42 | mode;
tx_buffer[pos++] = 0x00;
tx_buffer[pos++] = tms[0];
// 添加命令 2
tx_buffer[pos++] = 0x10 | mode;
tx_buffer[pos++] = 0x03;
tx_buffer[pos++] = 0x00;
memcpy(&tx_buffer[pos], data, 4);
pos += 4;
// 添加命令 3
tx_buffer[pos++] = 0x42 | mode;
tx_buffer[pos++] = 0x00;
tx_buffer[pos++] = tms[1];
// 一次性发送
libusb_bulk_transfer(dev, EP_OUT, tx_buffer, pos, NULL, 1000);
// 总计:1 次 USB 传输
6.2 频率优化
// FT2232H: 最高 30 MHz
mpsse_set_frequency(dev, 30000000);
// FT232H: 最高 30 MHz
mpsse_set_frequency(dev, 30000000);
// FT4232H: 最高 30 MHz
mpsse_set_frequency(dev, 30000000);
// 根据目标芯片调整
// ARM Cortex-M: 推荐使用 10-20 MHz
mpsse_set_frequency(dev, 15000000);
// 老旧芯片:降低频率
mpsse_set_frequency(dev, 1000000);
6.3 USB 延迟优化
// 设置最小延迟(2 ms)
libusb_control_transfer(dev,
0x40, 0x09, 0x02, 0, NULL, 0, 1000);
// 禁用时钟分频(最高频率)
uint8_t disable_div[] = {0x8A, 0x01};
libusb_bulk_transfer(dev, EP_OUT, disable_div, sizeof(disable_div), NULL, 1000);
7. 调试技巧
7.1 启用详细日志
# OpenOCD 调试模式
openocd -d3 -f your_config.cfg
# 查看 MPSSE 命令
# 添加到 mpsse.c
#define DEBUG_MPSSE_COMMANDS
#ifdef DEBUG_MPSSE_COMMANDS
LOG_DEBUG("MPSSE: 0x%02X 0x%02X 0x%02X",
cmd[0], cmd[1], cmd[2]);
#endif
7.2 USB 监控
# 使用 usbmon
sudo modprobe usbmon
sudo cat /sys/kernel/debug/usb/usbmon/2u > capture.pcap
# 使用 Wireshark 分析
wireshark capture.pcap
# 过滤: usb.device_address == YOUR_DEVICE_ADDR
7.3 常见问题
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 数据错误 | Mode 标志错误 | 检查 JTAG_MODE = 0x44 |
| 频率不稳定 | USB 延迟过高 | 设置 latency timer = 2ms |
| 设备未找到 | VID/PID 错误 | 使用 lsusb 确认设备 ID |
| TAP 状态卡死 | TMS 序列错误 | 使用正确的 TMS 状态迁移 |
参考资料
-
FTDI MPSSE 规范
https://www.ftdichip.com/Support/Documents/AppNotes/AN2232C-01_MPSSE_Cmnd.pdf -
FT2232H 数据手册
https://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf -
IEEE 1149.1 JTAG 标准
https://standards.ieee.org/ -
OpenOCD 源码
src/jtag/drivers/mpsse.csrc/jtag/drivers/mpsse.hsrc/jtag/drivers/ftdi.c
总结
MPSSE 协议开发要点:
- 初始化:设置 MPSSE 模式 + 频率
- Mode 标志:JTAG_MODE = 0x44 (LSB | POS_EDGE_IN | NEG_EDGE_OUT)
- 命令类型:
0x02/0x10:时钟数据(按位/按字节)0x42:带 TMS 控制
- 状态迁移:通过 TMS 序列驱动 TAP 状态机
- 性能优化:批量传输 + 高频率 + 低延迟
与 OpenJTAG 协议的区别:
- MPSSE:由 FTDI 芯片固件解析,标准 JTAG
- OpenJTAG:由设备固件解析,自定义协议
3万+

被折叠的 条评论
为什么被折叠?



