USB 子系统(二)USB 协议层数据格式

硬件拓扑结构

  • compound device :多个设备组合起来,通过 HUB 跟 Host 相连
  • composite device :一个物理设备有多个逻辑设备 (multiple interfaces)

在软件开发过程中,我们可以忽略 Hub 的存在,硬件拓扑图简化如下:

一个物理设备里面可能有多个逻辑设备,Hos 可以外接多个逻辑设备,硬件拓扑图如下:

协议层

  • 寻址设备阶段:USB 系统是一个 Host 对应多个设备,要传输数据首先要通知设备
    • 发出 IN 令牌包:表示想读数据,里面含有设备地址
    • 发出 OUT 令牌包:表示想写数据,里面含有设备地址
  • 数据传输阶段
    • Host 想读数据:发出 IN 令牌包后读取数据包
    • Host 想发出数据:发出 OUT 令牌包后发出数据包
  • 数据确认阶段
    • Host 读数据,设备可能未就绪,就会回应 NAK 包
    • Host 写数据,它发出数据后,设备正确接收了,就回复 ACK 包

SYNC 域

Host 发出 SOP 信号后,就会发出 SYNC 信号(前一节的那个 K-J-K-J-K-J-K 信号):它是一系列的、最大传输频率的脉冲,接收方使用它来同步数据。对于低速/全速设备,SYNC 信号是 8 位数据(从左到右是 00000001);对于高速设备,SYNC 信号是 32 位数据(从左到右是 00000000000000000000000000000001)。使用 NRZI 编码时,前面每个"0"都对应一个跳变。

在很多文档里,把 SOP 和 SYNC 统一称为"SYNC"。

包格式

USB 总线上传输的数据以包为单位。USB 包里含有哪些内容 ("域")?

  • SOP:用来表示包的起始
  • SYNC:用来同步时钟
  • PID:表示包的类型
  • 地址:在 USB 硬件体系中,一个 Host 对应多个 Logical Device,那么 Host 发出的包,如何确定发给谁?
    • 发给所有设备:包里不含有设备地址
    • 发给某个设备:包里含有设备地址、端点号
  • 帧号、数据等跟 PID 相关的内容
  • CRC 校验码

发起一次完整的传输,可能涉及多个包。那么,第 1 个包里含有设备地址、端点号,后续的包就没必要包含设备地址、端点号。

PID 域

注意:所有的 USB 文档提到的"输入"、"输出",都是基于 Host 的角度,"输出"表示从 Host 输出到设备,"输入"表示 Host 从设备得到数据。

有哪些 USB 包?根据包数据里的 PID 的 bit1, bit0 可以分为 4 类:

  • 令牌包 (Token):01B
  • 数据包 (Data):11B
  • 握手包 (Handshake):10B
  • 特殊包 (Special):00B

PID 有 4 位,使用 bit1,bit0 确定分类,使用 bit3,bit2 进一步细分。如下表(来自《圈圈教你玩 USB》) 所示:

在 USB 包中,PID 域使用 8 位来表示,格式如下:

前 4 位表示 PID,后 4 位是对应位的取反。接收方发现后 4 位不是前 4 位的取反的话,就认为发生了错误。

令牌包 (Token)

令牌类的 PID,起"通知作用",SOF 令牌包被用来通知所有设备,OUT/IN/SETUP 令牌包被用来通知某个设备。

对于 OUT、IN、SETUP 令牌包,它们都是要通知到具体的设备,格式如下:

USB 设备的地址有 7 位,格式如下:

对于 SOF 包,英文名为"Start-of-Frame marker and frame number"。对于 USB 全速设备,Host 每 1ms 产生一个帧;对于高速设备,每 125us 产生一个微帧,1 帧里有 8 个微帧。Host 会对当前帧号进行累加计数,在每帧或每微帧开始时,通过 SOF 令牌包发送帧号。对于高速设备,每 1 毫秒里有 8 个微帧,这 8 个微帧的帧号是一样的,每 125us 发送一个 SOF 令牌包。

SOF 令牌包格式如下:

数据包

Host 使用 OUT、IN、SETUP 来通知设备要传输数据了,数据通过"数据包"进行传输。

数据包也有 4 种类型:DATA0、DATA1、DATA2、MDATA。其中 DATA2、MDATA 在高速设备中使用。

Host 和设备都会维护自己的数据包切换机制,当数据包成功发送或者接收时,数据包类型切换。当检测到对方使用的数据包类型不对时,USB 系统认为发生了错误。

  • Host 发送 DATA0 给设备,设备返回 ACK 表示成功接收,设备期待下一个数据是 DATA1
  • 但是 Host 没有接收到 ACK,Host 认为数据没有发送成功,Host 继续使用 DATA0 发送上一次的数据
  • 设备再次接收到 DATA0 数据包,它就知道:这是重传的数据包

数据包格式如下:

对于全速设备,数据包中的数据做大是 1023 字节;对于高速设备,数据包中的数据做大是 1024 字节。

握手包

握手包有 4 类:ACK、NAK、STALL、NYET

  • ACK:数据接收方用来回复发送方,表示正确接收到了数据并且有足够的空间保存数据
  • NAK:Host 发送数据给设备时,设备可以回应 NAK 表示"我还没准备好,没办法接收数据";Host 想读取设备的数据时,设备可以回复 NAK 表示"我没有数据给你"
  • STALL:表示发生了错误,比如设备无法执行这个请求(不支持该端点等)、端点已经挂起。设备返回 STALL 后,需要主机进行干预才能接触 STALL 状态
  • NYET:仅适用于高速设备。Host 可以发出 PING 包用来确认设备有数据,设备可以回应 NYET 表示"还没呢"。Hub 也可以回应 NYET 表示低速/全速传输还没完结

传输细节

事务 (Transaction) 和传输 (Transfer)

USB 传输的基本单位是包 (Packet),包的类型由 PID 表示。一个单纯的包,是无法传输完整的数据的。完整的数据传输,需要涉及多个包:令牌包、数据包、握手包。这个完整的数据传输过程,被称为事务 (Transaction)。有些事务需要握手包,有些事务不需要握手包。

有四类** Transaction**:
- 批量事务:用来传输大量的数据,数据的正确性有保证,时效没有保证 - 中断事务:用来传输周期性的、小量的数据,数据的正确性和时效都有保证 - 实时事务:用来传输实时数据,数据的正确性没有保证,时效有保证 - 建立事务:跟批量事务类似,只不过令牌包是 SETUP 令牌包

有四类传输 (Transfer):
- 批量传输:就是使用批量事务实现数据传输,比如 U 盘 - 中断传输:就是使用中断事务实现数据传输,比如鼠标 - 实时传输:就是使用实时事务实现数据传输,比如摄像头 - 控制传输:由建立事务、批量事务组成,所有的 USB 设备都必须支持控制传输,用于"识别/枚举"

Filed、Packer、Transaction、Transfer 之间的关系:
- BIT 组成域 (Field) - 域组成包 (Packet) - 包组成事务 (Transaction) - 事务组成传输 (Transfer)

以 Isochronous TransFer 为例,如下图所示,表示了他们之间的关系:

过程 (stage) 和阶段 (phase)

事务(Transaction)由多个包组成,比如 Host 要发送数据给设备,这就会涉及很多个包:

  • Host 发出 OUT 令牌包,表示要发数据给哪个设备
  • Host 发出 DATA 数据包
  • 设备收到数据后,回应 ACK 包

这个完整的事务涉及 3 个包 (Packet),分为 3 个阶段 (Phase):

  • 令牌阶段 (Token phase):由令牌包实现
  • 数据阶段 (Data phase):由数据包实现
  • 握手阶段 (Handshake phase):由握手包实现

事务由包组成,这些包分别处于 3 个阶段 (phase):令牌阶段,数据阶段,握手阶段。

对于批量传输、中断传输、实时传输,它们分别由一个事务组成,不再细分为若干个过程。

但是控制传输由多个事务组成,这些事务分别处于 3 个过程:建立过程 (stage)、数据过程 (stage)、状态过程 (stage)。

总结起来就是:

  • 控制传输由多个过程 (stage) 组成,每个过程由一个事务来实现
  • 每个事务由多个阶段 (phase) 组成,每个阶段有一个包来实现

批量传输

批量传输用批量事务来实现,用于传输大量的数据,数据的正确性有保证,时效没有保证。

批量事务由 3 个阶段 (phase) 组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有 SOP、SYNC、PID、EOP。

下图中各个矩形框就对应一个完整的包。

《圈圈教你玩 USB》中有详细的示例:

中断传输

中断传输用中断事务来实现,用于传输小量的、周期性的数据,数据的正确性和时效都有保证。

中断事务由 3 个阶段 (phase) 组成:令牌阶段、数据阶段、握手阶段。每个阶段都是一个完整的包,含有 SOP、SYNC、PID、EOP。

下图中各个矩形框就对应一个完整的包。

中断事务跟批量事务非常类似,Host 使用它来周期性地读数据、写数据。

以鼠标为例,我们需要及时获得鼠标的数据,不及时的话你会感觉鼠标很迟钝。但是 USB 协议中并没有中断功能,它使用"周期性的读、写"来实现及时性。具体过程如下:

  • Host 每隔 n 毫秒发出一个 IN 令牌包
  • 鼠标有数据的话,发出 DATA0 或 DATA1 数据包给 Host;鼠标没有数据的话,发出 NAK 给 Host

中断事务的优先级比批量事务更高,它要求实时性,而批量事务不要求实时性。

实时传输

实时传输用实时事务来实现,用于传输实时数据,对数据的正确性没有要求。

实时事务由 2 个阶段 (phase) 组成:令牌阶段、数据阶段。每个阶段都是一个完整的包,含有 SOP、SYNC、PID、EOP。

下图中各个矩形框就对应一个完整的包。

实时事务跟中断事务非常类似,Host 也会周期性的发起实时事务,主要区别在于:

  • 实时事务不要求准确性,没有握手阶段
  • 实时事务传输的数据量比较大,中断事务传输的数据量比较小

控制传输

在使用批量传输时,使用 IN 令牌包或 OUT 令牌包表示数据传输方向。

控制传输的令牌包永远是 SETUP,怎么分辨是读数据,还是写数据?发出 SETUP 令牌包后,还要发出 DATA0 数据包,根据数据的内容来确定后续是读数据,还是写数据。这个过程称为"建立事务"(SETUP Transaction)。

但是控制传输由多个事务组成,这些事务分别处于 3 个过程:建立过程 (stage)、数据过程 (stage)、状态过程 (stage)。

  • 建立过程 (stage),使用 SETUP 事务:Host 发出 SETUP 令牌包、DATA0 数据包、得到 ACK 握手包
  • 数据过程 (stage),使用批量事务:
    • 对于输出:Host 发出 OUT 令牌包,发出 DATA0、DATA1 数据包、得到 ACK 握手包
    • 对于输入:Host 发出 IN 令牌包,读到 DATA0、DATA1 数据包、发出 ACK 握手包
  • 状态过程 (stage),使用批量事务:
    • 对于输出:Host 发出 IN 令牌包,读到 DATA1 数据包,发出 ACK 握手包
    • 对于输入:Host 发出 OUT 令牌包,发出 DATA1 数据包,等待 ACK 握手包

上图中的每一个方框,都是一个完整的事务,含有:Token Packet、Data Packet、Handshake Packet。

抓包

安装"usbprotocolsuite"后,可以在文档目录里找打很多示程序(后缀名为 usb):

使用"usbprotocolsuite"打开这些文件,即可体验 USB 数据传输:

参考文献

《韦东山老师相关课程》
《圈圈教你玩 USB》 《USB2.0 协议规范》