第3章 USB 3.0协议层:包格式、令牌包、数据包、握手包详解

看USB 3.0的枚举,说白了就是看主机和设备怎么用包来“讨价还价”。协议层这些包,就像快递单、包裹和回执单。我当年刚接触时,看着一堆英文缩写头都大了,后来用逻辑分析仪抓一次波形,才真正明白它们怎么协同工作。

3.1 包的基本结构

USB 3.0的包,比USB 2.0的复杂不少。每个包前面有一串同步序列(SYNC,32位),然后是一个包帧起始(SOP),接着是12字节的包头。如果包带数据,后面跟着最多1024字节的数据载荷,最后是4字节的CRC和包结束(EOP)。嗯,这里要注意:USB 3.0的包头本身不带CRC,而是用单独的链路层CRC保护。

字段长度(位)说明
SYNC32同步序列,接收器用它锁定时钟
SOP8包开始标志
Packet Header96包含包类型、地址、端点等信息
Data Payload(可选)0–8192数据载荷,按4字节对齐
CRC32对数据载荷的校验
EOP8包结束标志

我习惯在调试时,先看SYNC和SOP是否对齐,如果不对齐,往往就是链路层出问题了。曾经有一次,我调一块FPGA模拟设备,EOP一直发错,主机就是不认包,折腾了两天才发现是发送状态机少等了一个时钟。

3.2 令牌包( Packet)

令牌包,你可以理解成“命令包”。它告诉设备要干什么:是发数据过来,还是接收数据,或者是建立控制传输。令牌包的包头类型字段固定为,后面的子类型决定具体操作。

  • OUT:主机向设备发送数据
  • IN:主机向设备请求数据
  • SETUP:控制传输的启动阶段,主机发设置信息
  • PING:主机测试设备是否能接收数据(流量控制)
  • ERDY:设备通知主机自己准备好接收/发送了

令牌包里包含设备地址和端点号。你想想看,主机怎么知道包是发给谁的?就是靠这个地址。设备收到后,先检查地址是不是自己的,不是就直接扔了。

避坑指南 我曾经在写设备固件时,忘记判断包的DeviceAddress字段,结果主机发的包全部被响应,导致总线上多个设备冲突。记住:设备必须严格过滤地址,否则会死机。

令牌包不带数据载荷,所以只有12字节的包头,后面直接跟EOP。我建议你在看协议分析仪时,先抓几个令牌包,观察它们的Type和Direction,很快就能摸清传输方向。

3.3 数据包(Data Packet)

数据包是真正的“货物”。令牌包说“给我数据”,设备就回一个数据包;令牌包说“收数据”,主机就发数据包。数据包的包头里有一个Data Sequence Number(序列号),用来防丢包、去重。

  • DATA0 / DATA1 / DATA2 / MP(MData Packet):序列号交替使用,保证可靠传输
  • Data Payload:最大1024字节,但实际取决于端点描述符的wMaxPacketSize

我遇到过一种情况:设备枚举时,控制传输需要返回设备描述符,设备因为某些原因只返回了0长度的数据包。主机收不到数据,就一直重试。后来发现设备的状态机在“发数据”阶段跑飞了,根本没把描述符填入缓冲区。

调试技巧 在枚举阶段,如果主机反复请求同一个描述符,先用逻辑分析仪看设备回的是不是空数据包。如果包长度字段为0,基本可以断定设备端的问题。

3.4 握手包(Handshake Packet)

握手包是确认信号。它只有12字节的包头,没有数据。主机发完数据包后,设备得告诉主机“收到了,没问题”(ACK)或者“现在忙,别烦我”(NAK)或者“这个端点出错了,别发了”(STALL)。USB 3.0里还有一种叫NYET的握手包,用于流量控制场景。

握手类型含义典型场景
ACK数据已正确接收所有成功的数据交换
NAK设备忙,稍后再试中断传输时设备暂无数据
STALL端点出现严重错误不支持的请求或设备故障
NYET设备尚未准备好接收主机发PING后设备回复NYET

我个人习惯在固件里加一个握手包调试计数器,每次收到ACK就+1,收到STALL就打印错误。这样枚举过程中一旦握手异常,能立刻定位是哪个阶段卡住了。

3.5 包类型与事务流程

三类包并不是独立的,它们串在一起构成一个“事务”。比如典型的控制写事务:
主机 → 令牌包(SETUP) → 主机 → 数据包(DATA0) → 设备 → 握手包(ACK)
然后主机再发起一个控制读事务:
主机 → 令牌包(IN) → 设备 → 数据包(DATA1) → 主机 → 握手包(ACK)

下面这张图把整个关系画清楚了:

USB 3.0 包事务流程 令牌包 () 主机→设备命令 OUT / IN / SETUP PING / ERDY 含地址+端点号 数据包 (Data) 主机→设备 或 设备→主机 DATA0 / DATA1 / DATA2 / MP 含序列号+数据载荷 最大1024字节 握手包 (Handshake) 无数据,仅确认 ACK / NAK / STALL / NYET 触发 确认 令牌包 (OUT) 数据包 (DATA0) 握手包 (ACK) 说明 一个完整的事务 = 令牌包 → 数据包 → 握手包 控制传输的SETUP阶段先发SETUP令牌,再发DATA0,最后设备回ACK

你看,整个流程很清楚:主机先扔一个令牌包,告诉设备“我要干嘛”,然后要么主机发数据、要么设备发数据,最后收一个握手包确认。我刚开始写USB驱动时,总想着一步到位,结果枚举失败还不知道是哪个包丢了。后来老老实实抓出每个包的时间戳,对照这个顺序,才把问题找出来。

核心记忆口诀
令牌指路,数据载货,握手收工——三个包缺一不可。