如何成為一個HID設備(模擬鍵盤)


1、基礎知識

  通過《USB HID 設備類協議入門》一文和上一節的實例我們知道決定HID設備“身份”的因素有

1)5個標准描述符中與HID設備有關的部分有:

  • 設備描述符中bDeviceClass、bDeviceSubClass和bDeviceProtocol三個字段的值必須為零。
  • 接口描述符中bInterfaceClass的值必須為0x03,bInterfaceSubClass的值為0或1,為1表示HID設備符是一個啟動設備(Boot Device,一般對PC機而言才有意義,意思是BIOS啟動時能識別並使用您的HID設備,且只有標准鼠標或鍵盤類設備才能成為Boot Device。 bInterfaceProtocol的取值含義如下表所示: 
    HID接口描述符中bInterfaceProtocol的含義
    bInterfaceProtocol的取值(十進制) 含義
    0 NONE
    1 鼠標
    2 鍵盤
    3~255 保留

2)HID設備的描述符除了5個USB的標准描述符(設備描述符、配置描述符、接口描述符、端點描述符、字符串描述符,見百合電子工作室的另一篇文章:USB開發基礎--USB命令(請求)和USB描述符)外,還包括3個HID設備類特定描述符:HID描述符、報告描述符、實體描述符。

2、在上一節實例的基礎上作一些修改來將Easy USB 51 Programer改造成鍵盤

1)下載上一節實例

2)修改接口描述符中bInterfaceProtocol的值為0x02

在Descriptor.c中找到以下代碼

 
  1. 1,                          //bInterfaceProtocol為1代表鼠標  

將其修改為

 
  1. 2,                                      //bInterfaceProtocol為2代表鍵盤  

 3)在上一節中也提到“雖然我們將接口描述符中的bInterfaceProtocol設為1(代表鼠標),但這只是針對啟動設備(Boot Device)而言有才有效果(即PC機的BIOS加載后能識別和使用),但真正對HID設備的數據流格式進行描述的是報告描述符,所以 bInterfaceProtocol的取值實際意義不大”,所以現在我們來修改報告描述符

在Descriptor.c中找到以下代碼:

 
  1. code char MouseReportDescriptor[52] = {   
  2.     0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)   
  3.     0x09, 0x02,                    // USAGE (Mouse)   
  4.     0xa1, 0x01,                    // COLLECTION (Application)   
  5.     0x09, 0x01,                    //   USAGE (Pointer)   
  6.     0xa1, 0x00,                    //   COLLECTION (Physical)   
  7.     0x05, 0x09,                    //     USAGE_PAGE (Button)   
  8.     0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)   
  9.     0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)   
  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 (Cnst,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. };  

將其修改為

 
  1. code char MouseReportDescriptor[63] = {   
  2.     //表示用途頁為通用桌面設備   
  3.     0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)   
  4.   
  5.     //表示用途為鍵盤   
  6.     0x09, 0x06,                    // USAGE (Keyboard)   
  7.        
  8.     //表示應用集合,必須要以END_COLLECTION來結束它,見最后的END_COLLECTION   
  9.     0xa1, 0x01,                    // COLLECTION (Application)   
  10.        
  11.     //表示用途頁為按鍵   
  12.     0x05, 0x07,                    //   USAGE_PAGE (Keyboard)   
  13.   
  14.     //用途最小值,這里為左ctrl鍵   
  15.     0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)   
  16.     //用途最大值,這里為右GUI鍵,即window鍵   
  17.     0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)   
  18.     //邏輯最小值為0   
  19.     0x15, 0x00,                    //   LOGICAL_MINIMUM (0)   
  20.     //邏輯最大值為1   
  21.     0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)   
  22.     //報告大小(即這個字段的寬度)為1bit,所以前面的邏輯最小值為0,邏輯最大值為1   
  23.     0x75, 0x01,                    //   REPORT_SIZE (1)   
  24.     //報告的個數為8,即總共有8個bits   
  25.     0x95, 0x08,                    //   REPORT_COUNT (8)   
  26.     //輸入用,變量,值,絕對值。像鍵盤這類一般報告絕對值,   
  27.     //而鼠標移動這樣的則報告相對值,表示鼠標移動多少   
  28.     0x81, 0x02,                    //   INPUT (Data,Var,Abs)   
  29.     //上面這這幾項描述了一個輸入用的字段,總共為8個bits,每個bit表示一個按鍵   
  30.     //分別從左ctrl鍵到右GUI鍵。這8個bits剛好構成一個字節,它位於報告的第一個字節。   
  31.     //它的最低位,即bit-0對應着左ctrl鍵,如果返回的數據該位為1,則表示左ctrl鍵被按下,   
  32.     //否則,左ctrl鍵沒有按下。最高位,即bit-7表示右GUI鍵的按下情況。中間的幾個位,   
  33.     //需要根據HID協議中規定的用途頁表(HID Usage Tables)來確定。這里通常用來表示   
  34.     //特殊鍵,例如ctrl,shift,del鍵等   
  35.     
  36.   
  37.     //這樣的數據段個數為1   
  38.     0x95, 0x01,                    //   REPORT_COUNT (1)   
  39.     //每個段長度為8bits   
  40.     0x75, 0x08,                    //   REPORT_SIZE (8)   
  41.     //輸入用,常量,值,絕對值   
  42.     0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)   
  43.        
  44.     //上面這8個bit是常量,設備必須返回0   
  45.   
  46.   
  47.     //這樣的數據段個數為5   
  48.     0x95, 0x05,                    //   REPORT_COUNT (5)   
  49.     //每個段大小為1bit   
  50.     0x75, 0x01,                    //   REPORT_SIZE (1)   
  51.     //用途是LED,即用來控制鍵盤上的LED用的,因此下面會說明它是輸出用   
  52.     0x05, 0x08,                    //   USAGE_PAGE (LEDs)   
  53.     //用途最小值是Num Lock,即數字鍵鎖定燈   
  54.     0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)   
  55.     //用途最大值是Kana,這個是什么燈我也不清楚^_^   
  56.     0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)   
  57.     //如前面所說,這個字段是輸出用的,用來控制LED。變量,值,絕對值。   
  58.     //1表示燈亮,0表示燈滅   
  59.     0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)   
  60.   
  61.     //這樣的數據段個數為1   
  62.     0x95, 0x01,                    //   REPORT_COUNT (1)   
  63.     //每個段大小為3bits   
  64.     0x75, 0x03,                    //   REPORT_SIZE (3)   
  65.     //輸出用,常量,值,絕對   
  66.     0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)       
  67.     //由於要按字節對齊,而前面控制LED的只用了5個bit,   
  68.     //所以后面需要附加3個不用bit,設置為常量。    
  69.   
  70.     //報告個數為6   
  71.     0x95, 0x06,                    //   REPORT_COUNT (6)   
  72.     //每個段大小為8bits   
  73.     0x75, 0x08,                    //   REPORT_SIZE (8)   
  74.     //邏輯最小值0   
  75.     0x15, 0x00,                    //   LOGICAL_MINIMUM (0)   
  76.     //邏輯最大值255   
  77.     0x25, 0xFF,                    //   LOGICAL_MAXIMUM (255)   
  78.     //用途頁為按鍵   
  79.     0x05, 0x07,                    //   USAGE_PAGE (Keyboard)   
  80.     //使用最小值為0   
  81.     0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))   
  82.     //使用最大值為0x65   
  83.     0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)   
  84.     //輸入用,變量,數組,絕對值   
  85.     0x81, 0x00,                    //   INPUT (Data,Ary,Abs)   
  86.     //以上定義了6個8bit寬的數組,每個8bit(即一個字節)用來表示一個按鍵,所以可以同時   
  87.     //有6個按鍵按下。沒有按鍵按下時,全部返回0。如果按下的鍵太多,導致鍵盤掃描系統   
  88.     //無法區分按鍵時,則全部返回0x01,即6個0x01。如果有一個鍵按下,則這6個字節中的第一   
  89.     //個字節為相應的鍵值(具體的值參看HID Usage Tables),如果兩個鍵按下,則第1、2兩個   
  90.     //字節分別為相應的鍵值,以次類推。   
  91.   
  92.     //關集合,跟上面的對應   
  93.     0xc0                           // END_COLLECTION   
  94. };  

這們還需要將Descriptor.h中的以下代碼

 
  1. extern code char MouseReportDescriptor[52];  

修改為

 
  1. extern code char MouseReportDescriptor[63];  

   上面的報告描述符中只有一個報告,所以沒有報告ID,
因此返回的都是實際使用的數據。總共有8字節輸入,1字節輸出。其中輸入的
第一字節用來表示特殊按鍵,第二字節保留,后面的六字節為普通按鍵。如果
只有左ctrl鍵按下,則返回01 00 00 00 00 00 00 00(十六進制),如果
只有數字鍵1 按下,則返回00 00 59 00 00 00 00 00,如果數字
鍵1 和2 同時按下,則返回00 00 59 5A 00 00 00 00,如果
再按下左shift 鍵,則返回02 00 59 5A 00 00 00 00,
然后再釋放1   鍵,則返回02 00 5A 00 00 00 00 00,
然后全部按鍵釋放,則返回00 00 00 00 00 00 00 00。
這些數據(即報告)都是通過中斷端點返回的。當按下Num Lock鍵時,PC會發送
輸出報告,從報告描述符中我們知道,Num Lock的LED對應着輸出報告的最低位,
當數字小鍵盤打開時,輸出xxxxxxx1(二進制,打x的由其它的LED狀態決定);
當數字小鍵盤關閉時,輸出xxxxxxx0(同前)。取出最低位就可以控制數字鍵鎖定LED了。

 (注:以上說明摘自computer00的《USB HID報告及報告描述符簡介》一文)

5)將Descriptor中如下語句

 
  1. 0x66,0x02,                      //設備制造商定的產品ID  

修改為

 
  1. 0x66,0x03,                      //設備制造商定的產品ID  

 

4)模擬NumLock鍵盤和Windows鍵

  我們定義擴展板EXT-BOARD-A上的K1鍵對應Windows鍵(即USB HID Usage Table中定義的GUI鍵,Left GUI或Right GUI,我們這里就選Left GUI吧),而K2鍵盤對應NumLock鍵,當NumLock使能時應點亮NumLock指示燈,我定義D0為NumLock指示燈。

  根據報告描述符的定義,再參考USB HID Usage Table,要模擬Windows鍵盤,應將送給主機的8個字節的第一個字節置為0x04,要模擬NumLock鍵,應將第三個字節置為0x53。如果要模擬NumLock指示燈,不應該在主控芯片里判斷當到K2按下后就打開或熄滅LED,其正確的方法是:當主機接收到NumLock按鍵信息后,會根據系統當前NumLock的狀態決定打開還是關閉LED指示燈,然后將這一信息通過傳給設備(發送一個字節的數據給設置,最低位表示NumLock的狀態)。

更改Main.c文件中的main函數為:

 
  1. void main()   
  2. {      
  3.     unsigned char i = 0;   
  4.     signed char cKeyIn[8];   
  5.     static bit bKeyPressed  = 0;        //鍵按下標志,防止重入   
  6.        
  7.     if (Init_D12()!=0)                  //初始化D12   
  8.         return;                         //如果初始化不成功,返回   
  9.   
  10.     IT0 = 0;                            //外部中斷0為電平觸發方式   
  11.        
  12.     EX0 = 1;                            //開外部中斷0   
  13.     PX0 = 0;                            //設置外部中斷0中斷優先級   
  14.     EA  = 1;                                //開80C51總中斷   
  15.        
  16.     P0  = 0;   
  17.   
  18.     while(1)   
  19.     {   
  20.         usbserve();                     //處理USB事件   
  21.         if(bEPPflags.bits.configuration)   
  22.         {   
  23.             //在這里添加端點操作代碼                      
  24.             if(bEPPflags.bits.ep2_rxdone )  //主端點接收到數據(從主機發往設備的數據)   
  25.             {   
  26.                 bEPPflags.bits.ep2_rxdone       = 0;   
  27.                    
  28.                 //判斷NumLock狀態   
  29.                 if(EpBuf[0] & 0x01) //EpBuf為接收緩沖   
  30.                 {   
  31.                     P0  = 0x01;    
  32.                 }   
  33.                 else  
  34.                 {   
  35.                     P0  = 0x00;   
  36.                 }                  
  37.                            
  38.             }   
  39.                
  40.             K1  = 1;        //P3.5   
  41.             K2  = 1;        //P3.6   
  42.                
  43.             for(i=0;i<100;i++); //延時    
  44.                 
  45.             if(~K1 & K2)    //K1按下(模擬左Windows鍵)   
  46.             {   
  47.                 if(!bKeyPressed)       
  48.                 {   
  49.                     bKeyPressed = 1;   
  50.                        
  51.                     cKeyIn[0]=0x08;        
  52.                     cKeyIn[1]=0;            //保留   
  53.                     cKeyIn[2]=0;               
  54.                     cKeyIn[3]=0;   
  55.                     cKeyIn[4]=0;   
  56.                     cKeyIn[5]=0;   
  57.                     cKeyIn[6]=0;   
  58.                     cKeyIn[7]=0;           
  59.                        
  60.                     D12_WriteEndpoint(5,8,cKeyIn);          //發8個字節到PC機   
  61.                 }      
  62.             }   
  63.             else if(K1 & ~K2)   //K2按下(模擬NumLock鍵)   
  64.             {   
  65.                 if(!bKeyPressed)       
  66.                 {   
  67.                     bKeyPressed = 1;   
  68.                        
  69.                     cKeyIn[0]=0;           
  70.                     cKeyIn[1]=0;            //保留   
  71.                     cKeyIn[2]=0x53;            
  72.                     cKeyIn[3]=0;   
  73.                     cKeyIn[4]=0;   
  74.                     cKeyIn[5]=0;   
  75.                     cKeyIn[6]=0;   
  76.                     cKeyIn[7]=0;           
  77.                        
  78.                     D12_WriteEndpoint(5,8,cKeyIn);          //發8個字節到PC機   
  79.                 }   
  80.             }   
  81.             else if(~K1 & ~K2)  //K1和K2同時按下(Window和NumLock同時按下)   
  82.             {   
  83.                    
  84.                 if(!bKeyPressed)       
  85.                 {   
  86.                     bKeyPressed = 1;   
  87.                        
  88.                     cKeyIn[0]=0x08;        
  89.                     cKeyIn[1]=0;            //保留   
  90.                     cKeyIn[2]=0x53;            
  91.                     cKeyIn[3]=0;   
  92.                     cKeyIn[4]=0;   
  93.                     cKeyIn[5]=0;   
  94.                     cKeyIn[6]=0;   
  95.                     cKeyIn[7]=0;           
  96.                        
  97.                     D12_WriteEndpoint(5,8,cKeyIn);          //發8個字節到PC機   
  98.                 }   
  99.             }   
  100.             else if(K1 & K2)   
  101.             {   
  102.                 if(bKeyPressed)        
  103.                 {   
  104.                     bKeyPressed = 0;   
  105.                        
  106.                     cKeyIn[0]=0;           
  107.                     cKeyIn[1]=0;            //保留   
  108.                     cKeyIn[2]=0;               
  109.                     cKeyIn[3]=0;   
  110.                     cKeyIn[4]=0;   
  111.                     cKeyIn[5]=0;   
  112.                     cKeyIn[6]=0;   
  113.                     cKeyIn[7]=0;           
  114.                        
  115.                     D12_WriteEndpoint(5,8,cKeyIn);          //發8個字節到PC機   
  116.                 }   
  117.             }      
  118.            
  119.         }   
  120.     }   
  121. }  

  在測試這個例子時,按下EXT-BOARD-A上的K1鍵,會彈出開始菜單,按K2鍵,D0的狀態會改變,同時原有鍵盤上的NumLock指示燈也會同EXT-BOARD-A上的D0狀態同步,相反,按原有鍵盤上的NumLock鍵,D0的狀態也會跟着改變。

下載源代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM