USB枚举过程详解:设备插入检测、复位操作、地址分配、描述符请求
USB枚举,说白了就是主机和设备之间的「握手仪式」。
每次设备插上去,主机都要走一遍这个流程——确认你是谁、你是什么类型、你需要多少带宽。我刚开始做USB驱动时,总觉得枚举是个黑盒子,直到有一次调试一个U盘死活不识别,才逼着自己把每个字节都啃透了。
今天咱们就把这个流程拆开揉碎,一个一个看。
1. 设备插入检测
设备插上去,主机怎么知道的?
USB接口有4根线:VBUS(电源)、D+、D-、GND。设备插入时,D+或D-上的电平会发生变化。
- 全速/高速设备:设备内部把D+线拉高(1.5kΩ上拉电阻)
- 低速设备:设备内部把D-线拉高
主机端的根集线器(Root Hub)会持续监测这两根数据线的电平。一旦检测到电平变化,就知道有设备插入了。
2. 复位操作
检测到设备后,主机不会马上跟你聊天。它先做一件事——复位。
复位是怎么做的?主机把D+和D-同时拉低,持续至少10ms。设备检测到这个信号后,会把自己恢复到默认状态:
- 地址恢复为0(默认地址)
- 进入默认状态(Default State)
- 准备好接收控制传输
复位结束后,设备会进入「地址状态」之前的那个阶段——我们叫它「默认状态」。这时候设备只能用地址0跟主机通信。
3. 地址分配
复位完成后,主机要给设备分配一个唯一的地址。为什么?因为总线上可能挂多个设备,每个设备必须有自己的「门牌号」。
分配地址的流程是这样的:
- 主机发送SET_ADDRESS请求(控制传输,默认地址0)
- 请求中包含新地址(比如1、2、3...)
- 设备收到后,保存这个地址
- 设备返回状态阶段(Status Stage)
- 之后所有通信都使用新地址
这里有个细节:SET_ADDRESS请求的Data Stage是空的,地址信息放在Setup包中。设备必须在状态阶段完成后才能切换地址。
4. 描述符请求
地址分配完了,主机开始问设备:「你是谁?你有哪些功能?」
描述符就是设备的「身份证」。主机通过一系列GET_DESCRIPTOR请求来获取这些信息。
4.1 设备描述符(Device Descriptor)
这是第一个被请求的描述符。主机先要18个字节,看看基本信息。
设备描述符包含:
- bLength:描述符长度(固定18)
- bDescriptorType:描述符类型(1表示设备描述符)
- bcdUSB:USB规范版本号(如0x0200表示USB 2.0)
- bDeviceClass:设备类(0表示在接口描述符中定义)
- bDeviceSubClass:子类
- bDeviceProtocol:协议
- bMaxPacketSize0:端点0的最大包大小(8、16、32、64)
- idVendor:厂商ID(VID)
- idProduct:产品ID(PID)
- bcdDevice:设备版本号
- iManufacturer:厂商字符串描述符索引
- iProduct:产品字符串描述符索引
- iSerialNumber:序列号字符串描述符索引
- bNumConfigurations:配置描述符数量
4.2 配置描述符(Configuration Descriptor)
拿到设备描述符后,主机接着请求配置描述符。配置描述符告诉主机:这个设备需要多少电流、有多少个接口。
配置描述符结构:
- bLength:9字节
- bDescriptorType:2(配置描述符)
- wTotalLength:整个配置的总长度(包括接口、端点等)
- bNumInterfaces:接口数量
- bConfigurationValue:配置值(用于SET_CONFIGURATION)
- iConfiguration:配置字符串索引
- bmAttributes:属性(自供电、远程唤醒等)
- bMaxPower:最大电流(单位2mA)
注意:主机第一次请求时,通常只读前9字节,拿到wTotalLength后,再读完整的数据。
4.3 接口描述符(Interface Descriptor)
配置描述符后面跟着接口描述符。一个配置可以有多个接口,每个接口代表一个功能。
接口描述符包含:
- bLength:9字节
- bDescriptorType:4(接口描述符)
- bInterfaceNumber:接口编号
- bAlternateSetting:备用设置
- bNumEndpoints:端点数量(不包括端点0)
- bInterfaceClass:接口类(如HID、Mass Storage)
- bInterfaceSubClass:子类
- bInterfaceProtocol:协议
- iInterface:接口字符串索引
4.4 端点描述符(Endpoint Descriptor)
接口描述符后面跟着端点描述符。每个非0端点都需要一个描述符。
端点描述符结构:
- bLength:7字节
- bDescriptorType:5(端点描述符)
- bEndpointAddress:端点地址(位7:方向,0=OUT,1=IN;位3-0:端点号)
- bmAttributes:传输类型(0=控制,1=等时,2=批量,3=中断)
- wMaxPacketSize:最大包大小
- bInterval:轮询间隔(中断端点用,单位ms或125μs)
我举个例子:一个批量传输的IN端点,地址可能是0x81(端点1,方向IN),最大包大小64字节。
5. 枚举完成后的操作
所有描述符都读完后,主机已经掌握了设备的全部信息。接下来:
- 主机加载对应的驱动程序
- 驱动发送SET_CONFIGURATION请求,选择一个配置
- 设备进入配置状态(Configured State)
- 开始正常的数据传输
嗯,到这里,枚举流程就走完了。你想想看,整个过程其实就是一个「问-答」模式:主机问一句,设备答一句。但每个环节都有坑,尤其是描述符的长度和内容,错一个字节设备就认不出来。
最后说一句:枚举是USB通信的基础。你把这个流程吃透了,后面写驱动、调硬件,心里就有底了。