其實這個問題很多人都玩過了,而且HID Spec上有標準例子,但是USB鼠標和鍵盤的確很有意思,而且俺還嘗試了一點和別人不一樣的東西,在此以記之。
HID SPEC上講的鍵盤和鼠標都是支持boot的,就是可以被Bios支持的,比如在開機的時候設置Bios的時候就可以用。因此那個Report Descriptor真的是相當的複雜啊,都63個字節了,就差一個字節就超過俺的EP0的Max Pack Size。其實介紹Report Descriptor的最好網絡文章是《USB/HID設備報告描述符詳解》,看用詞像個臺灣同胞寫的,可以在下列地址閱讀:
http://blog.chinaunix.net/u2/63560/showart.php?id=1900045
其實這個似乎都還是比較複雜,我做了一個不支持boot的鍵盤的Report Descriptor,只支持一個字節的輸入,其實一個字節也是可以輸入101個鍵的,HID Spec裏面的Descriptor其實是支持6個鍵同時輸入的,所以用了6個字節。下面俺的簡陋型HID Descriptor就是這個樣子的:
char HidBoardReportDescriptor[23] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x1E, // USAGE_MINIMUM (Keyboard ! and 1)
0x29, 0x25, // USAGE_MAXIMUM (Keyboard * and 8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xff, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0 // END_COLLECTION
};
Descriptor中的幾個術語大概是這個意思,俺的通俗理解:
Usage Page:相當於用法類別,或者功能類別,用我的地址做比較,相當於“北京市海淀區”的“北京市”
Usage:相當於具體的用法,比如“海淀區”,一個完整的用法需要Usage Page和Usage配合才能完整表達,用地址類比一下。。。好像不能用“北京市海淀區”,海淀區中國只聽說過這一個!比如說“石門坎”吧,雲貴川有幾百個地方叫“石門坎”,因此必須說明“四川省寧南縣華彈鎮石門坎”纔有意義,兄弟,扯得有點遠了!
由於Usage Page是全局的,因此只聲明一次就行了,除非下面要什麼新的Usage Page。而Usage是要一個一個的聲明的。但是101個鍵要寫101次太麻煩,因此使用USAGE_MINIMUM 和USAGE_MAXIMUM來定義一個範圍,比如我上面的藍色的兩行就把1-8八個鍵都描述了。
LOGICAL_MINIMUM 和 LOGICAL_MAXIMUM 對應的是輸入數據的範圍,超出這個範圍不予處理。這個Lgical值的0表示沒輸入,1和上面的USAGE_MINIMUM是對應,也就是輸入1對應計算機的1,如果把USAGE_MINUM和USAGE_MAXIM改成如下:
0x19, 0x04, // USAGE_MINIMUM (Keyboard a and A)
0x29, 0x0B, // USAGE_MAXIMUM (Keyboard h and H)
那樣輸入1對應的就是計算機端的'A'了。
即原來的對應是1-->0x1E("1"鍵或"!"鍵),修改後爲1-->0x04("a" or "A"),關於每個USAGE對應的按鍵,在HID標準中有描述,這樣就把邏輯值和最後的鍵對應起來了。
REPORT_SIZE:表面的輸入的位寬度,俺的是8位, REPORT_COUNT是這樣的數有幾個,俺的只有一個。後面的INPUT表示有一個輸入。
COLLECTION在俺看來就是個大括號。
這樣一個鍵盤的簡單Descriptor就OK了。鼠標的就採用Boot就可以了。
HID的Report Descriptor是可以支持多個設備的,比如同時支持一個鼠標和鍵盤,這時就需要Report ID來參與了,這樣在上傳數據的時候就需要多一個字節表示Report ID來標識是哪個設備的數據,Report ID位於發送數據的第一個字節。切記,Report ID不能爲0,因爲系統默認已經用過了。(有人說這種複合設備在Configuration裏Subclass不能爲Boot,但是好像沒關係的)
比如假設鼠標的Report ID是2,則發送的數據應該如下:
0x02,00,05,00, 00
後面的紅色部分是原來不採用Report ID時發送的數據。下面是一個採用HID SPEC的鍵盤和鼠標Descriptor做的支持兩個設備的Descriptor。一旦枚舉成功,恭喜你,你會在設備管理器看到多出了一個鼠標和一個鍵盤.
const char HidBoardReportDescriptor[] = {
// Descriptors for Keyboard
0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */
0x09, 0x06, /* USAGE (Keyboard) */
0xa1, 0x01, /* COLLECTION (Application) */
0x85, 0x03, /* Report ID (3) */
0x05, 0x07, /* USAGE_PAGE (Keyboard) */
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
0x29, 0x05, // USAGE_MAXIMUM (Kana)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0xFF, // LOGICAL_MAXIMUM (255)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0, // END_COLLECTION
// Descriptors for Mouse
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x02, /* Usage (Mouse) */
0xA1, 0x01, /* Collection (Application) */
0x09, 0x01, /* Usage (Pointer) */
0xA1, 0x00, /* Collection (Physical) */
0x85, 0x02, /* Report ID (2) */
0x05, 0x09, /* Usage Page (Buttons) */
0x19, 0x01, /* Usage Minimum (01) */
0x29, 0x03, /* Usage Maximum (03) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x03, /* Report Count (3) */
0x81, 0x02, /* Input (Data, Variable, Absolute)*/
0x75, 0x05, /* Report Size (5) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x01, /* Input (Constant) ;5 bit padding */
0x05, 0x01, /* Usage Page (Generic Desktop) */
0x09, 0x30, /* Usage (X) */
0x09, 0x31, /* Usage (Y) */
0x09, 0x38, /* Usage (Wheel) */
0x15, 0x81, /* Logical Minimum (-127) */
0x25, 0x7F, /* Logical Maximum (127) */
0x75, 0x08, /* Report Size (8) */
0x95, 0x03, /* Report Count (3) */
0x81, 0x06, /* Input (Data, Variable, Relative)*/
0xC0, /* End Collection */
0xC0 /* End Collection */
};