3. 包结构与域定义

USB协议的数据传输,说到底就是一个个"包"在线上跑。每个包都有固定的骨架,不论你是全速、低速还是高速,这个骨架基本一致——SYNC、PID、ADDR、ENDP、CRC、EOP。搞懂这些域,你才算真正入了USB的门。我在项目中见过不少工程师,拿着逻辑分析仪抓了一堆数据,却看不懂包从哪里开始、到哪里结束,说白了就是没吃透这个结构。

3.1 SYNC同步域

所有USB包的开头都是SYNC。它的核心任务只有一个:让接收器的时钟和发送器对齐。USB总线没有独立的时钟线,收发双方靠的是NRZI编码里的电平跳变来同步。SYNC域就是给接收器一个"热身"的时间窗口。

为什么SYNC这么重要?你想想看,设备刚上电或者刚连上主机,接收器这边的PLL可能还没锁定发送器的时钟频率。如果没有SYNC,后面的数据位很可能被误判。嗯,这里要注意,SYNC的最后一位固定是1,这个1不是数据,而是同步结束的标记。接收器检测到这个1,就知道后面的位是真正的包内容了。

全速和低速模式下,SYNC是8位的二进制序列:00000001。前7个0在NRZI编码下产生连续的跳变,帮助PLL快速锁定。高速模式则把SYNC扩展到了32位:00000000 00000000 00000000 00000001,前31位全是0,最后1位是1。我在项目中遇到过一个大坑——某款USB控制器的FPGA实现,SYNC长度配错了,高速设备枚举时主机始终不认。折腾了两天,最后发现SYNC只配了8位,高速下根本锁不住时钟。

个人习惯:调试SYNC时,别光看逻辑分析仪的二进制列表,我习惯把波形调出来看NRZI解码后的电平翻转次数。全速模式下8位SYNC应该有7次跳变,少一次都说明PLL没锁住。
// 高速SYNC与全速SYNC的二进制模式对比
// 全速/低速: 00000001  (8位)
// 高速:      00000000 00000000 00000000 00000001  (32位)

3.2 PID包标识符

SYNC之后紧跟着PID。PID全称是Packet Identifier,说白了就是告诉接收端"这个包是什么类型"。USB协议里,PID一共8位:高4位是类型编码,低4位是类型编码的取反,用来做校验。

举个例子,令牌包中的OUT事务,PID编码是0001,那么低4位就是1110,整个字节是0001 1110(0x1E)。接收器通过校验低4位是不是高4位的反码,就能快速判断PID有没有被总线噪声干扰。

USB的PID分四大类:令牌类(IN、OUT、SETUP、SOF)、数据类(DATA0、DATA1、DATA2、MDATA)、握手类(ACK、NAK、STALL、NYET)以及特殊类(PRE、ERR、SPLIT、PING)。我在早期的项目中曾经把DATA0和DATA1的PID搞反过,结果数据包重传机制一直报错,后来查了几天才发现是PID赋值写死了。

3.3 ADDR与ENDP域

令牌包里,PID后面跟着ADDR和ENDP。这两个域共同决定了数据要发给谁、发给哪个端点。

  • ADDR(地址域):7位,可表示0~127。地址0保留给刚上电的设备使用,枚举后主机给分配一个非零地址。我见过有的设备地址配置寄存器只做了6位,结果挂到127个设备的测试环境里直接崩溃。
  • ENDP(端点域):4位,可表示0~15。端点0是控制端点,所有设备都必须支持,剩下的1~15用作数据端点。

这两个域在总线上是连续传输的,没有额外分隔符。接收器根据包类型来解析——只有令牌包才需要ADDR+ENDP,数据包和握手包里根本没有这两个域。

关键点:ADDR 7位 + ENDP 4位,总共11位。总线上最多挂127个设备,每个设备最多16个端点。设计USB Hub时,地址分配逻辑最容易出bug,我曾经踩过这个坑。

3.4 CRC校验域

USB用CRC来保证数据完整性。不同包类型使用的CRC位数不同:

  • 令牌包(IN/OUT/SETUP等):CRC5,校验PID之后的ADDR+ENDP共11位。生成多项式是 x⁵ + x² + 1。
  • 数据包(DATA0/DATA1等):CRC16,校验整个数据载荷。生成多项式是 x¹⁶ + x¹⁵ + x² + 1。
  • 握手包(ACK/NAK等):没有CRC,因为只有PID,没有后续数据。
避坑指南:我曾经被CRC16的初始值坑过一次。USB规范要求CRC16的初始值为0xFFFF,但有些第三方IP核默认用的是0x0000。两边的CRC算出来永远对不上,设备枚举到一半就挂掉了。排查了整整一个下午,最后用逻辑分析仪逐位比对才发现问题。

3.5 EOP包结束

每个USB包都有明确的结束标记——EOP(End of Packet)。接收器靠EOP来判断包已经传输完毕,而不是靠计时器超时。

  • 全速/低速:EOP由2个位宽的SE0(Single-Ended 0)加上1个位宽的J状态组成。SE0就是D+和D-同时拉低。全速模式下的J状态是D+高、D-低,低速模式下则相反。
  • 高速:EOP是8个位宽的NRZ 0(连续的0电平),之后跟着一个短暂的SE0。

我在测试一个USB音频设备时,遇到过EOP信号幅度不够的问题。示波器一看,SE0只有1个位宽,主机根本不认,直接把包丢掉了。后来发现是驱动器的摆率设置不对。

域名称 长度(位) 作用 适用包类型
SYNC 8(全速/低速)
32(高速)
时钟同步,最后1位标记同步结束 所有包
PID 8(4位类型 + 4位校验) 标识包类型(令牌、数据、握手、特殊) 所有包
ADDR 7 目标设备地址(0~127) 令牌包
ENDP 4 目标端点号(0~15) 令牌包
CRC 5(令牌包)
16(数据包)
检错,令牌包校验ADDR+ENDP,数据包校验载荷 令牌包、数据包
EOP 2~3(全速/低速)
8+(高速)
包结束标记,接收器据此判断包边界 所有包
USB包结构分解(令牌包为例) SYNC 8 / 32 位 PID 8 位 ADDR 7 位 ENDP 4 位 CRC 5 / 16 位 EOP 2 ~ 8+ 位 各域要点速查 • SYNC:全速/低速用8位 00000001,高速用32位 00000000 00000000 00000000 00000001 • PID:高4位是类型编码,低4位是反码校验位,接收器用反码做快速检错 • ADDR+ENDP:只有令牌包包含这两个域,数据包和握手包没有 • CRC:令牌包用CRC5,数据包用CRC16,握手包没有CRC • EOP:全速/低速用SE0+J状态,高速用连续8位NRZ 0
核心总结:USB包结构就是SYNC起、EOP止,中间按包类型填充对应域。SYNC的长度决定了你能跑多高的速率——8位给全速/低速,32位给高速。PID告诉你"这是个什么包",ADDR+ENDP告诉你"发给谁",CRC保证"没传错"。搞懂了这六个域,USB协议就算入门了。