モノブロ

ソフトウェア/ハードウェアに関する技術的な話や作ったモノについて書いていこうと思います。間違いなどありましたらご指摘ください。

有線キーボードをBluetooth化するアダプタの作成

 

f:id:tamagawa000:20210123142722j:plain

 はじめに

けっこう前にブログをやろうと思ってアカウントを作ったけど全然書いていませんでした。

最近はコロナで外に出る機会が減っておうち時間が増えたので、前から興味があった電子工作を本格的に初めてみました。

今回は友人からの頼みで、有線キーボードをBluetooth化するアダプタを作ってみました。

自分はどうせやるならなるべく実用的なものを作りたくなってしまう性格なので、なるべく実用的な物を作っていきたいと思います。

多分何回かに分けてブログに書きます。

第1回目の今回はとりあえず動くところまで作ることができたので途中報告したいと思います。

 概要

友人の要望で、小型でバッテリー駆動にして欲しいとのことなので、最小限の部品と構成で作ることを意識しました。

自分はキーボードに関してもBluetoothに関しても全く知識がなかったので、とりあえずネットでいろいろ探してみました。

結果、下図のような構成になりました。


f:id:tamagawa000:20210123142339j:plain

全体構成

メイン部分

メイン部分にはArduino Pro mini(3.3v)を使用しました。

Arduino Pro miniには3.3v 8MHzと5.0v 16MHzの2種類あります。

3.3v 8MHzを選んだ理由はUSBHostShieldとBluetoothモジュールとの通信ラインが3.3vであるためです。

5.0v 16MHzのArduino Pro miniを使用する場合は、各通信ラインをレベルシフト(5.0v⇆3.3v)する必要があるので少し面倒で回路が増えます。

本当はもっと小型のPICやBLEモジュールとか使って作ってみたかったのですが、ちょっと参考になるサイトが少なかったのでやめました。

しかし、さらに省電力、小型化を目指すならやってみる価値はあるので、このプロジェクトが終了したらやってみます。

 

USB Host Shield 

Arduinoをホストとして、USB周辺機器をコントロールするために必要なものらしい。

正直USBについては全然わからないので、どのような理由で必要なのか分からない。

ただ、回路的にはこのシールドがないとUSBキーボードとArduino Pro mini を接続することができないと分かります。 Arduino Pro mini には USBと通信するためのピン(D+,D-)がないので直接接続できません。そこで、このUSB Host Shield を通してSPI通信でArduino Pro miniと接続してデータのやりとりをするんだなぁ〜ぐらいの理解です。

 

Bluetoothモジュール

キーボートから入力されたデータをPCにBluetoothを使って送るためにモジュールにRN42(Microchip社)のものを使用します。

ネットではこのモジュールを使った作例がたくさんありました。

このモジュールはArduino Pro mini からUART通信でデータを送るだけでPCに送信してくれるので簡単に使うことができます。

また、Bluetoothで接続したキーボードを有線接続した場合と同じようにPCに認識させるためにHIDプロファイル対応のBluetoothモジュールが必要でした。

手軽に使えてHIDプロファイル対応のBluetoothモジュールはRN42の他にあまりいいものが見つからなかったです....

何かいいものがあれば教えてください。

 

 必要な部品まとめ

RN42は調べてみるといろいろな型番のものがありました。

秋月電子ではRN42-I/RMだけで、Mouserなどで調べると、RN42-I/RMの他にRN42HID-I/RM、RN42-I/RM615などありました。

どこかのサイトでRN42HID-I/RMを使ったと書いてあったのでMouserで同じ型番の物を買ってみました。

まぁ、HIDってついてるし、キーボードに使えそうだなと思ったので。

しかし、実際に届いたモジュールの型番が、RN42-I/RMだったので困惑しました。。

型番にHIDついてなくてもできるのか不安でしたが実際にやってみたら普通に使えました。

Mouserだと配送料が2000円かかったので、秋月で買えば良かったと後悔しています...

あと、USBシリアル変換アダプターはArduino Pro mini の書き込みで使用します。

この辺はArduino Pro mini の使い方などのサイト見れば書いてあります。

部品はSwitch Science、秋月電子Amazonで全部揃いますね。

 作成

USBホストシールドの加工

今回購入したUSBホストシールドは、Arduino Pro mini と重ねて使用するようにしました。

そこでちょっと加工が必要になります。 


f:id:tamagawa000:20210123144406j:plain

USB Host Shieldのパターンカット

まずは、パターンを1箇所カット。

USB機器を駆動させるためにVBusには5vを供給してあげる必要があります。そして、USBホストシールドに載っているMAX3421EはVccに3.3vが必要である。

しかし、ボードのパターンはVBusとVccがつながっているため、VBusに5vを供給するとVccにも5vが供給されてします。

なので、写真の部分のパターンをカットしてVBusとVccを分けるようにする。

次にUSBホストシールドのRAW端子からVBus端子に線をつなげる。

Arduino Pro mini と電源を共有するために、RAWからVBusに5V供給をするようにしました。

Arduino Pro mini はRAWから電源を供給してあげればレギュレータを通って3.3vを供給してくれるのでこれで大丈夫です。

 

Arduino Pro miniとUSBホストシールドの組み合わせ



f:id:tamagawa000:20210206103838j:plain
f:id:tamagawa000:20210206103855j:plain
ピンヘッダを付けた写真

Arduino Pro miniとUSBホストシールドを2段にして合わせるために、それぞれにピンヘッダとピンソケットを付けます。

Arduino側のピンヘッダは全部付けてもいいのですが、自分はなるべく無駄な物はつけたくない派なので必要なピンだけ付けました。

接続が必要な端子


f:id:tamagawa000:20210123152007j:plain

ArduinoProminiとminiUSBHostShieldの配線

お互いのRAW端子とGNDをつなげる。Arduino Pro mini のRAW端子から5v供給すると、Vccから3.3vが出るので、USBホストシールドのVccとつなぐ。

あとは、リセット端子もつなぐ。Arduino Pro miniには2箇所リセット端子があるけど、Vccの隣のリセット端子をつないでおけばオッケー。

また、SPI通信でデータのやりとりをするので、SS,SCLK,MISO,MOSIををつなげます。

SPI通信に必要な端子はArduino Pro mini 側とUSBホストシールド側で、重ねた時にちゃんと対応していました。

割り込み端子のINTは使ってるか分からなかったけど、一応つないでおきました。

 

RN42の線出し



f:id:tamagawa000:20210123093021j:plain
f:id:tamagawa000:20210123094703p:plain
RN42線だし

 今回購入したRN42はモジュール単体で、ピッチ変換基盤も評価ボードもないので自分でパッドに線を半田付けしないといけません。

必要な端子は、GND(1,12,28,29),VDD(11),UART_TX(14),UART_RX(13)の合計7つです。

これは一番大変な作業かもしれません。パッド1つの幅が0.8mmなので慎重に半田付けしなければなりません。隣のパッドとショートする可能性もあるので細心の注意を払って作業します。

また、RN42は熱に弱いので素早く半田付けしないといけません。

温度調整付きのハンダゴテで作業するのがいいと思います。

自分は250度に設定して、パッドにコテを当てる時間は1,2秒でやってました。

 

Arduino Pro miniとRN42をつなげる

 Arduino Pro miniとRN42の通信はUARTで行います。

Arduinoでシリアル通信を行う場合は、ハードウェアシリアルとソフトウェアシリアルの2種類があります。

2つの違いはネットで検索してもらうとして、速い通信速度でも安定して通信できることが重要なのでハードウェアシリアルを使いました。

なので、Arduino Pro miniのTx,Rx端子をそれぞれRN42のRx,Txにつなぐだけでオッケーです。

 

RN42の電源

RN42は3.3v駆動なので、電源をUSB(5v)で供給する場合レギュレータを使って3.3vにしてからVDDに入力します。

これで、必要な配線が終わりました。

回路全体の構成はこんな感じです。

とりあえず動くようにするための回路なのでこんな感じで。

動くことが確認できたらもっと回路を考えて作っていきたいと思います。

 


f:id:tamagawa000:20210124184026j:plain

回路全体イメージ

RN42初期設定

RN42はコマンドモードというものを使って、詳細な設定をすることができます。

この設定をするにはRN42とシリアル通信できればいいので、Arduinoでやる方法やPCとUSBシリアル変換アダプタを使って通信させる方法などがあります。

今回はPCとUSBシリアル変換アダプタを使って通信させて設定してみました。


f:id:tamagawa000:20210206104149j:plain


USBシリアル変換アダプタとRN42を接続した写真

写真のようにUSBシリアル変換アダプタをブレッドボード に差して、RN42から出ている線を対応するピンにつなげればオッケーです。

※今回使用しているUSBシリアル変換アダプタはVccが3.3v or 5.0v切替対応のものです。RN42は3.3v入力なので、必ず3.3vに切り替えてから使用する。

 

設定した項目

  • RN42の動作モードの変更
  • プロファイルをHIDにする
  • ディスクリプタタイプをキーボードにする

上記を設定するために、コマンド仕様にしたがって順番に入力していきます。

コマンド仕様の説明は省略します。


f:id:tamagawa000:20210206104216p:plain

TeratermでRN42にコマンドを送っている様子

まず、RN42をコマンド受付状態にするために、'$$$'を入力します。

この時Enterキーを押さなくてもCMDと表示されます。

CMDが表示されたらコマンド受付状態になっています。

あとは、設定したい項目に対応するコマンドをどんどん入力していくだけです。

'SM'コマンドでRN42の動作モードを変更します。

'SM,0'でスレーブモードで動作します。

入力したらEnterキーを押すと、'AOK'とコマンドが成功したことを知らせる文字がRN42から帰ってきます。

次に'S~,6'でHIDプロファイルに変更します。これをしないと、接続したパソコンからHIDデバイスとして認識されません。

あとは、'SH,0200'を送ってディスクリプタタイプをキーボードにします。

最後にR,1でリブートをする。

これをしないと設定が反映されません。

 

キーボードとRN42をコントロールするArduinoプログラム

#include <hidboot.h>
#include <usbhub.h>
#include <SPI.h>

//プロトタイプ宣言
void sendKeyCodesBySerial(uint8_t modifiers,
                          uint8_t keycode0,
                          uint8_t keycode1,
                          uint8_t keycode2,
                          uint8_t keycode3,
                          uint8_t keycode4,
                          uint8_t keycode5);


#define REPORT_KEYS 6
typedef struct {
  uint8_t modifiers;
  uint8_t reserved;
  uint8_t keys[REPORT_KEYS];
} KeyReport;
 
KeyReport keyReport;

                          
void sendReport() {
  sendKeyCodesBySerial(keyReport.modifiers,keyReport.keys[0],keyReport.keys[1],keyReport.keys[2],keyReport.keys[3],keyReport.keys[4],keyReport.keys[5]);
}


void releaseAll() {
  memset(&keyReport, 0, sizeof(KeyReport));
  sendReport();
}

void report_press(uint8_t key, uint8_t mod) {
  if (key != 0) {
    bool already = false;
    int empty_slot = -1;
    for(int i = 0; i < REPORT_KEYS; i++) {
      if (keyReport.keys[i] == key)
        already = true;
      if (keyReport.keys[i] == 0 && empty_slot < 0)
        empty_slot = i;      
    }
    if (empty_slot < 0)  // error condition.
      return;
    if (!already)
      keyReport.keys[empty_slot] = key;
  }
  keyReport.modifiers = mod;
  sendReport();  
}
 
void report_release(uint8_t key, uint8_t mod) {
  if (key != 0) {
    for(int i = 0; i < REPORT_KEYS; i++) {
      if (keyReport.keys[i] == key) {
        keyReport.keys[i] = 0;
        break;
      }
    }
  }
  keyReport.modifiers = mod;
  sendReport();  
}

class HIDKeyboardParser : public KeyboardReportParser
{
     void OnControlKeysChanged(uint8_t before, uint8_t after);
     void OnKeyDown (uint8_t mod, uint8_t key){ report_press(key, mod);  };
     void OnKeyUp  (uint8_t mod, uint8_t key) { report_release(key, mod); };
};

void HIDKeyboardParser::OnControlKeysChanged(uint8_t before, uint8_t after) {
  uint8_t change = before ^ after;
  if (change != 0) {
    if (change & after)
      report_press(0, after);
    else
      report_release(0, after);      
  }
}


USB     Usb;
HIDBoot<USB_HID_PROTOCOL_KEYBOARD>    HidKeyboard(&Usb);

HIDKeyboardParser parser;

void setup()
{
  Serial.begin( 115200 );

  Usb.Init();

  delay( 200 );
  
  HidKeyboard.SetReportParser(0, &parser);
}

void loop()
{
  Usb.Task();
}

void sendKeyCodesBySerial(uint8_t modifiers,
                          uint8_t keycode0,
                          uint8_t keycode1,
                          uint8_t keycode2,
                          uint8_t keycode3,
                          uint8_t keycode4,
                          uint8_t keycode5)
{
    //Raw Report Mode
    Serial.write(0xFD); // Raw Report Mode
    Serial.write(0x09); // Length
    Serial.write(0x01); // Descriptor 0x01=Keyboard

    Serial.write(modifiers); // modifier keys
    Serial.write(0x00);      // reserved
    Serial.write(keycode0);  // keycode0
    Serial.write(keycode1);  // keycode1
    Serial.write(keycode2);  // keycode2
    Serial.write(keycode3);  // keycode3
    Serial.write(keycode4);  // keycode4
    Serial.write(keycode5);  // keycode5
    delay(1);

}

基本的にはネットにあったものを組み合わせて作った物です。

しかし、ネットにあったものそのままでは動かなかったので少し修正してあります。

動作としては、キーボードが押されるとHIDKeyboardParserクラスのメソッド呼ばれて、押されたキーの情報をKeyReport構造体に保持します。

そのあと、sendKeyCodesBySerialメソッドを使ってRN42のHID report仕様に従った形式でデータを送信します。


f:id:tamagawa000:20210124191302p:plain

RN42 HIDreport仕様

 

1Byte目に0xFDを送るとRN42はRaw reportモードになり、それ以降10Byteのデータを受け付ける準備をします。

2Byte目はDescriptorとDataの合計サイズです。

3Byte目のDescriptorは0x01(キーボード)固定。

あとはModifierにShiftやAltなどの修飾キーとScanCodeにキーコードを送ればオッケーです。

ModifierとScanCode1の間は0x00固定。

つまずいた点

ネットにあったコードをArduino Pro mini 環境でコンパイルできなかった。

コンパイルできなかった原因は、HIDクラスがありません〜みたいなエラーでした。

Arduino Micro環境ではコンパイルするとできました。

この辺は調べてもよく分からなかったのですが、おそらくUSBをサポートしていないArduinoではHIDクラスが使えないのではないのかと思います。

なのでArduino Pro miniでやるときは、HIDクラスを使わないようなコードを書く必要があります。

動作

 特に大きな遅延もなく、有線接続しているキーボードと同じ感覚で使うことができました。

 今後の展望

 まずはバッテリー駆動できるようにしたいと思います。

USB電源駆動だとPCに直接キーボードを差して動かすのと変わらないので。。

バッテリーの充電、給電回路についても勉強していこうと思います。