HID 设备协议栈
本节介绍 XRUSB 的 USB HID(Human Interface Device) 设备类实现与扩展方式,覆盖:
- HID 模板化基类
LibXR::USB::HID<REPORT_DESC_LEN, TX_REPORT_LEN, RX_REPORT_LEN> - 自动生成配置描述符块(Interface + HID Descriptor + Endpoint Descriptors)
- 可选 Interrupt OUT(Output Report over Interrupt OUT)
- 标准请求
GET_DESCRIPTOR(HID / Report Descriptor) - HID 类请求(
GET_REPORT/SET_REPORT/GET_IDLE/SET_IDLE/GET_PROTOCOL/SET_PROTOCOL)处理框架 - Input Report 发送与 IN/OUT 完成回调
- 典型派生类:鼠标、键盘、手柄
1. 基类概览
1.1 LibXR::USB::HID<REPORT_DESC_LEN, TX_REPORT_LEN, RX_REPORT_LEN>
HID 为模板化 HID 基类(继承自 DeviceClass),通过模板参 数在编译期固化报告与描述符尺寸,方便派生实现键盘、鼠标、手柄等设备。
模板参数:
REPORT_DESC_LEN:Report Descriptor 长度(字节)TX_REPORT_LEN:Input Report 最大长度(Interrupt IN 端点最大包长)RX_REPORT_LEN:Output Report 最大长度(Interrupt OUT 端点最大包长)- 取
0表示不启用 OUT 中断端点
- 取
基类提供:
- 端点申请与配置:Interrupt IN(必选)+ Interrupt OUT(可选)
- 配置描述符块自动生成
GET_DESCRIPTOR(HID / Report)响应- HID 类请求处理框架(可在派生类中覆盖/扩展)
- Input Report 发送辅助:
SendInputReport(...) - IN/OUT 传输完成回调钩子:
OnDataInComplete(...)/OnDataOutComplete(...)
2. 描述符与端点布局
2.1 Interface
HID 基类贡献 1 个 HID 接口,不使用 IAD:
bInterfaceClass = 0x03(HID)bNumEndpoints = 1(仅 IN)或2(IN + OUT)
2.2 HID Descriptor(0x21)
配置描述符中包含 HID 类描述符(9 字节),关键字段:
bcdHID = 0x0111(HID v1.11)bNumDescriptors = 1bReportDescriptorType = 0x22wReportDescriptorLength = REPORT_DESC_LEN
2.3 Endpoints
| 端点 | 方向 | 类型 | 最大包长 | 用途 |
|---|---|---|---|---|
| IN 端点 | IN | INTERRUPT | TX_REPORT_LEN | 设备 → 主机 Input Report |
| OUT 端点(可选) | OUT | INTERRUPT | RX_REPORT_LEN | 主机 → 设备 Output Report |
轮询间隔:
in_ep_interval_/out_ep_interval_会写入端点描述符的bInterval
说明:不同速率下 bInterval 语义不同(FS 常按 1ms 帧;HS 为 microframe 编码)。本实现以“毫秒”语义组织参数,最终解释取决于底层 USB 控制器/栈。
3. 生命周期:Bind / Unbind
3.1 Bind(初始化)
初始化阶段通常完成:
- 记录接口号,并清理运行态标志
- 从
EndpointPool申请 Interrupt IN,按TX_REPORT_LEN配置 - 若
RX_REPORT_LEN > 0:申请 Interrupt OUT,按RX_REPORT_LEN配置 - 生成并提交配置描述符块(Interface + HID Descriptor + Endpoint Descriptors)
- 注册端点完成回调(IN 必选;OUT 可选)
- 若启用 OUT:启动首次 OUT 接收(之后每次完成会自动 re-arm)
- 置
inited_ = true
3.2 Unbind(释放)
解绑阶段通常完成:
- 清
inited_,关闭并归还 IN/OUT 端点到EndpointPool - 端点指针置空
4. 标准请求:GET_DESCRIPTOR(HID / Report)
基类处理标准请求 GET_DESCRIPTOR:
DescriptorType = 0x21:返回 HID DescriptorDescriptorType = 0x22:返回 Report Descriptor- 其他类型(如 Physical 0x23):返回不支持
返回数据会按 wLength 截断,避免超出主机请求长度。
派生类需要提供 Report Descriptor(基类会调用):
virtual ConstRawData GetReportDesc() = 0;
5. HID 类请求与数据阶段
基类支持常见 HID Class-Specific Requests:
GET_REPORT- 根据
wValue高字节区分INPUT/OUTPUT/FEATURE,并调用派生钩子生成返回数据
- 根据
SET_REPORT- Setup 阶段完成基本校验,并准备接收数据
- Data 阶段到来时回调派生钩子处理具体内容
GET_IDLE / SET_IDLE- 维护
idle_rate_(单位 4ms,常见实现仅支持report_id = 0)
- 维护
GET_PROTOCOL / SET_PROTOCOL- 维护
protocol_(BOOT / REPORT)
- 维护
建议派生类覆写的钩子(按需):
- 获取报告:
OnGetInputReport(...)/OnGetLastOutputReport(...)/OnGetFeatureReport(...) - 设置报告:
OnSetReport(...)(Setup 阶段)与OnSetReportData(...)(Data 阶段) - 自定义扩展:
OnCustomClassRequest(...)/OnCustomClassData(...)
6. 数据通路:Interrupt IN/OUT
6.1 发送 Input Report:SendInputReport(...)
典型行为:
- 校验初始化与端点存在
- 校验
0 < report_len <= TX_REPORT_LEN - 校验 IN 端点空闲,否则返回
BUSY - 复制 report 到 IN 端点缓冲并启动传输
常见返回码语义(以栈定义为准):
OK:成功启动传输BUSY:端点仍在传输ARG_ERR:报告长度非法FAILED/NO_BUFF:端点/缓冲不可用
6.2 接收 Output Report(Interrupt OUT,可选)
若启用 OUT 端点,基类默认会持续接收:
- Bind 后启动一次 OUT 接收
- 每次 OUT 完成后:
- 先回调
OnDataOutComplete(in_isr, data)让派生类消费 - 再自动 re-arm 继续接收下一帧
- 先回调
该路径适合处理“异步 Output Report”(例如键盘 LED 状态、手柄震动等)。
6.3 IN 完成回调
IN 发送完成后会触发 OnDataInComplete(in_isr, data),典型用途:
- 发送队列推进(发送下一帧)
- 统计/监测(吞吐、链路状态等)
7. 典型派生类(概述)
7.1 HIDMouse
- 标准 Boot 鼠标
- Input Report:常见为 4 字节(Buttons + X + Y + Wheel)
- 仅启用 IN 端点
7.2 HIDKeyboard
- 标 准 Boot 键盘
- Input Report:常见为 8 字节(Modifier + Reserved + 6 KeyCodes)
- 可选启用 OUT 端点(例如 1 字节 LED)
- 也可兼容主机通过控制端点下发 LED(
SET_REPORT)
7.3 HIDGamepadT
- 模板化手柄(例如 4 轴 + 8 按钮)
- Input Report 与 Report Descriptor 在编译期固化
- 通常提供便捷发送接口用于更新轴值与按键位图
8. 使用示例(概念性)
说明:具体 USB Device 框架如何注册 class 列表取决于上层 usb_dev 实现;以下示例仅展示典型调用方式。
8.1 鼠标
#include "hid_mouse.hpp"
LibXR::USB::HIDMouse hid_mouse;
// usb_dev class list: {{&hid_mouse}}
// usb_dev.Init();
// usb_dev.Start();
hid_mouse.Move(LibXR::USB::HIDMouse::LEFT, 10, 0);
hid_mouse.Release();
8.2 键盘(含 LED 回调)
#include "hid_keyboard.hpp"
// enable_out_endpoint=true 可启用 OUT 中断端点接收 LED(可选)
LibXR::USB::HIDKeyboard hid_kbd(true);
// 发送:Shift + A
hid_kbd.PressKey({LibXR::USB::HIDKeyboard::KeyCode::A},
LibXR::USB::HIDKeyboard::Modifier::LEFT_SHIFT);
hid_kbd.ReleaseAll();
8.3 手柄
#include "hid_gamepad.hpp"
LibXR::USB::HIDGamepad gamepad;
gamepad.Send(1024, 1024, 1024, 1024, LibXR::USB::HIDGamepad::BTN1);
9. 扩展建议(派生类实现要点)
实现一个新的 HID 派生类通常需要:
- 提供 Report Descriptor(实现
GetReportDesc()) - 定义并管理 Input Report 数据结构(长度不超过
TX_REPORT_LEN) - 如需 Output/Feature:
- 控制传输:覆写
OnSetReport(...) / OnSetReportData(...) - OUT 中断端点:启用
RX_REPORT_LEN > 0并覆写OnDataOutComplete(...)
- 控制传输:覆写
如需连续发送/队列化,可在 OnDataInComplete(...) 中实现“发送下一帧”的调度。