您现在的位置: USB开发网 > USB开源项目 > Easy USB 51 Programer Plus
- Easy USB 51 Programer Plus

USB键盘实例

------分隔线----------------------------

  在上一节中我们实现了USB鼠标实例,通过学习,知道决定HID设备的“身份”的因素有

  1)5个标准描述符中与HID设备有关的部分有:

  • 设备描述符中bDeviceSubClass、bDeviceProtocol和bDeviceClass三个字段的值必须为零。
  • 接口描述符中 bInterfaceProtocol的取值含义如下表所示: 

    HID接口描述符中bInterfaceProtocol的含义
    bInterfaceProtocol的取值(十进制) 含义
    0 NONE
    1 鼠标
    2 键盘
    3~255 保留

 

  • 接口描述符中bInterfaceSubClass的值为0或1,为1表示HID设备符是一个启动设备(Boot Device,一般对PC机而言才有意义,意思是BIOS启动时能识别并使用您的HID设备,且只有标准鼠标或键盘类设备才能成为Boot Device。
  • 接口描述符中bInterfaceClass的值必须为0x03

  2)队了上面提到的usb标准描述符里影响HID设备类型的地方外,还包括3个HID设备类特定描述符:HID描述符、报告描述符、实体描述符。

  如此看来我们只需在上一节实例的基础上把接口描述符中 bInterfaceProtocol 的值改为2,系统就应该能别设备识别成USB键盘。先下载上一节实例:

USB鼠标实例-控制光标移动

然后再将Descriptor.c文件里接口描述符的bInterfaceProtocol字段值改为2,保存,编译并烧录程序试试效果,下面是改好后的程序: 

USB键盘(在USB鼠标实例基础上只修改bInterfaceProtocol字段值)
 结果发现枚举后仍然是一个鼠标,而且鼠标功能也正常,看来影响枚举结果的不在于bInterfaceProtocol字段,其实应该是报告描述符,在后面的HID复合设备实例中也印证了这一点。

  下面我们就来修改报告描述符,把报告描述符的内容修改如下:

  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中定义这个报告描述符的相关代码。

  上面定义的报告描述符描述了输入(设备到主机)和输出(主机到设备)两个数据包,输入数据包由8个字节组成,第一个字节用来描述特殊键,第二字节保留,后面六个字节代表6个普通按键。

  特殊键用第一个字节的不同位来表示,特殊键由USB HID Usage Table定义(P59),如果按下的是左边那个Ctrl键,那应该发送01 00 00 00 00 00 00 00(十六进制),如果按下的是右边的Ctrl键,则应该发送10 00 00 00 00 00 00 00,如果同时按下左Ctrl和左Alt,应该发送:05 00 00 00 00 00 00 00。

  普通键用后面六个字节来表示,如果只按下字母“a”,应发送00 00 04 00 00 00 00 00,如果同时按下字母“a”和“b”应发送00 00 04 05 00 00 00 00或00 00 05 04 00 00 00 00都可以,如果同时按下左Shift和字母“a”,那就应该发送02 00 04 00 00 00 00 00,在此状态下先释放字母“a”再释放左Shitf,那应该先发送一次02 00 00 00 00 00 00 00,再发送一次00 00 00 00 00 00 00 00。

  输出报告是用来指示状态LED的,在USB HID Usage Table定义(P61)了各种状态LED,上面的报告描述符定义了可用的状态LED有Num Lock、Caps Lock 、Scroll Lock 、Compose、Kana五种(后面两种我也不知道是什么作用),当LED状态发生变化时,主机就会向设备发送只一个字节的数据包,用不两同的位来表示,如果没有灯亮,则发送0x00,Num Lock亮则发送0x01,Caps Lock 亮则发送0x02,如果两个都亮则发送0x03。

  根据上面的知识我们用扩展板上的K1和K2分别代表Windows键和NumLock键,并用扩展板上的一个LED指示灯来代表Num Lock灯,特别需要注意的是,控制NumLock指示灯不应该在主控芯片里判断当到K2按下后就打开或熄灭LED,其正确的方法是:当主机接收到NumLock按键信息后,会根据系统当前NumLock的状态决定打开还是关闭LED指示灯,然后将这一信息通过传给设备(发送一个字节的数据给设置,最低位表示NumLock的状态)。  修改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. }     

   烧录程序,当按下K1时,应弹出开始菜单,按下K2后,会发现电脑原来的键盘和Easy usb 51 programer plus扩展板上的LED指示灯同时会发生改变,这也印证了设备应该根据主机发过来的LED状态数据包来控制指示灯。

USB键盘实例

   实例中为了演示方便,没有对铵键进行消抖处理,实际应用中应加上。

------分隔线----------------------------
联系我们
  • Q Q: 1148374829 点击这里给我发消息
  • 旺旺:jhoneqhsieh 点击这里给我发消息
  • 电话:(0)15923141204
  • 淘宝网店