极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 22606|回复: 5

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

[复制链接]
发表于 2015-8-27 21:37:49 | 显示全部楼层 |阅读模式
前面介绍了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】的代码如下:

  1. #include "Max3421e.h"
  2. #include "Usb.h"


  3. #define DEVADDR 1
  4. #define CONFVALUE 1

  5. void setup();
  6. void loop();

  7. MAX3421E Max;
  8. USB Usb;

  9. void setup()
  10. {
  11.     Serial.begin( 115200 );
  12.     Serial.println("Start");
  13.     Max.powerOn();
  14.     delay( 200 );
  15. }

  16. void loop()
  17. {
  18. byte rcode;
  19.     Max.Task();
  20.     Usb.Task();
  21.     if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
  22.         mouse0_init();
  23.     }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
  24.     if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  //poll the keyboard
  25.         rcode = mouse0_poll();
  26.         if( rcode ) {
  27.           Serial.print("Mouse Poll Error: ");
  28.           Serial.println( rcode, HEX );
  29.         }//if( rcode...
  30.     }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
  31. }
  32. /* Initialize mouse */
  33. void mouse0_init( void )
  34. {
  35. byte rcode = 0;  //return code
  36.   /**/
  37.   Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) );              //copy device 0 endpoint information to device 1
  38.   /* Configure device */
  39.   rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  40.   if( rcode ) {
  41.     Serial.print("Error configuring mouse. Return code : ");
  42.     Serial.println( rcode, HEX );
  43.     while(1);  //stop
  44.   }//if( rcode...

  45.   Usb.setUsbTaskState( USB_STATE_RUNNING );
  46.   return;
  47. }
  48. /* Poll mouse using Get Report and print result */
  49. byte mouse0_poll( void )
  50. {
  51.   byte rcode,i;
  52.   char buf[ 4 ] = { 0 };      //mouse buffer
  53.   static char old_buf[ 4 ] = { 0 };  //last poll
  54.     /* poll mouse */
  55.     rcode = Usb.getReport( DEVADDR, 0, 4, 0, 1, 0, buf );
  56.     if( rcode ) {  //error
  57.       return( rcode );
  58.     }
  59.     for( i = 0; i < 4; i++) {  //check for new information
  60.       if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
  61.         break;
  62.       }
  63.     }
  64.     if( i == 4 ) {
  65.       return( 0 );  //all bytes are the same
  66.     }
  67.     /* print buffer */
  68.     if( buf[ 0 ] & 0x01 ) {
  69.       Serial.print("Button1 pressed ");
  70.     }
  71.     if( buf[ 0 ] & 0x02 ) {
  72.       Serial.print("Button2 pressed ");
  73.     }
  74.     if( buf[ 0 ] & 0x04 ) {
  75.       Serial.print("Button3 pressed ");
  76.     }
  77.     Serial.println("");
  78.     Serial.print("X-axis: ");
  79.     Serial.println( buf[ 1 ], DEC);
  80.     Serial.print("Y-axis: ");
  81.     Serial.println( buf[ 2 ], DEC);
  82.     Serial.print("Wheel: ");
  83.     Serial.println( buf[ 3 ], DEC);
  84.    
  85.     for( i = 0; i < 4; i++ ) {
  86.       old_buf[ i ] = buf[ i ];  //copy buffer
  87.     }
  88.     Serial.println("");
  89.     return( rcode );
  90. }
复制代码


运行结果



完整代码下载


看起来按键和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]应该是最高的符号位



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

  1.     Serial.print("X-axis: ");
  2.     x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
  3.     if (x & 0x800) {
  4.       Serial.print("-");
  5.       x = ((~x) & 0x7FF) +1;
  6.     }
  7.    
  8.     Serial.println(x, HEX);
  9.    
  10.     Serial.print("Y-axis: ");   
  11.     y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
  12.     if (y & 0x800) {
  13.       Serial.print("-");
  14.       y = ((~y) & 0x7FF) +1;
  15.     }   
  16. Serial.println(y, HEX);
复制代码



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

工作正常的程序如下:

  1. #include "Max3421e.h"
  2. #include "Usb.h"


  3. #define DEVADDR 1
  4. #define CONFVALUE 1

  5. void setup();
  6. void loop();

  7. MAX3421E Max;
  8. USB Usb;

  9. void setup()
  10. {
  11.     Serial.begin( 115200 );
  12.     Serial.println("Start");
  13.     Max.powerOn();
  14.     delay( 200 );
  15. }

  16. void loop()
  17. {
  18. byte rcode;
  19.     Max.Task();
  20.     Usb.Task();
  21.     if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
  22.         mouse0_init();
  23.     }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
  24.     if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {  //poll the keyboard
  25.         rcode = mouse0_poll();
  26.         if( rcode ) {
  27.           Serial.print("Mouse Poll Error: ");
  28.           Serial.println( rcode, HEX );
  29.         }//if( rcode...
  30.     }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
  31. }
  32. /* Initialize mouse */
  33. void mouse0_init( void )
  34. {
  35. byte rcode = 0;  //return code
  36.   /**/
  37.   Usb.setDevTableEntry( 1, Usb.getDevTableEntry( 0,0 ) );              //copy device 0 endpoint information to device 1
  38.   /* Configure device */
  39.   rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  40.   if( rcode ) {
  41.     Serial.print("Error configuring mouse. Return code : ");
  42.     Serial.println( rcode, HEX );
  43.     while(1);  //stop
  44.   }//if( rcode...

  45.   Usb.setUsbTaskState( USB_STATE_RUNNING );
  46.   return;
  47. }
  48. /* Poll mouse using Get Report and print result */
  49. byte mouse0_poll( void )
  50. {
  51.   byte rcode,i;
  52.   char buf[ 5 ] = { 0 };      //mouse buffer
  53.   static char old_buf[ sizeof(buf) ] = { 0 };  //last poll
  54.   unsigned long int libuf[ sizeof(buf) ];
  55.   unsigned long int x;
  56.   unsigned long int y;
  57.   
  58.     /* poll mouse */
  59.     rcode = Usb.getReport( DEVADDR, 0, sizeof(buf), 0, 1, 0, buf );
  60.     if( rcode ) {  //error
  61.       return( rcode );
  62.     }
  63.     for( i = 0; i < sizeof(buf); i++) {  //check for new information
  64.       if( buf[ i ] != old_buf[ i ] ) { //new info in buffer
  65.         break;
  66.       }
  67.     }
  68.     if( i == sizeof(buf)) {
  69.       return( 0 );  //all bytes are the same
  70.     }
  71.     /* print buffer */
  72.     if( buf[ 0 ] & 0x01 ) {
  73.       Serial.print("Button1 pressed ");
  74.     }
  75.     if( buf[ 0 ] & 0x02 ) {
  76.       Serial.print("Button2 pressed ");
  77.     }
  78.     if( buf[ 0 ] & 0x04 ) {
  79.       Serial.print("Button3 pressed ");
  80.     }
  81.    
  82.     for( i = 0; i < sizeof(buf); i++ ) {
  83.       old_buf[ i ] = buf[ i ];  //copy buffer
  84.       libuf[ i] = buf[i];
  85.     }

  86.     //Serial.print(libuf[0],HEX); Serial.print("  ");
  87.     //Serial.print(libuf[1],HEX); Serial.print("  ");
  88.     //Serial.print(libuf[2],HEX); Serial.print("  ");
  89.     //Serial.print(libuf[3],HEX); Serial.print("  ");     
  90.     //Serial.println("");
  91.    
  92.     Serial.print("X-axis: ");
  93.     x=((libuf[2] & 0xF)<<8)+(libuf[1] & 0xFF );
  94.     if (x & 0x800) {
  95.       Serial.print("-");
  96.       x = ((~x) & 0x7FF) +1;
  97.     }
  98.    
  99.     Serial.println(x, HEX);
  100.    
  101.     Serial.print("Y-axis: ");   
  102.     y=(((libuf[2]>>4) & 0xF) +((libuf[3] & 0xFF )<<4));
  103.     if (y & 0x800) {
  104.       Serial.print("-");
  105.       y = ((~y) & 0x7FF) +1;
  106.     }   
  107.     Serial.println(y, HEX);

  108.     Serial.print("Wheel: ");   
  109.     Serial.println(buf [4] & 0xFF, HEX);   
  110.     return( rcode );
  111. }
复制代码


完整代码下载:

补遗:在运行中,我还发现了一个奇怪的问题,鼠标向一个方向移动,会出现最大值。意思是:比如,向右一直移动鼠标,输出达到 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 方式来和鼠标通讯





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





本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

 楼主| 发表于 2015-8-27 21:54:23 | 显示全部楼层

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





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









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






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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 1 反对 0

使用道具 举报

 楼主| 发表于 2015-8-27 21:56:16 | 显示全部楼层

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





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









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






参考:
1.https://www.circuitsathome.com/c ... -hid-devices-part-1
2.http://www.microchip.com/forums/m482197.aspx MOUSE HID / Resolution problem
回复 支持 反对

使用道具 举报

发表于 2015-8-28 14:38:24 | 显示全部楼层
大神,没看明白这个可以用来干啥?
最近用Labview控制鼠标按时自动打卡的外挂,效果还不错。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2015-8-28 15:12:07 | 显示全部楼层
PINKWALKMAN 发表于 2015-8-28 14:38
大神,没看明白这个可以用来干啥?
最近用Labview控制鼠标按时自动打卡的外挂,效果还不错。

前几天用这个做了一个鼠标尺 http://www.guokr.com/post/696209/
回复 支持 反对

使用道具 举报

发表于 2015-8-28 17:49:53 | 显示全部楼层
zoologist 发表于 2015-8-28 15:12
前几天用这个做了一个鼠标尺 http://www.guokr.com/post/696209/

搜嘎!还是蛮好玩的。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|联系我们|极客工坊

GMT+8, 2026-6-16 22:17 , Processed in 0.036978 second(s), 20 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表