⌨️

オリジナルDIYキーボードキットでUSBキーボードを作るためのヒント2 - ロータリーエンコーダ

2023/10/21に公開

これは何?

ボクが作ったDIYキーボードキット DIY kit 01 - Keyboardを使ったUSBキーボードの作り方について説明しているよ。前回、こちらの記事ではキースイッチの入力を検出してPCにキーを入力する方法について説明したよ。

https://zenn.dev/nananauno/articles/11bb11308e07e2

DIYキーボードキットはBOOTHで発売中なので、まだ持っていない場合はぜひ購入してね!

BOOTHの商品ページ
DIY kit 01 - Keyboard XIAO RP2040

このページでは、DIYキーボードキットに2個搭載されたロータリーエンコーダの使い方について以下の内容を説明しているよ。

  • ロータリーエンコーダーって?
  • エンコーダの回転を検出する
  • エンコーダーの回転に合わせてキー入力する

対象読者

  • DIYキーボードキットをお買い上げ頂いたみんな
  • RP2040でロータリーエンコーダの扱い方を知りたいみんな

環境

  • DIY kit 01 - Keyboard
  • Arduino IDE
  • Arduino IDEが動作するPC

作りたいもの

このページで作るものを説明するよ。DIYキーボードキットの基板上にあるEnc1のロータリーエンコーダを回転させたときに、PCへPageDownキーもしくはPageUpキーを入力して画面をスクロールできるようにするよ。

DIYキーボードキットのEnc1はXIAO RP2040のA=D9ピンとB=D10ピンに接続されているよ。DIYキットを持っていない場合は、RP2040のD9ピンとD10ピンにロータリーエンコーダを接続するか、別のGPIOに接続した場合はこの後のプログラムのピンを適宜読み替えてね。


Enc1接続箇所 DIY kit 01 - Keyboard 回路図

ロータリーエンコーダって?

ロータリーエンコーダ(以下エンコーダ)は、回転を検出するための部品だよ。エンコーダには1つの軸があって、この軸を回転させた時の回転方向と回転量を検出することができるよ。一番身近に使われているところは、音量調整とかマウスのホイールかな。

DIYキーボードキットで使用しているエンコーダはALPSALPINEさんのEC12シリーズ、内部に2つの接点があって、回転に合わせて接点が接触したり離れたりして、電気信号が変化するよ。接点が接触したり離れたりって、これはよく見ると2つのスイッチだね。エンコーダにはA,B,Cの3つの端子があって、CはGNDで、A,Bがそれぞれ2つの接点と繋がっているよ。エンコーダを回転させることで、A,Bからパルスが出力されるんだけど、この時のパルスがAとBで一定時間ずれるように工夫されているよ。マイコンのGPIOピンを使って、AもしくはBのパルス数を数えることで、回転量が、AとBのパルスのずれを利用することで回転方向も分かるようになっているよ。


エンコーダ出力波形イメージ

エンコーダの回転を検出する

エンコーダの回転量と回転方向の調べ方は色んな実装があるけど、ここでは一例を挙げておくよ。

回転量の検出

エンコーダの回転量の検出方法について説明するよ。DIYキーボードキットで使用しているEC12E24404A6は軸が1回転すると24個のパルスが出力される仕様になっているよ。また、このエンコーダはクリック付きのエンコーダで、エンコーダの軸を回したらカチカチと引っかかるような動作になっているよ。軸1回転に24箇所の引っ掛かり(クリック)が有るという仕様になっているから、1クリックで1つのパルスが出力されることになるよ。エンコーダのA, B端子からこのパルスが出力されるようになっていて、回転量だけを見るならどちらかの端子を見るだけでオッケーだよ。


エンコーダの軸が1クリック分時計回りに回転した場合

上の図はエンコーダの軸を1クリック分時計回りに回転させた場合のAとBの出力波形イメージだよ。回転量を知るためにはAかBのどちらか一方の波形を見れば良いから、ここではAの波形だけに注目するよ。Aの波形を見ると、1つのクリックで1つのパルスが出力されているよ。つまり、このパルスの数を数えると、エンコーダの軸がどれだけ回転したか(回転量)を知ることができるよ。例えば上図の矢印箇所でパルスが立ち下がっているから、これを検出することで、パルスを数えることができるよ。立ち下がりだけを見るのか、立ち上がりを見るのか、両方見るのかは色んな考え方があると思うから、色々試してみてね。

AとBの波形がLOWのときにONになっている理由は、こちらのプルアップ?を参照してね。

Aの波形の立ち下がりを検出するためのサンプルスケッチを書いておくよ。
このサンプルコードでは、マイコンの割り込み機能を使って、Aが立ち下がったときにpulse_counterという関数を呼んでいるよ。pulse_counter関数が呼ばれる毎にcount変数をインクリメントして、値をシリアルに出力しているよ。

割り込み?

割り込み (interrupt) というのは、指定されたGPIOで特定の変化があったときに、現在実行している処理を一旦中断して、指定された処理を割り込ませて実行するための仕組みだよ。割り込み機能が無い場合は、例えば10ms毎にGPIOの状態をチェックして、GPIOが変化しているかどうかを確認する必要があるよ。これはポーリング(polling) と呼ばれる手法だよ。一方で、割り込みはマイコンが裏でGPIOの状態を監視してくれるので、自前でGPIOを確認する必要が無いよ。

#define PIN_ENC_1_A D9
#define PIN_ENC_1_B D10

volatile uint32_t count = 0;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(PIN_ENC_1_A, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_1_A), pulse_counter, FALLING);
}

void pulse_counter(){
  count++;
  Serial.printf("%d\n",count);
}

void loop() {
  // put your main code here, to run repeatedly:
}

サンプルスケッチをXIAO RP2040に書き込んで、Enc1を適当に回してみてね。シリアルから数字が出力されているかな?このサンプルスケッチではAの波形しか見ていないので、エンコーダを時計回りに回しても、反時計回りに回してもどんどん数字が増えていくよ。エンコーダのAかBの波形を見ることで回転量を検出できることは分かったよね。

ただ、気になるのは、1クリックだけ回しているのに、カウントされた数字は回した量と一致していないことだよね。上でもちょっと説明したけど、電気的な接触があるスイッチはチャタリング (bouncing) というノイズがあって、接点が1回だけ接触したように見えても実際は接点が何回か触れたり離れたりしているよ。チャタリングを軽減するための方法は色々あるけど、どうやったらチャタリングの影響を減らせるか考えてみてね。

回転方向の検出

続いてエンコーダの回転方向の検出について説明するよ。エンコーダの回転量はAかBどちらかの出力を見ることで検出することができたけど、回転方向を知るためには両方の出力を見る必要があるよ。DIYキットで使用しているEC12E24404A6はクリック安定点でBがLOWなのかHIGHなのかは規定できないという仕様だから、回転方向を検出するときも、まずはAの変化をチェックするよ。

エンコーダはAとBの出力をわざとずらしていて、このずれを利用して回転方向を検出するよ。下の2つの図を見てみてね。1つ目はエンコーダを時計回りに回転させた場合のAとBの出力イメージだよ。2つ目はエンコーダを反時計回りに回転させた場合のAとBの出力イメージだよ。両方の図でAのパルスが立ち下がっている部分に注目してね。Aのパルスが立ち下がったとき、Bの出力を見てみると、時計回りのときはBがOFF、反時計回りの時はBがONになっていることが分かるよ。


エンコーダを時計回りに回転させた場合


エンコーダを反時計回りに回転させた場合

ということは、Aが立ち下がったとき、BがOFFなら時計回り、ONなら反時計回りと判断できそうだね。この方法を実装したサンプルスケッチを以下に書いておくよ。XIAO RP2040に書き込んで実行してみてね。

#define PIN_ENC_1_A D9
#define PIN_ENC_1_B D10

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(PIN_ENC_1_A, INPUT_PULLUP);
  pinMode(PIN_ENC_1_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_1_A), pulse_counter, FALLING);
}

void pulse_counter(){
  int b = digitalRead(PIN_ENC_1_B);
  if(b == LOW){
    Serial.println("CCW");
  }else{
    Serial.println("CW");
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

サンプルスケッチを実行して、Enc1を適当に回してみてね。時計回りに回すとCW、反時計回りに回すとCCWとシリアルから出力されるはずなんだけど、実際はどうかな。たぶん、このサンプルスケッチは全然期待通りに動作しないはずだよ。

どうして正しく回転方向を検出できないの?
上の回転方向を検出するためのサンプルスケッチ、全然役に立たないことが分かったと思うけど、どうして正しく動作しないのかを説明しておくよ。このページでは何度もチャタリング (bouncing) ノイズで正しく動作しない可能性があると言っているけど、回転方向についても同じだよ。

エンコーダの理想的な出力波形であれば、Aが立ち下がったときのBの状態を見ることで、回転方向が分かるはずなんだけど、実際のエンコーダの出力波形は下図のようになっているよ。チャタリングはスイッチがONになるときだけじゃなくて、OFFになるときも発生するから、Aの立ち上がり時にもONとOFFを繰り返していて、そこでも立ち下がりが発生しているよ。Aの立ち上がり時に立ち下がりが検出されてしまうと、その時点でBの出力は既にONになっているから、先程のサンプルスケッチでは、半時計回りとして判断されてしまうよ。


期待通りに動かない原因はチャタリング(bouncing)ノイズ

ここでは回転量と回転方向の検出方法について理論的には正確に検出できる方法を説明したけど、現実のエンコーダは理想的な出力になっていないから、全く上手くいかないことが分かったよね。チャタリングノイズを軽減するためには、ハードウェアでなんとかする方法とソフトウェアでなんとかする方法があるよ。DIYキーボードキットはチャタリングノイズを軽減する回路は入れていないから、ソフトウェアでなんとかする必要があるよ。色々なやり方があると思うから、ノイズを軽減する工夫を考えて試してみてね。

手っ取り早くライブラリを使う

上で説明した回転量と回転方向の検出は実際に実装してみるとまともに動かないから、チャタリングノイズを軽減する工夫が必要だというお話しだったね。自分で実装しても良いし、もしそんな時間無いよっていうことなら、ここでは手っ取り早くエンコーダのライブラリを使用する方法を説明するよ。

Arduinoでエンコーダを扱うライブラリはたくさんあるけど、ボクが試した中でオススメのライブラリを貼っておくよ。今回は、このbrianlowさんのRotaryライブラリを使って、手っ取り早くエンコーダの回転量と回転方向を検出する方法について説明するよ。
https://github.com/brianlow/Rotary/tree/master

Rotaryライブラリのサンプルコードそのままで、エンコーダのピン番号だけをDIYキーボードキット用に変更することで、簡単にエンコーダの回転量と方向を検出できるよ。以下にサンプルスケッチを書いておくね。

#include <Rotary.h>

#define PIN_ENC_1_A D9
#define PIN_ENC_1_B D10

Rotary r = Rotary(PIN_ENC_1_B, PIN_ENC_1_A);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  r.begin(true);
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned char result = r.process();
  if (result) {
    Serial.println(result == DIR_CW ? "Right" : "Left");
  }
}

サンプルスケッチを簡単に説明しておくね。

Rotary r = Rotary(PIN_ENC_1_B, PIN_ENC_1_A);

Rotaryクラスをインスタンス化しているよ。Rotaryのコンストラクタの第1引数はAのピンを渡すはずなんだけど、それだと時計回りと反時計回りが逆になっていたので、サンプルではAとBを入れ替えているよ。

r.begin(true);

Rotaryライブラリを初期化しているよ。

unsigned char result = r.process();
  if (result) {
    Serial.println(result == DIR_CW ? "Right" : "Left");
  }

Rotaryライブラリのprocess関数を呼び出すことで、エンコーダの状態を確認するよ。process関数はエンコーダをポーリングすることで状態を確認しているから、loop関数で呼び出す必要があるよ。process関数はエンコーダの回転を検出すると時計回りの場合はDIR_CW、反時計回りの場合はDIR_CCW、回転していない場合はDIR_NONEを返すようになっているよ。

エンコーダの回転に合わせてキーを入力する

最後にRotaryライブラリとArduinoのKeyboardライブラリを組み合わせて、Enc1のエンコーダを回転させたときにPCへPageDownもしくはPageUpを入力して、画面をスクロールできるようにするよ。エンコーダの回転方向と入力するキーは次の通りだよ。

  • エンコーダを時計回りに回す: PageDown
  • エンコーダを反時計回りに回す: PageUp

ArduinoのKeyboardライブラリで使用するキーコードは以下を参照してね。

https://www.arduino.cc/reference/en/language/functions/usb/keyboard/keyboardmodifiers/

サンプルスケッチ

上で説明したRotaryライブラリのサンプルスケッチに、キースイッチの扱い方の説明で使用したKeyboardライブラリの処理を追加して、エンコーダが時計回りに回転したときにPageDown、反時計回りに回転したときにPageUpのキーを入力するようにしたよ。

どちらのライブラリも使い方は既に説明しているから、このサンプルコードの詳細な説明は省くよ。

#include <Rotary.h>
#include "Keyboard.h"

#define PIN_ENC_1_A D9
#define PIN_ENC_1_B D10

char pageDownKey = KEY_PAGE_DOWN;
char pageUpKey = KEY_PAGE_UP;

Rotary r = Rotary(PIN_ENC_1_B, PIN_ENC_1_A);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  r.begin(true);
  Keyboard.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  unsigned char result = r.process();
  if (result) {
    Serial.println(result == DIR_CW ? "Right" : "Left");
    if(result == DIR_CW){
      // CW
      Keyboard.press(pageDownKey);
      delay(10);
      Keyboard.releaseAll();
    }else{
      // CCW
      Keyboard.press(pageUpKey);
      delay(10);
      Keyboard.releaseAll();
    }
  }
}

スケッチが作成できたらXIAO RP2040にスケッチを書き込んで、Chromeなどのブラウザを開いてEnc1のエンコーダを適当に回してみてね。エンコーダを回すことで、ブラウザのページでスクロールができるようになったかな?

まとめ

このページでは、DIYキーボードキットに搭載されたエンコーダを扱う方法いついて説明したよ。エンコーダには電気的な接点があって、チャタリング (bouncing) ノイズの影響で正しく回転量や回転方向を検出できないことが分かったよね。チャタリングノイズの軽減方法は色々とあるから自分で考えて実装してみてね。また、このページでは1つのエンコーダが回転したときの処理だけだったから、Enc2のエンコーダを回転したときの処理も追加してみてね。

分からないことがあったらXで気軽に質問してね!
じゃあ、またね!

Discussion