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数据 2USB 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
OUT0001111011100001 = 0xE1
IN1001011001101001 = 0x69
SETUP1101001000101101 = 0x2D
DATA00011110011000011 = 0xC3
DATA11011010001001011 = 0x4B
ACK0010110111010010 = 0xD2
NAK1010010101011010 = 0x5A
STALL1110000100011110 = 0x1E

注意:PID的取反校验机制能有效检测传输错误,是USB协议可靠性的基础之一。

校验规则:如果 ,则 PID 有效。

PID类型速查表:

PID 值类型说明
0xE1OUT令牌Host → Device OUT 端点
0xD2ACK握手确认接收
0x69IN令牌Host ← Device IN 端点
0x5ANAK握手未就绪
0x2DSETUP令牌控制传输开始
0x1ESTALL握手端点挂起
0xC3DATA0数据数据包(偶数序号)
0x4BDATA1数据数据包(奇数序号)
0xA5SOF令牌帧起始
0x3CPRE特殊前导包
0x78SPLIT特殊分裂事务
0x87EXT特殊扩展(保留)

四、三种基本事务

USB 2.0定义了三种基本事务:SETUPINOUT。所有传输类型都由它们组合而成。

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 ToggleDevice Toggle结果
初始状态DATA0DATA0-
正常传输 + ACK切换 DATA1切换 DATA1成功
数据包丢失保持 DATA0保持 DATA0Host 重传 DATA0
ACK 丢失保持 DATA0切换 DATA1Host 重传 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 序列
SetupSETUP + DATA0 + ACKHost→DeviceSETUP→DATA0→ACK
Data OUTOUT + DATAx + ACKHost→DeviceOUT→DATA1→ACK→OUT→DATA0→ACK…
Data ININ + DATAx + ACKDevice→HostIN→DATA1→ACK→IN→DATA0→ACK…
Status OUTOUT + DATA1 + ACKHost→DeviceOUT→DATA1(ZLP)→ACK
Status ININ + DATA1 + ACKDevice→HostIN→DATA1(ZLP)→ACK

7.2 批量传输中的事务

批量传输由连续的IN或OUT事务组成。结束标志为短包(数据包长度 < 端点最大包大小)或ZLP(零长度包)。

7.3 中断传输中的事务

本质上是周期性的IN或OUT事务。Host按bInterval周期轮询,设备若无数据则返回NAK。

7.4 同步传输中的事务

无握手包,只有令牌包 + 数据包。不保证数据正确性(无CRC重传),DATA0/DATA1仍然交替用于同步。

八、帧与微帧中的事务调度

每帧/微帧以SOF包开始。调度规则:

  • 同步 + 中断优先(带宽预留,延迟保证)
  • 控制最多10%帧带宽
  • 批量使用剩余带宽

SOF包结构:

速度帧周期微帧数帧号范围
Full Speed1 ms10~2047
High Speed1 ms80~2047
微帧周期125 μs--

九、USB 3.x事务差异

特性USB 2.0USB 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方向
SETUPSETUPDATA0ACKHost→Device
ININDATA1ACKDevice→Host
OUTOUTDATA1ACKHost→Device

十一、总结

USB事务是连接数据包与上层传输的桥梁。核心要点:

要点说明
事务 = 2~3 个包令牌 + 数据 + 握手(同步传输无握手)
三种基本事务SETUP、IN、OUT
SETUP 用 DATA0且复位 Data Toggle
Data ToggleDATA0/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)