天天看點

閑暇就玩USB之: USB滑鼠和鍵盤

其實這個問題很多人都玩過了,而且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,                   

  0x09, 0x06,                   

  0xa1, 0x01,                   

  0x85, 0x03,                   

  0x05, 0x07,                   

 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,                   

  0x09, 0x02,                   

  0xA1, 0x01,                   

  0x09, 0x01,                   

  0xA1, 0x00,                   

  0x85, 0x02,                   

  0x05, 0x09,                   

  0x19, 0x01,                   

  0x29, 0x03,                   

  0x15, 0x00,                   

  0x25, 0x01,                   

  0x75, 0x01,                   

  0x95, 0x03,                   

  0x81, 0x02,                   

  0x75, 0x05,                   

  0x95, 0x01,                   

  0x81, 0x01,                   

  0x05, 0x01,                   

  0x09, 0x30,                   

  0x09, 0x31,                   

  0x09, 0x38,                   

  0x15, 0x81,                   

  0x25, 0x7F,                   

  0x75, 0x08,                   

  0x95, 0x03,                   

  0x81, 0x06,                   

  0xC0,                         

  0xC0                          

};

繼續閱讀