3、USB设备枚举过程:从插入到配置完成
大家好,我是老张。今天咱们聊聊USB设备枚举——说白了,就是USB主机怎么认识一个新插入的设备。
这个过程我调试过不下百次。每次新板子插上去没反应,第一反应就是枚举卡在哪一步了。嗯,今天我把整个流程拆开来讲,你跟着走一遍,以后遇到枚举问题心里就有底了。
3.1 设备插入检测——主机怎么知道有东西插进来了?
USB主机通过D+和D-上的电平变化来检测设备插入。全速和高速设备会在D+上拉一个1.5kΩ电阻,低速设备则在D-上拉。主机检测到这个电平变化,就知道有设备来了。
我记得刚入行那会儿,有块板子插上后主机死活没反应。查了半天,发现是上拉电阻虚焊了。嗯,这种问题最坑人——软件怎么调都没用,硬件一个焊点就搞定。
3.2 USB总线复位——给设备一个"重启"信号
检测到设备插入后,主机就会发送复位信号——说白了就是把D+和D-同时拉低至少10ms。设备收到复位信号后,会做三件事:
- 恢复到默认地址0
- 复位端点状态
- 准备接收控制传输
这里有个坑:复位期间设备不能响应任何请求。我见过一个项目,复位时间没给够,设备还没准备好主机就开始发请求了,结果设备没反应,枚举失败。
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字节,确认长度,再读完整内容。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度(18) |
| 1 | bDescriptorType | 1 | 描述符类型(0x01) |
| 2-3 | bcdUSB | 2 | USB规范版本号(如0x0200表示USB 2.0) |
| 4 | bDeviceClass | 1 | 设备类(0x00表示在接口描述符中定义) |
| 5 | bDeviceSubClass | 1 | 子类 |
| 6 | bDeviceProtocol | 1 | 协议 |
| 7 | bMaxPacketSize0 | 1 | 端点0最大包长(64字节常见) |
| 8-9 | idVendor | 2 | 厂商ID(VID) |
| 10-11 | idProduct | 2 | 产品ID(PID) |
| 12-13 | bcdDevice | 2 | 设备版本号 |
| 14 | iManufacturer | 1 | 厂商字符串索引 |
| 15 | iProduct | 1 | 产品字符串索引 |
| 16 | iSerialNumber | 1 | 序列号字符串索引 |
| 17 | bNumConfigurations | 1 | 配置描述符数量 |
你想想看,主机拿到VID和PID后,就能去匹配驱动了。Windows就是靠这个知道该加载哪个驱动的。
3.4.2 配置描述符(Configuration Descriptor)
配置描述符描述了一个配置的总体信息,包括供电方式、最大电流等。它的长度是9字节,但后面会跟着接口描述符和端点描述符。
主机获取配置描述符时,通常会先读9字节,从wTotalLength字段知道总长度,再一次性读完所有内容。
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设备枚举的完整流程就是这样。从插入检测到配置选择,每一步都有它的意义。你想想看,如果没有这套标准流程,主机怎么知道插上来的是键盘还是U盘?
下次调试时如果遇到枚举问题,按这个流程一步步排查,基本都能找到原因。
公众号:蓝海资料掘金营,微信deep3321