U2B: USB键盘转蓝牙键盘的设备 【已经完成】-Arduino中文社区 - Powered by Discuz! Archiver

Zoologist 发表于 2015-9-7 13:29

U2B: USB键盘转蓝牙键盘的设备 【已经完成】

参赛项目:
USB 键盘对蓝牙键盘转换器

参赛组员:
2人
Zoologist -普通程序员, Arduino 爱好者,负责代码编写调试Yana         -艺术工作者,负责产品美化

项目简介:
很多时候,我们更希望用无线的方式来使用键盘,但是通常喜欢的键盘都没有蓝牙版本的,反之蓝牙版本的键盘手感好的太贵。
因此,这里我使用 Arduino 来完成 USB键盘对蓝牙的转换,做一个转换模块。

预计完成时间:
预计9月下旬完成

团队照片:



Zoologist 发表于 2015-9-15 22:22

原型已经完成,在笔记本和IPAD上都测试OK
资料正在整理中,后面会放出详细的指南

Zoologist 发表于 2015-9-17 09:30

本帖最后由 Zoologist 于 2015-9-17 13:19 编辑

本文介绍如何使用 Arduino打造一个设备,能够将你的USB键盘转化为蓝牙键盘。
键盘可以算作PC上最古老的设备了,他的出现使得人类可以用非常简单的方法与电脑进行交互。同样的,由于各种历史原因,键盘也是PC上最复杂,兼容性问题最多的设备之一(类似的还有硬盘,不过从IDE到SATA的进化过程中,标准明确,兼容性问题少多了)。
网上流传着一篇DIY USB键盘转换为无线的文章,非常不幸的是,那篇文章是错误的,很明显的错误是作者认为键盘是单向传输,而实际上传输是双向的。比如,USB每次通讯都需要HOST和SLAVE的参与,即便是PS2键盘的通讯也同样如此。此外,大小写键之类切换是主机端进行控制的。
硬件部分Arduino UNO , USB Host Shield 和 HID 蓝牙芯片。强调一下这里使用的是 HID 蓝牙芯片,并非普通的蓝牙串口透传芯片。关于这个模块可以参考我在【参考1】中的实验。
硬件连接很简单,USB HOST Shield插在 Arduino上,然后VCC/GND/TX/RX将Arduino 和 HID蓝牙模块连接在一起。


原理:首先,为了通用性和编程简单,我们用USB HOST发送命令把键盘切换到Boot Protocol 模式下。这样即使不同的键盘,每次发出来的数据也都是统一的格式。然后,我们直接读取缓冲数据就可以解析出按键信息了。最后,将取下来的按键信息(ScanCode)按照HID蓝牙模块的格式要求通过串口送到模块上,主机端就收到了。

上述连接就可以正常工作了,但是为了美观和提高可靠性,我找到之前买的一个面包板Shield。


插好之后就是这样



/* 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;
boolean samemark=true;
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
            
          Serial.write(0x0c);//BYTE1      
          Serial.write(0x00);//BYTE2
          Serial.write(0xA1);//BYTE3
          Serial.write(0x01);//BYTE4
          Serial.write(00);//BYTE5         
          Serial.write(0x00);//BYTE6         
          Serial.write(0x1e);//BYTE7
          Serial.write(0);//BYTE8
          Serial.write(0);//BYTE9
          Serial.write(0);//BYTE10         
          Serial.write(0);//BYTE11
          Serial.write(0);//BYTE12
          delay(500);            
          Serial.write(0x0c);//BYTE1      
          Serial.write(0x00);//BYTE2
          Serial.write(0xA1);//BYTE3
          Serial.write(0x00);//BYTE4
          Serial.write(0);//BYTE5         
          Serial.write(0x00);//BYTE6         
          Serial.write(0);//BYTE7
          Serial.write(0);//BYTE8
          Serial.write(0);//BYTE9
          Serial.write(0);//BYTE10         
          Serial.write(0);//BYTE11
          Serial.write(0);//BYTE12
         
            
            
            break;
          case DELETE:
            line = false;
            break;
          case RETURN:
            line =! line;
            break;
          //default:
            //Serial.print(HIDtoA( buf[ i ], buf[ 0 ] ));
          //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 ...
   }//if( buf_compare( buf[ i ] ) == false ...
    }//for( i = 2...
   
    i=0;
    while (i<8)
      {
      if (old_buf!=buf) { i=12; }
      i++;
      }
    if (i==13) {
   // for (i=0;i<8;i++) {      
          //Serial.print(buf[ i ],HEX);
          //Serial.print(']');      
   //}
   // Serial.println(' ');         

          Serial.write(0x0c);//BYTE1      
          Serial.write(0x00);//BYTE2
          Serial.write(0xA1);//BYTE3
          Serial.write(0x01);//BYTE4
          Serial.write(buf);//BYTE5         
          Serial.write(0x00);//BYTE6         
          Serial.write(buf);//BYTE7
          Serial.write(buf);//BYTE8
          Serial.write(buf);//BYTE9
          Serial.write(buf);//BYTE10         
          Serial.write(buf);//BYTE11
          Serial.write(buf);//BYTE12
    }

    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 );
}


我在处理SCROLLLOCK 键的地方插入了一个测试代码,理论上按下这个键的时候,主机还会收到 1 这个字符,这样是为了测试工作是否正常。我在 x86 台式机上实测过,工作正常;小米4手机上实测过,工作正常;iPad 上是测过,工作也正常。在iPad上工作的视频可以在http://www.tudou.com/programs/view/6AtYX_qa044/?resourceId=414535982_06_02_99
完整代码下载

特别注意:1.   因为我们使用的是最简单的Boot Protocol,所以如果你的键盘上有音量键之类的有可能失效;2.   我不确定是否所有的键盘都会支持 Boot Protocol ,从之前玩USB鼠标的经验来看,确实有可能;3.   供电部分没有经过优化,不知道电力消耗如何,不确定一个充电宝能够工作的时间;参考:1.   http://www.lab-z.com/btkeyboard/蓝牙键盘模块的实验

ElecSpark 发表于 2015-9-17 12:02

感谢您参加本次比赛,请参照   项目提交要求 http://www.arduino.cn/thread-17410-1-1.html
按时提交项目

ElecSpark 发表于 2015-9-17 12:03

您本帖为报名帖,需新开一个项目贴,张贴二楼的内容。

Zoologist 发表于 2015-9-17 13:06

ElecSpark 发表于 2015-9-17 12:03
您本帖为报名帖,需新开一个项目贴,张贴二楼的内容。

哦 了解了
页: [1]
查看完整版本: U2B: USB键盘转蓝牙键盘的设备 【已经完成】