注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
cl1217982730的个人空间 https://home.eeworld.com.cn/space-uid-333779.html [收藏] [复制] [分享] [RSS]
日志

USB——HID Keyboard

已有 4813 次阅读2014-6-13 09:25

键盘发送给PC的数据每次8个字节
BYTE1 BYTE2 BYTE3 BYTE4 BYTE5 BYTE6 BYTE7 BYTE8
定义分别是:
BYTE1 --
       |--bit0:   Left Control是否按下,按下为1
       |--bit1:   Left Shift  是否按下,按下为1
       |--bit2:   Left Alt    是否按下,按下为1
       |--bit3:   Left GUI    是否按下,按下为1
       |--bit4:   Right Control是否按下,按下为1  
       |--bit5:   Right Shift 是否按下,按下为1
       |--bit6:   Right Alt   是否按下,按下为1
       |--bit7:   Right GUI   是否按下,按下为1
BYTE2 -- 暂不清楚,有的地方说是保留位
BYTE3--BYTE8 -- 这六个为普通按键
键盘经过测试。
例如:键盘发送一帧数据   02 00 0x04 0x05 00 00 00 00
表示同时按下了Left Shift + ‘a’+‘b’三个键


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的取值(十进制)含义
    0NONE
    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的状态也会跟着改变。

全部作者的其他最新日志
评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

热门文章