2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > DIY蓝牙键盘(2) - 理解HID报文描述符

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

时间:2023-12-24 01:32:06

相关推荐

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

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 ()

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

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

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

3. HID报文描述符的结构

以上就是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-1030115435849.png)]

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

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

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

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

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

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

(3) 写TLC

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数值。

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我们希望按如下的格式.

因为一个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. (/sites/default/files/hut1_22.pdf)第82页。

然后第8-9行指定了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。

3.6 Data Type

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

3.7 End Collection

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

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

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

Modifier key格式如下:

4.1 编写一个键盘的TLC

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

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

通过文档(/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的描述符如下:

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

通过文档(/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的描述符如下:

4.4 整合所有的描述符

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

5. 描述符宏

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

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

我们在这篇最前面提到要看两篇文档,其中一篇 Microsoft Word - HID1_11.doc ()。 里面主要就是跟你讲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报文描述符的入门,还有一些问题没有谈到。虽然这篇是入门篇,但至少看完这篇已经能自己编写描述符了,或者说已经能达到使用报文描述符的程度了。

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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。