核心概念:什么是 USB HID 命令?
首先要明确一个关键点:USB HID 并没有一个像网络协议那样标准化的“命令”列表。

它不像 Modbus 或 SPI 有固定的 0x03 读寄存器、0x06 写寄存器这样的操作码,相反,HID 的“命令”是通过一个数据包(称为 Report)来传递的,而这个数据包的格式和内容由设备自己定义。
- 设备端(固件):开发者设计一个数据结构(一个包含 8 个字节的数组),并规定每个字段的含义,第一个字节是按键码,第二个字节是鼠标 X 轴偏移,第三个字节是鼠标 Y 轴偏移。
- 主机端(驱动/应用程序):必须知道并遵守这个预先定义好的数据格式,当主机收到这个数据包时,它会按照预先的约定去解析每个字节。
当谈论“HID 命令”时,我们实际上是在谈论“自定义的 Report 格式”以及“如何通过这个格式来发送指令或数据”。
HID 的基石:Report 描述符
理解 Report 描述符是掌握 HID 命令的关键,它是设备向主机描述自己数据结构的“说明书”,主机通过解析这个描述符,才能知道:
- 设备有哪些功能(有 64 个按键,一个 X 轴,一个 Y 轴)。
- 每个功能的数据是什么类型(是按键(Usage Page 0x07)、鼠标(Usage Page 0x01)还是自定义数据(Usage Page 0xFF00))。
- 数据的格式(是数组、变量还是常量)。
- 数据的范围和逻辑最小/最大值。
一个典型的 HID 命令(数据包)的生成流程如下:

- 设计 Report 描述符:在设备固件中,开发者定义好 Report 描述符。
- 主机枚举并解析:当设备插入时,主机读取并解析这个 Report 描述符,生成一个“报告 ID”(Report ID)和“报告大小”的映射关系。
- 构建数据包:设备根据 Report 描述符的格式,填充一个数据数组。
- 发送数据包:设备通过 USB 端点(通常是端点 1 IN/OUT)将这个数据包发送给主机。
- 主机解析:主机收到数据包后,根据之前解析的 Report 描述符,正确地解读出数据。
HID 命令的实际形式与示例
让我们来看几个具体的例子,理解 HID 命令是如何工作的。
示例 1:最简单的自定义设备
假设我们想做一个设备,它只有一个功能:当主机发送一个 0xAA 命令时,设备上的 LED 亮;发送 0x55 时,LED 灭。
设备端(固件设计):
-
定义 Report 描述符: 这个设备需要一个输入报告(从设备到主机,通常用于状态反馈,我收到了命令”)和一个输出报告(从主机到设备,用于控制命令,点亮 LED”)。
(图片来源网络,侵删)一个简化的描述符可能是这样的(使用 C 语言风格描述):
// 输出报告: 1字节,用于接收主机的控制命令 0x06, 0xFF, 0xFF, // Usage Page (Vendor Defined 0xFF00) 0x09, 0x01, // Usage (Vendor Usage 1) 0x15, 0x00, // Logical Minimum (0) 0x26, 0xFF, 0x00, // Logical Maximum (255) 0x75, 0x08, // Report Size (8 bits) 0x95, 0x01, // Report Count (1) 0x09, 0x00, // Usage (Vendor Usage 0) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x09, 0x00, // Usage (Vendor Usage 0) 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) 0xC0, // End Collection
解读:
Usage Page 0xFF00:表示这是厂商自定义的功能。Output Report:定义了一个 8 位(1字节)的输出报告,这就是我们的“命令”入口,主机向这个报告写入0xAA或0x55。Input Report:定义了一个 8 位的输入报告,设备可以用来反馈状态,命令已收到”。
-
固件逻辑:
- 在固件的
USB_Setup()或中断服务程序中,检查是否有数据从主机的 Output Report 端点(通常是端点 1 OUT)到来。 - 如果收到了数据,读取这个 1 字节的数据。
- 如果数据是
0xAA,点亮 LED;如果是0x55,熄灭 LED。 - (可选)向主机的 Input Report 端点(端点 1 IN)发送一个
0x01,表示“操作成功”。
- 在固件的
主机端(应用程序设计,Python + hidapi):
import hid
# 1. 打开设备 (需要知道设备的 Vendor ID 和 Product ID)
device = hid.hid_open(0x1234, 0x0001) # 替换成你的 VID/PID
if not device:
print("设备未找到!")
exit()
# 2. 发送命令:点亮 LED
# hid_write 的第二个参数就是 Output Report 的数据
# 注意:有些设备可能需要一个 Report ID (通常是第一个字节)
# 假设我们的 Report ID 是 0
command_on = [0x00, 0xAA] # Report ID (0) + Command (0xAA)
hid_write(device, command_on)
# 3. 发送命令:熄灭 LED
command_off = [0x00, 0x55] # Report ID (0) + Command (0x55)
hid_write(device, command_off)
# 4. (可选) 读取设备反馈的数据
# hid_read 会从 Input Report 端点读取数据
data = hid_read(device, 64, 5000) # 读取最多64字节,超时5秒
if data:
print(f"从设备收到反馈: {data}")
hid_close(device)
在这个例子中,[0xAA] 和 [0x55] 就是我们的“HID 命令”。
示例 2:更复杂的命令(带 Report ID)
当一个设备有多个功能(同时处理键盘、鼠标和自定义命令)时,就需要使用 Report ID 来区分不同的报告。
假设一个设备:
- Report ID 1:键盘输入。
- Report ID 2:鼠标输入。
- Report ID 3:自定义控制命令。
主机发送命令给 Report ID 3:
数据包的结构会是:[Report ID] + [Command Data]
发送一个“读取设备版本号”的命令:
- Report ID:
0x03 - Command:
0x01(代表“读取版本号”) - 数据包:
[0x03, 0x01]
设备收到 [0x03, 0x01] 后,就知道这是发给“自定义控制”功能的“读取版本号”命令,然后执行相应操作,并通过 Input Report(Report ID 0x03)将版本号(如 0x01, 0x00 代表 V1.0)发回。
通用 HID (HID) vs. 自定义 HID (Vendor-defined)
| 特性 | 通用 HID (e.g., Keyboard, Mouse) | 自定义 HID (Vendor-defined) |
|---|---|---|
| Report 描述符 | 遵循 USB HID 规范中预定义的 Usage Page (如 0x01 Generic Desktop, 0x07 Keyboard)。 |
使用厂商自定义的 Usage Page (0xFF00 - 0xFFFF)。 |
| 驱动 | 操作系统自带通用 HID 驱动,无需安装额外驱动。 | 通常需要安装 INF 文件或使用通用 HID 驱动(如 hidapi, libusb)。 |
| 命令/数据 | 格式是标准化的,键盘扫描码是固定的。 | 格式完全由开发者自定义,非常灵活。 |
| 适用场景 | 标准人机交互设备。 | 工业控制、嵌入式系统、DIY 项目、数据采集等。 |
对于大多数开发者来说,自定义 HID 是更常用的选择,因为它给予了最大的自由度。
实用工具和库
-
分析工具:
- HIDusbfy: Windows 下的经典工具,可以实时监控 HID 通信,查看收发的 Report 数据。
- Wireshark + USBPcap: 可以抓取底层的 USB 数据包,非常强大。
- Linux
hidrd: 命令行工具,用于转换和解析 Report 描述符。
-
主机端开发库:
hidapi: 跨平台 (Windows, macOS, Linux, Android) 的 C/C++ 库,是开发 HID 应用的首选。- Python
hidapi: Python 封装的hidapi,使用非常方便。 - .NET
HidLibrary: .NET 平台下常用的库。 - Java
jinput/hid4java: Java 平台下的库。
- HID 命令的本质:不是固定的指令码,而是遵循设备自定义格式的数据包(Report)。
- 核心是 Report 描述符:它是设备与主机之间的“协议说明书”,定义了数据包的结构和含义。
- 双向通信:使用 Input Report(设备→主机)和 Output Report(主机→设备)进行数据交换。
- 灵活性:自定义 HID 极其灵活,允许你设计任何你需要的数据格式,是嵌入式与 PC 通信的强大工具。
- 开发流程:
- 设备端:编写固件,定义 Report 描述符,实现数据收发逻辑。
- 主机端:使用 HID 库(如
hidapi),根据设备的 Report 描述符来构建和解析数据包。
理解了这些,你就可以开始设计自己的 USB HID 设备和与之通信的应用程序了。
