Arduino 控制USB设备(6)解析USB鼠标的例子(上)

前面介绍了USB键盘的使用,这里介绍一下USB鼠标的调用。根据【参考1】的文章进行实验,这次我们的目标是:获得鼠标移动按键信息,串口输出之。

首先运行一下之前的取得描述符的工具抓取一下描述符:

Descriptor of HP Mouse
Start
Device addressed... Requesting device descriptor.
Device descriptor:

Descriptor Length: 12
USB version: 2.0
Class: 00 Use class information in the Interface Descriptor
Subclass: 00
Protocol: 00
Max.packet size: 08
Vendor ID: 046D
Product ID: C077
Revision ID: 6700
Mfg.string index: 01 Length: 18 Contents: Logitech
Prod.string index: 02 Length: 36 Contents: USB Optical Mouse
Serial number index: 00
Number of conf.: 01

Configuration number 0
Total configuration length: 34 bytes

Configuration descriptor:
Total length: 0022
Number of interfaces: 01
Configuration value: 01
Configuration string: 00
Attributes: A0 Remote Wakeup
Max.power: 31 98ma

Interface descriptor:
Interface number: 00
Alternate setting: 00
Endpoints: 01
Class: 03 HID (Human Interface Device)
Subclass: 01
Protocol: 02
Interface string: 00

HID descriptor:
Descriptor length: 09 9 bytes
HID version: 1.11
Country Code: 0 Not Supported
Class Descriptors: 1
Class Descriptor Type: 22 Report
Class Descriptor Length:67 bytes

HID report descriptor:

Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 1 Type: Local Tag: Usage Data: 02
Length: 1 Type: Main Tag: Collection Application (mouse, keyboard) Data: 01
Length: 1 Type: Local Tag: Usage Data: 01
Length: 1 Type: Main Tag: Collection Physical (group of axes) Data: 00
Length: 1 Type: Global Tag: Usage Page Button Data: 09
Length: 1 Type: Local Tag: Usage Minimum Data: 01
Length: 1 Type: Local Tag: Usage Maximum Data: 08
Length: 1 Type: Global Tag: Logical Minimum Data: 00
Length: 1 Type: Global Tag: Logical Maximum Data: 01
Length: 1 Type: Global Tag: Report Size Data: 01
Length: 1 Type: Global Tag: Report Count Data: 08
Length: 1 Type: Main Tag: Input Data,Variable,Absolute………
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 2 Type: Global Tag: Logical Minimum Data: 01 Data: F8
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 07
Length: 1 Type: Global Tag: Report Size Data: 0C
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30
Length: 1 Type: Local Tag: Usage Data: 31
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 1 Type: Global Tag: Logical Minimum Data: 81
Length: 1 Type: Global Tag: Logical Maximum Data: 7F
Length: 1 Type: Global Tag: Report Size Data: 08
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Local Tag: Usage Data: 38
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 1 Type: Global Tag: Usage Page Consumer Data: 0C
Length: 2 Type: Local Tag: Usage Data: 38 Data: 02
Length: 1 Type: Global Tag: Report Count Data: 01
Length: 1 Type: Main Tag: Input Data,Variable,Relative………
Length: 0 Type: Main Tag: End Collection
Length: 0 Type: Main Tag: End Collection

Endpoint descriptor:
Endpoint address: 01 Direction: IN
Attributes: 03 Transfer type: Interrupt
Max.packet size: 0006
Polling interval: 0A 10 ms

根据上面的结果,计算长度应该是1x8+Cx2+8x1=40 bits= 5 bytes ,修改【参考1】的代码如下:

#include "Max3421e.h"
#include "Usb.h"

 
#define DEVADDR 1
#define CONFVALUE 1
 
void setup();
void loop();
 
MAX3421E Max;
USB Usb;
 
void setup()
{
    Serial.begin( 115200 );
    Serial.println("Start");
    Max.powerOn();
    delay( 200 );
}
 
void loop()
{
 byte rcode;
    Max.Task();
    Usb.Task();
    if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
        mouse0_init();
    }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
    if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  //poll the keyboard
        rcode = mouse0_poll();
        if( rcode ) {
          Serial.print("Mouse Poll Error: ");
          Serial.println( rcode, HEX );
        }//if( rcode...
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse0_init( void )
{
 byte rcode = 0;  //return code
  /**/
  Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) );              //copy device 0 endpoint information to device 1
  /* Configure device */
  rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  if( rcode ) {
    Serial.print("Error configuring mouse. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }//if( rcode...

  Usb.setUsbTaskState( USB_STATE_RUNNING );
  return;
}
/* Poll mouse using Get Report and print result */
byte mouse0_poll( void )
{
  byte rcode,i;
  char buf[ 4 ] = { 0 };      //mouse buffer
  static char old_buf[ 4 ] = { 0 };  //last poll
    /* poll mouse */
    rcode = Usb.getReport( DEVADDR, 0, 4, 0, 1, 0, buf );
    if( rcode ) {  //error
      return( rcode );
    }
    for( i = 0; i < 4; i++) {  //check for new information
      if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
        break;
      }
    }
    if( i == 4 ) {
      return( 0 );  //all bytes are the same
    }
    /* print buffer */
    if( buf[ 0 ] & 0x01 ) {
      Serial.print("Button1 pressed ");
    }
    if( buf[ 0 ] & 0x02 ) {
      Serial.print("Button2 pressed ");
    }
    if( buf[ 0 ] & 0x04 ) {
      Serial.print("Button3 pressed ");
    }
    Serial.println("");
    Serial.print("X-axis: ");
    Serial.println( buf[ 1 ], DEC);
    Serial.print("Y-axis: ");
    Serial.println( buf[ 2 ], DEC);
    Serial.print("Wheel: ");
    Serial.println( buf[ 3 ], DEC);
    
    for( i = 0; i < 4; i++ ) {
      old_buf[ i ] = buf[ i ];  //copy buffer
    }
    Serial.println("");
    return( rcode );
}

 

运行结果

usb61

完整代码下载

mouse0

看起来按键和X Y都已经正确,但是 wheel 并不正常。再仔细研究代码,取出来的信息长度和描述符中的不一致。就是说,程序虽然能够运行也能够取到变化的值,但是解读有误。

再进一步研读 Descriptor
Length: 1 Type: Global Tag: Usage Page Generic Desktop Controls Data: 01
Length: 2 Type: Global Tag: Logical Minimum Data: 01 Data: F8 //最小 F801 = 1111 1000 0000 0001 = -2047
Length: 2 Type: Global Tag: Logical Maximum Data: FF Data: 07//最大 07FF = 0000 0111 1111 1111 = 2047
Length: 1 Type: Global Tag: Report Size Data: 0C //这里指出了上面数值的大小是 12位
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30 // X
Length: 1 Type: Local Tag: Usage Data: 31 // Y

应该是意味着2个12bits长度的数值,对应X Y 分配如下,其中 X[B] 和Y[B]应该是最高的符号位

usb62

根据这个改写程序,按照上面的表格取值再拼接起来,特别需要注意的是,我发现Arduino在不同尺寸的数据转换上似乎有一些bug,我只能把值放在 unsigned long int中然后才能进行拼接和输出的动作。

    Serial.print("X-axis: ");
    x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
    if (x & 0x800) {
      Serial.print("-");
      x = ((~x) & 0x7FF) +1;
    }
   
    Serial.println(x, HEX);
    
    Serial.print("Y-axis: ");    
    y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
    if (y & 0x800) {
      Serial.print("-");
      y = ((~y) & 0x7FF) +1;
    }    
Serial.println(y, HEX);

 

最终,程序可以正常执行。

工作正常的程序如下:

#include "Max3421e.h"
#include "Usb.h"

 
#define DEVADDR 1
#define CONFVALUE 1
 
void setup();
void loop();
 
MAX3421E Max;
USB Usb;
 
void setup()
{
    Serial.begin( 115200 );
    Serial.println("Start");
    Max.powerOn();
    delay( 200 );
}
 
void loop()
{
 byte rcode;
    Max.Task();
    Usb.Task();
    if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
        mouse0_init();
    }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
    if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  //poll the keyboard
        rcode = mouse0_poll();
        if( rcode ) {
          Serial.print("Mouse Poll Error: ");
          Serial.println( rcode, HEX );
        }//if( rcode...
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse0_init( void )
{
 byte rcode = 0;  //return code
  /**/
  Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) );              //copy device 0 endpoint information to device 1
  /* Configure device */
  rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  if( rcode ) {
    Serial.print("Error configuring mouse. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }//if( rcode...

  Usb.setUsbTaskState( USB_STATE_RUNNING );
  return;
}
/* Poll mouse using Get Report and print result */
byte mouse0_poll( void )
{
  byte rcode,i;
  char buf[ 5 ] = { 0 };      //mouse buffer
  static char old_buf[ sizeof(buf) ] = { 0 };  //last poll
  unsigned long int libuf[ sizeof(buf) ];
  unsigned long int x;
  unsigned long int y;
  
    /* poll mouse */
    rcode = Usb.getReport( DEVADDR, 0, sizeof(buf), 0, 1, 0, buf );
    if( rcode ) {  //error
      return( rcode );
    }
    for( i = 0; i < sizeof(buf); i++) {  //check for new information
      if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
        break;
      }
    }
    if( i == sizeof(buf)) {
      return( 0 );  //all bytes are the same
    }
    /* print buffer */
    if( buf[ 0 ] & 0x01 ) {
      Serial.print("Button1 pressed ");
    }
    if( buf[ 0 ] & 0x02 ) {
      Serial.print("Button2 pressed ");
    }
    if( buf[ 0 ] & 0x04 ) {
      Serial.print("Button3 pressed ");
    }
    
    for( i = 0; i < sizeof(buf); i++ ) {
      old_buf[ i ] = buf[ i ];  //copy buffer
      libuf[ i] = buf[i];
    }

    //Serial.print(libuf[0],HEX); Serial.print("  "); 
    //Serial.print(libuf[1],HEX); Serial.print("  "); 
    //Serial.print(libuf[2],HEX); Serial.print("  "); 
    //Serial.print(libuf[3],HEX); Serial.print("  ");     
    //Serial.println("");
    
    Serial.print("X-axis: ");
    x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
    if (x & 0x800) {
      Serial.print("-");
      x = ((~x) & 0x7FF) +1;
    }
   
    Serial.println(x, HEX);
    
    Serial.print("Y-axis: ");    
    y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
    if (y & 0x800) {
      Serial.print("-");
      y = ((~y) & 0x7FF) +1;
    }    
    Serial.println(y, HEX);

    Serial.print("Wheel: ");    
    Serial.println(buf [4] & 0xFF, HEX);    
    return( rcode );
}

 

完整代码下载:

mousea

补遗:在运行中,我还发现了一个奇怪的问题:当使用 Get_Report取得数据时,鼠标向一个方向移动,会出现最大值。意思是:比如,向右一直移动鼠标,输出达到 7FF 之后,继续右移,输出值会维持在 7FF 而不会变化。没有找到直接描述这个问题的资料,不过有一些类似的介绍【参考2】,上面提到USB鼠标在送出数据的时候,会有两种方式,一种是Relative ,一种是Absolute。差别在于,比如前者输出 (1,1),意思是告诉系统鼠标移动了(1,1);而后者如果输出 (1,1)则是告诉系统,鼠标移动到(1,1)。看起来,我这个鼠标描述符中提到的是Relative,但是实际输出是 Absolute。

Length: 1 Type: Global Tag: Report Size Data: 0C
Length: 1 Type: Global Tag: Report Count Data: 02
Length: 1 Type: Local Tag: Usage Data: 30
Length: 1 Type: Local Tag: Usage Data: 31
Length: 1 Type: Main Tag: Input Data,Variable,Relative………

于此可以形成对照的是 Wheel,每次滚动之后,很快会再次输出一个 0 的位置,下次再给出新的滚动值。
至于Windows 下,根据资料,系统会自动根据鼠标信息计算出一个大概范围方便使用(比如,屏幕是 1024x768,鼠标是Relative -100到+100的话,如果不做优化,从左上到右下需要做几次才行)。而我用的这个鼠标,声明自己是Relative,但是实际上发出Absolute,只是因为自己的范围很大(-7FF到+7FF),所以也不会有什么感觉。

上述只是猜测,是目前已知的最合理的解释而已。使用逻辑分析仪分析USB鼠标信息得到的结果相同。

逻辑分析仪抓取的结果,Windows使用 Interrupt 方式来和鼠标通讯

usb64

下面展示一下USB逻辑分析仪抓到的启动时的一些信息,首先是 Overview

image016

image005

image006
不清楚为什么VendorID 居然是罗技的,一种可能是因为这个是假的HP鼠标,乱写一个ID;另外的可能是HP 找罗技代工的。

image007

image008

可以看到,MaxPower确实是一个很古怪的数值:98ma

image009

image010

image011

image012

接下来是最重要的HID 描述符
image013

image014

image015

参考:
1.https://www.circuitsathome.com/communicating-arduino-with-hid-devices-part-1
2.http://www.microchip.com/forums/m482197.aspx MOUSE HID / Resolution problem

Arduino 打造一个“鼠标尺”

鼠标是目前玩电脑的必不可少的配备了。我最早接触的是机械鼠标,中间有一个滚球,带动内部的机械,配合光学器件将旋转信号转化为电子信号,通过串口或者PS2接口传输到电脑上。
image001
【参考1】

这样的鼠标缺点是定位不是很准确,并且容易脏,下面的球滚动一段就会蹭上泥垢,看起来比较恶心。
再后来出现了光电鼠标,那时候的光电鼠标必须在专用的鼠标垫上才能用,那种鼠标垫上面有网格状的东西,只有在它上面,鼠标发出光线才能正确的反馈,换句话说没有特定的鼠标垫根本无法使用。当然,那时候用电脑还是一项非常严重的事情,上机需要按照“团结紧张严肃活泼”的方针,先洗手再换鞋套之类的。
再再后来就出现了目前我们最常见到的光电鼠标,各种材质都可以使用,非常方便。原理上也发生了巨大的变化,简单的说就是现在的鼠标下面有一个类似照相机一样的东西不断给接触面拍照,移动会导致每次画面的变化,然后DSP根据画面不同计算出移动的距离再反馈给PC当前的移动距离信息。
当然,未来还可能出现更新原理的鼠标,比如:象象利用加速度传感器做的 “体感空中鼠”,等等。

我始终有一个想法:是否可以用鼠标来做一个电子尺,因为鼠标移动特定距离之后的输出也是固定的,意思是:鼠标移动10cm,输出的点位应该是固定的,比如:2000之类(DPI是定值)。反过来,如果我们知道鼠标移动了多少点,也就能确定鼠标移动了多少距离。最近接触了Arduino USB Shield ,于是打造一个鼠标尺。

材料准备
Arduino Uno
Arduino USB Shield
USB鼠标
USB充电宝
1602 LCD
导线 4根

实现原理

使用 USB Shield 来做USB HOST,在初始化的时候将鼠标切换到Boot Protocol,切换的好处是避免针对每种鼠标特别分析Descriptor,可以直接使用输出的数据(虽然是都要遵循HID协议,还真有不支持Boot Protocol的,这种情况需要单独处理了)。每次鼠标有动作,都会用Interrupt 方式通知到HOST来处理。我们根据输出的位移(相对位移),可以得知移动了多少。

#include "max3421e.h"
#include "usb.h"
#include "LiquidCrystal_I2C.h"
#include

#define DEVADDR 1
#define CONFVALUE 1
#define EP_MAXPKTSIZE 5
EP_RECORD ep_record[ 2 ]; //endpoint record structure for the mouse

void setup();
void loop();

MAX3421E Max;
USB Usb;

LiquidCrystal_I2C lcd(0x27,16,2);

double distance =0;

void setup()
{
lcd.init(); //初始化LCD
lcd.backlight(); //打开背光

Serial.begin( 115200 );
Serial.println("Start");
Max.powerOn();

lcd.setCursor(0,0);
lcd.print(" Distance ");

delay( 200 );
}

void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
mouse1_init();
}//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { //状态机运行
rcode = mouse1_poll();
if( rcode ) {
Serial.print("Mouse Poll Error: ");
Serial.println( rcode, HEX );
}//if( rcode...
}//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...

}
/* 鼠标初始化 */
void mouse1_init( void )
{
byte rcode = 0; //return code
byte tmpdata;
byte* byte_ptr = &tmpdata;
/**/
ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 )); //copy endpoint 0 parameters
ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
ep_record[ 1 ].sndToggle = bmSNDTOG0;
ep_record[ 1 ].rcvToggle = bmRCVTOG0;
Usb.setDevTableEntry( 1, ep_record );
/* Configure device */
rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
if( rcode ) {
Serial.print("Error configuring mouse. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}//if( rcode...
rcode = Usb.getIdle( DEVADDR, 0, 0, 0, (char *)byte_ptr );
if( rcode ) {
Serial.print("Get Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
Serial.print("Idle Rate: ");
Serial.print(( tmpdata * 4 ), DEC ); //rate is returned in multiples of 4ms
Serial.println(" ms");
tmpdata = 0;
rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata ); //切换为Boot Protocol
if( rcode ) {
Serial.print("Set Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
Usb.setUsbTaskState( USB_STATE_RUNNING );
return;
}
/* Poll mouse via interrupt endpoint and print result */
/* assumes EP1 as interrupt endpoint */
byte mouse1_poll( void )
{
byte rcode,i;
byte x;
byte y;
char res[12];

char buf[ 4 ] = { 0 };
/* poll mouse */
rcode = Usb.inTransfer( DEVADDR, 1, 4, buf, 1 ); //
if( rcode ) { //error
if( rcode == 0x04 ) { //NAK
rcode = 0;
}
return( rcode );
}
/* print buffer */
if( buf[ 0 ] & 0x01 ) {
distance=0;
}

//目前已经切换到 Boot Protocol, 输出的 Byte 2 3 分别是 X 和 Y
x=abs(buf[1]);
y=abs(buf[2]);

Serial.print("X-axis: ");
Serial.print( buf[1], DEC); Serial.print(" "); Serial.println( x, DEC);
Serial.print("Y-axis: ");
Serial.print( buf[2], DEC); Serial.print(" "); Serial.println(y , DEC);

distance=distance+sqrt(x*x+y*y); //计算移动距离
String dataString = "";
//Double转String
dataString = dtostrf(distance, 5, 4, res);

Serial.println(dataString);

lcd.setCursor(0,1);
lcd.print(" ");
lcd.print(dataString);
lcd.print(" ");

return( rcode );
}
制作过程也就是连接过程,Shield直接插,1602 LCD四根线,GND VCC SDA SCL 接对了就好。

image003

工作时就是这个样子

image005

实际上显示的是移动了多少点,具体还要进行换算为实际的距离,每个鼠标的DPI不同,相同的点位表示的实际距离也不相同。上述程序我没有做这个转换,如果有需要在编程的时候拉一个已知长度的线段折算一下即可知。

image007

工作视频

http://www.tudou.com/programs/view/hcsKy9hKCVg/?resourceId=414535982_06_02_99

完整的代码下载
mruler

完成之后,到了老婆提问时间,我们有如下对话
她:这个是干什么的啊?
我:这个是尺
她:尺子是干什么的?
我:尺子就是测量距离的啊
她:那为什么要测量距离?
我:比如一根直线你想知道它多长,需要测量啊
她:那家里有直尺为什么不用。
(这个问题很Sharp…….我想了一会才回答)
我:万一你要测量一个非直线的物体你需要用这个啊。比如,测量一个圆。
她:我为什么要测量一个圆的周长啊
我:你能不能问点有建设性的问题?
她:唔。那我可以用直尺测量圆的直径然后根据圆周率测量周长啊。
(又是一个Sharp的问题!)
我:直径不好确定,另外,没有直接测量方便啊!
她:那你要沿着圆走一圈哎~ 这样不准
(更加Sharp的问题!!我想了一下)
我:从理论上来说,人类无法直接测量出一个圆的周长,因为Pi是无限不循环小数,周长一定也是一个无限不循环小数。人类历史上在逼近圆周率的时候就是采用……..
她:为什么要测量周长呢?
(沉思片刻)
我:比如,你要测量一个椭圆的周长
她:椭圆周长有公式,数学书上有,虽然我现在不记得但是确定有。
(长考虑中…….)
我:再举个例子,比如,你要测量一个抛物线(嗯,这个她听过,肯定也知道公式)。不~ 你要测量一个悬链线(我打赌她肯定都没有听说过)。
她:为什么我要…….
我:不说了,我去唱歌了 《1999谢谢你的爱》

参考:
1. 图片来自 http://it.21cn.com/hardware/mouse/a/2009/0428/20/6209325.shtml 蓝影PK激光!最强游戏鼠标“溜冰“测试
2. http://acc.pconline.com.cn/mouse/study_mouse/0902/1550361_1.html 让你一夜成高手!鼠标知识最全面充电宝典
3. http://www.lab-z.com/usbkb/ Arduino 控制USB设备(5)解析USB键盘的例子

Arduino 控制USB设备(5)解析USB键盘的例子

下面是一个获得 USB 键盘数据的例子【参考1】。原理上说,是将键盘切换为 Boot Protocol 这样就避免了需要具体解析HID的麻烦。

/* MAX3421E USB Host controller LCD/keyboard demonstration */
//#include <Spi.h>
#include "Max3421e.h"
#include "Usb.h"

/* keyboard data taken from configuration descriptor */
#define KBD_ADDR        1
#define KBD_EP          1
#define KBD_IF          0
#define EP_MAXPKTSIZE   8
#define EP_POLL         0x0a
/**/
//******************************************************************************
//  macros to identify special charaters(other than Digits and Alphabets)
//******************************************************************************
#define BANG        (0x1E)
#define AT          (0x1F)
#define POUND       (0x20)
#define DOLLAR      (0x21)
#define PERCENT     (0x22)
#define CAP         (0x23)
#define AND         (0x24)
#define STAR        (0x25)
#define OPENBKT     (0x26)
#define CLOSEBKT    (0x27)

#define RETURN      (0x28)
#define ESCAPE      (0x29)
#define BACKSPACE   (0x2A)
#define TAB         (0x2B)
#define SPACE       (0x2C)
#define HYPHEN      (0x2D)
#define EQUAL       (0x2E)
#define SQBKTOPEN   (0x2F)
#define SQBKTCLOSE  (0x30)
#define BACKSLASH   (0x31)
#define SEMICOLON   (0x33)
#define INVCOMMA    (0x34)
#define TILDE       (0x35)
#define COMMA       (0x36)
#define PERIOD      (0x37)
#define FRONTSLASH  (0x38)
#define DELETE      (0x4c)
/**/
/* Modifier masks. One for both modifiers */
#define SHIFT       0x22
#define CTRL        0x11
#define ALT         0x44
#define GUI         0x88
/**/
/* "Sticky keys */
#define CAPSLOCK    (0x39)
#define NUMLOCK     (0x53)
#define SCROLLLOCK  (0x47)
/* Sticky keys output report bitmasks */
#define bmNUMLOCK       0x01
#define bmCAPSLOCK      0x02
#define bmSCROLLLOCK    0x04
/**/
EP_RECORD ep_record[ 2 ];  //endpoint record structure for the keyboard

char buf[ 8 ] = { 0 };      //keyboard buffer
char old_buf[ 8 ] = { 0 };  //last poll
/* Sticky key state */
bool numLock = false;
bool capsLock = false;
bool scrollLock = false;
bool line = false;

void setup();
void loop();

MAX3421E Max;
USB Usb;

void setup() {
  Serial.begin( 9600 );
  Serial.println("Start");
  Max.powerOn();
  delay( 200 );
}

void loop() {
    Max.Task();
    Usb.Task();
    if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) { 
 //wait for addressing state
        kbd_init();
        Usb.setUsbTaskState( USB_STATE_RUNNING );
    }
    if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  
//poll the keyboard  
        kbd_poll();
    }
}
/* Initialize keyboard */
void kbd_init( void )
{
 byte rcode = 0;  //return code
/**/
    /* Initialize data structures */
    ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 ));  
                           //copy endpoint 0 parameters
    ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
    ep_record[ 1 ].Interval  = EP_POLL;
    ep_record[ 1 ].sndToggle = bmSNDTOG0;
    ep_record[ 1 ].rcvToggle = bmRCVTOG0;
    Usb.setDevTableEntry( 1, ep_record );              
                           //plug kbd.endpoint parameters to devtable
    /* Configure device */
    rcode = Usb.setConf( KBD_ADDR, 0, 1 );                    
    if( rcode ) {
        Serial.print("Error attempting to configure keyboard. Return code :");
        Serial.println( rcode, HEX );
        while(1);  //stop
    }
    /* Set boot protocol */
    rcode = Usb.setProto( KBD_ADDR, 0, 0, 0 );
    if( rcode ) {
        Serial.print("Error attempting to configure boot protocol. Return code :");
        Serial.println( rcode, HEX );
        while( 1 );  //stop
    }
    delay(2000);
    Serial.println("Keyboard initialized");
}

/* Poll keyboard and print result */
/* buffer starts at position 2, 0 is modifier key state and 1 is irrelevant */
void kbd_poll( void )
{
 char i;
 static char leds = 0;
 byte rcode = 0;     //return code
    /* poll keyboard */
    rcode = Usb.inTransfer( KBD_ADDR, KBD_EP, 8, buf );
    if( rcode != 0 ) {
        return;
    }//if ( rcode..
    for( i = 2; i < 8; i++ ) {
     if( buf[ i ] == 0 ) {  //end of non-empty space
        break;
     }
      if( buf_compare( buf[ i ] ) == false ) {   //if new key
        switch( buf[ i ] ) {
          case CAPSLOCK:
            capsLock =! capsLock;
            leds = ( capsLock ) ? leds |= bmCAPSLOCK : leds &= ~bmCAPSLOCK;    
                      // set or clear bit 1 of LED report byte
            break;
          case NUMLOCK:
            numLock =! numLock;
            leds = ( numLock ) ? leds |= bmNUMLOCK : leds &= ~bmNUMLOCK;       
                                  // set or clear bit 0 of LED report byte
            break;
          case SCROLLLOCK:
            scrollLock =! scrollLock;
            leds = ( scrollLock ) ? leds |= bmSCROLLLOCK : leds &= ~bmSCROLLLOCK; 
                                  // set or clear bit 2 of LED report byte
            break;
          case DELETE:
            line = false;
            break;
          case RETURN:
            line =! line;
            break;  
          default:
            //Serial.print(HIDtoA( buf[ i ], buf[ 0 ] ));
            break;
        }//switch( buf[ i ...
        Serial.print(buf[ i ],HEX);
        Serial.print(' ');        
        Serial.println(buf[ 0 ],HEX);        
        rcode = Usb.setReport( KBD_ADDR, 0, 1, KBD_IF, 0x02, 0, &leds );
        if( rcode ) {
          Serial.print("Set report error: ");
          Serial.println( rcode, HEX );
        }//if( rcode ...
     }//if( buf_compare( buf[ i ] ) == false ...
    }//for( i = 2...
    for( i = 2; i < 8; i++ ) {                    //copy new buffer to old
      old_buf[ i ] = buf[ i ];
    }
}
/* compare byte against bytes in old buffer */
bool buf_compare( byte data )
{
 char i;
 for( i = 2; i < 8; i++ ) {
   if( old_buf[ i ] == data ) {
     return( true );
   }
 }
 return( false );
}

/* HID to ASCII converter. Takes HID keyboard scancode, returns ASCII code */
byte HIDtoA( byte HIDbyte, byte mod )
{
  /* upper row of the keyboard, numbers and special symbols */
  if( HIDbyte >= 0x1e && HIDbyte <= 0x27 ) {
    if(( mod & SHIFT ) || numLock ) {    //shift key pressed
      switch( HIDbyte ) {
        case BANG:  return( 0x21 );
        case AT:    return( 0x40 );
        case POUND: return( 0x23 );
        case DOLLAR: return( 0x24 );
        case PERCENT: return( 0x25 );
        case CAP: return( 0x5e );
        case AND: return( 0x26 );
        case STAR: return( 0x2a );
        case OPENBKT: return( 0x28 );
        case CLOSEBKT: return( 0x29 );
      }//switch( HIDbyte...
    }
    else {                  //numbers
      if( HIDbyte == 0x27 ) {  //zero
        return( 0x30 );
      }
      else {
        return( HIDbyte + 0x13 );
      }
    }//numbers
  }//if( HIDbyte >= 0x1e && HIDbyte <= 0x27
  /**/
  /* number pad. Arrows are not supported */
  if(( HIDbyte >= 0x59 && HIDbyte <= 0x61 ) && 
                   ( numLock == true )) {  // numbers 1-9
    return( HIDbyte - 0x28 );
  }
  if(( HIDbyte == 0x62 ) && ( numLock == true )) {  //zero
    return( 0x30 );
  }
  /* Letters a-z */
  if( HIDbyte >= 0x04 && HIDbyte <= 0x1d ) {
    if((( capsLock == true ) && ( mod & SHIFT ) == 0 ) 
          || (( capsLock == false ) && ( mod & SHIFT ))) {  
                     //upper case
      return( HIDbyte + 0x3d );
    }
    else {  //lower case
      return( HIDbyte + 0x5d );
    }
  }//if( HIDbyte >= 0x04 && HIDbyte <= 0x1d...
  /* Other special symbols */
  if( HIDbyte >= 0x2c && HIDbyte <= 0x38 ) {
    switch( HIDbyte ) {
      case SPACE: return( 0x20 ); 
      case HYPHEN:
        if(( mod & SHIFT ) == false ) {
         return( 0x2d );
        }
        else {
          return( 0x5f );
        }
      case EQUAL:
       if(( mod & SHIFT ) == false ) {
        return( 0x3d );
       }
       else {
        return( 0x2b );
       }
       case SQBKTOPEN:
         if(( mod & SHIFT ) == false ) {
          return( 0x5b );
         }
         else {
          return( 0x7b );
         }
       case SQBKTCLOSE:
         if(( mod & SHIFT ) == false ) {
          return( 0x5d );
         }
         else {
          return( 0x7d );
         } 
       case BACKSLASH:
         if(( mod & SHIFT ) == false ) {
           return( 0x5c );
         }
         else {
           return( 0x7c );
         }
       case SEMICOLON:
         if(( mod & SHIFT ) == false ) {
           return( 0x3b );
         }
         else {
           return( 0x3a );
         }
      case INVCOMMA:
        if(( mod & SHIFT ) == false ) {
          return( 0x27 );
        }
        else {
          return( 0x22 );
        }
      case TILDE:  
        if(( mod & SHIFT ) == false ) {
          return( 0x60 );
        }
        else {
          return( 0x7e );
        }
      case COMMA:   
        if(( mod & SHIFT ) == false ) {
          return( 0x2c );
        }
        else {
          return( 0x3c );
        }
      case PERIOD:
        if(( mod & SHIFT ) == false ) {
          return( 0x2e );
        }
        else {
          return( 0x3e );
        }
      case FRONTSLASH:
        if(( mod & SHIFT ) == false ) {
          return( 0x2f );
        }
        else {
          return( 0x3f );
        }
      default:
        break;
    }//switch( HIDbyte..
  }//if( HIDbye >= 0x2d && HIDbyte <= 0x38..
  return( 0 );
}

实验依然使用上一次的USB小键盘。上面的按键分布如下:

usbskb1

关于键值的介绍可以在【参考1】找到

NumLock OFF的情况下,各输出键值:

usbskb2
*输出三次62,相当于输出3个0

NumLock ON的情况下,各输出键值:
usbskb3
*输出三次62外加一个53

运行结果

kbr

本文完整代码下载 LCDkbd

参考:
1. https://github.com/felis/USB_Host_Shield/tree/master/examples/descriptor_parser USB_Host_Shield/examples/LCDkbd/ 本文原始代码
2. http://www.quadibloc.com/comp/scan.htm