USB深度解析(二):设备枚举
USB深度解析(二):设备枚举
在上一篇中,我们深入剖析了USB低速和全速模式的电气特性与通信协议。当物理层连接建立、底层事务能够正常交互后,主机便开始通过逻辑层的“控制传输”对设备进行配置,这一过程即设备枚举(Enumeration)。USB协议定义了一套标准化的设备描述符体系和主机标准请求指令,并通过固定的设备枚举流程,完成主机与设备的握手、信息交互、地址分配、功能配置。这是USB设备从“硬件接入”到“系统可用”的核心逻辑,也是USB即插即用特性的底层实现原理。
本文第一、第二章节介绍标准请求和描述符的基本概念,第三章节以CDC类为例,具体描述一个设备的枚举过程。

1. 标准请求
USB主机通过控制传输向设备发送标准请求,以获取设备信息、设置设备地址、配置设备等。所有标准请求均通过SETUP事务的数据阶段传输,该数据包固定为8字节,如下图所示:

1.1 请求特性字段(bmRequestType,1字节)
该字段为位组合字段,用于定义请求的传输方向、请求类型、接收对象,是解析请求的首要依据,8位数据分为三段:
| 位 | 名称 | 含义 |
|---|---|---|
| D7 | Direction 方向 | 0 = 主机到设备(OUT,主机发数据) 1 = 设备到主机(IN,设备返回数据) |
| D6~5 | Type 请求类型 | 00 = Standard(标准请求,USB 规范定义) 01 = Class(类请求,如 CDC、HID 类定义) 10 = Vendor(厂商自定义请求) 11 = Reserved(保留) |
| D4~0 | Recipient 接收者 | 00000 = Device(设备) 00001 = Interface(接口) 00010 = Endpoint(端点) 00011 = Other(其他) 其余 = 保留 |
1.2 请求码字段(bRequest,1字节)
用于标识具体的标准请求指令,USB2.0规范定义了11种核心标准请求,每种请求对应唯一功能,是枚举过程的核心指令。
| bRequest值 | 请求名称 | 描述 |
|---|---|---|
| 0x00 | GET_STATUS | 获取设备、接口或端点的状态 |
| 0x01 | CLEAR_FEATURE | 清除指定的特性 |
| 0x03 | SET_FEATURE | 设置指定的特性 |
| 0x05 | SET_ADDRESS | 设置设备地址 |
| 0x06 | GET_DESCRIPTOR | 获取描述符 |
| 0x07 | SET_DESCRIPTOR | 设置描述符 |
| 0x08 | GET_CONFIGURATION | 获取当前配置值 |
| 0x09 | SET_CONFIGURATION | 设置设备配置 |
| 0x0A | GET_INTERFACE | 获取接口备用设置 |
| 0x0B | SET_INTERFACE | 设置接口备用设置 |
| 0x0C | SYNCH_FRAME | 同步帧编号 |
除了11种核心标准请求,不同的设备类还有自己的类请求。
1.3 数值字段(wValue,2字节)
请求参数值,高低字节组合使用,不同请求指令对应不同含义,用于传递描述符类型、设备状态、配置编号等关键参数。
| 请求 | wValue 含义 |
|---|---|
| SET_ADDRESS | 低字节:分配给设备的新地址(1~127) |
| GET_DESCRIPTOR | 高字节:描述符类型(如 0x01=设备,0x02=配置) 低字节:描述符索引(如第几个配置、第几个字符串) |
| SET_CONFIGURATION | 低字节:要激活的配置值(bConfigurationValue) |
| SET_INTERFACE | 低字节:要切换到的备用设置值(bAlternateSetting) |
1.4 索引字段(wIndex,2字节)
通常用于指定目标接口或端点,或者在某些请求中作为附加参数:
| 请求 | wIndex 含义 |
|---|---|
| GET_DESCRIPTOR (字符串) | 低字节:语言 ID(如 0x0409 表示英语) |
| GET_INTERFACE / SET_INTERFACE | 低字节:接口号 |
| GET_STATUS / CLEAR_FEATURE (端点) | 低字节:端点地址(如 0x81 表示端点1 IN) |
| 设备级请求 | 通常设为 0 |
1.5 长度字段(wLength,2字节)
表示数据阶段(Data Stage)要传输的字节数:
- 如果
bmRequestType.D7 = 1(IN),设备最多返回wLength字节。 - 如果
bmRequestType.D7 = 0(OUT),主机将发送wLength字节。 - 如果请求不需要数据阶段(如 SET_ADDRESS),此值设为
0。
2. USB描述符(Descriptors)
描述符是USB设备向主机“自我介绍”的标准化数据结构,是主机识别设备、匹配驱动、配置功能的核心依据。所有描述符均为固定字节格式,由设备固件预先定义,主机通过GET_DESCRIPTOR请求读取。
2.1 设备描述符(Device Descriptor)
设备描述符是主机枚举时首先读取的描述符,长度固定为18字节,包含了设备的全局信息。具体数据包格式如下表:
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度,固定为 0x12(18字节) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x01(设备描述符) |
| 2 | 2 | bcdUSB |
USB规范版本号(BCD码),如0x0200表示USB 2.0 |
| 4 | 1 | bDeviceClass |
设备类代码(如0x02=CDC类),也可设为0,由接口描述符指定 |
| 5 | 1 | bDeviceSubClass |
设备子类代码,配合bDeviceClass使用 |
| 6 | 1 | bDeviceProtocol |
设备协议代码,配合bDeviceClass使用 |
| 7 | 1 | bMaxPacketSize0 |
端点0的最大包大小(低速必须为8,全速可为8/16/32/64) |
| 8 | 2 | idVendor |
厂商ID(VID),由USB-IF分配 |
| 10 | 2 | idProduct |
产品ID(PID),厂商自定义 |
| 12 | 2 | bcdDevice |
设备版本号(BCD码),如0x0100表示1.0版 |
| 14 | 1 | iManufacturer |
厂商字符串描述符索引,0表示无 |
| 15 | 1 | iProduct |
产品字符串描述符索引,0表示无 |
| 16 | 1 | iSerialNumber |
序列号字符串描述符索引,0表示无 |
| 17 | 1 | bNumConfigurations |
设备支持的配置数量 |
2.2 配置描述符(Configuration Descriptor)
配置描述符长度为9字节,描述了一个完整的设备配置(包括接口、端点等)。一个设备可以有多个配置,但同一时刻只能激活一个。
根据 USB 规范,配置描述符(Configuration Descriptor)从来不是单独存在的,主机在获取配置描述符时,设备必须一次性返回配置描述符集合(包含接口描述符、类特定描述符或其目录、端点描述符)。当主机发送 GET_DESCRIPTOR 请求获取配置描述符时,流程是这样的:
- 主机首先请求 9 个字节(
wLength = 9)。主机从中解析出wTotalLength - 主机立刻发起第二次请求,这次
wLength直接设为刚才读到的wTotalLength,要求设备一次性把所有的描述符全部传回来。
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度,固定为 0x09(9字节) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x02(配置描述符) |
| 2 | 2 | wTotalLength |
该配置下所有描述符的总长度(配置+接口+端点等) |
| 4 | 1 | bNumInterfaces |
该配置包含的接口数量 |
| 5 | 1 | bConfigurationValue |
配置值,用于SET_CONFIGURATION请求 |
| 6 | 1 | iConfiguration |
配置字符串描述符索引,0表示无 |
| 7 | 1 | bmAttributes |
配置特性: • D7: 保留(设为1) • D6: 自供电(1=自供电,0=总线供电) • D5: 远程唤醒(1=支持,0=不支持) • D4..0: 保留(设为0) |
| 8 | 1 | bMaxPower |
总线功耗,以 2mA 为单位(如0x32表示100mA) |
大多数嵌入式 USB 设备只有一种配置。
2.3 接口描述符(Interface Descriptor)
接口描述符长度为9字节,描述了一个特定接口的功能。一个配置可以包含多个接口,每个接口代表一个独立的功能单元(如USB耳机包含音频输入、输出两个接口)。
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度,固定为 0x09(9字节) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x04(接口描述符) |
| 2 | 1 | bInterfaceNumber |
接口编号,从0开始递增 |
| 3 | 1 | bAlternateSetting |
备用设置编号,用于同一接口的不同配置(默认设为0) |
| 4 | 1 | bNumEndpoints |
该接口使用的端点数量(不含端点0) |
| 5 | 1 | bInterfaceClass |
接口类代码(如0x03=HID类,0x08=Mass Storage类) |
| 6 | 1 | bInterfaceSubClass |
接口子类代码 |
| 7 | 1 | bInterfaceProtocol |
接口协议代码 |
| 8 | 1 | iInterface |
接口字符串描述符索引,0表示无 |
2.4 类特定描述符(Class-Specific Descriptors)
USB 规范为标准描述符(设备、配置、接口、端点)提供了统一的框架,但不同设备类(CDC、HID、MSC、Audio 等)往往有自己独有的功能和数据结构。为了描述这些类特有的属性,USB 允许每个类定义自己的类特定描述符。
在配置描述符集合中,在接口描述符和端点描述符之间定义完整的类特定描述符或者类特定描述符的目录(如HID,配置描述符集合中只放了一个 HID Descriptor(HID 描述符) 作为“目录”,真正的 Report Descriptor 由主机单独请求获取。)
| 对比项 | 模式一:直接嵌入(如CDC) | 模式二:目录+按需获取(如HID) |
|---|---|---|
| 类特定描述符是否在配置集合里 | 是,直接包含完整数据 | 否,集合里只有简短的"目录" |
| 主机获取方式 | 随配置描述符集合一次性返回 | 先解析目录,再单独请求 |
| 适用场景 | 描述符短小、必须、所有主机都需要 | 描述符可能很长、非所有主机都需要 |
| 典型代表 | CDC 功能描述符、Audio 类描述符 | HID Report Descriptor |
注意:无论哪种模式,类特定描述符都不属于标准描述符体系。它们由各个设备类规范自行定义,USB 核心规范只规定了它们的大致存放位置和获取方式。
2.5 端点描述符(Endpoint Descriptor)
端点描述符长度为7字节,描述了除端点0(默认控制端点)之外的每个端点的属性。每个端点都有唯一的方向和传输类型。
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度,固定为 0x07(7字节) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x05(端点描述符) |
| 2 | 1 | bEndpointAddress |
端点地址与方向: • D7: 方向(1=IN,0=OUT) • D6..4: 保留(设为0) • D3..0: 端点号(1~15) |
| 3 | 1 | bmAttributes |
端点属性: • D1..0: 传输类型(00=控制,01=同步,10=批量,11=中断) • D3..2: 同步类型(若为同步传输:00=无同步,01=异步,10=自适应,11=同步) • D5..4: 使用类型(若为同步传输:00=数据端点,01=反馈端点,10=隐式反馈数据端点) • D7..6: 保留 |
| 4 | 2 | wMaxPacketSize |
端点最大包大小(字节): • 低速/全速: - 控制/批量/中断:最大64字节 - 同步:全速最大1023字节 • 高速:批量/中断最大512,同步最大1024 |
| 6 | 1 | bInterval |
轮询间隔(单位:帧或微帧): • 低速/全速中断:1~255ms • 全速同步:1ms(忽略) • 高速中断:1~16(实际间隔=2^(bInterval-1) × 125µs) • 高速同步:1~16(实际间隔=2^(bInterval-1) × 125µs) |
2.6 字符串描述符(String Descriptor)
字符串描述符采用UNICODE编码(UTF-16LE),提供人类可读的信息。其长度可变,由bLength指定。此外,索引为0的字符串描述符是特殊的语言ID描述符,用于声明设备支持的语言列表。
通用字符串描述符格式:
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度(包括本字节和类型字节,以及所有UNICODE字符) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x03(字符串描述符) |
| 2 | N | bString |
UNICODE字符串(每字符2字节,小端格式),以\0\0结尾(可选) |
语言ID描述符(索引0)格式:
| 偏移 | 大小 | 字段名 | 描述与含义 |
|---|---|---|---|
| 0 | 1 | bLength |
描述符长度,至少为4(2字节头 + 2字节语言ID) |
| 1 | 1 | bDescriptorType |
描述符类型,固定为 0x03 |
| 2 | 2 | wLANGID[0] |
第一语言ID(如0x0409表示英语-美国) |
| 4 | 2 | wLANGID[1] |
第二语言ID(可选,可多个) |
注意:主机首先读取索引0的字符串描述符获取支持的语言列表,随后在获取其他字符串描述符时,通过
GET_DESCRIPTOR请求的wIndex字段指定所需的语言ID。
3. 枚举过程(以全速 CDC 为例)
USB 枚举就是主机读取设备描述符、分配地址、选择配置的过程。
3.1 连接检测与复位
- 设备插入主机,DP(全速)或 DM(低速)上拉电阻被拉高,主机检测到连接。
- 主机对总线进行复位(SE0 状态持续 10ms 以上),设备进入默认状态,使用默认地址
0x00。
3.2 第一次获取设备描述符
3.2.1 建立阶段:主机发起标准请求

第一次主机不知道设备端点0的数据包最大数量,主机向地址 0 发送 GET_DESCRIPTOR 请求8字节(默认大小),目的是读取设备描述符中的 bMaxPacketSize0(端点 0 最大包大小),以便后续用正确的包大小通信。
SETUP 事务的 8 字节请求(地址 0,请求设备描述符):
| 字段 | 值(十六进制) | 说明 |
|---|---|---|
bmRequestType |
0x80 |
方向 IN,标准请求,接收者=设备 |
bRequest |
0x06 |
GET_DESCRIPTOR |
wValue |
0x0100 |
高字节=0x01(设备描述符),低字节=0x00(索引) |
wIndex |
0x0000 |
语言 ID(设备描述符忽略) |
wLength |
0x0008 |
主机请求 8字节 |
3.2.2 数据阶段:设备返回8字节设备描述符内容

详细格式如下:
| 字段 | 值(十六进制) | 说明 |
|---|---|---|
bLength |
0x12 |
描述符总长度 = 18 字节 |
bDescriptorType |
0x01 |
描述符类型:设备描述符(Device Descriptor) |
bcdUSB |
0x0200 |
USB 规范版本(USB 2.0),描述符中字节序为 00 02 |
bDeviceClass |
0x02 |
设备类:CDC 通信类 |
bDeviceSubClass |
0x02 |
设备子类:Abstract Control Model (ACM) |
bDeviceProtocol |
0x00 |
设备协议:无 |
bMaxPacketSize0 |
0x40 |
端点 0 最大包大小 = 64 字节(全速模式) |
3.2.3 状态阶段:控制传输成功结束
设置流程如下图:

3.3 设置设备地址
主机为设备分配一个唯一的非零地址(假设分配地址 0x02),后续通信都使用这个新地址。设置流程如下图:

SETUP 包(8 字节):
00 05 02 00 00 00 00 00
00:方向 OUT,标准请求,接收者=设备05:SET_ADDRESS02 00:wValue = 0x0002(新地址)wLength = 0:无数据阶段
设备收到后,在状态阶段结束后 2μs 内切换至新地址 0x02。
3.4 获取完整设备描述符
主机用新地址再次获取设备描述符,详细流程如下图所示:

数据阶段的DATA为:
12 01 00 02 02 02 00 40 83 04 40 57 00 02 01 02 03 01
| 字段 | 值(十六进制) | 说明 |
|---|---|---|
bLength |
0x12 |
描述符总长度 = 18 字节 |
bDescriptorType |
0x01 |
描述符类型:设备描述符(Device Descriptor) |
bcdUSB |
0x0200 |
USB 规范版本(USB 2.0),描述符中字节序为 00 02 |
bDeviceClass |
0x02 |
设备类:CDC 通信类 |
bDeviceSubClass |
0x02 |
设备子类:Abstract Control Model (ACM) |
bDeviceProtocol |
0x00 |
设备协议:无 |
bMaxPacketSize0 |
0x40 |
端点 0 最大包大小 = 64 字节(全速模式) |
idVendor |
0x0483 |
厂商 ID:STMicroelectronics,描述符中字节序为 83 04 |
idProduct |
0x5740 |
产品 ID:STM32 Virtual ComPort,描述符中字节序为 40 57 |
bcdDevice |
0x0200 |
设备版本号 2.00,描述符中字节序为 00 02 |
iManufacturer |
0x01 |
厂商字符串描述符索引 = 1 |
iProduct |
0x02 |
产品字符串描述符索引 = 2 |
iSerialNumber |
0x03 |
序列号字符串描述符索引 = 3 |
bNumConfigurations |
0x01 |
设备支持的配置数量 = 1 |
3.5 获取配置描述符(两次请求)
这是枚举的核心步骤,主机分两次获取配置描述符集合。
3.5.1 获取 9 字节配置描述符头
详细流程如下图所示:

SETUP 包:
80 06 00 02 00 00 09 00
设备返回(来自 USBD_CDC_CfgFSDesc 的前 9 字节):
09 02 43 00 02 01 00 80 32
| 字段 | 值(十六进制) | 说明 |
|---|---|---|
bLength |
0x09 |
描述符长度 = 9 字节 |
bDescriptorType |
0x02 |
描述符类型:配置描述符(Configuration Descriptor) |
wTotalLength |
0x0043 |
整个配置描述符集合的总长度 = 67 字节(描述符中字节序为 43 00) |
bNumInterfaces |
0x02 |
接口数量 = 2(通信接口 + 数据接口) |
bConfigurationValue |
0x01 |
配置值 = 1(用于 Set Configuration 请求) |
iConfiguration |
0x00 |
配置字符串描述符索引 = 0(无配置字符串) |
bmAttributes |
0x80 |
属性:总线供电(Bit6=1,D7 固定为 1) |
bMaxPower |
0x32 |
最大功耗:50 × 2mA = 100mA |
3.5.2 获取完整的配置描述符集合(67 字节)
使用上一步读到的wTotalLength=0x0043作为本次wLength参数,详细流程如上图“获取 9 字节配置描述符头”中类似。
SETUP 包:
80 06 00 02 00 00 43 00
设备返回完整的 67 字节(即 USBD_CDC_CfgFSDesc 数组的全部内容):
09 02 43 00 02 01 00 80 32 // 配置描述符
09 04 00 00 01 02 02 01 00 // 接口 0:通信接口
05 24 00 10 01 // Header 功能描述符
05 24 01 00 01 // Call Management 功能描述符
04 24 02 02 // ACM 功能描述符
05 24 06 00 01 // Union 功能描述符
07 05 82 03 08 00 FF // 端点 2 IN(中断)
09 04 01 00 02 0A 00 00 00 // 接口 1:数据接口
07 05 03 02 40 00 00 // 端点 3 OUT(批量)
07 05 81 02 40 00 00 // 端点 1 IN(批量)
主机解析这个集合,识别出:
- 这是一个 CDC ACM 设备(接口 0 类=0x02,子类=0x02)
- 通信接口使用中断端点 2 IN 进行控制命令传输
- 数据接口使用批量端点 3 OUT 和 1 IN 进行串口数据收发
- Union 描述符将接口 0 和接口 1 绑定为一个虚拟串口
3.6 设置配置
主机根据配置描述符的bConfigurationValue=0x01,发送SET_CONFIGURATION请求,激活该配置。详细流程如下图所示:

3.6.1 建立阶段:SET_CONFIGURATION请求
主机向地址0x02发送SETUP事务,请求激活配置
| 字段 | 值(十六进制) | 说明 |
|---|---|---|
bmRequestType |
0x00 |
方向OUT,标准请求,接收者=设备 |
bRequest |
0x09 |
请求类型:SET_CONFIGURATION |
wValue |
0x0001 |
配置值:0x01(激活第一个配置) |
wIndex |
0x0000 |
保留(无含义) |
wLength |
0x0000 |
无数据阶段 |
3.6.2 状态阶段:配置激活
设备收到请求后,激活配置0x01,状态阶段主机发送ZLP,设备回复ACK,配置生效。
3.7 枚举完成
至此,枚举结束。主机根据设备描述符的 bDeviceClass = 0x02 加载 CDC 类驱动(如 Windows 的 usbser.sys),驱动解析接口描述符后创建一个虚拟 COM 口。应用程序即可通过串口 API 进行读写。

浙公网安备 33010602011771号