日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/15 编程问答 66 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 (usb.org)

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

對于初學者來說,直接上手這兩篇文檔很不友好,因為這兩篇文檔看起來比較吃力。當然功力扎實的朋友,直接從這兩篇文檔入手也是可以的。

為了讓初學都抓住主干,我決定按我的理解給各位來個簡單的入門。

3. HID報文描述符的結構

0報文描述符描述
1Usage Page (0x01),Generic Desktop Usage Page
2Usage (0x06),Keyboard Usage
3Collection (Application),集合開始
4Report Id(1),Report Id, 集合的報文都必須有一個report id
5Report Size (8),報文的大小,它的單位是一個bit。這里為8表示1個字節
6Report Count (4),報文的個數,上面的報文大小為一個字節,這里個數為4,就表示整個報文的長度為4個字節
7Usage Page (7),下面的報文usage在哪個usage page
8Usage Minimum (0),usage的最小值
9Usage Maximum (0xDD),usage的最大值
10Logical Minimum (0),邏輯的最小值
11Logical Maximum (0xFF),邏輯的最大值
12Input (Data, Variable, Absolute),報文的類型,鍵盤是把數據上報給主機,當然是input報文。
13End 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報文描述符描述
1Usage Page (0x01),Generic Desktop Usage Page
2Usage (0x02),Mouse Usage
3Collection (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報文描述符描述
1Usage Page (0x01),Generic Desktop Usage Page
2Usage (0x07),KeypadUsage
3Collection (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 0Byte 1Byte 2Byte 3Byte 4Byte 5
0x010x000x040x000x000x00

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 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
R WinR ShiftR AltR CtrlL WinL ShiftL AltL 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的最小值與最大值,這有什么用?

7Usage Page (7),下面的報文usage在哪個usage page
8Usage Minimum (0),usage的最小值
9Usage 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。

10Logical Minimum (0),邏輯的最小值
11Logical Maximum (0xFF),邏輯的最大值

3.6 Data Type

上表第12行用來表示報文數據的類型,鍵盤的報文數據有modifer key和qwerty key, 這兩種數據都是鍵盤要發送給主機的,因此是input報文。如果是主機發給鍵盤的,而是output報文。另外qwerty key和modifier是用無符號數來表示的,因此是absolute屬性。

12Input (Data, Variable, Absolute),報文的類型,鍵盤是把數據上報給主機,當然是input報文。

3.7 End Collection

一個集合的結束必須要加上end collection, 它與上表的第3行的collection必須是成對出現的。

4. 構建鍵盤的HID報文描述符

有了上面的知識基礎,我們就可以來構建鍵盤的報文描述符了。我們希望編寫一個HID報文描述符,讓主機解析我們的報文格式如下:

Byte 0Byte 1Byte 2 - 5
Report IdModifier keyQwerty Key

Modifier key格式如下:

bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
R WinR ShiftR AltR CtrlL WinL ShiftL AltL Ctrl

4.1 編寫一個鍵盤的TLC

通過文檔(https://usb.org/sites/default/files/hut1_22.pdf)查找到鍵盤的usage page為0x01, usage為0x06, 于是TLC的描述符如下:

0報文描述符描述
1Usage Page (0x01),Generic Desktop Usage Page
2Usage (0x06),Keyboard Usage
3Collection (Application),集合開始
4Report Id(1)鍵盤報文的report id
5End 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報文描述符描述
1Report Size (1),一個報文元素的大小,它的單位是一個bit
2Report Count (8),報文的個數,上面的報文大小為一個字節,這里個數為8,就表示整個報文的長度為8個bit
3Usage Page (7),下面的報文usage在哪個usage page
4Usage Minimum (0xE0),usage的最小值
5Usage Maximum (0xE7),usage的最大值
6Logical Minimum (0),邏輯的最小值
7Logical Maximum (1),邏輯的最大值
8Input (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報文描述符描述
1Report Size (8),一個報文元素的大小,它的單位是一個bit
2Report Count (4),報文的個數,上面的報文大小為一個字節,這里個數為4,就表示整個報文的長度為4個字節
3Usage Page (7),下面的報文usage在哪個usage page
4Usage Minimum (0x04),usage的最小值
5Usage Maximum (0xDD),usage的最大值
6Logical Minimum (0),邏輯的最小值
7Logical Maximum (0xFF),邏輯的最大值
8Input (Data, Variable, Absolute),數據類型為輸入

4.4 整合所有的描述符

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

0報文描述符描述
1Usage Page (0x01),Generic Desktop Usage Page
2Usage (0x06),Keyboard Usage
3Collection (Application),集合開始
4Report Id(1)鍵盤報文的report id
5Report Size (1),一個報文元素的大小,它的單位是一個bit
6Report Count (8),報文的個數,上面的報文大小為一個字節,這里個數為8,就表示整個報文的長度為8個bit
7Usage Page (7),下面的報文usage在哪個usage page
8Usage Minimum (0xE0),usage的最小值
9Usage Maximum (0xE7),usage的最大值
10Logical Minimum (0),邏輯的最小值
11Logical Maximum (1),邏輯的最大值
12Input (Data, Variable, Absolute),數據類型為輸入
13Report Size (8),一個報文元素的大小,它的單位是一個bit
14Report Count (4),報文的個數,上面的報文大小為一個字節,這里個數為8,就表示整個報文的長度為8個bit
15Usage Page (7),下面的報文usage在哪個usage page
16Usage Minimum (0x04),usage的最小值
17Usage Maximum (0xDD),usage的最大值
18Logical Minimum (0),邏輯的最小值
19Logical Maximum (0xFF),邏輯的最大值
20Input (Data, Variable, Absolute),數據類型為輸入
21End 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报文描述符的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。