第二章:USB枚举过程详解
USB枚举,说白了就是设备和主机互相认识的过程。我经常跟团队里的新人说,搞懂枚举,你就掌握了USB通信的一半。今天咱们就一步步拆解这个过程。
2.1 设备插入检测
当USB设备插入端口时,物理层会先发生变化。D+和D-这两条数据线上的电平会改变——全速设备会把D+拉高,低速设备则拉高D-。主机端的根集线器检测到这个变化后,会通知USB主机控制器。
嗯,这里要注意:主机并不是立即就开始通信。它会先等待100毫秒左右,让设备稳定下来。我见过不少新手工程师忽略这个延时,结果枚举总是失败。
关键点:主机检测到设备插入后,会发送总线复位信号。这个复位信号会持续至少10毫秒,把设备状态重置到默认状态。
2.2 复位(Reset)
复位操作其实很简单——主机把D+和D-都拉低,持续10毫秒以上。设备收到复位信号后,会做以下几件事:
- 把地址重置为0(默认地址)
- 准备好接收控制传输
- 配置端点0(默认控制端点)
我曾经在一个项目中遇到过复位时序问题。设备对复位信号的响应太慢,主机以为设备没反应,反复复位。最后发现是设备端的去耦电容太大,导致电平变化太慢。换了个小电容就解决了。
经验之谈:复位完成后,设备必须在10毫秒内准备好接收Setup包。这个时间窗口很关键,我建议你在固件里加个超时处理。
2.3 设置地址(Set Address)
复位之后,设备还在默认地址0上。主机需要给它分配一个唯一的地址。这个过程分两步:
- 主机发送Set Address请求,告诉设备新地址
- 设备收到后,必须在50毫秒内切换到新地址
这里有个坑:Set Address请求本身是在旧地址上完成的。设备收到请求后,不能立即切换地址,要等到状态阶段完成后才能切换。你想想看,如果设备提前切换了地址,那状态阶段的握手包就发不回去了。
// Set Address请求格式
// bmRequestType: 0x00 (主机到设备,标准请求)
// bRequest: 0x05 (SET_ADDRESS)
// wValue: 新地址 (1-127)
// wIndex: 0
// wLength: 0
// 示例:设置地址为2
// 主机发送: 0x00 0x05 0x02 0x00 0x00 0x00 0x00 0x00
注意:地址0是保留地址,只能用于枚举过程。设备完成枚举后,必须使用非零地址。我见过有些设备固件写错了,一直用地址0通信,结果主机根本不理它。
2.4 获取描述符(Get Descriptor)
地址分配好了,主机就要开始了解这个设备到底是什么。它会通过Get Descriptor请求来获取设备的各种信息。描述符就像设备的身份证,里面包含了设备类型、厂商ID、产品ID、端点信息等。
获取描述符的顺序是这样的:
- 先获取设备描述符(只取前8字节,了解端点0的最大包大小)
- 再获取完整的设备描述符(18字节)
- 然后获取配置描述符
- 接着获取接口描述符和端点描述符
- 最后获取字符串描述符(如果有的话)
我个人习惯在固件里把描述符写得尽量完整。有些主机对描述符的要求很严格,少一个字段都可能导致枚举失败。我记得有一次调试一个HID设备,主机就是认不出来,最后发现是报告描述符里少了个结束标记。
| 描述符类型 | 长度(字节) | 包含信息 |
|---|---|---|
| 设备描述符 | 18 | USB版本、设备类、厂商ID、产品ID等 |
| 配置描述符 | 9 | 配置属性、功耗、接口数量等 |
| 接口描述符 | 9 | 接口编号、端点数量、协议类型等 |
| 端点描述符 | 7 | 端点地址、传输类型、最大包大小等 |
核心要点:主机通过描述符来加载合适的驱动程序。如果描述符写错了,轻则枚举失败,重则蓝屏死机。我建议你在写描述符时,对照USB规范逐字节检查。
2.5 配置(Set Configuration)
这是枚举的最后一步。主机根据获取到的描述符信息,选择一个合适的配置,然后发送Set Configuration请求。设备收到这个请求后,就要按照配置描述符里的要求,初始化相应的端点、分配缓冲区、准备数据传输。
配置完成后,设备就进入就绪状态了。主机可以开始进行数据传输。不过要注意,有些设备支持多个配置,主机可以选择其中一个。但大多数设备只有一个配置,所以这个步骤通常就是走个过场。
说白了,配置这一步就是告诉设备:「好了,我已经了解你了,现在开始干活吧!」
避坑指南:我曾经遇到过一个问题:设备在收到Set Configuration后,需要花很长时间来初始化硬件。结果主机等不及,超时了。解决方案是在配置描述符里把bMaxPower设大一点,让主机知道这个设备需要更多启动时间。
2.6 枚举过程总结
整个枚举过程,从设备插入到配置完成,通常只需要几十毫秒。但每个环节都有可能出现问题。我建议你在调试时,用USB分析仪抓包看看,哪个步骤卡住了,一目了然。
下面这张图展示了枚举的核心流程:
这张图把枚举的五个核心步骤串起来了。每个步骤都有它的意义,缺一不可。你在实际调试时,可以对照这个流程,看看设备卡在哪一步。