2. USB协议基础(一):USB数据包结构、包类型与总线事务
USB通信的本质是包(Packet)的交换。主机与设备之间所有的数据传输,最终都被拆解为一系列符合USB协议规范的包。理解包的结构、类型以及由包组成的事务,是开发USB设备驱动和深入主机控制器原理的基石。
2.1 USB数据包结构
一个标准的USB数据包由以下字段按顺序组成:
| 字段 | 全称 | 长度(位) | 说明 |
|---|---|---|---|
| SOP | Start of Packet | — | 同步序列,用于接收器锁定时钟。由SYNC字段实现(通常为8位,全0后跟一个1)。 |
| PID | Packet Identifier | 8 | 包标识符,包含4位类型码和4位校验码。决定包的类型和方向。 |
| ADDR | Address | 7 | 设备地址,范围0~127。地址0用于默认控制管道(枚举阶段)。 |
| ENDP | Endpoint | 4 | 端点号,范围0~15。控制端点必须为0。 |
| CRC | Cyclic Redundancy Check | 5或16 | 循环冗余校验。Token包使用5位CRC,Data包使用16位CRC。 |
| EOP | End of Packet | — | 包结束标志,由SE0状态(D+和D-同时为低)持续2位时间构成。 |
关键点: PID字段中的4位校验码是类型码的按位取反。例如,OUT令牌的PID为
0001,其校验码为1110,组合后为0001 1110(0x1E)。接收方通过校验码快速检测PID传输错误。
2.1.1 数据包结构示意图
下图展示了USB数据包在总线上的完整帧结构,从SOP到EOP的每个字段依次排列:
2.2 包类型(Packet Types)
USB协议定义了四大类包,由PID字段区分。每一类包服务于总线事务的不同阶段。
2.2.1 Token包(令牌包)
Token包由主机发出,用于启动一次总线事务。它包含设备地址和端点号,指定了后续数据传输的方向和参与者。
- OUT (PID=0001):通知设备,主机即将发送数据到该设备的指定端点。
- IN (PID=1001):通知设备,主机准备从该设备的指定端点接收数据。
- SETUP (PID=1101):用于控制传输的建立阶段,主机向设备发送控制请求。
- SOF (PID=0101):Start of Frame,主机每1ms(全速/低速)或125µs(高速)广播一次,用于同步帧起始。
2.2.2 Data包(数据包)
Data包承载实际的有效载荷数据。根据数据切换(Data Toggle)机制,分为DATA0和DATA1两种类型,用于保证数据完整性。
- DATA0 (PID=0011):初始数据包或偶数序号数据包。
- DATA1 (PID=1011):奇数序号数据包。
2.2.3 Handshake包(握手包)
Handshake包用于报告事务的完成状态。它是最简单的包,仅由PID和EOP组成。
- ACK (PID=0010):确认。数据正确接收,无错误。
- NAK (PID=1010):否认。设备暂时无法发送或接收数据(忙),但未发生错误。
- STALL (PID=1110):停滞。设备端点出现严重错误,需要主机干预(如控制端点发送错误请求)。
- NYET (PID=0110):未准备好。高速传输中,设备尚未准备好接收下一个数据包。
2.2.4 Special包(特殊包)
特殊包用于更高级的总线管理功能,主要在高速模式下使用。
- PRE (PID=1100):前导包。用于主机在高速总线上与低速设备通信前,先发送PRE包切换总线模式。
- ERR (PID=1100):错误包。在高速分裂事务中,集线器向主机报告下游总线错误。
- SPLIT (PID=1000):分裂事务包。用于高速主机通过集线器与全速/低速设备通信。
- PING (PID=0100):探测包。高速模式下,主机在发送数据前先探测设备端点是否准备好接收。
实战要点: 在驱动开发中,最常见的握手包是ACK、NAK和STALL。NAK通常表示设备忙,驱动应稍后重试;STALL表示协议错误,驱动需要执行清除停滞(Clear Feature)操作。
2.2.5 包类型分类表
| 大类 | PID (二进制) | 名称 | 方向 | 用途 |
|---|---|---|---|---|
| Token | 0001 | OUT | 主机→设备 | 启动主机到设备的数据传输 |
| 1001 | IN | 主机→设备 | 启动设备到主机的数据传输 | |
| 1101 | SETUP | 主机→设备 | 启动控制传输的建立阶段 | |
| 0101 | SOF | 主机→设备 | 帧起始广播 | |
| Data | 0011 | DATA0 | 双向 | 偶数序号数据 |
| 1011 | DATA1 | 双向 | 奇数序号数据 | |
| Handshake | 0010 | ACK | 接收方→发送方 | 确认成功接收 |
| 1010 | NAK | 设备→主机 | 设备忙,无法处理 | |
| 1110 | STALL | 设备→主机 | 端点停滞,协议错误 | |
| 0110 | NYET | 设备→主机 | 高速模式下未准备好 | |
| Special | 1100 | PRE/ERR | 主机→设备 | 前导/错误报告 |
| 1000 | SPLIT | 主机→集线器 | 分裂事务 | |
| 0100 | PING | 主机→设备 | 高速探测 | |
| — | Reserved | — | 保留 |
2.3 总线事务(Bus Transactions)
一次总线事务由主机发起,包含令牌阶段、数据阶段(可选)和握手阶段(可选)。事务是USB数据传输的基本单元。
2.3.1 事务的三种基本类型
- IN事务:主机从设备读取数据。
- 令牌阶段:主机发送IN Token包。
- 数据阶段:设备返回DATA0/DATA1包(如果设备有数据要发送)。
- 握手阶段:主机发送ACK(正确接收)或NAK/STALL(错误)。
- OUT事务:主机向设备发送数据。
- 令牌阶段:主机发送OUT Token包。
- 数据阶段:主机发送DATA0/DATA1包。
- 握手阶段:设备返回ACK(成功接收)、NAK(忙)或STALL(停滞)。
- SETUP事务:控制传输的建立阶段,用于发送标准请求。
- 令牌阶段:主机发送SETUP Token包。
- 数据阶段:主机发送DATA0包(固定为DATA0,8字节标准请求)。
- 握手阶段:设备必须返回ACK(成功接收),否则主机重试。
2.3.2 事务流程示意图
下图展示了IN事务和OUT事务的完整包序列:
2.3.3 事务与传输的关系
USB协议定义了四种传输类型(Transfer Types),每种传输由多个总线事务组成:
| 传输类型 | 包含的事务 | 典型应用 |
|---|---|---|
| 控制传输 | SETUP事务 + 0/多个IN/OUT事务 + 状态阶段事务 | 设备枚举、标准请求(如Get Descriptor) |
| 批量传输 | 多个IN或OUT事务 | U盘、打印机、网络适配器 |
| 中断传输 | 单个IN或OUT事务(保证轮询间隔) | 鼠标、键盘、游戏手柄 |
| 等时传输 | 单个IN或OUT事务(无握手阶段) | 音频、视频、实时数据流 |
常见误区: 初学者常将“事务”与“传输”混淆。一次控制传输可能包含2~3个事务(建立、数据、状态),而一次批量传输可能包含成千上万个IN/OUT事务。事务是总线上的原子操作,传输是逻辑上的数据交换过程。
2.4 数据切换(Data Toggle)机制
为了保证数据包不会丢失或重复,USB协议引入了数据切换机制。每个端点维护一个Toggle位(0或1),初始值为0。
- 主机和设备各自维护独立的Toggle状态。
- 每次成功的事务(收到ACK)后,Toggle位翻转(0→1或1→0)。
- 发送方发送与当前Toggle匹配的DATA包(DATA0对应Toggle=0,DATA1对应Toggle=1)。
- 接收方验证接收到的DATA包类型是否与期望的Toggle一致。如果不一致,说明发生了错误,接收方丢弃该包并返回NAK或ACK(取决于协议)。
驱动开发提示: 在Linux USB驱动中,
usb_submit_urb()函数会自动管理Toggle位。但在某些底层调试场景(如使用usb_control_msg或自定义批量传输),开发者需要确保Toggle状态正确,否则设备会持续返回NAK或STALL。