查看: 18626|回复: 37

U2B: USB键盘转蓝牙键盘的设备

[复制链接]

该用户从未签到

发表于 2015-9-17 13:18 | 显示全部楼层 |阅读模式
本帖最后由 Zoologist 于 2015-9-17 21:48 编辑

本文介绍如何使用 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蓝牙模块连接在一起。


092722d4h4ed8eqh0qpmm4.jpg







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


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


092803o6aybbg6eescygd7.jpg

插好之后就是这样

092826qvfluyuvxnrvlfgn.jpg


kittenblock中小学创客名师推荐的图形化编程软件

/* 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[1]);  //BYTE5          
          Serial.write(0x00);  //BYTE6          
          Serial.write(buf[2]);  //BYTE7
          Serial.write(buf[3]);  //BYTE8
          Serial.write(buf[4]);  //BYTE9
          Serial.write(buf[5]);  //BYTE10          
          Serial.write(buf[6]);  //BYTE11
          Serial.write(buf[7]);  //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上工作的视频可以在

完整代码下载
zip.gif BKC2COM.zip (20.68 KB, 下载次数: 1) [size=0.83em]3 小时前 上传
点击文件名下载附件





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

参考:
1.     http://www.lab-z.com/btkeyboard/蓝牙键盘模块的实验



该用户从未签到

发表于 2015-11-13 15:55 | 显示全部楼层
如果我用32u4的 keyboard功能请问该如何做?

点评

那你的目标是什么? Uno 解析Usb键盘 再从 32u4出去?  发表于 2015-11-14 20:37

该用户从未签到

发表于 2015-11-13 15:56 | 显示全部楼层
新手,请推荐一个HID蓝牙芯片

点评

https://item.taobao.com/item.htm?spm=a1z09.2.0.0.ha7tIZ&id=521222818182&_u=ekf8s9b41a 这家买的,你和老板说,你要做 键盘的  发表于 2015-11-14 20:39

该用户从未签到

发表于 2016-1-14 13:07 | 显示全部楼层
楼主你好,您提供的这个源代码里面的底层文件是MAC系统的、请问这个在应用Windows系统中是否会出现问题呢?

该用户从未签到

 楼主| 发表于 2016-1-14 15:51 | 显示全部楼层
zhaoroc 发表于 2016-1-14 13:07
楼主你好,您提供的这个源代码里面的底层文件是MAC系统的、请问这个在应用Windows系统中是否会出现问题呢? ...

没有mac系统的文件,我只是在 pad上演示

x86的 windows也可以用,没问题的

该用户从未签到

发表于 2016-1-20 17:06 | 显示全部楼层
楼主你好,我买了你推荐的蓝牙模块,但是没有你图中的底座,请问何如接线呢?

该用户从未签到

 楼主| 发表于 2016-1-20 21:27 | 显示全部楼层
zhaoroc0913 发表于 2016-1-20 17:06
楼主你好,我买了你推荐的蓝牙模块,但是没有你图中的底座,请问何如接线呢? ...

那个底座就是一个面包板,主要是链接串口的 rx tx 和  gnd

建议你先找个 usb转串口线试试这个模块发送
  • TA的每日心情
    开心
    2015-9-9 16:15
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2016-1-25 14:03 | 显示全部楼层
    如何改成蓝牙鼠标呢?

    该用户从未签到

     楼主| 发表于 2016-1-25 19:53 | 显示全部楼层
    三好学生 发表于 2016-1-25 14:03
    如何改成蓝牙鼠标呢?

    应该也差不多,只是可能牵扯到速度的问题

    鼠标对于灵敏度的要求远比键盘高啊
  • TA的每日心情
    奋斗
    2016-9-18 21:59
  • 签到天数: 24 天

    [LV.4]偶尔看看III

    发表于 2016-2-4 08:43 | 显示全部楼层
    本帖最后由 nickz 于 2016-2-4 08:53 编辑

    用了一周时间来研究Arduino UNO , USB Host Shield 和 HID 蓝牙芯片,当然了东西都是用的楼主推荐的东西,USB Host Shield 用的是推荐的蓝色板子的那种。这里说下hid的蓝牙键盘模块,客服只给发模块的pdf,键盘包的编码表没有win这个键的编码,用USB Host Shield 抓取键盘包解也看不出win的是什么走了一段弯路。第二个难点就是对C语言真的是崩溃,找了好多资料才能知道楼主的程序那块是干嘛的。最后说下楼主的程序有个小bug,只能输入小写,单独按下shift会处于一直按下的状态,修改最后的发送编码包和比较部分就可以了,别的都不用动。

    kittenblock中小学创客名师推荐的图形化编程软件

    i = 0;
            //扫描8位缓存区与当前是否一致,如果一致表示没有按下按键,否则按下了。按下了此时i=12,i++为13
            while (i < 8)
            {
                    if (old_buf != buf)
                    {
                            i = 12;
                    }
                    i++;
            }
            if (i == 13) //按下了新按键,输出按键内容
            {
                   Serial.write(0x0c);  //BYTE1      
                    Serial.write(0x00);  //BYTE2
                    Serial.write(0xA1);  //BYTE3
                    Serial.write(0x01);  //BYTE4
                    Serial.write(buf[0]);//BYTE5                此位为ctrl,alt,shift,gui判别位
                    Serial.write(buf[1]);  //BYTE6                固定为0x00怎么来怎么发   
                    Serial.write(buf[2]);  //BYTE7                按下的按键编码
                    Serial.write(buf[3]);  //BYTE8                按下的按键编码
                    Serial.write(buf[4]);  //BYTE9                按下的按键编码
                    Serial.write(buf[5]);  //BYTE10            按下的按键编码    
                    Serial.write(buf[6]);  //BYTE11                按下的按键编码
                    Serial.write(buf[7]);  //BYTE12                按下的按键编码
            }
    
            for (i = 0; i < 8; i++) {                    //将当前编码写入旧编码区
                    old_buf = buf;
            }


    那个蓝牙键盘模块,客服给发的pdf中写的,支持休眠控制,理论上应该在每次按键盘的时候加个唤醒,但是问题又来了,pdf里面给的是独立模块的,买的时候客服还送了个托板,托班有6个引脚,4根是数据和电源,还两根一个写的CE/CLR 还一个写的STATE不知道具体是干嘛的,问客服也没说,我英文还不知道不知道具体都是什么的简写,希望有知道的能告诉我。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    热门推荐

    [限时福利]5分钟带你快速了解新一代开发板:M5STACK
    [限时福利]5分钟带你快速
    一、什么是M5Stack M5Stack是一种模块化、可堆叠扩展的开发板,每个模块
    点灯·blinker-1物联网初次尝试-Arduino物联网控制LED灯-零基础篇
    点灯·blinker-1物联网初
    疫情期间,待在家中在抖音上看到“暴改车间”分享的手机小爱同学物联网控制电脑开
    OLED 128*64自制可达10000000个选项的菜单(已更新)
    OLED 128*64自制可达10000
    OLED 128*64自制可达10000000个选项的菜单 温馨提示: 建议占个楼再食用本帖子
    萌新跪求arduinoUNO板对接无线模块(如何接和程序)
    萌新跪求arduinoUNO板对接
    哪位dalao能帮帮我啊???急!!! (提供有偿服务可加我QQ3285396460)
    DHT11温度模块写不了
    DHT11温度模块写不了
    Arduino:1.8.11 (Windows 10), 开发板:"Generic ESP8266 Module, 80 MHz, Flash, ck,
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
    快速回复 返回顶部 返回列表