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 | 编码 | 用途 | 数据切换 |
|---|---|---|---|
| DATA0 | 0011 | 默认第一个数据包 | 偶次数据 |
| DATA1 | 1011 | 交替数据包 | 奇次数据 |
| DATA2 | 0111 | 高速等时高带宽 | 不切换 |
| MDATA | 1111 | 分裂事务专用 | 不切换 |
为什么要搞 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 类型及其子类别串起来,方便你快速回忆。
看到这张图,你应该能快速定位每个 PID 所属的大类。以后无论是在分析仪上抓包,还是写固件解析 USB 包,记不住时就回来看看这张图。
我个人习惯:把 PID 编码表打印出来贴在调试台上。每次看到 ACK 都要瞄一眼二进制对不对——有一次发现是板子上的上拉电阻虚焊导致 SYNC 错误,PID 对但包被破坏了。