3. 设备描述符结构:标准设备描述符的字段解析
设备描述符(Device Descriptor)是 USB 枚举过程中主机读取的第一个描述符,它提供了设备的全局信息,包括 USB 规范版本、厂商 ID、产品 ID 以及设备所属类别等。该描述符的结构由 USB 规范严格定义,长度为 18 字节,是所有描述符的根节点。
3.1 标准设备描述符的字段布局
标准设备描述符共包含 14 个字段,每个字段都有固定的偏移量和字节长度。下表列出了完整的字段结构及其含义:
| 偏移量(字节) | 字段名称 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | bLength | 1 | 描述符长度,固定为 0x12(18 字节) |
| 1 | bDescriptorType | 1 | 描述符类型,设备描述符固定为 0x01 |
| 2-3 | bcdUSB | 2 | USB 规范版本号(BCD 编码),如 0x0200 表示 USB 2.0 |
| 4 | bDeviceClass | 1 | 设备类代码(由 USB-IF 分配),0x00 表示在接口描述符中定义 |
| 5 | bDeviceSubClass | 1 | 设备子类代码,与 bDeviceClass 配合使用 |
| 6 | bDeviceProtocol | 1 | 设备协议代码,0x00 表示不使用协议 |
| 7 | bMaxPacketSize0 | 1 | 端点 0 的最大包大小(字节),常见值为 8/16/32/64 |
| 8-9 | idVendor | 2 | 厂商 ID(由 USB-IF 分配),小端序存储 |
| 10-11 | idProduct | 2 | 产品 ID(厂商自行分配),小端序存储 |
| 12-13 | bcdDevice | 2 | 设备版本号(BCD 编码),用于区分固件版本 |
| 14 | iManufacturer | 1 | 厂商字符串描述符的索引,0 表示无字符串 |
| 15 | iProduct | 1 | 产品字符串描述符的索引,0 表示无字符串 |
| 16 | iSerialNumber | 1 | 序列号字符串描述符的索引,0 表示无序列号 |
| 17 | bNumConfigurations | 1 | 设备支持的配置描述符数量,至少为 1 |
bMaxPacketSize0 字段必须与底层控制传输的硬件能力匹配。例如,对于 USB 2.0 全速设备,该值通常为 8 或 16;对于高速设备,固定为 64。错误设置该字段会导致枚举阶段控制传输失败。
3.2 关键字段深度解析
3.2.1 bLength 与 bDescriptorType
这两个字段是所有 USB 描述符的公共头部。主机通过 bDescriptorType 区分当前读取的描述符类型,从而决定后续解析方式。设备描述符的 bLength 固定为 0x12,bDescriptorType 固定为 0x01。在移植过程中,如果自定义描述符结构,必须确保这两个字段的值与 USB 规范一致,否则主机将拒绝识别设备。
3.2.2 bcdUSB:USB 规范版本
该字段使用二进制编码十进制(BCD)格式表示设备遵循的 USB 规范版本。高字节表示主版本号,低字节表示次版本号。常见取值如下:
- 0x0110:USB 1.1(全速/低速)
- 0x0200:USB 2.0(高速/全速/低速)
- 0x0300:USB 3.0(超速)
在移植适配时,该字段必须与硬件控制器的 USB 版本能力一致。例如,将 USB 2.0 控制器设置为 0x0300 会导致主机期望超速传输,而实际硬件无法支持,造成枚举失败。
3.2.3 idVendor 与 idProduct:设备身份标识
idVendor(厂商 ID)由 USB 实施者论坛(USB-IF)统一分配,每个厂商拥有唯一的 16 位标识符。idProduct(产品 ID)由厂商自行分配,用于区分同一厂商的不同产品。这两个字段共同构成设备的唯一身份标识,主机通过它们加载对应的驱动程序。
3.2.4 bMaxPacketSize0:端点 0 的最大包大小
端点 0 是所有 USB 设备必须实现的双向控制端点。该字段决定了控制传输中数据阶段的最大包长度。不同速度模式下的典型值如下:
| USB 速度模式 | bMaxPacketSize0 典型值 | 说明 |
|---|---|---|
| 低速(1.5 Mbps) | 8 | 控制传输数据阶段最大 8 字节 |
| 全速(12 Mbps) | 8、16、32、64 | 常用 8 或 64,取决于控制器能力 |
| 高速(480 Mbps) | 64 | 固定为 64 字节 |
| 超速(5 Gbps) | 512 | USB 3.0 及以上固定值 |
3.3 设备描述符在枚举流程中的位置
设备描述符是 USB 枚举的起点。主机在检测到设备连接并完成复位后,通过默认地址 0 发送 GET_DESCRIPTOR 请求,读取设备描述符的前 8 字节(仅获取 bMaxPacketSize0),然后使用该值重新设置控制传输的最大包大小,再读取完整的 18 字节设备描述符。下图展示了这一流程:
3.4 设备描述符的代码实现示例
在嵌入式系统中,设备描述符通常以结构体或字节数组的形式定义。以下是一个典型的 USB 2.0 全速设备描述符实现:
/* 标准设备描述符结构体定义 */
typedef struct __attribute__((packed)) {
uint8_t bLength; /* 描述符长度:0x12 */
uint8_t bDescriptorType; /* 描述符类型:0x01 */
uint16_t bcdUSB; /* USB 版本:0x0200 */
uint8_t bDeviceClass; /* 设备类:0x00 */
uint8_t bDeviceSubClass; /* 设备子类:0x00 */
uint8_t bDeviceProtocol; /* 设备协议:0x00 */
uint8_t bMaxPacketSize0; /* 端点0最大包大小:64 */
uint16_t idVendor; /* 厂商ID:0x1234 */
uint16_t idProduct; /* 产品ID:0x5678 */
uint16_t bcdDevice; /* 设备版本:0x0100 */
uint8_t iManufacturer; /* 厂商字符串索引:1 */
uint8_t iProduct; /* 产品字符串索引:2 */
uint8_t iSerialNumber; /* 序列号字符串索引:3 */
uint8_t bNumConfigurations; /* 配置描述符数量:1 */
} usb_device_descriptor_t;
/* 设备描述符实例 */
const usb_device_descriptor_t device_descriptor = {
.bLength = 0x12,
.bDescriptorType = 0x01,
.bcdUSB = 0x0200, /* USB 2.0 */
.bDeviceClass = 0x00, /* 在接口中定义 */
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = 64, /* 全速/高速设备 */
.idVendor = 0x1234, /* 测试用厂商ID */
.idProduct = 0x5678, /* 测试用产品ID */
.bcdDevice = 0x0100, /* 版本1.00 */
.iManufacturer = 1, /* 字符串描述符索引 */
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1
};
- bLength:确认值为 0x12,不可多写或少写字节。
- bDescriptorType:确认值为 0x01,与主机期望一致。
- bcdUSB:与硬件控制器支持的 USB 版本匹配。
- bMaxPacketSize0:与控制器实际能力一致,高速设备必须为 64。
- idVendor / idProduct:开发阶段使用测试 ID,量产前替换为合法 ID。
- bNumConfigurations:至少为 1,且与后续配置描述符数量一致。
3.5 设备描述符与地址分配的关联
设备描述符的读取发生在地址分配之前。主机在读取完整的设备描述符后,才会发送 SET_ADDRESS 请求为设备分配唯一地址。这意味着设备描述符中的 bMaxPacketSize0 字段必须在默认地址 0 下正确传输,否则后续的地址分配和配置过程将无法进行。在移植过程中,必须确保控制传输在默认地址 0 下能够可靠地传输至少 18 字节的数据。
3.6 常见移植错误与调试方法
在移植 USB 设备栈时,设备描述符相关的错误通常表现为枚举失败或设备无法被识别。以下列出三种常见错误及其调试方法:
| 错误现象 | 可能原因 | 调试方法 |
|---|---|---|
| 主机无法获取设备描述符 | bMaxPacketSize0 设置过大,超出控制器实际能力 |
使用 USB 分析仪抓取控制传输,检查 SETUP 阶段是否超时 |
| 设备描述符内容错误 | 字节序错误(小端序未正确实现)或字段值越界 | 打印或导出设备描述符的原始字节,与规范逐字段比对 |
| 地址分配后设备无响应 | bNumConfigurations 与实际配置数量不一致 |
检查配置描述符链表是否完整,确认数量字段正确 |