3、USB设备枚举过程:从插入到配置完成

大家好,我是老张。今天咱们聊聊USB设备枚举——说白了,就是USB主机怎么认识一个新插入的设备。

这个过程我调试过不下百次。每次新板子插上去没反应,第一反应就是枚举卡在哪一步了。嗯,今天我把整个流程拆开来讲,你跟着走一遍,以后遇到枚举问题心里就有底了。

USB设备枚举完整流程 ① 设备插入检测 ② USB总线复位 ③ 分配设备地址 ④ 获取设备描述符 ④ 获取配置描述符 ④ 获取接口/端点描述符 ⑤ 配置选择(Set Configuration) ✅ 枚举完成,设备就绪

3.1 设备插入检测——主机怎么知道有东西插进来了?

USB主机通过D+和D-上的电平变化来检测设备插入。全速和高速设备会在D+上拉一个1.5kΩ电阻,低速设备则在D-上拉。主机检测到这个电平变化,就知道有设备来了。

我记得刚入行那会儿,有块板子插上后主机死活没反应。查了半天,发现是上拉电阻虚焊了。嗯,这种问题最坑人——软件怎么调都没用,硬件一个焊点就搞定。

💡 小提示: 调试时可以用USB分析仪抓取总线状态。如果看到D+或D-电平根本没变化,先检查硬件上拉电阻。

3.2 USB总线复位——给设备一个"重启"信号

检测到设备插入后,主机就会发送复位信号——说白了就是把D+和D-同时拉低至少10ms。设备收到复位信号后,会做三件事:

  • 恢复到默认地址0
  • 复位端点状态
  • 准备接收控制传输

这里有个坑:复位期间设备不能响应任何请求。我见过一个项目,复位时间没给够,设备还没准备好主机就开始发请求了,结果设备没反应,枚举失败。

⚠️ 注意: USB 2.0规范要求复位信号持续至少10ms,但很多主机实际会拉长到20-50ms。设备端设计时建议按50ms来考虑。

3.3 地址分配——给设备一个"身份证号"

复位完成后,设备还在默认地址0。主机通过Set Address请求给设备分配一个唯一地址(1-127)。

流程是这样的:

主机 → 设备:SETUP包(Set Address请求,地址=3)
设备 → 主机:ACK(确认收到)
// 设备切换地址到3
主机 → 设备:IN令牌(地址=3,端点=0)
设备 → 主机:DATA0包(空数据,表示就绪)

我个人习惯在设备端加一个延时——收到Set Address后等5ms再切换地址。为什么?因为有些USB主机控制器实现有bug,地址切换太快会丢包。这算是我踩过的坑换来的经验。

3.4 描述符获取——主机在"查户口"

地址分配好了,主机就开始问设备"你是谁?你能干什么?"。这个过程就是获取描述符。

3.4.1 设备描述符(Device Descriptor)

这是第一个要获取的描述符,长度固定18字节。主机先读前8字节,确认长度,再读完整内容。

偏移字段长度说明
0bLength1描述符长度(18)
1bDescriptorType1描述符类型(0x01)
2-3bcdUSB2USB规范版本号(如0x0200表示USB 2.0)
4bDeviceClass1设备类(0x00表示在接口描述符中定义)
5bDeviceSubClass1子类
6bDeviceProtocol1协议
7bMaxPacketSize01端点0最大包长(64字节常见)
8-9idVendor2厂商ID(VID)
10-11idProduct2产品ID(PID)
12-13bcdDevice2设备版本号
14iManufacturer1厂商字符串索引
15iProduct1产品字符串索引
16iSerialNumber1序列号字符串索引
17bNumConfigurations1配置描述符数量

你想想看,主机拿到VID和PID后,就能去匹配驱动了。Windows就是靠这个知道该加载哪个驱动的。

3.4.2 配置描述符(Configuration Descriptor)

配置描述符描述了一个配置的总体信息,包括供电方式、最大电流等。它的长度是9字节,但后面会跟着接口描述符和端点描述符。

主机获取配置描述符时,通常会先读9字节,从wTotalLength字段知道总长度,再一次性读完所有内容。

🔑 关键点: 一个设备可以有多个配置,但同一时刻只能激活一个。大多数设备只有一个配置,但像USB-C这类设备可能会有多个配置来支持不同角色。

3.4.3 接口描述符(Interface Descriptor)

接口描述符描述了一个功能单元。比如一个USB摄像头可能有视频接口和音频接口。每个接口可以有多个设置(Alternate Setting),用于切换不同的数据传输模式。

我曾经调试过一个音频设备,播放时声音断断续续。查了半天,发现是接口的Alternate Setting没设置对——默认设置用的是同步传输,但实际需要的是异步传输。改过来就好了。

3.4.4 端点描述符(Endpoint Descriptor)

端点描述符描述了数据传输的端点。每个端点有:

  • 端点地址:IN还是OUT,端点号多少
  • 传输类型:控制、批量、中断、同步
  • 最大包长:每次传输能传多少字节
  • 轮询间隔:中断传输时主机多久查询一次

嗯,这里要注意:端点0是默认的控制端点,不需要在描述符中声明。其他端点必须在接口描述符后面跟着声明。

3.5 配置选择——最后一锤定音

主机拿到所有描述符后,会选择一个配置来激活。通过Set Configuration请求,告诉设备使用哪个配置。

设备收到这个请求后,就要按照配置描述符里的要求,把对应的端点初始化好,准备好收发数据。

我习惯在设备端做一件事:收到Set Configuration后,先检查配置值是否合法。如果不合法,返回STALL。有些设备不检查这个,结果主机发了个无效配置,设备就懵了。

💡 实战建议: 枚举完成后,建议用USB分析工具抓一下完整的枚举过程。看看主机发了哪些请求,设备回了什么数据。这是排查枚举问题最有效的方法。

好了,USB设备枚举的完整流程就是这样。从插入检测到配置选择,每一步都有它的意义。你想想看,如果没有这套标准流程,主机怎么知道插上来的是键盘还是U盘?

下次调试时如果遇到枚举问题,按这个流程一步步排查,基本都能找到原因。


公众号:蓝海资料掘金营,微信deep3321