4. 标准设备请求详解:GetDescriptor、SetAddress、SetConfiguration 等标准请求的 bmRequestType、bRequest、wValue、wIndex、wLength 格式
USB 枚举过程的核心是主机通过 默认控制管道(Endpoint 0) 向设备发送一系列标准设备请求。每个请求都是一个固定格式的 8 字节 Setup 包,随后可能跟随数据阶段(Data Stage)和状态阶段(Status Stage)。理解这 8 字节的编码规则,是解读枚举日志、定位枚举失败故障的基石。
调试要点: 在 USB 分析仪或主机端日志中,标准请求的 8 字节 Setup 包通常以十六进制序列呈现。掌握每个字段的位含义,能让你直接从原始数据中还原出主机意图,快速判断是请求格式错误还是设备响应异常。
4.1 标准请求的 8 字节 Setup 包结构
所有 USB 标准设备请求均通过控制传输的 Setup 阶段发送,其数据结构由 USB 2.0 规范第 9.3 节定义。8 字节的布局如下:
| 偏移量(字节) | 字段名称 | 长度(位) | 说明 |
|---|---|---|---|
| 0 | bmRequestType | 8 | 请求特性:方向、类型、接收者 |
| 1 | bRequest | 8 | 请求编号(如 GET_DESCRIPTOR = 0x06) |
| 2-3 | wValue | 16 | 请求参数(高字节/低字节,含义因请求而异) |
| 4-5 | wIndex | 16 | 索引或偏移(如接口号、端点号、语言 ID) |
| 6-7 | wLength | 16 | 数据阶段期望传输的字节数 |
关键原则: 主机发送 Setup 包后,设备必须在 5ms(高速)或 50ms(全速/低速)内完成握手。如果设备无法解析 bmRequestType 或 bRequest,将返回 STALL 握手,导致枚举失败。
4.2 bmRequestType:请求的方向、类型与接收者
bmRequestType 是 8 位位图,决定了请求的传输方向、请求类型以及目标接收者。其位定义如下:
| 位范围 | 字段 | 值 | 含义 |
|---|---|---|---|
| D7 | 方向 | 0 = 主机到设备(Host-to-Device) 1 = 设备到主机(Device-to-Host) |
控制传输数据阶段的方向 |
| D6:D5 | 类型 | 00 = 标准(Standard) 01 = 类(Class) 10 = 厂商(Vendor) 11 = 保留 |
请求所属的规范层级 |
| D4:D0 | 接收者 | 00000 = 设备(Device) 00001 = 接口(Interface) 00010 = 端点(Endpoint) 00011 = 其他(Other) 其他 = 保留 |
请求的目标对象 |
常见枚举请求的 bmRequestType 值:
- 0x80:设备到主机,标准类型,接收者为设备(如 GetDescriptor)。
- 0x00:主机到设备,标准类型,接收者为设备(如 SetAddress、SetConfiguration)。
- 0x01:主机到设备,标准类型,接收者为接口(如 SetInterface)。
- 0x02:主机到设备,标准类型,接收者为端点(如 ClearFeature)。
常见故障点: 设备固件在解析 bmRequestType 时,如果错误地检查了保留位(如 D6:D5 = 11),或者将方向位与数据阶段实际方向混淆,会导致设备 STALL 请求。调试时,务必确认设备固件对 bmRequestType 的位掩码处理是否正确。
4.3 bRequest:标准请求码
bRequest 字段标识具体的请求操作。USB 2.0 规范定义了 10 个标准请求码,枚举过程中最常用的有 4 个:
| bRequest 值 | 请求名称 | 枚举阶段 | 方向(数据阶段) |
|---|---|---|---|
| 0x00 | GetStatus | 可选(状态查询) | 设备 → 主机 |
| 0x01 | ClearFeature | 可选(清除特性) | 主机 → 设备 |
| 0x03 | SetFeature | 可选(设置特性) | 主机 → 设备 |
| 0x05 | SetAddress | 地址分配 | 主机 → 设备(无数据阶段) |
| 0x06 | GetDescriptor | 描述符获取 | 设备 → 主机 |
| 0x07 | SetDescriptor | 极少使用 | 主机 → 设备 |
| 0x08 | GetConfiguration | 配置查询 | 设备 → 主机 |
| 0x09 | SetConfiguration | 配置激活 | 主机 → 设备(无数据阶段) |
| 0x0A | GetInterface | 可选 | 设备 → 主机 |
| 0x0B | SetInterface | 可选(备用设置) | 主机 → 设备 |
调试技巧: 在枚举日志中,如果看到 bRequest = 0x06 但设备返回 STALL,首先检查 wValue 中的描述符类型是否被设备支持。例如,某些设备可能不支持字符串描述符(类型 0x03),或者只支持有限的语言 ID。
4.4 wValue、wIndex、wLength:请求参数详解
这三个字段的含义随 bRequest 变化。下面以枚举中最关键的三个请求为例,说明其编码规则。
4.4.1 GetDescriptor(bRequest = 0x06)
主机通过此请求获取设备的描述符信息。wValue 的高字节(wValueH)指定描述符类型,低字节(wValueL)指定描述符索引。wIndex 通常用于指定字符串描述符的语言 ID,其他描述符时设为 0。wLength 指定主机期望接收的字节数。
| 字段 | 值示例 | 含义 |
|---|---|---|
| bmRequestType | 0x80 | 设备到主机,标准,设备 |
| bRequest | 0x06 | GetDescriptor |
| wValueH | 0x01 | 描述符类型:设备描述符(DEVICE) |
| wValueL | 0x00 | 描述符索引:第 0 个(通常只有一个) |
| wIndex | 0x0000 | 语言 ID(仅字符串描述符时有效) |
| wLength | 0x0012 | 期望 18 字节(设备描述符标准长度) |
常见描述符类型值(wValueH):
- 0x01:设备描述符(Device Descriptor)
- 0x02:配置描述符(Configuration Descriptor)
- 0x03:字符串描述符(String Descriptor)
- 0x04:接口描述符(Interface Descriptor)
- 0x05:端点描述符(Endpoint Descriptor)
- 0x06:设备限定描述符(Device Qualifier Descriptor,USB 2.0 高速)
- 0x07:其他速度配置描述符(Other Speed Configuration)
- 0x08:接口功率描述符(Interface Power Descriptor)
故障案例: 主机发送 GetDescriptor(配置描述符, wLength=9) 请求前 9 字节,设备必须正确返回配置描述符的前 9 字节(包括总长度字段)。如果设备返回的数据中总长度字段错误(例如小于实际描述符长度),主机可能无法完成后续的完整描述符读取,导致枚举失败。
4.4.2 SetAddress(bRequest = 0x05)
此请求为主机分配一个唯一的地址给设备。SetAddress 是唯一一个在数据阶段没有数据包的控制请求(即只有 Setup 和 Status 阶段)。
| 字段 | 值示例 | 含义 |
|---|---|---|
| bmRequestType | 0x00 | 主机到设备,标准,设备 |
| bRequest | 0x05 | SetAddress |
| wValue | 0x0003 | 新地址 = 3(有效范围 1-127) |
| wIndex | 0x0000 | 未使用,必须为 0 |
| wLength | 0x0000 | 无数据阶段 |
时序关键点: 设备在收到 SetAddress 请求并完成状态阶段后,必须在 2ms 内切换到新地址。主机在发送 SetAddress 后,会等待至少 2ms 再使用新地址发送后续请求。如果设备切换地址延迟过长,主机将无法与设备通信,导致枚举超时。
4.4.3 SetConfiguration(bRequest = 0x09)
主机通过此请求激活设备的某个配置。wValue 的低字节指定配置值(bConfigurationValue),该值必须与配置描述符中的 bConfigurationValue 字段匹配。
| 字段 | 值示例 | 含义 |
|---|---|---|
| bmRequestType | 0x00 | 主机到设备,标准,设备 |
| bRequest | 0x09 | SetConfiguration |
| wValue | 0x0001 | 选择配置值 1 |
| wIndex | 0x0000 | 未使用,必须为 0 |
| wLength | 0x0000 | 无数据阶段 |
常见故障: 如果设备固件在 SetConfiguration 后未能正确初始化端点(例如端点 FIFO 未清空、端点使能位未设置),主机后续的批量或中断传输将失败。调试时,应在 SetConfiguration 后立即检查端点状态寄存器和端点描述符的匹配情况。
4.5 标准请求的固件处理要点
在嵌入式固件中实现标准请求处理时,以下要点直接影响枚举成功率:
- Setup 包解析顺序: 先检查 bmRequestType 的 D6:D5 位是否为 00(标准类型),再检查 bRequest 值。非标准请求应返回 STALL。
- wLength 校验: 设备返回的数据长度不能超过 wLength。如果描述符实际长度大于 wLength,设备应截断返回;如果小于 wLength,设备应返回实际长度(主机通过数据包结束判断)。
- SetAddress 的特殊处理: 设备必须在状态阶段完成后立即启用新地址,但不应在状态阶段完成前改变地址。建议在状态阶段完成中断中设置新地址。
- SetConfiguration 后的端点初始化: 设备必须在收到 SetConfiguration 后,根据所选配置描述符中的接口和端点描述符,正确初始化所有非默认端点(包括端点类型、最大包大小、FIFO 分配等)。
- 描述符缓存对齐: 描述符数据通常存储在 Flash 或 RAM 中,返回给 USB 外设时需注意字节对齐(通常要求 4 字节对齐),否则可能导致 DMA 传输错误。
调试日志示例: 在固件中打印每个标准请求的 8 字节 Setup 包,格式如
[SETUP] 80 06 00 01 00 00 12 00,可以快速与 USB 分析仪日志对比,定位是主机发送错误还是设备解析错误。
4.6 标准请求故障排查速查表
| 故障现象 | 可能原因 | 检查点 |
|---|---|---|
| GetDescriptor 返回 STALL | 描述符类型不支持或描述符索引越界 | 检查 wValueH 描述符类型、wValueL 索引 |
| SetAddress 后主机超时 | 设备地址切换延迟超过 2ms | 检查固件中地址切换的时序逻辑 |
| SetConfiguration 后端点无响应 | 端点未正确初始化或端点描述符不匹配 | 检查端点类型、最大包大小、FIFO 地址 |
| 设备返回数据长度与 wLength 不匹配 | 描述符总长度字段错误或固件截断逻辑有误 | 验证描述符中的 bLength 和 wTotalLength |
| 主机重复发送同一请求 | 设备未正确握手(ACK/STALL) | 检查 USB 外设中断状态和端点就绪位 |
总结: 标准设备请求是 USB 枚举的通信语言。bmRequestType 定义了对话的“方向、话题和对象”,bRequest 指定了“动作”,wValue/wIndex/wLength 提供了“参数”。掌握这 8 字节的编码规则,就等于拿到了解读枚举日志的钥匙。在下一章中,我们将深入分析描述符的结构与常见错误,进一步巩固枚举故障排查能力。