查看: 19830|回復: 40
打印 上一主題 下一主題

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藍牙模塊連接在一起。










原理:首先,為了通用性和編程簡單,我們用USB HOST發送命令把鍵盤切換到Boot Protocol 模式下。這樣即使不同的鍵盤,每次發出來的數據也都是統一的格式。然后,我們直接讀取緩沖數據就可以解析出按鍵信息了。最后,將取下來的按鍵信息(ScanCode)按照HID藍牙模塊的格式要求通過串口送到模塊上,主機端就收到了。


上述連接就可以正常工作了,但是為了美觀和提高可靠性,我找到之前買的一個面包板Shield。




插好之后就是這樣




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上工作的視頻可以在

完整代碼下載
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系統中是否會出現問題呢?

該用戶從未簽到

5#
 樓主| 發表于 2016-1-14 15:51 | 只看該作者
zhaoroc 發表于 2016-1-14 13:07
樓主你好,您提供的這個源代碼里面的底層文件是MAC系統的、請問這個在應用Windows系統中是否會出現問題呢? ...

沒有mac系統的文件,我只是在 pad上演示

x86的 windows也可以用,沒問題的

該用戶從未簽到

6#
發表于 2016-1-20 17:06 | 只看該作者
樓主你好,我買了你推薦的藍牙模塊,但是沒有你圖中的底座,請問何如接線呢?

該用戶從未簽到

7#
 樓主| 發表于 2016-1-20 21:27 | 只看該作者
zhaoroc0913 發表于 2016-1-20 17:06
樓主你好,我買了你推薦的藍牙模塊,但是沒有你圖中的底座,請問何如接線呢? ...

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

建議你先找個 usb轉串口線試試這個模塊發送
  • TA的每日心情
    開心
    2015-9-9 16:15
  • 簽到天數: 2 天

    [LV.1]初來乍到

    8#
    發表于 2016-1-25 14:03 | 只看該作者
    如何改成藍牙鼠標呢?

    該用戶從未簽到

    9#
     樓主| 發表于 2016-1-25 19:53 | 只看該作者
    三好學生 發表于 2016-1-25 14:03
    如何改成藍牙鼠標呢?

    應該也差不多,只是可能牽扯到速度的問題

    鼠標對于靈敏度的要求遠比鍵盤高啊
  • TA的每日心情
    奮斗
    2016-9-18 21:59
  • 簽到天數: 24 天

    [LV.4]偶爾看看III

    10#
    發表于 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不知道具體是干嘛的,問客服也沒說,我英文還不知道不知道具體都是什么的簡寫,希望有知道的能告訴我。
    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規則

    熱門推薦

    兩個ESP8266通訊,有大佬會的嗎?
    兩個ESP8266通訊,有大佬會
    我可以讓手機APP和8266遠程傳輸一些數據,現在要加多一個8266,應該怎么做?
    arduino連接ESP8266-01給微信發消息,消息提醒等
    arduino連接ESP8266-01給
    材料準備 ESP8266-01/ESP8266-01s一塊 arduino 一塊 手機一塊 接線 ESP8266-0
    我有個大膽的想法,大神請進
    我有個大膽的想法,大神請
    在B站,偶爾看到有大神用單片機做出各種智能設備,感覺很神奇,突然茅塞頓開,意識到
    又雙叒更新同步開關狀態 ,小愛同學+app+本地按鍵控制狀.....
    又雙叒更新同步開關狀態
    你有沒有發現app上面的按鍵在操作完畢后你就不知道開關的狀態了;或者小愛操作完畢后
    小白求助,急急急,各路大神幫幫忙
    小白求助,急急急,各路大
    傳感器技術,不知如何寫代碼
    Copyright   ©2015-2016  Arduino中文社區  Powered by©Discuz!   
    快速回復 返回頂部 返回列表
    财神捕鱼游戏手机版下载