第一章 USB协议基础:体系结构、传输类型、枚举过程
做USB调试这么多年,我最大的感受就是——协议本身不难,难在脑中有没有一张完整的图。你想想看,USB是个分层的东西,从物理电气到传输事务,再到软件栈,每一层都可能出问题。我记得刚入行那会儿,遇到一个握手失败,死活查不到原因,后来才发现是枚举过程的地址分配阶段超时了。所以这一章,我们先把这个基础框架打牢。
1.1 USB体系结构——谁在管谁?
USB是主从结构,只有主机(Host)能发起事务。设备(Device)只能被动响应。拓扑上是一颗树——根集线器挂在主机上,下面可以级联集线器,最多7层(包括根集线器)。
这里有个坑:层数超过7层,信号质量会崩,设备可能根本认不出来。我曾经在产线遇到一个案子,客户用了5级集线器扩展,插上U盘就报“无法识别的设备”。实测第6层设备连复位都过不去,最后只能砍掉两级。
主机内部有三大软件组件:
- USB主机控制器:硬件,负责产生帧起始包(SOF)和处理总线事务。
- USB驱动程序(USBD):软件,管理设备枚举、管道分配和标准请求。
- 集线器驱动程序:管理端口状态变化,比如插拔事件。
设备端则分为功能设备(如U盘、键盘)和集线器设备。每个设备有一个地址(0~127),默认地址是0,枚举时才换成唯一地址。
个人习惯:排查设备连接问题时,我会先用逻辑分析仪抓SOF包。如果SOF都没有,说明主机控制器没工作;有SOF但没设备响应,大概率是地址分配或描述符请求出了岔子。
1.2 传输类型——四种“快递”方式
USB定义了四种传输类型,对应不同的需求。我画个表对比,这样看着清楚:
| 传输类型 | 典型设备 | 特点 | 错误重试 | 带宽保证 |
|---|---|---|---|---|
| 控制传输 | 所有设备(枚举用) | 双向,有固定阶段(Setup→Data→Status) | 硬件重试3次 | 10%保留带宽 |
| 批量传输 | U盘、打印机 | 尽量传,不保证延迟,但保证不丢数据 | 硬件重试直到成功 | 无 |
| 中断传输 | 鼠标、键盘 | 周期轮询,延迟可预测 | 重试1次 | 有,按轮询间隔分配 |
| 等时传输 | 音频、摄像头 | 实时,无重试,允许丢包 | 无重试 | 有,按带宽预约 |
这里容易误解的是控制传输。很多人以为它只用来枚举,其实设备运行中也可以发,比如请求状态、切换配置。我遇到过一台打印机在打印中途发控制请求获取纸尽状态,结果因为请求超时导致USB Bus Reset,打印任务直接中断——坑不坑?
1.3 枚举过程——设备从死到活
枚举是USB协议最核心的环节,没有之一。握手失败十有八九都发生在这个阶段。我把完整过程拆成几步:
- 连接检测:主机检测到D+/D-电平变化(全速/低速上拉),给设备供电,等待至少100ms。
- 复位:主机将D+/D-拉低至少10ms,设备复位到默认地址0。
- 设备速度识别:复位结束后,主机发送一次GetDescriptor(Device),设备返回设备描述符。注意——此时设备地址还是0。
- 分配地址:主机发送SetAddress(新地址),设备确认后切换到新地址。
- 获取配置描述符:主机再发GetDescriptor(Config),设备返回完整配置描述符(包括接口和端点)。
- 选择配置:主机发SetConfiguration(配置值),设备进入就绪状态,以后就能正常通信了。
下面这张SVG图,是我个人梳理的枚举流程,覆盖了常见握手失败点:
1.4 握手失败的前哨——枚举阶段最容易翻车的三个点
- SetAddress 无 ACK:这是握手失败的重灾区。设备切换到新地址后,如果主机发的下一个请求还发到旧地址,设备根本不理你。我遇到过某款芯片固件 Bug,SetAddress 成功后忘记更新地址寄存器,结果主机再发 GetDescriptor 时设备还在用地址0回应——主机收到的干包全是 STALL。
- 描述符长度不匹配:设备描述符必须至少18字节,配置描述符只返回前9字节也没用,主机还会再发全长。有一次我在一个 IoT 设备上调试,配置描述符的接口数填错了,结果主机反复请求描述符,设备忙不过来,最后超时复位。那个下午……别提了。
- 复位时序不满足:有些低成本晶振起振慢,设备复位后还没准备好,主机就发 GetDescriptor,等到的全是垃圾数据。我建议你在复位后至少等待20ms再开始枚举,虽然规范只要求10ms——但多一倍的余量,能挡掉很多产线不良。
一句话总结本章核心:USB调试的起点是枚举,枚举的瓶颈在地址分配和描述符交互。我花了三个通宵踩坑才明白——丢了握手不一定是物理层问题,看看枚举到了哪一步,心里就有数了。