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察応の自䜜キヌボヌドを詊䜜しおみたした。倧きな問題はなかったものの実甚的かどうかは人によるような出来です。

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

以䞊です。お読みいただきありがずうございたした。

コメントする

メヌルアドレスが公開されるこずはありたせん。 ※ が付いおいる欄は必須項目です