1. 前情回顾

上篇主要讲了键盘报文的分类与格式,并留下了一个问题:那主机为什么知道我这些报文的格式?那肯定是主机要提前知道我们发的报文的格式,那么问题就变成了:在发送报文前我们要怎么通知主机,让它知道我们报文的格式。

这篇将回答这个问题,主机如何知道键盘报文的格式。答案就是键盘将发送HID report descriptor(HID报文描述符)给主机,主机根据HID描述符就知道键盘的报文格式。

2. HID 报文描述符简介

我们先理解一下逻辑:

(1) 键盘通过发送报文描述符给主机,告诉主机它后面发出来的报文的格式

(2) 主机通过解析报文描述符,从而知道了键盘后面要发的报文的格式

(3) 键盘发送报文给主机

(4) 主机已经知道了报文的格式,收到报文后根据报文的内容做出相应的动作

这里隐含了一个问题:键盘发送的报文描述符,主机为什么能解析?

因为这个报文描述符是USB协会定义的,所有的主机都支持USB协会定义的这个描述符。从这也能感受到USB协会的强大,所有的主机都支持USB定义的HID报文描述符!!!这里的主机包括:Windows主机,macOS主机,Android主机等,总之是所有主机都支持。

于是我们的问题变成,怎么按照我们的报文格式写出对应的HID报文描述符?那自然就是要仔细阅读下面这两篇HID报文描述符相关的文档:

(1) HID描述符介绍: Microsoft Word - HID1_11.doc (usb.org)

(2) HID Usage表:https://usb.org/sites/default/files/hut1_22.pdf

对于初学者来说,直接上手这两篇文档很不友好,因为这两篇文档看起来比较吃力。当然功力扎实的朋友,直接从这两篇文档入手也是可以的。

为了让初学都抓住主干,我决定按我的理解给各位来个简单的入门。

3. HID报文描述符的结构

0 报文描述符 描述
1 Usage Page (0x01), Generic Desktop Usage Page
2 Usage (0x06), Keyboard Usage
3 Collection (Application), 集合开始
4 Report Id(1), Report Id, 集合的报文都必须有一个report id
5 Report Size (8), 报文的大小,它的单位是一个bit。这里为8表示1个字节
6 Report Count (4), 报文的个数,上面的报文大小为一个字节,这里个数为4,就表示整个报文的长度为4个字节
7 Usage Page (7), 下面的报文usage在哪个usage page
8 Usage Minimum (0), usage的最小值
9 Usage Maximum (0xDD), usage的最大值
10 Logical Minimum (0), 逻辑的最小值
11 Logical Maximum (0xFF), 逻辑的最大值
12 Input (Data, Variable, Absolute), 报文的类型,键盘是把数据上报给主机,当然是input报文。
13 End Collection 集合结束

以上就是HID报文描述符的结构,基本上一个HID报文描述符都必须包含以上的这些元素。但我估计初学都看完还是一头雾水,下面我将带大家自上而下理解这个报文。

3.1 HID TLC 描述

第1、2、3行组成一个TLC(Top Level Collection), 这个TLC是用来让主机加载相应的驱动的。也就是说主机收到前3行的数据后,它会在设备管理器里生成一个设备结点,并把键盘的驱动挂到这个结点上。于是我们在主机上会看到下面中红框圈起来的结点,而且结点上已经加载了一个键盘驱动。是不是很简单,用3行描述符就可以让主机生成一个设备结点,并加载一个驱动到这个结点上。

以这个例子为例,主机已经创建了键盘结点并加载了键盘驱动,那么以后收到report id为1的报文,都由这个结点的键盘驱动来进行解析。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KPpJPJFx-1635581808785)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\image-20211030115435849.png)]

稍微拓展一下,如果我要让主机生成一个鼠标的设备结点,那要怎么写描述符。根据(https://usb.org/sites/default/files/hut1_22.pdf)第30页的描述。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-00mJZ9Xy-1635581808787)(C:\Users\86180\AppData\Roaming\Typora\typora-user-images\image-20211030132857916.png)]

鼠标的HID TLC描述如下表,我们只需要把usage page的值改为1, usage改为2就可以了。

0 报文描述符 描述
1 Usage Page (0x01), Generic Desktop Usage Page
2 Usage (0x02), Mouse Usage
3 Collection (Application), 集合开始

所以如果我们要写一个写一个TLC, 重点就是要学会查表。 所有的HID usage page/usage都可以在(https://usb.org/sites/default/files/hut1_22.pdf)查到。比如我们要查一个keypad的TLC, 按下面的步骤来:

(1) 查找到"usage name"为keypad, 然后"usage types" 为CA的

(2) 然后我们找到Generic Desktop Page (0x01), Keypad(0x07)

(3) 写TLC

0 报文描述符 描述
1 Usage Page (0x01), Generic Desktop Usage Page
2 Usage (0x07), KeypadUsage
3 Collection (Application), 集合开始

3.2 HID Report Id

在报文描述符的第4行有一个report id, 这个report id就代表这一组的报文。 上面的报文report id为1, 主机如果收到report id是1的话,它就知道它收到的报文是一个键盘的report. 总之一句话,report id跟这个TLC(或是说这个报文)是绑定在一起的。

所以键盘发给主机的内容除了报文,还需要发给主机一个report id. 以上一篇介绍的报文为例,按下按键A, 键盘发给主机的报文应该为:

其中Byte 0表示report id, Byte 2表示字母A的usage数值。

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5
0x01 0x00 0x04 0x00 0x00 0x00

3.3 Report Size / Report Count

一个报文里面可以包括多种数据类型,比如上篇说听qwerty key和modifier key. qwerty key我们用4个字节来表示,用4个字节就是表示说同一个报文里可以向主机发送4个qwerty key. 那怎么与report size 与 report count对应起来呢?

(1) Report Size: 一个qwerty key我们想用一个字节来表示,于是report size要设为8. 因为report size的单位是1 bit.

(2) Report Count: 一个report里有4个qwerty key, 因此report count就是4.

我们可以类比为, report size就是数组的元素的大小,report count就是数组元素的个数。

我们来举一反三: 如何设置modifer key的 report size与 report count?

先来回顾一下上篇的内容, modifer key我们希望按如下的格式.

bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
R Win R Shift R Alt R Ctrl L Win L Shift L Alt L Ctrl

因为一个modifier key使用一个bit来表示,因此report size设置为1。 总共有8个modifier key, 因此report count设置为8.

3.4 Usage Min / Usage Max

在说usage之前,需要搞清楚usage与usage page的关系。 一个usage page里面有很多个usage, 不同的usage page里面的usage的值是可以相同的。因此在谈到usage的时候,要先指定usage page, 不然主机无法解析这个usage.

上表的第7-9行,就是用来表示usage. 首先它先指定usage page(0x07). 通过查表知道,它属于keyboard/keypad的page. (https://usb.org/sites/default/files/hut1_22.pdf)第82页。

然后第8-9行指定了usage的最小值与最大值,这有什么用?

7 Usage Page (7), 下面的报文usage在哪个usage page
8 Usage Minimum (0), usage的最小值
9 Usage Maximum (0xDD), usage的最大值

在上一篇里提到,我们用4个字节来表示4个qwerty key, 每个字节可用来表示一个qwerty key, 也就是每一个字节的值就是一个qwerty key的usage的值。 这里的Usage Minimum/Usage Maximum就表示这每一个字节的值的范围,即它的有效值是 0 - 0xDD.

为什么把值设定为0 - 0xDD呢? 查表知道, 0x00 - 0xDD已经能表示所以键盘的qwerty key的usage值了。

3.5 Logic Min / Logic Max

上表的第10-11行,表示每个字节的范围,这个可以和Usage Minimum/Usage Maximum设置成一样,即0 - 0xDD。但设置为0 - 0xFF, 因为一个字节可以表示的最大范围就是0 - 0xFF。

10 Logical Minimum (0), 逻辑的最小值
11 Logical Maximum (0xFF), 逻辑的最大值

3.6 Data Type

上表第12行用来表示报文数据的类型,键盘的报文数据有modifer key和qwerty key, 这两种数据都是键盘要发送给主机的,因此是input报文。如果是主机发给键盘的,而是output报文。另外qwerty key和modifier是用无符号数来表示的,因此是absolute属性。

12 Input (Data, Variable, Absolute), 报文的类型,键盘是把数据上报给主机,当然是input报文。

3.7 End Collection

一个集合的结束必须要加上end collection, 它与上表的第3行的collection必须是成对出现的。

4. 构建键盘的HID报文描述符

有了上面的知识基础,我们就可以来构建键盘的报文描述符了。我们希望编写一个HID报文描述符,让主机解析我们的报文格式如下:

Byte 0 Byte 1 Byte 2 - 5
Report Id Modifier key Qwerty Key

Modifier key格式如下:

bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
R Win R Shift R Alt R Ctrl L Win L Shift L Alt L Ctrl

4.1 编写一个键盘的TLC

通过文档(https://usb.org/sites/default/files/hut1_22.pdf)查找到键盘的usage page为0x01, usage为0x06, 于是TLC的描述符如下:

0 报文描述符 描述
1 Usage Page (0x01), Generic Desktop Usage Page
2 Usage (0x06), Keyboard Usage
3 Collection (Application), 集合开始
4 Report Id(1) 键盘报文的report id
5 End Collection 集合结束

4.2 编写Modifier key描述符

(1) Report Size: 每个modifier key用一个bit来表示,因此report size为1.

(2) Report Count: 总共有8个modifier key,因此report count为8

(3) Usage Page: 0x07, Usage Minimum: 0xE0, Usage Maximum: 0xE7

通过文档(https://usb.org/sites/default/files/hut1_22.pdf) 搜索“LeftControl” 可以查到8个modifer key在usage page 7, usage 范围0xE0 - 0xE7.

(4) Logic Minimum: 0, Logic Maximum: 1

因为每个元素为1个bit, 最小值是0, 最大值是1。

于是得到modifier key的描述符如下:

0 报文描述符 描述
1 Report Size (1), 一个报文元素的大小,它的单位是一个bit
2 Report Count (8), 报文的个数,上面的报文大小为一个字节,这里个数为8,就表示整个报文的长度为8个bit
3 Usage Page (7), 下面的报文usage在哪个usage page
4 Usage Minimum (0xE0), usage的最小值
5 Usage Maximum (0xE7), usage的最大值
6 Logical Minimum (0), 逻辑的最小值
7 Logical Maximum (1), 逻辑的最大值
8 Input (Data, Variable, Absolute), 数据类型为输入

4.3编写Qwerty key描述符

(1) Report Size: 每个qwerty key用一个byte来表示,因此report size为8.

(2) Report Count: 一个report里面总共有4个qwerty key,因此report count为4

(3) Usage Page: 0x07, Usage Minimum: 0x04, Usage Maximum: 0xDD

通过文档(https://usb.org/sites/default/files/hut1_22.pdf) 搜索“Keyboard a and A” 可以查到键盘qwerty key在usage page 7, usage 范围0x04 - 0xDD.

(4) Logic Minimum: 0, Logic Maximum: 0xFF

因为每个元素为1个byte, 最小值是0, 最大值是0xFF。

于是得到modifier key的描述符如下:

0 报文描述符 描述
1 Report Size (8), 一个报文元素的大小,它的单位是一个bit
2 Report Count (4), 报文的个数,上面的报文大小为一个字节,这里个数为4,就表示整个报文的长度为4个字节
3 Usage Page (7), 下面的报文usage在哪个usage page
4 Usage Minimum (0x04), usage的最小值
5 Usage Maximum (0xDD), usage的最大值
6 Logical Minimum (0), 逻辑的最小值
7 Logical Maximum (0xFF), 逻辑的最大值
8 Input (Data, Variable, Absolute), 数据类型为输入

4.4 整合所有的描述符

把上面的TLC 描述符, modifer key描述符, qwerty的描述符组合起来如下:

0 报文描述符 描述
1 Usage Page (0x01), Generic Desktop Usage Page
2 Usage (0x06), Keyboard Usage
3 Collection (Application), 集合开始
4 Report Id(1) 键盘报文的report id
5 Report Size (1), 一个报文元素的大小,它的单位是一个bit
6 Report Count (8), 报文的个数,上面的报文大小为一个字节,这里个数为8,就表示整个报文的长度为8个bit
7 Usage Page (7), 下面的报文usage在哪个usage page
8 Usage Minimum (0xE0), usage的最小值
9 Usage Maximum (0xE7), usage的最大值
10 Logical Minimum (0), 逻辑的最小值
11 Logical Maximum (1), 逻辑的最大值
12 Input (Data, Variable, Absolute), 数据类型为输入
13 Report Size (8), 一个报文元素的大小,它的单位是一个bit
14 Report Count (4), 报文的个数,上面的报文大小为一个字节,这里个数为8,就表示整个报文的长度为8个bit
15 Usage Page (7), 下面的报文usage在哪个usage page
16 Usage Minimum (0x04), usage的最小值
17 Usage Maximum (0xDD), usage的最大值
18 Logical Minimum (0), 逻辑的最小值
19 Logical Maximum (0xFF), 逻辑的最大值
20 Input (Data, Variable, Absolute), 数据类型为输入
21 End Collection 集合结束

5. 描述符宏

键盘要发给主机的描述符是应该是一些整形数据才对,怎么上面的描述符是一些字符串加数字呢?

这主要是为了让大家好容易读与理解,比如说看到Report Id(1),就知道希望表示report id为1.其实Report Id是一个宏,最终发给主机的时候需要把这些宏展开的。

描述符宏 展开宏后的值
Report Id(1) 0x85, 0x01,
Report Size (1), 0x75, 0x01
Report Count (8), 0x95, 0x08
Usage Page (7), 0x06, 0x07
Usage Minimum (0xE0), 0x19, 0xE0
Usage Maximum (0xE7), 0x29, 0xE8
Logical Minimum (0), 0x15, 0x00
Logical Maximum (1), 0x25, 0x01
Input (Data, Variable, Absolute), 0x81, 0x00

我们在这篇最前面提到要看两篇文档,其中一篇 Microsoft Word - HID1_11.doc (usb.org)。 里面主要就是跟你讲report id, report count这些表要怎么表示。对于我们初学都来说,只要有像“Report Id, Report Count”这些宏可以使用,那就撇开这篇HID文档的介绍了。当然如果你要深入的话,肯定是要把这个文档啃几遍的。

这里我把一些常用的HID描述符的宏列在这里方便大家使用。

#define Physical                        (0x00U)
#define Undefined                       (0x00U)
#define Application                     (0x01U)
#define Logical                         (0x02U)#define Data_Arr_Abs                    (0x00U)
#define Const_Arr_Abs                   (0x01U)
#define Data_Var_Abs                    (0x02U)
#define Const_Var_Abs                   (0x03U)
#define Data_Var_Rel                    (0x06U)
#define Data_Var_Abs_Null               (0x42U)
#define BuffBytes                       (0x01U)#define HID_REPORT_ID(a)                0x85U,(a)
#define HID_USAGE(a)                    0x09U,(a)
#define HID_USAGE_SENSOR_DATA(a,b)      (a)|(b)     #define HID_COLLECTION(a)               0xA1U,(a)
#define HID_REPORT_SIZE(a)              0x75U,(a)
#define HID_REPORT_COUNT(a)             0x95U,(a)
#define HID_REPORT_COUNT_16(a,b)        0x96U,(a),(b)
#define HID_UNIT_EXPONENT(a)            0x55U,(a)
#define HID_UNIT(a)                     0x65U,(a)#define HID_USAGE_8(a)                  0x09U,(a)
#define HID_USAGE_16(a,b)               0x0AU,(a),(b)#define HID_USAGE_PAGE_8(a)             0x05U,(a)
#define HID_USAGE_PAGE_16(a,b)          0x06U,(a),(b)#define HID_USAGE_MIN_8(a)              0x19U,(a)
#define HID_USAGE_MIN_16(a,b)           0x1AU,(a),(b)#define HID_USAGE_MAX_8(a)              0x29U,(a)
#define HID_USAGE_MAX_16(a,b)           0x2AU,(a),(b)#define HID_LOGICAL_MIN_8(a)            0x15U,(a)
#define HID_LOGICAL_MIN_16(a,b)         0x16U,(a),(b)
#define HID_LOGICAL_MIN_32(a,b,c,d)     0x17U,(a),(b),(c),(d)#define HID_LOGICAL_MAX_8(a)            0x25U,(a)
#define HID_LOGICAL_MAX_16(a,b)         0x26U,(a),(b)
#define HID_LOGICAL_MAX_32(a,b,c,d)     0x27U,(a),(b),(c),(d)#define HID_PHYSICAL_MIN_8(a)           0x35U,(a)
#define HID_PHYSICAL_MIN_16(a,b)        0x36U,(a),(b)
#define HID_PHYSICAL_MIN_32(a,b,c,d)    0x37U,(a),(b),(c),(d)#define HID_PHYSICAL_MAX_8(a)           0x45U,(a)
#define HID_PHYSICAL_MAX_16(a,b)        0x46U,(a),(b)
#define HID_PHYSICAL_MAX_32(a,b,c,d)    0x47U,(a),(b),(c),(d)#define HID_INPUT_8(a)                  0x81U,(a)
#define HID_INPUT_16(a,b)               0x82U,(a),(b)
#define HID_INPUT_32(a,b,c,d)           0x83U,(a),(b),(c),(d)#define HID_OUTPUT_8(a)                 0x91U,(a)
#define HID_OUTPUT_16(a,b)              0x92U,(a),(b)
#define HID_OUTPUT_32(a,b,c,d)          0x93U,(a),(b),(c),(d)#define HID_FEATURE_8(a)                0xB1U,(a)
#define HID_FEATURE_16(a,b)             0xB2U,(a),(b)
#define HID_FEATURE_32(a,b,c,d)         0xB3U,(a),(b),(c),(d)#define HID_END_COLLECTION              0xC0U

6. 总结

本篇主要讲了HID报文描述符,以及如何根据报文的格式编写报文描述符。这只是对HID报文描述符的入门,还有一些问题没有谈到。虽然这篇是入门篇,但至少看完这篇已经能自己编写描述符了,或者说已经能达到使用报文描述符的程度了。

下篇将继续深入报文描述符,讲一些本篇还没有讲到的一些点。


欢迎大家来评论与指正,你的点赞与评论将有助于作者改善文章质量,并继续前行

DIY蓝牙键盘(2) - 理解HID报文描述符相关推荐

  1. DIY蓝牙键盘(1) - 理解 键盘报文

    DIY蓝牙键盘(1) - 理解键盘报文 1. 键盘报文体验 一个键盘对于用户的体验是,用户按按键A他能看到字母A会在主机上显示出来.那这是如何实现的? 其实很简单,只要键盘发送下面的两个报文给主机,字 ...

  2. HID报表描述符(目前最全的解析,也是USB最复杂的描述符)

    说一下为什么写这篇文章,主要是最近在做关于USB-HID设备的描述符,看到关于HID报表描述符的解析有点少,自己看了下,后续还会发布还有关于USB的各种解析,有兴趣可以看看,可以让你更加明白USB工作 ...

  3. STM32-USB学习系列(六):USB-HID键盘的实现以及键盘报文描述符的简介

    目录 一.整体步骤 二.USB 鼠标HID更改成键盘HID步骤 1.使用STM32CubeMX生成鼠标HID模版,并且进行修改 2.修改HID的接口描述符与报文描述符 3.修改USBD_HID_Set ...

  4. USB描述符(附加USB HID报告描述符 )

    USB描述符介绍 USB描述符是主机识别USB设备的依据,主机根据设备的描述符来加载相应的驱动 USB描述符的作用 USB描述符信息存储在USB设备中,在枚举过程中,USB主机会向USB设备发送Get ...

  5. HID 报告描述符的填充方式

    前言 本文结合博文:http://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ 以及实践,介绍HID 报告描述符的编写过程中 ...

  6. USB(六)-HID(报告描述符的结构实现)

    USB HID设备是通过报告(report)来传输数据的,报告有输入报告和输出报告.输入报告是USB设备发送给主机:输出报告是主机发送给USB设备. 报告描述符是用来描述一个报告的结构以及该报告里面的 ...

  7. 深入理解Linux进程描述符task_struct结构体

    进程是处于执行期的程序以及它所管理的资源(如打开的文件.挂起的信号.进程状态.地址空间等等)的总称.注意,程序并不是进程,实际上两个或多个进程不仅有可能执行同一程序,而且还有可能共享地址空间等资源. ...

  8. 蓝牙HID规范的报告描述符【另外一篇文章】

    SYD8801是一款低功耗高性能蓝牙低功耗SOC,集成了高性能2.4GHz射频收发机.32位ARM Cortex-M0处理器.128kB Flash存储器.以及丰富的数字接口.SYD8801片上集成了 ...

  9. 游戏手柄HID描述符

    游戏手柄HID描述符 详细介绍文档地址USB HID Report详解 以及游戏手柄HID报告描述符分析.pdf

最新文章

  1. Smarty中文手册,Smarty教程,Smarty模板的入门教材
  2. 上拉电阻和下拉电阻_硬件基础:下拉电阻和上拉电阻如何工作
  3. 【BZOJ】4259: 残缺的字符串 FFT
  4. 深入理解计算机系统:网络编程 上
  5. Lua语言学习-垃圾回收
  6. C++对自定义结构体变量排序
  7. ubuntu开启mysql日志记录
  8. 【转】03.Dicom 学习笔记-DICOM C-Get 消息服务
  9. 随机生成一定范围的随机数
  10. 跨平台 webapp 开发技术之 Hybrid App
  11. 超全树叶 叶子免抠元素素材网站整理
  12. 利用分析仪测量二极管的伏安特性
  13. python多条件求和_使用sumifs进行多条件求和
  14. DeepFool对抗算法_学习笔记
  15. 几乎所有食物的英文翻译
  16. xp装html5,WindowsXP系统如何安装IIS5.1
  17. srsLTE 源码分析 UE_09 随机接入 之PRACH发送
  18. PyTorch学习系列教程:构建一个深度学习模型需要哪几步?
  19. kubernetes云原生纪元:共享存储-PVPVC(上)
  20. x = x(x-1)

热门文章

  1. 计算机系统结构专业都学什么,计算机专业都学些什么?
  2. 红米8a的android版本是多少,小米Redmi 8A推送最新MIUI 11稳定版 基于安卓10大版本
  3. codeforces 628.div2
  4. c语言的积木编程,c语言入门第3节,掌握它就能随心所欲的编程了,自己造积木...
  5. WPF输入框双向绑定Decimal类等数据无法输入小数点
  6. 亚马逊商城最新品牌授权流程-2022年
  7. 热爱生活,更热爱代码
  8. 快速批量修改文件名字
  9. 软考高级系统架构设计师:响应式Web设计和主从复制机制的好处
  10. uos应用_统信 UOS 私有化应用商店解决方案发布,支持应用分发管理