USB HID 教學 #2 (轉載)

這篇是我讀 frank "USnooBie’s USB HID Report Descriptor Tutorial 1" 一文而寫的筆記,在此我要感謝 frank 的慷慨分享。
mouse 範例
我們將製作一個含有 3 個按鈕和 X, Y 軸移動 (movements) 的標準滑鼠。也就是說,我們要送按鈕跟 X, Y 軸移動的資料給 host。這會需要:一個 bit 代表一個按鈕,一個 byte 代表一個座標軸的移動,移動值是有符號的整數 (signed integer)。所以,我們需要像這樣的資料結構:
Bit 3-7Bit 2Bit 1Bit 0
Byte 0Padding BitsLeft ButtonMiddle ButtonRight Button
Byte 18 bit signed relative coordinate x
Byte 28 bit signed relative coordinate Y
在 C 語言中資料結構會是這樣:

1typedef struct{
2    uchar   buttonMask;
3    char    dx;
4    char    dy;
5}report_t;

在我們的 report descriptor 中,首先定義按鈕,總共 3 個按鈕:

1USAGE_PAGE (Button)
2USAGE_MINIMUM (Button 1)
3USAGE_MAXIMUM (Button 3)

按鈕狀態不是 0 就是 1:

1LOGICAL_MINIMUM (0)
2LOGICAL_MAXIMUM (1)

3 個按鈕總共 3 個位元:

1REPORT_COUNT (3)
2REPORT_SIZE (1)

這個是要送給 host 的變數資料:

1INPUT (Data,Var,Abs)

得到的結果為:

1USAGE_PAGE (Button)
2USAGE_MINIMUM (Button 1)
3USAGE_MAXIMUM (Button 3)
4LOGICAL_MINIMUM (0)
5LOGICAL_MAXIMUM (1)
6REPORT_COUNT (3)
7REPORT_SIZE (1)
8INPUT (Data,Var,Abs)

這些代表按鈕。
其它 5 個位元是填充位元 (padding bits):

1REPORT_COUNT (1)
2REPORT_SIZE (5)
3INPUT (Const,Var,Abs)

接著定義 X 軸的移動:

1USAGE_PAGE (Generic Desktop)
2USAGE (X)

我們要讓它當作一個 byte 的有符號整數,所以值域是 -127  到 +127:

1LOGICAL_MINIMUM (-127)
2LOGICAL_MAXIMUM (127)

佔 1 個 8 個位元的 byte:

1REPORT_SIZE (8)
2REPORT_COUNT (1)

然後把它當作要送給 host 的相對座標變數:

1INPUT (Data,Var,Rel)

於是你得到底下用來代表 X 軸的結果:

1USAGE_PAGE (Generic Desktop)
2USAGE (X)
3LOGICAL_MINIMUM (-127)
4LOGICAL_MAXIMUM (127)
5REPORT_SIZE (8)
6REPORT_COUNT (1)
7INPUT (Data,Var,Rel)

那麼 Y 軸呢?你可以這樣寫:

01USAGE_PAGE (Generic Desktop)
02USAGE (X)
03LOGICAL_MINIMUM (-127)
04LOGICAL_MAXIMUM (127)
05REPORT_SIZE (8)
06REPORT_COUNT (1)
07INPUT (Data,Var,Rel)
08USAGE_PAGE (Generic Desktop)
09USAGE (Y)
10LOGICAL_MINIMUM (-127)
11LOGICAL_MAXIMUM (127)
12REPORT_SIZE (8)
13REPORT_COUNT (1)
14INPUT (Data,Var,Rel)

這樣做是可以,不過為了節省記憶體,可以用這個替代方法:

1USAGE_PAGE (Generic Desktop)
2USAGE (X)
3USAGE (Y)
4LOGICAL_MINIMUM (-127)
5LOGICAL_MAXIMUM (127)
6REPORT_SIZE (8)
7REPORT_COUNT (2)
8INPUT (Data,Var,Rel)

你得到底下的結果,這些代表 3 個按鈕和 X, Y 軸移動的資料:

01USAGE_PAGE (Button)
02USAGE_MINIMUM (Button 1)
03USAGE_MAXIMUM (Button 3)
04LOGICAL_MINIMUM (0)
05LOGICAL_MAXIMUM (1)
06REPORT_COUNT (3)
07REPORT_SIZE (1)
08INPUT (Data,Var,Abs)
09REPORT_COUNT (1)
10REPORT_SIZE (5)
11INPUT (Cnst,Var,Abs)
12USAGE_PAGE (Generic Desktop)
13USAGE (X)
14USAGE (Y)
15LOGICAL_MINIMUM (-127)
16LOGICAL_MAXIMUM (127)
17REPORT_SIZE (8)
18REPORT_COUNT (2)
19INPUT (Data,Var,Rel)

不過我們還沒完成,為了讓 host 知道這是滑鼠,我們得這麼做:

1USAGE_PAGE (Generic Desktop)
2USAGE (Mouse)
3COLLECTION (Application)
4  USAGE (Pointer)
5  COLLECTION (Physical)
6   ... 把我們已經寫好的東西放在這
7  END_COLLECTION
8END_COLLECTION

最後,這就是標準滑鼠的 USB HID report descriptor:

010x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
020x09, 0x02,                    // USAGE (Mouse)
030xa1, 0x01,                    // COLLECTION (Application)
040x09, 0x01,                    //   USAGE (Pointer)
050xa1, 0x00,                    //   COLLECTION (Physical)
060x05, 0x09,                    //     USAGE_PAGE (Button)
070x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
080x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
090x15, 0x00,                    //     LOGICAL_MINIMUM (0)
100x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
110x95, 0x03,                    //     REPORT_COUNT (3)
120x75, 0x01,                    //     REPORT_SIZE (1)
130x81, 0x02,                    //     INPUT (Data,Var,Abs)
140x95, 0x01,                    //     REPORT_COUNT (1)
150x75, 0x05,                    //     REPORT_SIZE (5)
160x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
170x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
180x09, 0x30,                    //     USAGE (X)
190x09, 0x31,                    //     USAGE (Y)
200x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
210x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
220x75, 0x08,                    //     REPORT_SIZE (8)
230x95, 0x02,                    //     REPORT_COUNT (2)
240x81, 0x06,                    //     INPUT (Data,Var,Rel)
250xc0,                          //   END_COLLECTION
260xc0                           // END_COLLECTION

這是用 HID Descriptor Tool 產生的,HID Descriptor Tool 免除了翻閱 HID Usage Tables 查詢代碼的麻煩。事實上,HID Descriptor Tool 本身就有提供這個滑鼠範例,你只要打開 mouse.hid 把它存成 .h 檔就可以得到上面的 report descriptor。
補充說明
底下補充前面不足之處。
在 report descriptor 中,首先第一件事是告訴 host 這是一個滑鼠裝置,要達到這個目的,必須宣告 "USAGE (Mouse)",不過在這之前必須先切到 "USAGE (Mouse)" 所在的 Usage Page,查詢 HID Usage Tables (p.26) 顯示 Mouse Usage 是在 Generic Desktop 這個 Usage Page。一旦切到 Generic Desktop Usage Page 並指定好 Mouse Usage 後,當 host 拿到這個資訊時,它就知道要把這個裝置加到滑鼠列表。
所有 report descriptor 至少都要有一個 Application Collection,跟在 Application Collection 之後的 Usage 是用來指定 Collection 的一般功能 (general function)。在這個範例中,跟著的是 Pointer Usage,因為 Pointer Usage 也是列在 Generic Desktop Usage Page,所以不需要再指定一次 Generic Desktop Usage Page。
滑鼠上有 X 和 Y 軸的移動資料,根據 HID Device Class Definition  文件,Physical Collection 用來表示一個幾何點 (geometric point) 上的資料,因此這個範例把 X, Y 軸和 3 個按鈕通通集中放到 Physical Collection 裏。剩下的前面已經解釋過了。
範例程式
要找一個簡單的滑鼠,可以參考 V-USB 的 hid-mouse 這個範例:
hid-mouse 製作了一個含有 3 個按鈕, X, Y 軸以及滾輪移動 (movements) 的標準滑鼠,底下是它的 report descriptor:

01PROGMEM char usbHidReportDescriptor[52] = { /* USB report descriptor, size must match usbconfig.h */
02    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
03    0x09, 0x02,                    // USAGE (Mouse)
04    0xa1, 0x01,                    // COLLECTION (Application)
05    0x09, 0x01,                    //   USAGE (Pointer)
06    0xA1, 0x00,                    //   COLLECTION (Physical)
07    0x05, 0x09,                    //     USAGE_PAGE (Button)
08    0x19, 0x01,                    //     USAGE_MINIMUM
09    0x29, 0x03,                    //     USAGE_MAXIMUM
10    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
11    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
12    0x95, 0x03,                    //     REPORT_COUNT (3)
13    0x75, 0x01,                    //     REPORT_SIZE (1)
14    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
15    0x95, 0x01,                    //     REPORT_COUNT (1)
16    0x75, 0x05,                    //     REPORT_SIZE (5)
17    0x81, 0x03,                    //     INPUT (Const,Var,Abs)
18    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
19    0x09, 0x30,                    //     USAGE (X)
20    0x09, 0x31,                    //     USAGE (Y)
21    0x09, 0x38,                    //     USAGE (Wheel)
22    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
23    0x25, 0x7F,                    //     LOGICAL_MAXIMUM (127)
24    0x75, 0x08,                    //     REPORT_SIZE (8)
25    0x95, 0x03,                    //     REPORT_COUNT (3)
26    0x81, 0x06,                    //     INPUT (Data,Var,Rel)
27    0xC0,                          //   END_COLLECTION
28    0xC0,                          // END COLLECTION
29};

比較前面的 report decriptor,hid-mouse 的 report descriptor 只多了底下這行滾輪 (wheel) 的設定:

1USAGE (Wheel)

延伸閱讀




留言

這個網誌中的熱門文章

python serial 模組使用方法 #1

USB HID 教學 #1(轉載)