HID报告描述符教程 手把手教你编写HID报告描述符
USB HID报告描述符是USB主机可以从USB设备请求获取的描述符之一。HID 设备使用报告向主机发送数据,HID报告描述符告诉主机如何解释数据。这里我们将尝试向您展示如何编写这些描述符。
什么是USB HID 报告描述符?
HID 协议使设备的实现非常简单。设备定义它们的数据包,然后向主机提供一个“HID报告描述符”。HID报告描述符是一个硬编码的字节数组,用于描述设备的数据包。这包括:设备支持多少数据包,数据包有多大,以及数据包中每个字节和位的用途。
例如,带有计算器程序按钮的键盘可以告诉主机按钮的按下/释放状态存储为数据包ID为4的第 6 个字节中的第 2 位(注意:这些位置仅用于说明,并且是特定于设备的)。设备通常将 HID报告描述符存储在 ROM 中,并且不需要本质上理解或解析 HID 描述符。当今市场上的一些鼠标和键盘硬件仅使用 8 位 CPU 实现。
HID报告描述符入门编写
作为一个简单的起点,让我们制作一个标准鼠标。只需三个按钮,并在 X 和 Y 轴上移动。所以我们想发送有关按钮和移动的数据。用一位来表示每个按钮,用一个字节将一个轴上的运动表示为有符号整数。所以我们可以说我们希望数据结构看起来像这样。
然后我们可以说我们在 C 中的数据结构看起来像
struct mouse_report_t
{
uint8_t buttons;
int8_t x;
int8_t y;
}
所以现在在我们的描述符中,我们的第一项必须描述按钮,其中三个
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)
每个按钮状态由一个位表示,0 或 1
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
这些有三位
REPORT_COUNT (3)
REPORT_SIZE (1)
将此可变数据发送到主机
INPUT (Data,Var,Abs)
最终结果看起来像
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (3)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
这将代表按键
但是五个无用的填充位呢?
REPORT_COUNT (1)
REPORT_SIZE (5)
INPUT (Cnst,Var,Abs)
现在我们进行 X 轴运动
USAGE_PAGE (Generic Desktop)
USAGE (X)
我们希望它是一个占用一个字节的有符号整数,因此它的值介于 -127 和 +127 之间(实际上是 -128 和 +127,但我想保持平衡)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
我们希望它占用一个 8 位的整个字节
REPORT_SIZE (8)
REPORT_COUNT (1)
并将其作为可变相对坐标发送到计算机
INPUT (Data,Var,Rel)
你最终会得到这样的东西来代表 X 轴运动
USAGE_PAGE (Generic Desktop)
USAGE (X)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
INPUT (Data,Var,Rel)
Y轴呢?你可以试试
USAGE_PAGE (Generic Desktop)
USAGE (X)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
INPUT (Data,Var,Rel)
USAGE_PAGE (Generic Desktop)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
这会起作用,但为了节省内存,我们可以这样做
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (2)
INPUT (Data,Var,Rel)
所以你所有的数据最终看起来像
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (3)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
REPORT_COUNT (1)
REPORT_SIZE (5)
INPUT (Cnst,Var,Abs)
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (2)
INPUT (Data,Var,Rel
啊但是我们还没做完,为了让电脑知道这是鼠标,我们做
USAGE_PAGE (Generic Desktop)
USAGE (Mouse)
COLLECTION (Application)
USAGE (Pointer)
COLLECTION (Physical)
... 我们上面写的内容
END COLLECTION
END COLLECTION
所以最后,这是标准鼠标的 USB HID 报告描述符
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x03, // REPORT_COUNT (3)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
这实际上是 USB HID 文档提供的示例描述符,您也可以将其作为 HID 工具提供的示例。
以上就是我们完成的鼠标报告描述符内容编写,现在我们将遇到一些可能有疑问的概念,并给出说明和解释:
USAGE_PAGE
我认为文档中没有很好地解释一件事,USAGE、USAGE_PAGE、USAGE_MINIMUM 和 USAGE_MAXIMUM。在描述符中,您首先设置一个 USAGE_PAGE,并且某些 USAGE 可用。在鼠标示例中,USAGE_PAGE(通用桌面)允许您使用 USAGE(鼠标),当使用页面更改为 USAGE_PAGE(按钮)时,USAGE_MINIMUM 和 USAGE_MAXIMUM 允许您指定按钮,然后才能使用 USAGE (X) 和 USAGE (Y),使用页面更改回 USAGE_PAGE(通用桌面)。使用页面就像一个命名空间,更改使用页面会影响可用的“使用”。阅读名为“HID 使用表”的文档以获取更多信息。
Collections
阅读有关官方正确使用集合的文档。用我自己的话来说,集合可以用来组织你的数据,例如,一个键盘可能有一个内置的触摸板,那么键盘的数据应该保存在一个应用程序集合中,而触摸板数据保存在另一个应用程序集合中。我们可以为每个集合分配一个“报告 ID”,稍后我将向您展示。
嘿,这是你可以做的,通过将“USAGE (Mouse)”变成“USAGE (Gamepad)”,你可以让计算机认为它是一个带有一个操纵杆和 3 个按钮的游戏手柄。将 Playstation 2 控制器转换为 USB 游戏手柄怎么样?控制器有 16 个按钮和两个拇指棒,所以我们希望数据看起来像
所以我们的数据结构看起来像
struct gamepad_report_t
{
uint16_t buttons;
int8_t left_x;
int8_t left_y;
int8_t right_x;
int8_t right_y;
}
我们让电脑明白它是一个游戏手柄
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
...
END COLLECTION
END COLLECTION
对于按钮
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 16)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (16)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
用于四个拇指杆轴
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
USAGE (Z)
USAGE (Rx)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (4)
INPUT (Data,Var,Abs)
注意:Z 用于表示右摇杆的 X 轴,Rx 用于表示右摇杆的 Y 轴。这没有任何意义,但这就是大多数现有 USB 游戏手柄的工作方式。我已经使用 Battlefield Bad Company 2 对此进行了测试,它可以工作。
注意:对于像操纵杆这样的东西使用“绝对”,而对于像鼠标这样的东西使用“相对”。
所以现在你最终得到
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 16)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (16)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
USAGE (Z)
USAGE (Rx)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (4)
INPUT (Data,Var,Abs)
END COLLECTION
END COLLECTION
嘿,怎么样?这里是Collection派用场的地方
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (1)
...
END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (2)
...
END COLLECTION
END COLLECTION
填写数据区域,你最终得到
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (1)
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 16)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (16)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
USAGE (Z)
USAGE (Rx)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (4)
INPUT (Data,Var,Abs)
END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (2)
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 16)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (16)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
USAGE (Z)
USAGE (Rx)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (4)
INPUT (Data,Var,Abs)
END COLLECTION
END COLLECTION
这非常重要:您必须更改数据结构以包含ReportId
struct multiplayer_gamepad_report_t
{
uint8_t report_id;
uint16_t buttons;
int8_t left_x;
int8_t left_y;
int8_t right_x;
int8_t right_y;
}
必须在将数据发送到计算机之前手动设置ReportId,以便计算机了解数据属于哪个player数据包。
multiplayer_gamepad_report_t player1_report;
multiplayer_gamepad_report_t player2_report;
player1_report.report_id = 1;
player2_report.report_id = 2;
您还可以使用集合和报告 ID 来制作复合设备。到目前为止,我已经向您展示了键盘、鼠标和游戏手柄。这里描述了一个复合设备,它是一个键盘、鼠标和两个玩家游戏手柄。
SAGE_PAGE (Generic Desktop)
USAGE (Keyboard)
COLLECTION (Application)
REPORT_ID (1)
...
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Mouse)
COLLECTION (Application)
USAGE (Pointer)
COLLECTION (Physical)
REPORT_ID (2)
...
END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (3)
...
END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
COLLECTION (Physical)
REPORT_ID (4)
...
END COLLECTION
END COLLECTION
当然,您的数据结构与添加的报告 ID。
struct keyboard_report_t
{
uint8_t report_id;
uint8_t modifier;
uint8_t reserved;
uint8_t keycode[6];
}
struct mouse_report_t
{
uint8_t report_id;
uint8_t buttons;
int8_t x;
int8_t y;
}
struct gamepad_report_t
{
uint8_t report_id;
uint16_t buttons;
int8_t left_x;
int8_t left_y;
int8_t right_x;
int8_t right_y;
}
但是由于我们更改了数据结构,您的设备不再支持启动协议,因此您不需要定义协议。因此,请确保相应地更改 usbconfig.h。
想看看这个在行动吗?将此示例项目加载到项目中,让 Windows 的“设备和打印机”向您展示!