USB协议作为嵌入式开发和系统编程中的核心通信标准,其事务层(Transaction Layer)是连接数据包与上层传输的关键枢纽。无论你是在用C++编写USB设备驱动,还是在Python中调试USB抓包,理解事务机制都能帮你快速定位通信问题。本文将深入剖析USB事务的三种基本类型、数据切换机制及错误处理,并对比USB 2.0与3.x的差异。
一、事务在USB分层中的位置
USB协议采用分层架构,事务层位于数据包层之上、传输层之下。它负责将一次完整的传输(如读取1024字节文件)分解为多个事务,管理数据包的顺序(Data Toggle),处理握手响应(ACK/NAK/STALL),并控制总线访问时机。
三者的关系如下:
| 层级 | 单位 | 组成 | 类比 |
|---|---|---|---|
| 传输(Transfer) | 一次完整的数据交换 | 1~N 个事务 | 一次文件读取 |
| 事务(Transaction) | 一次独立的通信操作 | 2~3 个包 | 一次请求-响应 |
| 包(Packet) | 总线上的一次信号突发 | SYNC + PID + 数据 + EOP | 一个数据帧 |
二、USB包的类型与结构
所有USB包都以SYNC字段开始,以EOP结束。通用结构包括:SYNC(8 bit)、PID(8 bit)、数据载荷(0~1023 byte)、CRC(5/16 bit)和EOP。
令牌包(Token Packet):由Host发送,用于启动事务并声明目标端点。结构包含PID、ADDR(7 bit)、ENDP(4 bit)和CRC5。
| PID | 名称 | 功能 |
|---|---|---|
| OUT | 输出令牌 | Host 向 Device 的 OUT 端点发送数据 |
| IN | 输入令牌 | Host 从 Device 的 IN 端点读取数据 |
| SETUP | 设置令牌 | Host 向 Device 发送 Setup 数据(控制传输) |
| SOF | 帧起始令牌 | Host 发送帧/微帧开始标记 |
✅ 数据包(Data Packet):携带实际数据,由Host或Device发送。类型包括DATA0、DATA1、DATA2和MDATA。
| PID | 名称 | 用途 |
|---|---|---|
| DATA0 | 数据 0 | 数据包(首次或偶数次) |
| DATA1 | 数据 1 | 数据包(奇数次) |
| DATA2 | 数据 2 | USB 2.0 高速分裂传输 |
| MDATA | 数据 M | 高速分裂传输 |
⚠️ 握手包(Handshake Packet):无数据载荷,用于确认或拒绝传输。类型包括ACK、NAK、STALL和NYET。
| PID | 名称 | 发送者 | 含义 |
|---|---|---|---|
| ACK | 确认 | Host/Device | 数据正确接收,准备下一包 |
| NAK | 未就绪 | Device | 暂时无法处理,请重试 |
| STALL | 停止 | Device | 端点挂起,需要 Host 干预 |
| NYET | 未就绪 | Device | 高速传输中,尚未准备好 |
特殊包(Special Packet):包括PRE、ERR、SPLIT和PING等,用于特定场景。
| PID | 名称 | 功能 |
|---|---|---|
| PRE | 前导 | 告诉 Hub 即将与低速设备通信 |
| SPLIT | 分裂 | 高速 Hub 分裂事务(连接全速/低速设备) |
| ERR | 错误 | Hub 报告分裂事务错误 |
三、PID(Packet Identifier)详解
PID占8位,低4位标识类型,高4位是低4位的取反,用于校验。编码示例如下:
┌────────┬────────┐
│ PID[7~4] │ PID[3~0] │
│ ~低4位 │ 类型码 │
│ 校验用 │ 实际含义 │
└────────┴────────┘
| PID 名称 | 类型码 | 取反 | 完整 PID |
|---|---|---|---|
| OUT | 0001 | 1110 | 11100001 = 0xE1 |
| IN | 1001 | 0110 | 01101001 = 0x69 |
| SETUP | 1101 | 0010 | 00101101 = 0x2D |
| DATA0 | 0011 | 1100 | 11000011 = 0xC3 |
| DATA1 | 1011 | 0100 | 01001011 = 0x4B |
| ACK | 0010 | 1101 | 11010010 = 0xD2 |
| NAK | 1010 | 0101 | 01011010 = 0x5A |
| STALL | 1110 | 0001 | 00011110 = 0x1E |
注意:PID的取反校验机制能有效检测传输错误,是USB协议可靠性的基础之一。
校验规则:如果 ,则 PID 有效。
PID类型速查表:
| PID 值 | 类型 | 组 | 说明 |
|---|---|---|---|
| 0xE1 | OUT | 令牌 | Host → Device OUT 端点 |
| 0xD2 | ACK | 握手 | 确认接收 |
| 0x69 | IN | 令牌 | Host ← Device IN 端点 |
| 0x5A | NAK | 握手 | 未就绪 |
| 0x2D | SETUP | 令牌 | 控制传输开始 |
| 0x1E | STALL | 握手 | 端点挂起 |
| 0xC3 | DATA0 | 数据 | 数据包(偶数序号) |
| 0x4B | DATA1 | 数据 | 数据包(奇数序号) |
| 0xA5 | SOF | 令牌 | 帧起始 |
| 0x3C | PRE | 特殊 | 前导包 |
| 0x78 | SPLIT | 特殊 | 分裂事务 |
| 0x87 | EXT | 特殊 | 扩展(保留) |
四、三种基本事务
USB 2.0定义了三种基本事务:SETUP、IN和OUT。所有传输类型都由它们组合而成。
4.1 SETUP事务
用于控制传输的Setup阶段,由Host发起,向设备发送8字节的Setup数据包。规则:
- 总是Host → Device
- 总是使用DATA0
- 设备必须ACK(不允许NAK或STALL)
- 收到SETUP后,端点的Data Toggle复位为DATA0
4.2 IN事务
用于从Device读取数据。Host发送IN令牌,Device返回数据或NAK/STALL。规则:
- Host发送IN令牌,声明要读取的端点
- Device响应:有数据→发送DATAx + Host ACK;无数据→NAK;端点挂起→STALL
- 数据包使用DATA0/DATA1交替
4.3 OUT事务
用于向Device写入数据。Host发送OUT令牌和数据,Device返回握手。规则:
- Host发送OUT令牌 + 数据包
- Device响应:接收成功→ACK;缓冲区满→NAK;端点挂起→STALL
- 数据包使用DATA0/DATA1交替
五、事务的详细流程与错误处理
5.1 正常事务流程
Host决定发送事务后,先发送令牌包(OUT/IN/SETUP),然后等待响应。若数据正确且收到ACK,则切换Data Toggle,事务完成;否则超时重传。
5.2 NAK响应处理
NAK表示设备暂时无法处理(如缓冲区满)。Host收到NAK后,不重试同一帧,等待下一帧/微帧再试。NAK不切换Data Toggle。
5.3 STALL响应处理
STALL表示端点发生错误或被挂起。Host收到STALL后,停止向该端点发送事务,必须通过CLEAR_FEATURE(ENDPOINT_HALT)清除。清除后,端点Data Toggle复位为DATA0。
5.4 超时与重传
Host发送令牌包后启动超时计时器。超时时间取决于速度(FS约16~18 bit times,HS更短)。超时后重传,不切换Data Toggle,通常重试3次后放弃。
六、数据切换机制(Data Toggle)
Data Toggle是USB保证数据包按正确顺序传输的机制。发送方和接收方各自维护一个Data Toggle位,成功传输后双方同时切换(DATA0 ↔ DATA1)。
核心规则:
- 接收方期望的PID与实际不符→丢弃数据但返回ACK
- 发送方超时未收到ACK→重传相同PID,不切换
各种情况下的Data Toggle行为:
| 场景 | Host Toggle | Device Toggle | 结果 |
|---|---|---|---|
| 初始状态 | DATA0 | DATA0 | - |
| 正常传输 + ACK | 切换 DATA1 | 切换 DATA1 | 成功 |
| 数据包丢失 | 保持 DATA0 | 保持 DATA0 | Host 重传 DATA0 |
| ACK 丢失 | 保持 DATA0 | 切换 DATA1 | Host 重传 DATA0,Device 收到重复数据,丢弃但 ACK |
| SETUP 包 | 复位 DATA0 | 复位 DATA0 | 控制传输开始 |
| CLEAR_FEATURE(HALT) | - | 复位 DATA0 | 恢复端点 |
ACK丢失的特殊情况:数据到达但ACK丢失,Host未收到ACK认为传输失败,Device已切换为DATA1。Host重传DATA0,Device收到不匹配的PID,丢弃数据但返回ACK,最终双方恢复同步。
七、事务与传输类型的关系
7.1 控制传输中的事务
控制传输由三个阶段组成:Setup阶段(SETUP事务)、Data阶段(可选,IN/OUT事务)、Status阶段(IN/OUT事务)。控制传输事务组合:
| 阶段 | 事务类型 | 数据方向 | PID 序列 |
|---|---|---|---|
| Setup | SETUP + DATA0 + ACK | Host→Device | SETUP→DATA0→ACK |
| Data OUT | OUT + DATAx + ACK | Host→Device | OUT→DATA1→ACK→OUT→DATA0→ACK… |
| Data IN | IN + DATAx + ACK | Device→Host | IN→DATA1→ACK→IN→DATA0→ACK… |
| Status OUT | OUT + DATA1 + ACK | Host→Device | OUT→DATA1(ZLP)→ACK |
| Status IN | IN + DATA1 + ACK | Device→Host | IN→DATA1(ZLP)→ACK |
7.2 批量传输中的事务
批量传输由连续的IN或OUT事务组成。结束标志为短包(数据包长度 < 端点最大包大小)或ZLP(零长度包)。
7.3 中断传输中的事务
本质上是周期性的IN或OUT事务。Host按bInterval周期轮询,设备若无数据则返回NAK。
7.4 同步传输中的事务
无握手包,只有令牌包 + 数据包。不保证数据正确性(无CRC重传),DATA0/DATA1仍然交替用于同步。
八、帧与微帧中的事务调度
每帧/微帧以SOF包开始。调度规则:
- 同步 + 中断优先(带宽预留,延迟保证)
- 控制最多10%帧带宽
- 批量使用剩余带宽
SOF包结构:
| 速度 | 帧周期 | 微帧数 | 帧号范围 |
|---|---|---|---|
| Full Speed | 1 ms | 1 | 0~2047 |
| High Speed | 1 ms | 8 | 0~2047 |
| 微帧周期 | 125 μs | - | - |
九、USB 3.x事务差异
| 特性 | USB 2.0 | USB 3.0+ |
|---|---|---|
| 通信方向 | 半双工 | 全双工 |
| 事务类型 | 广播 | 路由到目标设备 |
| 握手机制 | ACK/NAK/STALL 每包 | 基于信用的流控制 |
| 数据同步 | DATA0/DATA1 | 序列号机制 |
| 广播 | 所有设备接收 | 仅目标设备接收 |
| 端点数量 | 16 (IN+OUT) | 32 (IN+OUT) |
| 最大包大小 | 1024 (HS) | 1024 |
| 突发传输 | 无 | 最多 16 个包不握手 |
USB 3.0事务简化:不再使用DATA0/DATA1,改用序列号;允许突发传输(Burst),连续发送多个包;使用信用机制(Credit-based Flow Control)代替NAK。
十、事务抓包实例分析
以下是一次GET_DESCRIPTOR(Device, 8)请求的事务级抓包:
事务1:SETUP事务
[Host] TOKEN: SETUP ADDR=0 ENDP=0 CRC5=OK
PID: 0x2D (00101101)
[Host] DATA0: Setup Packet (8 bytes)
80 06 00 01 00 00 08 00
PID: 0xC3 (11000011)
CRC16: OK
[Device] HANDSHAKE: ACK
PID: 0xD2 (11010010)
事务2:IN事务(Data阶段)
[Host] TOKEN: IN ADDR=0 ENDP=0 CRC5=OK
PID: 0x69 (01101001)
[Device] DATA1: Device Descriptor (8 bytes)
12 01 00 02 00 00 00 08
PID: 0x4B (01001011)
CRC16: OK
[Host] HANDSHAKE: ACK
PID: 0xD2 (11010010)
事务3:OUT事务(Status阶段)
[Host] TOKEN: OUT ADDR=0 ENDP=0 CRC5=OK
PID: 0xE1 (11100001)
[Host] DATA1: ZLP (Zero Length Packet)
PID: 0x4B (01001011)
CRC16: OK
[Device] HANDSHAKE: ACK
PID: 0xD2 (11010010)
完整控制传输 = 3个事务 = 9个包:
| 事务 | 包 1 | 包 2 | 包 3 | 方向 |
|---|---|---|---|---|
| SETUP | SETUP | DATA0 | ACK | Host→Device |
| IN | IN | DATA1 | ACK | Device→Host |
| OUT | OUT | DATA1 | ACK | Host→Device |
十一、总结
USB事务是连接数据包与上层传输的桥梁。核心要点:
| 要点 | 说明 |
|---|---|
| 事务 = 2~3 个包 | 令牌 + 数据 + 握手(同步传输无握手) |
| 三种基本事务 | SETUP、IN、OUT |
| SETUP 用 DATA0 | 且复位 Data Toggle |
| Data Toggle | DATA0/DATA1 交替,保证顺序 |
| NAK | 暂时无法处理,不重试同一帧 |
| STALL | 端点挂起,需 CLEAR_FEATURE 恢复 |
| 同步传输 | 无握手,实时但不可靠 |
| 帧调度 | SOF → 同步 → 中断 → 控制 → 批量 |
掌握事务机制,能让你在C++、JavaScript、Python、Go或TypeScript的USB编程中更高效地处理通信问题。无论是调试设备驱动还是开发USB应用,理解SETUP、IN、OUT事务及Data Toggle机制都是必备技能。
[AFFILIATE_SLOT_1]推荐阅读:USB协议完整学习指南,从包到传输,一站式掌握。
[AFFILIATE_SLOT_2](PID & 0x0F) == (~(PID >> 4) & 0x0F)
浙公网安备 33010602011771号