2. USB 握手协议:ACK / NACK / STALL 握手包详解

搞 USB 调试,我猜你最先碰到的就是握手包。说白了,握手就是设备告诉主机:「我收到了,再发」或者「我不行,别催了」。

当年我刚入门时,以为握手就是 ACK 和 NACK 来回切。结果第一次调 U 盘,死活报 STALL,吓得我以为是芯片烧了。后来才发现,是因为端点配置错了——方向没对齐。嗯,从那以后我再也不敢轻视这三兄弟了。

2.1 握手包长什么样?

握手包是 USB 事务中最短的包,只有 1 个字节(8 bit),格式固定:

PID (4 bit)  +  校验/补码 (4 bit)

PID 由 USB 规范定义,每种握手有固定值:

握手类型 PID(二进制) PID(16进制) 含义
ACK 0010 1101 0x2D 数据正确,继续
NAK 1010 0101 0xA5 暂未就绪,重试
STALL 1110 0001 0xE1 致命错误,别来了

注意:PID 的低 4 位是原始值,高 4 位是低 4 位的取反。比如 ACK = 0010 取反得 1101,拼接成 0010 1101。这种设计为了物理层抗干扰。

2.2 三种握手的「性格」

每种握手其实代表了设备的不同「态度」。你可以把它们想象成三个角色:

ACK —— 老实人,一切正常

设备确认主机发来的数据正确,并且准备好接收下一个包。主机收到 ACK 后,会进入下一阶段事务。

我遇到过的坑:有一次抓了总线波形,看到连续 30 个 ACK,但设备就是没反应。后来才发现是 FIFO 满溢了——设备虽然发 ACK,但实际上把数据丢了。注意,ACK 只代表「收到并校验通过」,不代表「已处理」。这是新手最容易忽略的。

NAK —— 拖延症,先别催

设备说:「收到了,但我现在忙,没法处理,你过会儿重试。」主机看到 NAK 后不会报错,而是会轮询重试(重试次数视具体控制器而定)。

在批量传输端点中,NAK 是常态。你想想看,U 盘在写大文件时,大部分时间都在回 NAK。这其实是好现象——说明设备还活着,只是还没就绪。

我曾踩过的雷:某次测一个 USB 转串口模块,一直收到 NAK,以为坏掉了。结果查手册才知道,该模块的写端点一次只能处理 64 字节,我一次发 256 字节,它只能 NAK。后来切成分批发送就好了。

STALL —— 摆烂了,别再试

STALL 是 USB 协议里的「死口」。设备一旦回复 STALL,代表这个端点当前根本无法工作。通常原因:

  • 端点不存在(配置描述符里没暴露)
  • 端点方向错误(主机试图向 IN 端点写数据)
  • 控制传输中不支持的标准请求
  • 固件内部发生了不可恢复的错误

在批量传输中,STALL 不会自动清除。主机必须通过控制端点发送 ClearFeature(ENDPOINT_HALT) 才能让设备重新工作。忘记清 STALL 是很多复测自动化脚本写不稳的根本原因。

2.3 握手阶段在事务中的位置

一个典型的 USB 事务包含三个阶段:令牌 → 数据 → 握手。以批量输出(OUT)为例:

  1. 令牌阶段:主机发 OUT 令牌包,指定设备地址 + 端点号
  2. 数据阶段:主机发 DATA0/DATA1 数据包
  3. 握手阶段:设备回复 ACK/NAK/STALL

如果是批量输入(IN):

  1. 主机发 IN 令牌
  2. 设备发 DATA0/DATA1 数据
  3. 主机发 ACK(正确接收)或 NAK/STALL(不会发,主机只能回复 ACK)

注意:主机不会在数据阶段向设备回复 NAK/STALL,但主机可以通过不发送 ACK 来强制设备重发——这叫「Ping 协议」,在高速传输中会用到。

调试时抓波形,我习惯先看握手阶段。如果握手丢掉了,说明问题在 PHY 层或链路层;如果握手一直在,但功能不对,那多半是数据内容错了。

2.4 握手失败常见场景与注入思路

做复测时,我们经常需要「模拟握手失败」。怎么做?可以提前想好几种场景:

异常类型 注入方法 预期系统行为
ACK 缺失 用逻辑分析仪或 FPGA 截断握手包 主机超时重试(3 次后可能重置端点)
NAK 风暴 让固件在批量端点连续回 NAK 主机反复轮询,直至超时
STALL 保持 固件对某个请求回 STALL 后不清除 主机后续事务全部失败,需上层驱动重枚举
握手 PID 错误 硬件上翻转 PID 校验位 主机 CRC 失败,丢弃包

我个人习惯在做自动化测试脚本时,先写一个「握手包捕获」的 hook。用 Python 调 libusb 的底层 API,把每次事务的握手状态记录下来。等跑完一轮,看哪个端点回 NAK 超过 10 次,基本就是瓶颈。

2.5 核心知识结构一览

下面这张图是我自己画的结构,帮你在脑子里串起来。

USB 握手协议核心结构 握手包定义 1 Byte: PID + 校验补码 ACK=0x2D, NAK=0xA5, STALL=0xE1 三种握手类型 ACK NAK STALL USB 事务三段式 令牌()→ 数据(Data)→ 握手(Handshake) 常用注入场景 ACK缺失 / NAK风暴 / STALL保持 调试核心原则 先看握手是否存在?再看握手类型是否预期? NAK 不是错,STALL 才是死穴

2.6 避坑指南

最后说几个我在实战中流过的血:

  • 不要凭肉眼判断 NAK 次数:Host 控制器内部可能重试了很多次,你看到一次 NAK,但实际总线上跑了 5 次。用逻辑分析仪抓原始波形最准。
  • STALL 后立刻重试是徒劳的:我曾经写了一个死循环,STALL 后立即重发同个请求,结果端口被锁死直到重新枚举。正确做法:先发 ClearFeature。
  • 控制端点的 NAK 含义不同:控制传输的 NAK 不一定是忙,也可能是设备在等待 setup 阶段完成。我就被这个坑过,调了三天才发现是 setup 包没发全。

好了,第二章就先到这儿。三种握手的「性格」记住了就行,后面第三章我们会真的动手抓波形,看看它们怎么在总线上跳的。