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 请求获取设备描述符。下图展示了设备描述符在枚举第一阶段的关键位置:

设备描述符在 USB 枚举流程中的位置 1. 设备连接 2. 总线复位 3. 获取设备描述符 GET_DESCRIPTOR (wValue=0x0100) 4. 解析设备描述符 5. 获取配置/接口/端点描述符 ⚠ 枚举失败高发区 设备描述符填充错误 → 主机无法识别设备 → 枚举终止

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 设备描述符字段间的依赖关系

设备描述符中的某些字段存在逻辑依赖关系,填充时必须保持一致性。下图展示了这些依赖关系:

设备描述符字段依赖关系 bDeviceClass bDeviceClass = 0x00 bDeviceSubClass = 0x00 bDeviceProtocol = 0x00 bDeviceClass = 0xFF 厂商自定义协议 需提供厂商驱动

4.8 调试建议:如何验证设备描述符

  1. 使用 USB 分析仪: 抓取枚举阶段的控制传输数据,检查主机收到的 18 字节是否与预期一致。
  2. 逻辑分析仪解码: 在 D+/D- 线路上捕获 USB 包,验证 SETUP 令牌、DATA0 数据包和 ACK 握手。
  3. 固件打印调试: 在设备端添加调试输出,确认 GET_DESCRIPTOR 请求被正确响应,且返回的数据指针正确。
  4. 对比参考实现: 使用已知正常的设备(如 USB 鼠标)抓取其设备描述符,与自己的填充值逐字节对比。
实用技巧: 在 Linux 主机上,使用 lsusb -v 命令可以查看已枚举设备的完整描述符信息。如果设备枚举失败,使用 dmesg | tail 查看内核日志,通常会显示“device descriptor read/64, error -71”等错误信息,帮助定位问题。