🎹
M5StackでロータリーエンコーダをBluetoothキーボード化する
はじめに
機械学習のラベル付け(アノテーション)を楽にするために、ロータリーエンコーダをBluetoothキーボードとして使ってみました。
以前、Raspberry Pi Picoでロータリーエンコーダを読み出し、シリアル信号として出力する記事を書きました。
ただ、やはり無線の方が使いやすく、かつキーボードとして認識される方が様々なデバイス(iPadなど)と接続できるので、今回はM5Stackを使ってBluetoothキーボード化してみました。
動作中の様子
動作中の様子は以下の通りです。
ロータリーエンコーダを回したり内蔵ボタンA〜Cを押下すると、接続されたデバイスにキーが送出されます。
スケッチ
今回作成したスケッチは以下の通りです。いくつかポイントを列挙しておきます。
- 手元にあったM5Stack Grayを使っています
-
BleKeyboard
ライブラリを使ってBluetoothキーボード化を実現しています -
M5KBD
の部分がBluetoothキーボードの名前として表示されます -
KEY_NUM_0
などの定数はBleKeyboard.h
で定義されているはずですが、なぜか参照できずエラーとなったため、即値で記載していま - ロータリーエンコーダの読み込み用クラス
RotaryEncoder
は自作しました-
ESP32Encoder
ライブラリも試してみましたが、チャタリングの影響か期待通りに動作しませんでした - そのため、今回は愚直に処理することにしました
- ただし、10ミリ秒の待ち時間を挟んでいるので、高速に回すとカウント漏れが発生します
-
- ロータリーエンコーダは2つ接続されています
- ロータリエンコーダ0は、2ピン、5ピンに接続されています
- ロータリエンコーダ1は、16ピン、17ピンに接続されています
- それぞれ内蔵プルアップ抵抗を使っています
- ロータリーエンコーダは2パルス/1クリックの仕様なので、カウンタの下位1ビットが0の場合だけキーを送出しています
- 以下のキーが送出されるようになっています
- 内蔵Aボタン: Aキー
- 内蔵Bボタン: Bキー
- 内蔵Cボタン: Cキー
- ロータリエンコーダ0(時計回り/反時計回り): テンキーの0/1
- ロータリエンコーダ1(時計回り/反時計回り): テンキーの2/3
- 以下を内蔵ディスプレイに表示しています
- 起動してからの経過ミリ秒
- Bluetoothキーボードとして接続されているかどうか
- ロータリーエンコーダ0の位置
- ロータリーエンコーダ1の位置
#include <M5Stack.h>
#include <BleKeyboard.h>
class RotaryEncoder {
public:
int position;
bool cw;
bool ccw;
protected:
int pin_a;
int pin_b;
bool last_pin_a_value;
public:
RotaryEncoder(const int pin_a, const int pin_b) {
this->pin_a = pin_a;
this->pin_b = pin_b;
this->position = 0;
}
void begin() {
pinMode(pin_a, INPUT_PULLUP);
pinMode(pin_b, INPUT_PULLUP);
this->last_pin_a_value = digitalRead(this->pin_a) == HIGH;
}
void update() {
const bool pin_a_value = digitalRead(this->pin_a) == HIGH;
const bool pin_b_value = digitalRead(this->pin_b) == HIGH;
this->cw = false;
this->ccw = false;
if (pin_a_value != this->last_pin_a_value) {
if (pin_b_value != pin_a_value) {
this->position += 1;
this->cw = true;
} else {
this->position -= 1;
this->ccw = true;
}
}
this->last_pin_a_value = pin_a_value;
}
};
BleKeyboard bleKeyboard("M5KBD", "M5Stack");
RotaryEncoder re0(2, 5);
RotaryEncoder re1(16, 17);
void setup() {
M5.begin();
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("M5KBD\n");
bleKeyboard.begin();
re0.begin();
re1.begin();
}
void loop() {
M5.update();
re0.update();
re1.update();
if (re0.cw || re0.ccw || re1.cw || re1.ccw) {
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("Time: %d\n", millis());
M5.Lcd.printf("Keyboard: %s\n", (bleKeyboard.isConnected() ? "connected" : "disconnected"));
M5.Lcd.printf("re0: %d \n", re0.position);
M5.Lcd.printf("re1: %d \n", re1.position);
}
if (M5.BtnA.wasPressed()) {
if (bleKeyboard.isConnected()) {
bleKeyboard.write('a');
}
}
if (M5.BtnB.wasPressed()) {
if (bleKeyboard.isConnected()) {
bleKeyboard.write('b');
}
}
if (M5.BtnC.wasPressed()) {
if (bleKeyboard.isConnected()) {
bleKeyboard.write('c');
}
}
if (bleKeyboard.isConnected()) {
if ((re0.position & 1) == 0) {
if (re0.cw) {
bleKeyboard.write(0xEA); // KEY_NUM_0
} else if (re0.ccw) {
bleKeyboard.write(0xE1); // KEY_NUM_1
}
}
if ((re1.position & 1) == 0) {
if (re1.cw) {
bleKeyboard.write(0xE2); // KEY_NUM_2
} else if (re1.ccw) {
bleKeyboard.write(0xE3); // KEY_NUM_3
}
}
}
delay(10);
}
おわりに
BleKeyboard
ライブラリを使うことで、ロータリーエンコーダを簡単にBluetoothキーボード化することができました。
ロータリーエンコーダをあと3つとボタンをいくつか接続することで、ラベル付けを高速化するツールを作る予定です。
本記事が何らかの参考になれば幸いです。
Discussion
やっぱりチャタリング防止は必要でした。対策のために0.01uFのデバウンスコンデンサを入れました。また改訂版の記事を書きたいと思います。
M5Stack(と言うかESP32)のGPIO2、GPIO5ピンは、ブートストラップにも使われるピンでした。そのため、ロータリーエンコーダの端子状態によっては書き込みモードに遷移できなくなるので注意が必要です。
M5Stack、自由に使えるピンが案外少ないですね。ESP32開発ボードを使うか・・・。