ESP32と自作キーボードでBluetooth/QMK/VIA対応のキーボードを作る

自作キーボードがある程度形になってきて、そろそろ自作キーボードで遊ぶのも終わりかなーなんて思っていたのですが、まだやっていないことがありました。

それがBluetooth接続のキーボードですね。調べてみると自作キーボードを作る人には一定数はやってみたい人がいるようです。

おそらく有名なのがsekigon氏のBLE Micro Proだと思うのですが、1からファームフェアを作れないこと(ライセンスの都合でnRF52ブランチが古い)ことが個人的に問題のように感じます。個人的なところだと、今までのものと違いすぎて使いにくそう、そもそもProMicroは使っていない、そもそもクローズな感じがして手を出したくない、というのがあります。

なので自分でなんとかBluetooth対応のキーボードを作りたいなと思うのですが、求める要件はひとまずはこんな感じ。

  • QMKだけでなくVIAに対応する(書き換えはUSBでOK)
  • USB/Bluetooth両方に対応
  • Bluetoothはモジュールで後付けする形にしたい(技適の関係など)
  • 最新のQMKでコンパイルできるようにする
  • ライセンス的にも問題ないか形になれば嬉しい

こんな感じですね。USB/Bluetoothのスイッチングは何かしらで実現できると思うので、USBに接続しながらBluetoothを使うみたいな細かいシチュエーションは無視して、とりあえず作ってみます。

で、どういうキーボードを想定するかというと、ProMicroを使わない専用の基板を使う想定で考えます。

ProMicroではなくPCB埋め込みタイプで考える

どのみち現状のボードをそのまま用いてBluetooth化は無理だと思いますし、やり方さえ考えられればProMicroに落とし込むのはさほど難しくないでしょう。あと、この記事ではProMicroに搭載されるATmega32U4(AVR)ではなくSTM32F系(検証ではSTM32F303CBT6)を使います。

しかし、ほぼ同様の方法でAVR系のマイコンでも実現できると思うので適切に読み替えながら読んでいただけたら良いかと思います。

というわけで実装していってみます。

目次

QMKとBluetooth

QMKファームウェアは基本的にはUSBキーボードのファームウェアを作製できるツールですね。なので、基本的にはBluetoothをサポートしないスタンスではあると思います。

ですが、公式に対応しているものが二つあります。詳細は以下のページにあります。

GitHub
https://github.com/qmk/qmk_firmware/blob/master/docs/feature_bluetooth.md

ここにある通り、RN-42とnRF51822ですね。加えて、対応しているのはAVR系マイコンのみです。私が設計しているのは基本的にはSTM32系(ARM)なのでそもそも条件から外れます。

AVR系の代表ProMicro君

なんでAVR系のみなんだろう?なぜ新しいチップをサポートしないのだろう?調べてみると答えは簡単です。ライセンスの関係です。nRFが最近は流行っているようですが、これはNordicのライセンスにひっかかるようです。移植したのをプルリクして拒否されているのを目撃しています。

BLE Micro Proもよくわからないブラックボックスにしていて使い勝手が最悪なのはライセンスの関係でソースコードを配布できないからですね。古いコードを使えばできるようですが、日々更新されるコードで古いのを使うのは…というのはあります。

というわけで、STM32系のチップでなんとかBluetoothキーボードを作りたいというのが今回の話の出発点です。

ESP32を選んだ理由

STM32系の自作キーボードを使ってBluetoothキーボードを作りたいという話の流れは理解いただけたかと思うのですが、タイトルにある通り今回はESP32を使って実装しています。その理由ですね。

ESP32-WROOM-32D

まずはコードをどういう形で実装するかを考えておきます。上で書いた通り、基本的にQMKファームウェアで対応しているものや、nRF系のものはライセンス関係で直接的にQMKファームウェアで使うというのは大変です。となると、ライセンスに引っかからないような形で実装できるものか、完全に別のコードとして作製することで、QMKには組み込まないで配布できるような形にしてしまうのが将来的には良いでしょう。

また、今回はモジュール形式で後付けにしたいというのもあるので、どうやっても別コードして実装にはなります。なので、QMKには組み込まない方向でやっていこうと思います。

QMKからは独立したコードを作製すると決めたところで、いよいよチップの選定ですが、別コードで実装するとなれば、QMKに即したライセンスである必要はないので、選べるチップが広がります。RN-42でもnRF系でもおそらくは問題ありません。

これらの中から選んでも良かったのですが、それ以外の選択肢があるのかというと割と数はありません。この手のことを調べて居る方だとご存じの通り、日本にはいわゆる技適マークを付けたものしか使ってはいけないという法律があるので、それに準拠したチップである必要があります。正しくはチップではなく、アンテナ部も含めたモジュールの形で技適を通っていれば組み込んで使ってもOKというルールです。そうなると個人の範囲で使いやすそうなものだとESP32、RN、nRF、Raspberry Pi Pico Wあたりでしょうか。

nRF52844搭載のMS88SF2モジュール

他にもみんな大好き蟹さんのチップやBroadcomのような有名どころのモジュールもあるにはあるのですが、サンプルコードもないですし、リファレンスもよくわからなかったりと、個人の手で追うには難しい代物が多いです。

なので、簡単に作れそうなチップでキーボードを作製する場合のコードを比較してみます。RN-42は調べもしないで使わないと決めていたのですが、それがBluetoothのバージョンが古いためです。nRFは最新のものであればBLEなどにも対応しているため、デバイス的な問題はありませんが、コードが煩雑です。結構深いところまで書かないと動かなさそうなコードでした。サンプルコードで公開されている分でこれなので、凝ったものを作るとなると中々大変そうです。RPi Pico Wは発売されて間もないというのと記事執筆段階では技適が怪しいので、今後の期待はできるかもしれませんが、現段階ではなしです。ESP32はBLEキーボードライブラリがあるのでかなり簡単に実装することができます。

この中で個人で気軽に試すなら一目瞭然でESP32だったので、EPS32を選んでいます。他にも入手が容易だったり、安価だったりと試しで作る分には良さそうという面があります。実装方法次第では、他のデバイスでの応用も効くでしょうし、試作にはもってこいです。もっとくだらない理由だと、それっぽいことをやって失敗していたり、QMKをESP32に書き込もう的な謎の夢を見ている人がいたのを見て、やってみようと思ったなんてのもあります。

キーボードの動作を知る

ESP32を使ったことがないので使い方を学びつつ実装していってみます。とりあえずキーボードの簡単な仕組みぐらいは知っていないと実装できないので簡単に紹介しておきます。キーボードがやっていることは極めてシンプルで、以下の様にキーコードというものを送信しています。

USBキーボードの簡単な仕組み

さらに細かく書いておくと、USBデバイスはディスクリプタという構造体をセットにしてPC等のホストデバイスにデータを送り付けています。その中にはこのデバイスがどんなデバイスなのか(例えばマウスなのか)といったデータや、そのデバイスがどんなふるまいをする・したのかのデータが入っています。

その識別にまずはクラスというものがあり、HID(Human Input Device)クラス内にはキーボードやマウスといったものがあります。HIDクラスにはHIDレポートという、デバイスがどのようなふるまいをしたかというデータの本体があります。

このデータ内には上で書いたキーコードというもの、そのキーが押されたかのデータなどが入っています。キーコードとは、「A」のキーなら2桁の16進数で04みたいなキーごとに対する割り当てのことを指します。このキーが押されたらキーコードとその「押された」という状態を送り、キーが離されたらキーコードと「離された」というHIDレポートが送られます。もっと細かいことはUSBの仕様書でもみてください。

https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf

細かいことはさておき、QMKファームウェアではこのような内部の動作を簡単にやってくれています。HIDレポートなんて気にしたことない方が大半です。今回の内容を理解するうえではHIDレポート中身についてぐらいは最低限理解しておくのが良いでしょう。

ちなみに、Bluetoothでも同じような規格で成り立っており同様にHIDクラスが定義され、HIDレポートの内容も既定されています。キーコードはUSBのそれと一緒です。

用意したもの

実際に使ったものを紹介しておきます。ブレッドボードなんかはものを選ぶのでここはきっちり読んでおいていただけると後悔しないかもしれません。

  • TH25TK(STM32F103の自作キーボード)
  • EPS32-DevkitC v4
  • ブレッドボード
  • ジャンパワイヤ
  • USBシリアル変換(CP2102)

まずは自作キーボードとしては自分の作ったTH25TK。一応BOOTHで販売もしていますが、今回実験で使ったのとはチップが違うのと、シリアル通信用の線がキーマトリックスの一部と共用なので、遊び半分でやってみて満足したら普通のキーボードとして使える方におすすめです。

TH25TKでLチカの謎構図

ESP32-DevkitC v4とブレッドボードはセットで使います。

ブレッドon ESP32

後悔してほしくないので念のため書いておきますが、以下のリンクのサンハヤトのやつを新規で買っておきましょう。

理由は簡単で、これじゃないと横幅が足りません。他のものは5列のものが多いですが、6列のサンハヤトのやつでも写真の通りギリギリです。悪いことは言わないのでおとなしくこれを選びましょう。後はピン数が多いので抜くのが固くて強引に抜くとピンが曲がるので専用にするぐらいの気持ちが良いです。

ピン曲がり悲劇のProMicro

ESP32自体は正直Devkit CでもVでも使いやすいものであればなんでも良いと思います。国内でデータが多いのはDevkitCなのでCは無難かもしれません。

ジャンパワイヤは長さも含めてご自身が使いやすい長さで良いと思います。別の用途でも使えるでしょうし、今後のことも考えて買うと良いかもしれません。

CP2102のUSBシリアル変換ボードは動作確認で使えるので、持っておいて損は無いと思います。あと、経験上ESP32DevkitCに乗っているCP2102(とそのパチモン)は割と壊れやすいので、デバッグ&壊れたときの保健用に1個は持っておくのがおすすめです。

これらの部品をつかって検証をしました。

実際に作ってみる

まずはQMKに対応した自作キーボードは単体で動作する前提ですので、自作キーボードからはHIDレポートである必要はありませんがキーコードとキープレスの情報を送信、ESP32でそれらを受信してHIDレポートとその送信を行う必要あります。

となると、最低限一つは自作キーボードとESP32の間に伝送路が必要となるので、今回はシリアル通信を用いて通信します。ESP32側でのシリアル通信はすぐ調べたら出てくるので、とりあえず自作キーボード側で実装します。シリアル通信であればUARTのページを見てシリアル通信を有効化します。

GitHub
https://github.com/qmk/qmk_firmware/blob/master/docs/uart_driver.md

使いたいシリアル通信に合わせてここらへんは実装してもらうとして、少し躓いたのが他のファイルへの記述。まず、keyboad.cにuart.hをインクルードする必要があります。また、シリアルの初期化を以下の関数でしておきます。これは別の関数内で処理してもよかったのですが、ドキュメントに指定が無かったのでとりあえずここでしています。


#include "uart.h"

//******何か処理があるなら*****

void keyboard_post_init_kb(void){
  uart_init(115200);
  }

これだけだとビルドで怒られるので、rules.mkこれを追加します。

QUANTUM_LIB_SRC += uart.c

一応これで何かしらの処理を書けば最低限送受信ができます。適当にデバッグしたら実際にキーコードを送り付けてみます。QMKファームウェアのキーコードを見てみると、通常のキーコードでは使わない桁数まで拡張したものを内部では使う場合があるようです。これらはQMK専用の特殊なキーでのみ発生するので、今回はそれを無視します。また、HIDクラスの定義によるとキーコードは231までのようなのでそれを活かしてコードを書いてみます。デバッグのときからコードをいじってないので要らない機能とか使途不明なものがあると思いますがそこらへんは無視してください。

#include "print.h"
#include "btest.h"
#include "uart.h"

uint8_t LowByte(uint16_t);
uint8_t HighByte(uint16_t);
uint16_t combu8t(uint8_t,uint8_t);

void keyboard_post_init_user_kb(void) {
  // Customise these values to desired behaviour
  debug_enable=true;
  debug_matrix=true;
  //debug_keyboard=true;
  //debug_mouse=true;
}

void keyboard_post_init_kb(void){
  uart_init(115200);
  }

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  uint8_t data[5]={0};
  data[0]=0xFF;
  data[1]=0xFF;
  data[2]=HighByte(keycode);
  if(data[2]!=0x00){
    return true;
    }
  data[3]=LowByte(keycode);
  data[4]=record->event.pressed;
  uprintf("Keycode: %u ,0x%x\ndata[0]=0x%x\ndata[1]=0x%x\n",keycode,keycode,data[2],data[3]);
  uprintf("data[2]=0x%x\nPress: %u\n\n",data[4],record->event.pressed);
  uprintf("readPin(A11)=%u\nreadPin(A12)=%u\n",readPin(A11),readPin(A12));
  uart_transmit(data,5);
  if(uart_available()){
  uprintf("*******recive data: %u*********\n",(uint8_t)uart_read());
  }
	return true;
}

uint8_t LowByte(uint16_t u16){
  return (uint8_t)u16;
  }
  
uint8_t HighByte (uint16_t u16){
  uint8_t ans=u16>>8;
  return ans;
  }
uint16_t combu8t(uint8_t HB,uint8_t LB){
  return (HB<<8)|LB;
}

色々考慮して使ってない予約用のデータ配列とかも用意しています。このコードでキーコードの本体となるのはdata[3]です。data[2]は16ビットのうち上側8ビットを判定していて、これが0以上なら少なくともベーシックキーコードではないので、それを無視するようにしています。後は適当にビット演算の関数を作って実装しています。テストついでにデータを受け取ったときの関数を作りましたが要らなかったですね。

これによって得られるデータを確認すると正しくキーコードやキープレスのデータを確認できます。

ESP32でBLEキーボードを作るライブラリとしてArduinoIDE環境で動くESP32-BLE-Keyboardがありますが、これは少し機能不足というか、求める機能がないので私のフォークで改造したものを使うと簡単に実装できます。改造したライブラリは以下です。

ESP32-BLE-keyboard THUN fork
https://github.com/T-H-Un/ESP32-BLE-Keyboard

導入方法はオリジナルのものと同じです。これには以下の関数を追加しています。

  • bleKeyboard.HIDpress(uint8_t)
  • bleKeyboard.HIDrelease(uint8_t)

この関数は直接uint8型のキーコード値をHIDレポートに入れて送信してくれる関数です。名前の通りHIDpressを押した際に、HIDreleaseを話した際に実行するとキーボードとして機能します。

これらの関数を使って書いたのがこのコードです。自分用にLEDを繋いでいたり、デバッグ用の操作があるで、適切に消したりして使えば良いかと思います。

#include <BleKeyboard.h>
#define LED_PIN 21
#define LED_PIN1 13
#define NMOS 15
#define BAUD 115200

#define SERIAL_DEBUG
//#define BLEKBD_TEST

BleKeyboard bleKeyboard("ESP32 KEYBOARD"); //デバイスの名前4

HardwareSerial sSerial1(1);


void setup() {
  Serial.begin(BAUD);
  Serial.println("Starting BLE work!\n");
  bleKeyboard.begin();
  pinMode(0,INPUT_PULLUP);    //GPIO0(BOOT)をプルアップ付き入力設定
  //Serial2.begin(BAUD);
  sSerial1.begin(115200, SERIAL_8N1, 26, 27);
  Serial.println("Waiting for Handshake\n");
  delay(2000);
  Serial.println("Connected!");
  pinMode(LED_PIN, OUTPUT);
  pinMode(LED_PIN1, OUTPUT);
  pinMode(NMOS, OUTPUT);
}

void loop() {
  uint8_t data[5]={0};
  digitalWrite(LED_PIN, LOW);
  digitalWrite(LED_PIN1, LOW);
  digitalWrite(NMOS, LOW);
  while( sSerial1.available()){ //Serial1に受信データがあるか
    data[0]=(uint8_t)sSerial1.read();
    if(data[0]==0xFF){
      data[1]=(uint8_t)sSerial1.read();//Check of 0xFF headers
      if(data[1]==0xFF){
    data[2] = (uint8_t)sSerial1.read();//HighByte of a keycode of QMK_Firmware/
    data[3] = (uint8_t)sSerial1.read();//LowByte of a keycode of HID regulations.
    data[4] = (uint8_t)sSerial1.read();//Keypress
    #ifdef SERIAL_DEBUG
    digitalWrite(LED_PIN, HIGH);
    Serial.print("data[2]=");
    Serial.println(data[2]);
    Serial.print("data[3]=");
    Serial.println(data[3]);
    Serial.print("data[4]=");
    Serial.println(data[4]);
    #endif
    if(bleKeyboard.isConnected()) {
      if(data[4]==1){
       bleKeyboard.HIDpress(data[3]);
       Serial.print("data[3] Press=");
       Serial.println(data[3]);
      }
      else{
       bleKeyboard.HIDrelease(data[3]);
       Serial.print("data[3] Release=");
       Serial.println(data[3]);
       }
    }
    else
      Serial.print("BLE Keyboard is not connected");
      }
    }
  }
  #ifdef SERIAL_DEBUG
  while( Serial.available()){//コンソールに入力を入れたら返すデバッグ用関数
    Serial.println((char)Serial.read());
    }
  #endif  
  if(bleKeyboard.isConnected()) {    //接続されているとき
    if(digitalRead(0) == LOW){//BOOTスイッチが押されている時
      #ifdef BLEKBD_TEST
      bleKeyboard.write(KEY_RETURN); //Enterを送信
      sSerial1.write(0x04);
      digitalWrite(LED_PIN1, HIGH);
      delay(200);
      #endif
      digitalWrite(NMOS, HIGH);
      }
  }
}

今日載せているコードはとりあえずコピペしたら動きはするのでとこから好きな形にしていけば動作確認はできると思います。

これらのコードを使って動作確認をしたところ、多少もたつきを感じる場面がありましたが、文字入力など一般的な使い方であれば使えないことは無い程度だと感じました。ゲームなんかには間違いなく使えない感じです。

ちなみに、キーボード本体側をQMKに対応させていれば当然ながらUSBで接続すればキーマップを書き換えることも可能です。Bluetoothから書き換えるのはできませんが、これはQMKのキーボード全てで言えることなので諦めましょう。

というわけでタイトル通りのBLEキーボードを作製することができました。この実装方法ならある程度ライセンスを気にしないで大丈夫だと思うので、他のチップでも似たような方法で実装できそうです。

考えれる問題

入力遅延は多少はとあると思うので少し気になるかなというのが一つ目。もう一つが接続が切れたときですね。押しっぱなしが継続されると思うので、入力されっぱなしの状態が出てくるかもしれません。テストで作ってみた程度なので長期的にどうかというのもわかりませんね。

また、これをバッテリー駆動にしようと思うとかなりしんどいと思います。というのも、ESP32はBLEのデータそう送信時に150mAとかそのレベルの電流が流れるそうなので、かなり消費電力的にはきついはずです。常に電力を供給できる環境なら使えるかな。

ここらへんは実装次第ではありますが、USB接続をしている状態で、PC等とペアリングをしていると二重で入力されてしまいます。これは色々な方法で実現できると思うので、今回のコードではという話ですね。要改良ということです。

まとめ

ESP32と自作キーボードを使ってBluetooth/QMK/VIA対応の自作キーボードを試作してみました。大きな問題はなかったものの実用的かどうかは人によるような出来です。

ただ、研究的な内容としては楽しかったですし、得るものもあったかなと思うので、興味のある方は是非やってみてください。そんなに部品代も高いわけではないですしね。

以上です。お読みいただきありがとうございました。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です