4. 设备描述符(Device Descriptor):结构定义与字段填充规则
设备描述符是 USB 枚举过程中主机获取的第一个描述符,它定义了设备的整体属性,包括协议版本、厂商/产品标识、设备分类以及配置数量。主机通过默认控制端点(端点 0)发送标准请求 GET_DESCRIPTOR(bRequest = 0x06,wValue = 0x0100)来获取该描述符。设备描述符的结构由 USB 规范严格定义,任何字段填充错误都会导致枚举失败。
4.1 结构定义与字节布局
设备描述符的长度固定为 18 字节,所有 USB 设备必须支持。其数据结构在 USB 规范 2.0 第 9.6.1 节中定义,C 语言结构体如下:
typedef struct {
uint8_t bLength; // 描述符长度(固定 0x12 = 18)
uint8_t bDescriptorType; // 描述符类型(0x01 = 设备描述符)
uint16_t bcdUSB; // USB 规范版本号(BCD 编码)
uint8_t bDeviceClass; // 类代码
uint8_t bDeviceSubClass; // 子类代码
uint8_t bDeviceProtocol; // 协议代码
uint8_t bMaxPacketSize0; // 端点 0 最大包长度
uint16_t idVendor; // 厂商 ID(USB-IF 分配)
uint16_t idProduct; // 产品 ID(厂商自定义)
uint16_t bcdDevice; // 设备版本号(BCD 编码)
uint8_t iManufacturer; // 厂商字符串描述符索引
uint8_t iProduct; // 产品字符串描述符索引
uint8_t iSerialNumber; // 序列号字符串描述符索引
uint8_t bNumConfigurations; // 支持的配置描述符数量
} __attribute__((packed)) usb_device_descriptor_t;
关键提示: 结构体必须使用
__attribute__((packed)) 或等效的 1 字节对齐指令,否则编译器填充的字节会导致主机解析错位,枚举立即失败。
4.2 字段含义与填充规则
下表详细列出每个字段的含义、取值范围及常见填充值:
| 字段 | 偏移 | 大小 | 含义 | 常见值 | 填充规则 |
|---|---|---|---|---|---|
| bLength | 0 | 1 字节 | 描述符总长度 | 0x12 | 固定为 18 字节,不可更改 |
| bDescriptorType | 1 | 1 字节 | 描述符类型标识 | 0x01 | 设备描述符类型码固定为 1 |
| bcdUSB | 2 | 2 字节 | USB 规范版本号(BCD 编码) | 0x0200(USB 2.0) 0x0110(USB 1.1) |
高字节为主版本,低字节为次版本;USB 2.0 填 0x0200,USB 1.1 填 0x0110 |
| bDeviceClass | 4 | 1 字节 | 设备类代码 | 0x00(在接口描述符中定义) 0xFF(厂商自定义) |
0x00 表示每个接口独立定义类;0xFF 表示厂商自定义;其他值由 USB-IF 分配 |
| bDeviceSubClass | 5 | 1 字节 | 设备子类代码 | 0x00 | 当 bDeviceClass 为 0x00 时,此字段必须为 0x00 |
| bDeviceProtocol | 6 | 1 字节 | 设备协议代码 | 0x00 | 当 bDeviceClass 为 0x00 时,此字段必须为 0x00 |
| bMaxPacketSize0 | 7 | 1 字节 | 端点 0 最大包长度 | 0x40(64 字节) 0x08(8 字节) |
USB 2.0 高速设备必须为 64;全速设备可为 8/16/32/64;低速设备固定为 8 |
| idVendor | 8 | 2 字节 | 厂商 ID(小端序) | 0x0483(STMicro) 0x1A86(WCH) |
必须从 USB-IF 购买;小端序存储,低字节在前 |
| idProduct | 10 | 2 字节 | 产品 ID(小端序) | 0x5740(示例) | 厂商自定义,与 idVendor 组合唯一标识设备 |
| bcdDevice | 12 | 2 字节 | 设备版本号(BCD 编码) | 0x0100(v1.00) | 高字节为主版本,低字节为次版本;用于驱动匹配 |
| iManufacturer | 14 | 1 字节 | 厂商字符串描述符索引 | 0x01 | 0 表示无字符串;非 0 值对应字符串描述符的索引号 |
| iProduct | 15 | 1 字节 | 产品字符串描述符索引 | 0x02 | 0 表示无字符串;非 0 值对应字符串描述符的索引号 |
| iSerialNumber | 16 | 1 字节 | 序列号字符串描述符索引 | 0x03 | 0 表示无序列号;非 0 值对应字符串描述符的索引号 |
| bNumConfigurations | 17 | 1 字节 | 配置描述符数量 | 0x01 | 至少为 1;多配置设备可大于 1 |
4.3 设备描述符在枚举流程中的位置
主机在检测到设备连接并完成复位后,立即发送 GET_DESCRIPTOR 请求获取设备描述符。下图展示了设备描述符在枚举第一阶段的关键位置:
4.4 字段填充的常见陷阱与避坑指南
- bLength 错误: 必须为 0x12。如果填入 0x10 或 0x14,主机解析会偏移,导致后续所有字段读取错误。
- bcdUSB 版本不匹配: USB 2.0 设备填 0x0200,USB 1.1 设备填 0x0110。如果高速设备填 0x0110,主机可能以全速模式枚举,降低性能。
- bMaxPacketSize0 与速度不匹配: 高速设备必须为 64 字节(0x40),全速设备可选 8/16/32/64,低速设备固定 8。填入错误值会导致控制传输失败。
- idVendor 未注册: 使用 0x0000 或 0xFFFF 等保留值,主机可能拒绝加载驱动。必须使用 USB-IF 分配的合法 VID。
- 小端序错误: idVendor、idProduct、bcdUSB、bcdDevice 均为小端序。例如 VID=0x0483 在内存中应为
0x83, 0x04,而非0x04, 0x83。 - 字符串索引冲突: iManufacturer、iProduct、iSerialNumber 必须指向有效的字符串描述符索引,或填 0 表示不存在。如果填 1 但未提供对应的字符串描述符,主机请求字符串时会超时。
枚举失败典型案例: 某开发者在移植 STM32 USB 设备栈时,将
bMaxPacketSize0 误填为 0x08(8 字节),但实际硬件为高速设备(HS)。主机在控制传输阶段发现包长度不匹配,连续 3 次超时后放弃枚举,设备管理器显示“未知设备(设备描述符请求失败)”。
4.5 设备描述符的典型填充示例
以下是一个符合 USB 2.0 规范的 HID 设备描述符填充示例:
const uint8_t device_descriptor[18] = {
0x12, // bLength: 18 字节
0x01, // bDescriptorType: 设备描述符
0x00, 0x02, // bcdUSB: USB 2.0 (0x0200, 小端序)
0x00, // bDeviceClass: 在接口描述符中定义
0x00, // bDeviceSubClass: 0
0x00, // bDeviceProtocol: 0
0x40, // bMaxPacketSize0: 64 字节
0x83, 0x04, // idVendor: 0x0483 (STMicroelectronics, 小端序)
0x40, 0x57, // idProduct: 0x5740 (自定义, 小端序)
0x00, 0x01, // bcdDevice: 1.00 (0x0100, 小端序)
0x01, // iManufacturer: 字符串索引 1
0x02, // iProduct: 字符串索引 2
0x03, // iSerialNumber: 字符串索引 3
0x01 // bNumConfigurations: 1 个配置
};
4.6 设备描述符与枚举失败的关联分析
当设备描述符填充错误时,主机在枚举过程中会表现出不同的失败模式。下表总结了常见错误与对应的故障现象:
| 错误字段 | 错误值 | 主机行为 | 故障现象 |
|---|---|---|---|
| bLength | ≠ 0x12 | 解析偏移错误,后续字段全部错位 | 设备描述符请求失败,设备管理器显示“未知设备” |
| bDescriptorType | ≠ 0x01 | 主机无法识别描述符类型,丢弃数据 | 枚举超时,主机重试 3 次后放弃 |
| bMaxPacketSize0 | 与速度不匹配 | 控制传输数据包长度不一致,CRC 错误 | USB 总线复位循环,设备反复连接/断开 |
| idVendor | 0x0000 或 0xFFFF | 主机找不到匹配的驱动程序 | 设备管理器显示“未知设备”,驱动安装失败 |
| bNumConfigurations | 0 | 主机认为设备没有配置,无法继续枚举 | 枚举在获取设备描述符后立即停止 |
4.7 设备描述符字段间的依赖关系
设备描述符中的某些字段存在逻辑依赖关系,填充时必须保持一致性。下图展示了这些依赖关系:
4.8 调试建议:如何验证设备描述符
- 使用 USB 分析仪: 抓取枚举阶段的控制传输数据,检查主机收到的 18 字节是否与预期一致。
- 逻辑分析仪解码: 在 D+/D- 线路上捕获 USB 包,验证 SETUP 令牌、DATA0 数据包和 ACK 握手。
- 固件打印调试: 在设备端添加调试输出,确认
GET_DESCRIPTOR请求被正确响应,且返回的数据指针正确。 - 对比参考实现: 使用已知正常的设备(如 USB 鼠标)抓取其设备描述符,与自己的填充值逐字节对比。
实用技巧: 在 Linux 主机上,使用
lsusb -v 命令可以查看已枚举设备的完整描述符信息。如果设备枚举失败,使用 dmesg | tail 查看内核日志,通常会显示“device descriptor read/64, error -71”等错误信息,帮助定位问题。