4. PID 类型详解:、Data、Handshake 与 Special

PID,全称 Packet Identifier,是 USB 包最开头那 4 位。别看它短,整条总线靠它认亲。我最早学 USB 时,记 PID 表背到崩溃——后来发现,按功能分组比死记硬背管用得多。

USB 规范把 PID 分成四大类: 包、Data 包、Handshake 包和 Special 包。每类里的 PID 都有明确分工。下面我们一个一个来过。

核心要点:PID 共 8 位(4 位编码 + 4 位校验),解码时只看高 4 位即可判断类型。任何 PID 错误都会导致整个包被忽略。

4.1 包——谁跟谁说话

包是主机发出来的,用来告诉设备:接下来要发生事务了。它包含 OUT、IN、SOF、SETUP 四种。

  • OUT (PID = 0001):主机要往外设写数据。我调试过一个 U 盘,发现 OUT 包里的地址总不对,结果是 DMA 地址配置反了。
  • IN (PID = 1001):主机要从外设读数据。
  • SOF (PID = 0101):帧起始包,每毫秒发一次(高速模式下每 125µs 发一个)。设备用它对时钟、同步帧号。说实话,除非做分析仪,平时不太直接碰它。
  • SETUP (PID = 1101):控制传输的专用包,**只能出现在控制端点 0**。它后面紧跟一个 DATA0 包,携带设置请求。

四种 包的结构完全一样:SYNC + PID + ADDR + ENDP + CRC5。你想想看,主机每发一个 ,就要占用总线,所以 USB 协议设计了批量传输来合并多次事务——减少 开销。

我曾经踩过的坑:SETUP 包后面必须跟 DATA0,不能是 DATA1。我开始写固件时没注意,照着批量传输的切换规则写,结果控制传输总失败。查了两天才发现是 PID 序列搞错了。

4.2 Data 包——真正要传的数据

Data 包只有四种 PID:DATA0、DATA1、DATA2、MDATA。其中 DATA0 和 DATA1 是最常用的,DATA2 和 MDATA 主要用于高速下的等时传输。

PID编码用途数据切换
DATA00011默认第一个数据包偶次数据
DATA11011交替数据包奇次数据
DATA20111高速等时高带宽不切换
MDATA1111分裂事务专用不切换

为什么要搞 DATA0/DATA1 切换?说白了,就是防丢包。接收方收到一个数据包后,通过比较 PID 序列来判断是不是重复包。我在项目中见过有人把 toggle 位写死在固件里,结果批量传输总超时——因为主机一看 PID 没变,直接丢弃。

我的习惯:写 USB 设备端固件时,一定在端点控制寄存器里清掉 DATA toggle 位再启动传输。初始化顺序错了,后面全是乱的。

4.3 Handshake 包——成不成就一句话

Handshake 包只有 8 位(SYNC + PID),没有数据段,也没有 CRC。主机或设备都能发,用来告诉对方:事务结果如何。

  • ACK (PID = 0010):成功,继续发。我最喜欢看到这个 PID。
  • NAK (PID = 1010):设备忙,暂时收不了数据。这不是错误,是正常协议流控。测过一个摄像头,NAK 率过高导致帧率下降,后来发现是内部 FIFO 太小。
  • STALL (PID = 1110):设备出错了,或者端点不支持请求。一般用端点 halt 功能触发。
  • NYET (PID = 0110):高速模式下,设备没准备好接收下一个数据包。注意,**NYET 只在高速传输中出现**。

你可能会问:什么时候发 NAK,什么时候发 STALL?NAK 是临时忙,过一会可能就好了;STALL 是永久故障,需要主机重新配置端点才能恢复。我曾经在一个 HID 键盘里错误地发了 STALL,结果主机直接报未知设备——因为主机以为端点坏了。

4.4 Special 包——奇奇怪怪的专用信号

Special 包在高速协议里不算多,但作用关键。

  • PRE (PID = 0011):全速/低速 preamble,用于告诉高速 Hub:接下来是低速或全速传输。Hub 要切换到对应速度的端口。
  • ERR (PID = 1100):分裂事务中的错误报告。当主机发 SPLIT 后,设备通过 ERR 告诉主机:分片出问题了。
  • SPLIT (PID = 1000):其实 SPLIT 既是一个 包类型,也属于 Special 用途。它把一个大事务切分成多个微帧,在高速总线上模拟全速/低速行为。这个机制很巧妙,既兼容又高效。
  • PING (PID = 0100):高速模式下,主机在发送数据前先发 PING,看设备能不能接收。如果设备回 ACK,主机再发数据;如果回 NAK,主机等等再试。这大大减少了带宽浪费。
  • Reserved (PID = 1111 等):预留给未来使用。我现在没见过哪个设备用保留 PID,但规范建议接收方遇到保留 PID 直接忽略。

实战提醒:PING 和 NYET 配合使用,就是高速批量传输的流量控制核心。如果你设计的设备 NAK 率降不下来,可以考虑在固件里增加内部 FIFO 深度,或者优化中断处理时机。

4.5 一张图看懂 PID 全家

下面这张图把四种 PID 类型及其子类别串起来,方便你快速回忆。

USB PID 类型一览 PID 类型 Data Handshake Special OUT IN SOF SETUP DATA0 DATA1 DATA2 MDATA ACK NAK STALL NYET PRE ERR SPLIT PING 说明 • : 主机发,标识地址和端点。OUT/IN/SOF/SETUP 四种。 • Data: 携带数据。DATA0/DATA1 用于批量/中断传输的 toggle 机制;DATA2/MDATA 用于等时。 • Handshake: 返回事务结果。ACK成功,NAK忙,STALL错,NYET高速流控。 • Special: 特殊控制。PRE/ERR用于速度转换,SPLIT分裂事务,PING预检测。 • 保留PID(如1111)规范禁止使用,遇到直接忽略。

看到这张图,你应该能快速定位每个 PID 所属的大类。以后无论是在分析仪上抓包,还是写固件解析 USB 包,记不住时就回来看看这张图。

我个人习惯:把 PID 编码表打印出来贴在调试台上。每次看到 ACK 都要瞄一眼二进制对不对——有一次发现是板子上的上拉电阻虚焊导致 SYNC 错误,PID 对但包被破坏了。