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
移植适配要点: 在嵌入式系统中移植 USB 设备栈时,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)由厂商自行分配,用于区分同一厂商的不同产品。这两个字段共同构成设备的唯一身份标识,主机通过它们加载对应的驱动程序。

移植风险提示: 在开发阶段,切勿使用已注册的商用厂商 ID 进行测试。建议使用 USB-IF 预留的测试 ID(如 0xFFFF)或厂商内部测试 ID,避免与正式产品冲突。量产前必须申请合法的厂商 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 字节设备描述符。下图展示了这一流程:

设备描述符读取流程 设备描述符读取流程(枚举阶段) 主机检测设备连接并复位 发送 GET_DESCRIPTOR(前8字节) 解析 bMaxPacketSize0 字段 重新设置控制传输包大小 读取完整18字节设备描述符 关键说明 主机先读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 字节的数据。

设备描述符与地址分配时序 设备描述符与地址分配时序关系 复位 t0 读设备描述符 t1 SET_ADDRESS t2 配置描述符 t3 移植适配关键约束 • 设备描述符必须在默认地址0下完成读取(t1阶段) • bMaxPacketSize0 必须保证控制传输在t1阶段正确 • 地址分配(t2)之后,主机才使用新地址通信

3.6 常见移植错误与调试方法

在移植 USB 设备栈时,设备描述符相关的错误通常表现为枚举失败或设备无法被识别。以下列出三种常见错误及其调试方法:

错误现象 可能原因 调试方法
主机无法获取设备描述符 bMaxPacketSize0 设置过大,超出控制器实际能力 使用 USB 分析仪抓取控制传输,检查 SETUP 阶段是否超时
设备描述符内容错误 字节序错误(小端序未正确实现)或字段值越界 打印或导出设备描述符的原始字节,与规范逐字段比对
地址分配后设备无响应 bNumConfigurations 与实际配置数量不一致 检查配置描述符链表是否完整,确认数量字段正确
调试工具推荐: 在嵌入式开发中,使用 USB 协议分析仪(如 Beagle USB 480)或逻辑分析仪捕获枚举阶段的控制传输数据,可以直观地看到主机发送的请求和设备返回的描述符内容,是定位设备描述符问题的最有效手段。