4. 描述符家族:从设备到端点的完整画像
描述符是什么?说白了,就是 USB 设备的「身份证」加「简历」。主机插上你的设备后,第一件事就是通过标准请求把这一整套描述符读走,然后才知道:你是谁?你能干什么?该怎么跟你通信?
我个人习惯把描述符家族比作俄罗斯套娃——最外层是设备描述符,打开里面是配置描述符,再打开是接口描述符,最里面是端点描述符。字符串描述符和 HID 描述符则是额外的「附件说明」。今天我们就一个一个拆开看。
4.1 设备描述符:设备的「户口本」
设备描述符是所有描述符的「老大」。主机枚举时,第一个标准请求就是获取设备描述符(长度 18 字节)。它告诉主机:这个设备用了什么 USB 版本、厂商是谁、产品是什么、需要多大电流。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定 0x12 (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 最大包长,USB 2.0 通常为 64 |
| 8-9 | idVendor | 2 | 厂商 ID (VID),由 USB-IF 分配 |
| 10-11 | idProduct | 2 | 产品 ID (PID),厂商自定义 |
| 12-13 | bcdDevice | 2 | 设备版本号 |
| 14 | iManufacturer | 1 | 厂商字符串描述符索引 |
| 15 | iProduct | 1 | 产品字符串描述符索引 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符索引 |
| 17 | bNumConfigurations | 1 | 支持的配置数量 |
4.2 配置描述符:设备的「功能清单」
配置描述符描述了一个配置的整体信息。一个设备可以有多个配置(比如一个配置用电池供电,另一个用总线供电),但同一时刻只能激活一个。
配置描述符本身只有 9 字节,但它后面会跟着接口描述符和端点描述符。主机通过读取配置描述符的 wTotalLength 字段,就知道整个配置「家族」的总长度。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 固定 0x09 (9) |
| 1 | bDescriptorType | 1 | 配置描述符类型,0x02 |
| 2-3 | wTotalLength | 2 | 整个配置描述符家族的总长度 |
| 4 | bNumInterfaces | 1 | 该配置下的接口数量 |
| 5 | bConfigurationValue | 1 | 配置编号,用于 Set_Configuration 请求 |
| 6 | iConfiguration | 1 | 配置字符串描述符索引 |
| 7 | bmAttributes | 1 | 供电属性:D7=1 总线供电,D6=1 自供电,D5=1 支持远程唤醒 |
| 8 | bMaxPower | 1 | 最大电流,单位 2mA,如 0x32 表示 100mA |
4.3 接口描述符:设备的「功能模块」
接口描述符定义了一个功能接口。比如一个复合设备可能有 HID 接口和音频接口,每个接口都有自己的接口描述符。
接口描述符也是 9 字节。注意它的 bInterfaceNumber 和 bAlternateSetting 字段——前者是接口编号,后者是备用设置。同一个接口可以有多个备用设置,用于切换不同的带宽需求。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 固定 0x09 |
| 1 | bDescriptorType | 1 | 接口描述符类型,0x04 |
| 2 | bInterfaceNumber | 1 | 接口编号,从 0 开始 |
| 3 | bAlternateSetting | 1 | 备用设置编号 |
| 4 | bNumEndpoints | 1 | 该接口使用的端点数量(不包括端点 0) |
| 5 | bInterfaceClass | 1 | 接口类代码,如 HID 为 0x03 |
| 6 | bInterfaceSubClass | 1 | 接口子类代码 |
| 7 | bInterfaceProtocol | 1 | 接口协议代码 |
| 8 | iInterface | 1 | 接口字符串描述符索引 |
4.4 端点描述符:数据的「出入口」
端点是 USB 通信的最小单元。每个端点描述符 7 字节,描述了端点的地址、方向、传输类型和最大包长。
端点 0 是默认控制端点,不需要描述符。其他端点(1~15)都需要在接口描述符后面跟着对应的端点描述符。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 固定 0x07 |
| 1 | bDescriptorType | 1 | 端点描述符类型,0x05 |
| 2 | bEndpointAddress | 1 | 端点地址:D7=方向(0 OUT, 1 IN),D3-D0=端点号 |
| 3 | bmAttributes | 1 | 传输类型:0=控制,1=等时,2=批量,3=中断 |
| 4-5 | wMaxPacketSize | 2 | 最大包长,USB 2.0 高速批量端点最大 512 |
| 6 | bInterval | 1 | 轮询间隔,中断端点单位是 ms(全速)或 125μs(高速) |
4.5 字符串描述符:设备的「名字牌」
字符串描述符是可选的,但强烈建议加上。它让设备在主机上显示一个友好的名字,而不是「Unknown Device」。
字符串描述符使用 Unicode 编码(UTF-16LE)。第一个字节是长度,第二个字节是类型(0x03),后面是 Unicode 字符串。注意:字符串描述符的索引 0 是特殊的,它返回的是语言 ID 列表。
// 字符串描述符示例:语言 ID 列表
// 索引 0:支持的语言(英语美国)
0x04, 0x03, 0x09, 0x04
// 字符串描述符示例:厂商字符串 "MyUSB"
// 索引 1
0x0C, 0x03, 'M', 0x00, 'y', 0x00, 'U', 0x00, 'S', 0x00, 'B', 0x00
4.6 HID 描述符:人机交互设备的「说明书」
HID 描述符是 HID 类设备特有的。它告诉主机:这个设备用什么协议、报告描述符有多长。HID 描述符本身很短,但它指向的报告描述符才是真正的「重头戏」。
| 偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 固定 0x09 |
| 1 | bDescriptorType | 1 | HID 描述符类型,0x21 |
| 2-3 | bcdHID | 2 | HID 规范版本号,通常 0x0111 |
| 4 | bCountryCode | 1 | 国家代码,0x00 表示不指定 |
| 5 | bNumDescriptors | 1 | 下级描述符数量,通常为 1 |
| 6 | bDescriptorType | 1 | 下级描述符类型,报告描述符为 0x22 |
| 7-8 | wDescriptorLength | 2 | 报告描述符的总长度 |
4.7 实战填充:一个完整的 HID 键盘描述符集合
说了这么多理论,我们来填一个真实的例子。下面是一个 USB 全速 HID 键盘的描述符集合,包含设备、配置、接口、HID、端点和字符串描述符。
// 设备描述符 (18 字节)
0x12, // bLength
0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB (2.00)
0x00, // bDeviceClass (在接口中定义)
0x00, // bDeviceSubClass
0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 (64)
0x34, 0x12, // idVendor (0x1234)
0x56, 0x78, // idProduct (0x7856)
0x00, 0x01, // bcdDevice (1.00)
0x01, // iManufacturer (字符串索引 1)
0x02, // iProduct (字符串索引 2)
0x03, // iSerialNumber (字符串索引 3)
0x01, // bNumConfigurations (1 个配置)
// 配置描述符 (9 字节)
0x09, // bLength
0x02, // bDescriptorType (Configuration)
0x29, 0x00, // wTotalLength (41 字节,整个家族)
0x01, // bNumInterfaces (1 个接口)
0x01, // bConfigurationValue (配置编号 1)
0x00, // iConfiguration (无字符串)
0x80, // bmAttributes (总线供电)
0x32, // bMaxPower (100mA)
// 接口描述符 (9 字节)
0x09, // bLength
0x04, // bDescriptorType (Interface)
0x00, // bInterfaceNumber (接口 0)
0x00, // bAlternateSetting
0x01, // bNumEndpoints (1 个端点,不包括端点 0)
0x03, // bInterfaceClass (HID)
0x01, // bInterfaceSubClass (Boot 子类)
0x01, // bInterfaceProtocol (键盘)
0x00, // iInterface (无字符串)
// HID 描述符 (9 字节)
0x09, // bLength
0x21, // bDescriptorType (HID)
0x11, 0x01, // bcdHID (1.11)
0x00, // bCountryCode
0x01, // bNumDescriptors
0x22, // bDescriptorType (Report)
0x41, 0x00, // wDescriptorLength (65 字节)
// 端点描述符 (7 字节)
0x07, // bLength
0x05, // bDescriptorType (Endpoint)
0x81, // bEndpointAddress (端点 1 IN)
0x03, // bmAttributes (中断传输)
0x08, 0x00, // wMaxPacketSize (8 字节)
0x0A, // bInterval (10ms)