简介《USB HID Usage Tables 1.3》是USB Implementers Forum发布的人机交互设备使用表最新版本覆盖1996年至2022年更新适用于键盘、鼠标、游戏控制器、触摸屏等HID类设备开发面向硬件工程师、驱动开发者与系统集成人员。文档系统定义了标准Usage ID、Usage Page、Report Descriptor结构以及Boot Protocol等传输规则帮助操作系统准确识别并处理设备输入。包体为1个PDF文件大小约4.09MB内容完整、便于查阅。该版本经微软、英特尔、谷歌、苹果、罗技等公司专家参与制定并纳入多项HUT评审意见权威性与实用性兼具。已有558人学习下载适合需要实现跨平台兼容或进行USB外设底层开发的技术人员可为设备描述符编写、输入报告解析及产品规范符合性测试提供直接依据。1. 先聊清楚这份 HID Usage Tables 1.3为什么 2022 年还要翻 20 年前的规范干 USB HID 驱动、写设备固件的人桌面上大多躺着两份文档一份是 HID 规范本身一份就是这份《USB HID Usage Tables 1.3》。后者是 2022 年 1 月发布的版本把 1996 年到 2022 年间攒下的 Usage ID、Usage Page、设备类别补充全部收在一本字典里1.3 这个版本号看着老实际上合并了 101 到 107 号审查请求还有显示器控制、POS 收款设备、电源设备、物理接口设备等一堆新类别。三年前我在调一个自定义旋钮设备设备枚举正常但按键值始终不对论坛翻遍无果最后逐字节对照 1.3 里的 Consumer Page 才找到原因。它不是高深理论而是一本给键盘、鼠标、触摸屏、游戏外设、VR 控制器分配身份证号码的字典适合固件工程师、驱动开发者和做设备认证的人也适合所有被 HID 描述符折磨过的人。2. Usage Table 的结构从 Usage Page 到 Usage ID 的查表逻辑很多新手拿到这份文档第一反应是从头翻到尾翻到第三页就晕了。正确读法完全不同先搞清楚 Usage Page 是“哪本字典”再查 Usage ID 是“哪个词条”最后查 Usage Type 判断“这个词条的行为属性”。2.1 Usage Page 体系与 1.3 的边界HID Usage Table 里最顶层的概念就是 Usage Page。它就像一个命名空间同一个数值在不同 Page 下含义完全不同。比如 0x01 在 Generic Desktop Page 里是“Mouse”但在 Button Page 里就是“Button 1”的起始值在 Keyboard/Keypad Page 里则是“Esc”。所以描述符里 Usage Page 和 Usage 必须成对出现单看 Usage ID 没有任何意义。1.3 版本里常见 Page 分配可以快速过一遍Usage Page 值页面名称典型设备0x01Generic Desktop Page键盘、鼠标、旋钮、方向键0x02Simulation Controls Page飞行摇杆、赛车方向盘、坦克模拟0x03VR Controls PageVR 手柄、头部追踪0x04Sport Controls Page球拍、健身器材0x05Game Controls Page游戏手柄、射击枪、弹球台0x06Generic Device Controls Page设备背景控制、握持检测0x07Keyboard/Keypad Page键盘按键矩阵0x08LED Page键盘指示灯、RGB LED0x09Button Page鼠标按键、通用按键0x0AOrdinal Page顺序编号0x0BTelephony Device Page电话机、呼叫控制0x0CConsumer Page多媒体键、音量、播放控制0x0DDigitizer Page触摸屏、笔、触控板注意 1.3 里 0x06 Generic Device Controls 被大幅扩充了里面新增了 Background/Nonuser Controls、Device Grip Controls、Versioning 这些子类。做笔记本触控板、带握持检测的手柄的开发者之前只能把这些信息塞进厂商自定义区现在表里给了标准位置主机端就能统一解析。另外 0x03 VR Controls Page 也是近几个版本逐步完善的里面定义了 VR 手柄上的摇杆、扳机、系统按键的标准化 ID。2.2 Usage Type 决定设备行为同一张表里每个 Usage 条目还带一个类型属性这个属性经常被忽略但它决定了驱动如何处理这个控件。文档 3.4 节把这套体系讲得很清楚我挑几个开发中真正影响代码的Control 类里有 Linear Control线性控制、On/Off Control开关、Momentary Control瞬间触发、One Shot Control单次触发、Re-Trigger Control重复触发。鼠标 X 轴就是典型的 Linear Control值是连续有方向的按键是 Momentary Control按下为 1 松开为 0电话摘机这种动作更像 One Shot Control。Data 类里有 Selector、Static Value、Dynamic Flag、Dynamic Value。Selector 表示当前位置从一组值里选一个比如方向盘的角度和档位Dynamic Value 是不断变化的数值加速度计的输出就属于这一类。写驱动时Momentary Control 通常直接映射为按键事件Selector 则是查表映射Static Value 往往表示一个配置项而不是输入事件。把类型看错最直接的后果是同一个 usage 在你设备上表现为“一直按着”而不是“点一下”主机端行为完全拧巴。另外还有 Collection 类型涉及 Named Array、Application、Logical、Physical 几类。Application Collection 表示一个完整的设备功能块Physical Collection 表示空间上关联的一组控件。描述符里 Collection 嵌套层级错了也会导致主机把一组按键当成多个独立设备来处理。2.3 真正学会查表我的查表习惯是三步走第一步确认设备主用途去对应的大类 Page 里定位第二步找控件看它的 Usage ID 落在哪一段第三步看 Usage Type 和 Description 确认它的语义。举个实际例子键盘上的 A 键在 0x07 Keyboard/Keypad Page 里 Usage ID 是 0x04方向键左在同一个 Page 里是 0x50鼠标左键在 0x09 Button Page 里 Usage ID 是 0x01音量加在 0x0C Consumer Page 里是 0xE9。这几个值做 HID 的人应该闭着眼都能写出来但如果你要做一个带滚轮且可下压的旋钮就需要同时翻 0x01 Generic Desktop 里的 Axis Usages0x30 是 X、0x31 是 Y、0x38 是 Wheel和 0x09 Button Page 里的按键 ID。查表的关键是永远“先 Page 后 ID”并且 1.3 里对同一个物理控件给出了明确的“推荐用途”不要自创 ID 去撞保留区间。3. 从表格到报告描述符键盘与媒体键的逐字节拆解规范是字典描述符才是真正发给主机的“自我介绍”。这一章我把最常见两类设备的描述符逐字节拆开对照 1.3 的 Usage Table 讲清楚每个值是从哪来的、能改什么、改错了会怎样。3.1 一份标准 6 键无冲键盘描述符下面这份描述符是很多 USB 键盘固件的标准模板我加了详细注释。它对应 1.3 里 0x07 Keyboard/Keypad Page 的键盘键位表和 0x08 LED Page 的指示灯表// 6键无冲键盘报告描述符Usage ID 均对照 HID Usage Tables 1.3 0x05, 0x01, // Usage Page (Generic Desktop) -- 全局通用桌面页 0x09, 0x06, // Usage (Keyboard) -- 告诉主机这是一个键盘 0xA1, 0x01, // Collection (Application) -- 应用集合开始 // ---- 修饰键区Ctrl/Shift/Alt/Win 等 8 个按键每个占用 1 bit ---- 0x05, 0x07, // Usage Page (Keyboard/Keypad) -- 切到键盘页 0x19, 0xE0, // Usage Minimum (0xE0) -- 起始 Usage: Left Control 0x29, 0xE7, // Usage Maximum (0xE7) -- 结束 Usage: Right Meta 0x15, 0x00, // Logical Minimum (0) -- 逻辑最小值 0 0x25, 0x01, // Logical Maximum (1) -- 逻辑最大值 1 0x75, 0x01, // Report Size (1) -- 每个字段 1 bit 0x95, 0x08, // Report Count (8) -- 共 8 个字段 0x81, 0x02, // Input (Data, Variable, Absolute) -- 可读输入可变 // ---- 保留字节固定占 8 bit让按键数组从整字节开始 ---- 0x95, 0x01, // Report Count (1) 0x75, 0x08, // Report Size (8) 0x81, 0x01, // Input (Constant) -- 常量固定 0x00 // ---- LED 输出区NumLock/CapsLock/ScrollLock 等 5 个指示灯 ---- 0x95, 0x05, // Report Count (5) 0x75, 0x01, // Report Size (1) 0x05, 0x08, // Usage Page (LED) -- 切到 LED 页 0x19, 0x01, // Usage Minimum (Num Lock) -- 0x01 是 Num Lock 0x29, 0x05, // Usage Maximum (Kana) -- 0x05 是 Kana 0x91, 0x02, // Output (Data, Variable, Absolute) -- 输出报告 // ---- LED 区剩余 padding3 bit 常量 ---- 0x95, 0x01, // Report Count (1) 0x75, 0x03, // Report Size (3) 0x91, 0x01, // Output (Constant) // ---- 普通按键区6 个按键槽位每个 8 bit值就是键位的 Usage ID ---- 0x95, 0x06, // Report Count (6) -- 支持 6 键同时按下 0x75, 0x08, // Report Size (8) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x65, // Logical Maximum (101) -- 0x65 101键盘最大键位 0x05, 0x07, // Usage Page (Keyboard/Keypad) -- 切回键盘页 0x19, 0x00, // Usage Minimum (0) -- 0 代表无按键 0x29, 0x65, // Usage Maximum (101) -- 101 对应 1.3 表里最后一个键位 0x81, 0x00, // Input (Data, Array) -- 数组模式值即 Usage ID 0xC0 // End Collection这段描述符的逻辑是先把设备声明为 Generic Desktop 下的 Keyboard 集合然后依次描述三块数据——修饰键用位图表示每个修饰键占 1 bitLED 用输出报告控制普通按键用数组表示每个槽位填一个 Usage ID0 表示没有按下。最后一段Input (Data, Array)是数组输入与修饰键的Data, Variable不同数组模式下同一时刻可以填多个值主机按字节值直接查键盘表Variable 模式下主机按 bit 位去找对应的 Usage ID。两种模式的解析逻辑完全不同这也是新手写描述符最容易出问题的地方。3.2 Consumer Page 媒体控制描述符媒体键这类设备通常不需要 6 键无冲它们要解决的是“同时只有一个动作”的场景。一个常见写法是把每个功能映射为 1 bit报表大小 12 bit一次最多表达 12 个媒体键中的任意一个// 媒体控制描述符音量、播放控制对照 0x0C Consumer Page 0x05, 0x0C, // Usage Page (Consumer) -- 消费类页面 0x09, 0x01, // Usage (Consumer Control) -- 消费控制集合 0xA1, 0x01, // Collection (Application) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) -- 每位取值 0 或 1 0x75, 0x01, // Report Size (1) -- 每功能占 1 bit 0x95, 0x0C, // Report Count (12) -- 共 12 个功能位 // 下面每行定义一个媒体键Usage ID 都是 0x0C 页里查到的标准值 0x09, 0xE9, // Usage (Volume Increment) -- 音量加 0x09, 0xEA, // Usage (Volume Decrement) -- 音量减 0x09, 0xB5, // Usage (Next Track) -- 下一曲 0x09, 0xB6, // Usage (Previous Track) -- 上一曲 0x09, 0xB7, // Usage (Stop) -- 停止 0x09, 0xCD, // Usage (Play/Pause) -- 播放/暂停 0x09, 0xB8, // Usage (Eject) -- 弹出光盘 0x09, 0xB0, // Usage (Play) -- 播放 0x09, 0xB1, // Usage (Pause) -- 暂停 0x09, 0xB3, // Usage (Fast Forward) -- 快进 0x09, 0xB4, // Usage (Rewind) -- 快退 0x09, 0xB2, // Usage (Record) -- 录制 0x81, 0x02, // Input (Data, Variable, Absolute) -- 每位独立有效 0xC0 // End Collection这段描述符里每个媒体键都是独立的 1 bit 字段所以可以做到了即触发。注意 0x09, 0xCD 是 Play/Pause 的独立 Usage它和 0xB0Play、0xB1Pause在表里是三个不同条目说明书写时一定要先翻 Consumer Page 确认设备要暴露哪些标准键而不是凭习惯用 Play 代替 Play/Pause。主机端对这两类键的处理策略不同前者更接近媒体播放器的“切换播放状态”后者是明确的开始或暂停。3.3 字节序、Padding 与位宽对齐描述符第三个常见问题是位宽对齐。HID 报告是按位打包的不是按字节打包所以每个 report 的位宽必须凑成整字节。上面键盘描述符里修饰键 8 bit、保留字节 8 bit、LED 5 bit 后面跟着 3 bit padding、普通按键 6 × 8 bit全部字段加起来是 8 8 8 48 72 bit正好 9 字节。如果你自己定义一个带触摸板的键盘X 轴用 12 bit、Y 轴用 12 bit、压力用 6 bit前面还有 8 bit 按键区总位宽 38 bit就必须补 2 bit padding 凑成 5 字节。很多设备在 Windows 上显示“HID 设备描述符请求失败”原因就是 report 位宽不对齐。逻辑最小值和最大值的类型也容易翻车。键盘普通按键区Logical Minimum 0, Maximum 101这个 0 是有意义的它表示“空槽位”。如果把 Logical Minimum 设成带符号数 -128那数组模式下槽位值就可能被解析成负数主机端查表直接错乱。1.3 表里键盘最大键位到 101也就是 0x65一般键盘只用 0x00 到 0x65 这段。设置 Logical Maximum 时不要顺手写 255主机端按数组解析时会认为所有值都在表内超出表范围的行为就不可控了。4. 1.3 相比 1.2 更新了什么显示器控制、电源设备与电话状态管理这一章对已经有 1.2 使用经验的人最有用。1.3 不是简单修几个错别字而是把一批长期散落在其他类别的 Usage 正式收编了进来。4.1 版本更新概览先看版本线1.0 是 1997 年 10 月1.1 是 1999 年 4 月之后跳了 21 年才到 1.22020 年 7 月紧接着 1.212020 年 10 月、1.222021 年 4 月最后是 2022 年 1 月的 1.3。1.3 合并的是 HUT Review Requests 101 到 107引用文件里明确提到了 USB Monitor Control Class Specification、Open Arcade Architecture Device Data Format Specification、HID Point of Sale Usage Tables、HID Power Devices以及 PID 物理接口设备规范。简单说1.3 是把显示器控制、街机外设、POS 收款设备、电源设备和物理接口设备的 Usage 全部统一进了主表。对各路开发者的意义也很直接开发者人群1.3 带来的变化做 USB 显示器/扩展坞显示器亮度、对比度等控制有了标准 Usage不再依赖厂商私有描述符做收款机/扫码枪/钱箱POS 相关 Usage 进主表多系统兼容不再靠厂商自定义做充电器/电源适配器Power Device 的 Usage 表被引用进来电池状态、充电控制有了统一 ID做笔记本/平板Dockable Devices、Spatial Controls 等新子类补全了设备的扩展场景做手柄/VR 外设1.3 补了 VR Controls Page 的大量细节手柄按键映射更明确4.2 显示器控制是这次更新的重头1.3 里并入的 Monitor Control 类 Usage让显示器亮度、对比度、输入源切换这些动作可以作为标准 HID 控件出现。以前做带 OSD 按键的显示器厂商只能把这些功能放在 Vendor-Defined Usage Page 里Windows 下自己写驱动或靠第三方工具才能调亮度现在可以用通用 HID 驱动直接读取和设置。这个类别的落地场景是 USB 扩展坞和外接显示器笔记本通过 USB-C 接显示器时显示器厂商可以把亮度控制暴露成 HID Consumer Control操作系统无需额外驱动就能支持亮度调节。给固件工程师的建议是如果产品做的是显示器 OSD 菜单先到 1.3 的 Generic Desktop Page 4.10 System Display Controls 一节找亮度相关的控件再决定哪些继续放厂商自定义区。标准区如果已经有对应控件就不要再自造 ID否则 Windows 更新后可能突然识别行为变化。4.3 Resolution Multiplier 和 Feature Notifications 容易被忽略1.3 里 Generic Desktop Page 保留了 Resolution Multiplier 这个特殊控件它的存在感很低但游戏外设非常需要。简单说它是一个输出报告字段用来把设备的物理分辨率乘以一个系数。游戏鼠标常见的做法是硬件报告 1000 CPI实际需要 8000 CPI过去是靠设备自己缩放现在可以通过 Resolution Multiplier 让主机端配合完成。如果你在做高报告率的电竞鼠标这个控件值得细看它和 1.3 里 4.6 Buffered Bytes、4.8 Feature Notifications 是配套使用的Feature Notifications 用来让主机主动通知设备“准备接收新的倍率值”。这两个部分在 1.2 里就已经存在但直到 1.3 才被大量设备采用因为显示器控制需要它们配合完成动态调节。4.4 电话状态管理做统一通信设备的要留意4.16 Call State Management Control 是 1.3 里新补的重点。它给电话设备和 UC 客户端之间定义了一套标准 HID 用法包含摘机、挂断、来电接听、通话保持、静音等状态。做话务耳机、桌面电话、USB 音视频一体机的厂商之前这些状态都是通过私有 HID 报告或厂商驱动上报给客户端的现在可以做到标准枚举。文档里给了两个示例一个描述物理按键如何映射到 Call State另一个描述如何通过 Feature Report 让主机读取当前通话状态。这类设备开发中容易被坑的地方是电话状态不是单纯按键它需要双向通信所以要同时定义 Input用户按下接听键和 Feature主机查询当前是否在通话中两条路径。5. 常见问题与排查枚举失败、Usage 错位、描述符过长的处理做 HID 设备的血泪经验十有八九集中在枚举、Usage 分配和报告尺寸这三件事上。这里写五条我实际踩过、也帮别人排查过的坑每一条都按“现象 → 原因 → 解决”讲清楚。5.1 设备能枚举但按键完全错位现象插上设备后 Windows 和 Linux 都能识别出“USB 输入设备”但按键输出完全对不上按 A 出 B按功能键出字母。原因最常见的是 Usage Page 和 Usage ID 不匹配。比如把音量键写进了 Generic Desktop Page 而不是 Consumer Page主机收到 Usage ID 0xE9 时在键盘页或桌面页里查不到对应含义就会忽略或者乱映射。另一个原因是数组模式下 Logical Maximum 设置过小超出范围的值被截断了。解决抓一次枚举时的 Report Descriptor逐字节对照 1.3 的 Usage Table 查每个 Usage 的归属页。我一般会把描述符导出成十六进制文本按 Chapter 3 的格式逐段拆开确认 Page 切换的位置正确、每个 Usage ID 在对应 Page 里真实存在。特别注意同一个用法值在不同 Page 下语义完全不同检查时不能只看数字还要看前面的 Page 声明。5.2 自定义设备在 Windows 上被当成鼠标现象设备是一个带滚轮的旋钮但 Windows 把这个设备识别为鼠标而 Linux 下行为正常。原因Windows 对 HID 的解析规则和 Linux 不完全一致。旋钮如果直接用了 Generic Desktop Page 里的 X 值0x30作为滚轮数据源Windows 会很自然地把它归到鼠标类设备Linux 的 hid-generic 驱动解析更宽松不会立刻套用鼠标模板。另一个常见原因是报告里没有声明 Consumer Page 的 Consumer Control 集合Windows 拿不到媒体键上下文只能按桌面页的通用轴来处理。解决给旋钮设备同时声明 Generic Desktop 的 X、Y 轴和 Consumer Page 的 Consumer Control 集合让 Windows 识别出这是一台多功能输入设备。商品化设备最好再实现一个 Feature Report 给主机查询设备能力而不是让主机猜。5.3 描述符超过 64 字节导致中断传输失败现象USB 全速设备中断传输最大包长 64 字节描述符定义完编译烧录后设备枚举失败总线上一中断就报 STALL。原因报告描述符本身没有硬性长度上限但中断 IN 端点的最大包长是有限制的。全速设备一般是 64 字节高速设备可以到 1024 字节。如果你把所有按键、轴、LED、传感器都塞进一个报告总长超过端点包长传输自然失败。解决按功能拆报告给每个逻辑功能块分配独立的 Report ID。键盘按键占一个 Report ID媒体键占另一个传感器数据占第三个。Host 通过 Report ID 区分当前收的是哪段数据每段都控制在端点包长以内。我习惯把 Report ID 放在报告的第一个字节值 1 到 255枚举时通过描述符里的 Usage 关联。工程上还有一个折中技巧把实时性要求高的输入放进短报告把配置类数据放进 Feature Report可以大幅度降低中断传输压力。5.4 Logical Minimum 设成负数导致数据解析异常现象传感器设备输出加速度值Linux 下用hidraw读到的数据符号不对正数变负数小值漂移严重。原因报告里逻辑最小值被定义成带符号数比如Logical Minimum (-32768)、Logical Maximum (32767)而主机端驱动按无符号数解析就会把超过 0x7FFF 的值当成负数处理或者反过来。这类问题不是 Usage Table 的锅是描述符里数值类型没和驱动约定一致。解决先明确数据的物理语义再做类型选择。加速度计、陀螺仪这类本来就有方向的传感器建议用带符号数并且Report Size和Logical Maximum要覆盖量程单纯的电量百分比、温度这种非负量用无符号数Logical Minimum设 0。描述符改完后用hidrd-convert或者内核的hid_debug接口读一遍解析结果确认数值符号与物理方向一致再批量烧录。5.5 设备休眠唤醒后丢失功能现象设备正常工作合盖休眠再打开按键没响应指示灯状态也丢了必须重新插拔。原因设备没有正确处理主机发来的 Set_Report/Get_Report 请求。HID 规范里LED 状态、传感器采样率这类配置是通过 Output Report 或 Feature Report 下发到设备的休眠唤醒后主机不会主动重发这些配置设备固件如果只在枚举时初始化一次就会一直停留在错误的配置状态。解决设备固件里把配置信息持久化保存每次枚举成功后在 Set Report 回调里更新配置唤醒后主动对比上次保存的配置不一致就重新加载。涉及传感器采样率、分辨率倍率这类运行时配置的要同时实现 Feature Report 的 Get/Set 路径并保证设备在总线复位后能恢复到默认枚举配置。这类坑在调试时最容易忽略因为正常插拔永远复现不出来只有不间断运行几小时后才会暴露。5.6 没有总线分析仪时的三层排查法没有逻辑分析仪也能排查大部分问题。我的排查顺序是先看系统层是否识别再看驱动层解析出的报告值最后对比自己发的原始数据。Linux 下可以用usbhid的内核日志和hidraw设备节点来验证# 查看内核是否识别到 HID 设备以及描述符解析结果 dmesg | grep -i hid # 列出 hidraw 设备节点确认设备枚举成功 ls /dev/hidraw* # 用 hexdump 从 hidraw 读取原始报告确认设备是否在发送数据 sudo cat /dev/hidraw0 | hexdump -C正常工作的设备在cat /dev/hidraw0后不会有输出因为报告只有事件产生时才发送如果有连续输出说明报告里某个字段没有处于空闲态可能是有数据在不停刷新。收到报告后用第 6 章的脚本做对照检查能快速定位 Usage 映射错误。如果手头有 Windows 机器设备管理器里看“符合 HID 标准的用户控制设备”也是有效的旁证这类设备节点出现后按键仍然无效问题十有八九出在报告描述符本身。6. 把一个日常习惯固化成脚本描述符对表检查法我的日常工作流里最后一步是把描述符里用到的每一个 Usage Page 和 Usage ID 自动过一遍 1.3 的对照表。这个过程完全可以脚本化最简单的版本只需要一个 Python 脚本。它的核心不是复杂算法而是把规范里合法的 Page 和用法整理成数据然后逐项校验描述符# 简易 HID Usage 对照检查脚本 # 实际项目里我会把 1.3 的键盘/Consumer/Button 表导出成 JSON这里给出核心思路 known_pages { 0x01: Generic Desktop Page, 0x02: Simulation Controls Page, 0x03: VR Controls Page, 0x04: Sport Controls Page, 0x05: Game Controls Page, 0x06: Generic Device Controls Page, 0x07: Keyboard/Keypad Page, 0x08: LED Page, 0x09: Button Page, 0x0A: Ordinal Page, 0x0B: Telephony Device Page, 0x0C: Consumer Page, 0x0D: Digitizer Page, 0x0F: Physical Interface Device Page, 0x12: Game Player Page, } def check_usage(page, usage): # 先检查 Page 是否存在 if page not in known_pages: return f警告Page 0x{page:02X} 不在常见表内确认是否为厂商自定义页 # 实际工程中这里还会查每个 Page 下的 usage 白名单 return f正常Page 0x{page:02X} ({known_pages[page]}) 已收录继续检查 Usage 0x{usage:02X} # 模拟从描述符里提出来的一组 (Page, Usage) 值 desc_usages [(0x01, 0x06), (0x07, 0x04), (0x0C, 0xE9), (0xFF, 0x01)] for page, usage in desc_usages: print(check_usage(page, usage))这个脚本的参数含义很直白page是描述符里全局段声明的 Usage Pageusage是当前控件的 Usage ID。工程化使用时会直接把 1.3 里的键盘表、Button 表、Consumer 表导出成 JSON 文件再配合一个报告描述符解析器把字节流里的每个 Usage 提取出来自动对表。脚本跑完输出两类结果一类是“不在常见表内”的警告说明该值落在厂商自定义区间另一类是“已收录”直接对应到 1.3 的具体章节。保存一个约定每次写新描述符编译前先跑一遍这个脚本再联机验证。从那以后我每次接一块新的输入设备固件都会花十分钟把对应 Page 的 usage 白名单导进脚本让它在编译前自动扫一遍。这个动作救过我很多次尤其是熬夜调新板子的深夜总线抓包看到的是一堆十六进制脚本直接告诉你“这个值不是合法的 Volume Increase”比事后翻设备管理器错报省心得多。希望帮到你。本文还有配套的精品资源点击获取