[PRK Firmware Advent Calendar 2021] ロータリエンコーダ完全に理解した(前編)

hasumikin is a programmer.


おはようございます。2021年PRK Firmwareアドベントカレンダー 14日目の朝です。

PRK Firmwareの0.9.8では、ロータリエンコーダの読み取りにおいて大幅な性能向上を達成しました。 そこで学んだことを共有します。

みなさん、お手元にロータリエンコーダをご用意して触りながらお読みください。

ロータリエンコーダの理屈

エンコーダの内部には電気接点がふたつあります。 A接点とB接点と呼ぶことにします。 停止しているエンコーダ内部では、両接点ともオフ(接続されていない)の状態です。 さて、お手元のエンコーダを回してみてください。

エンコーダのつまみを時計方向へ回すと、先にA接点がオンになります(このとき、つまみにバネの力が生じていることがわかると思います)。 さらに回し続けると、B接点もオンになります。 そしてA接点が先にオフになり、 最終的にB接点がオフになります(バネから解放されます)。

ここまでがエンコーダのひと目盛(クリックストップ)です。 2進数を用いて状態を表現しましょう。 A接点の状態を2ビット目、B接点の状態を1ビット目とすると、初期状態は 0b00 です。 これが 0b10 0b11 0b01 0b00 の順に変化すると、ひと目盛ぶん動いたことになります。

プログラムからエンコーダを読み取る場合、状態遷移管理に4ビットつかいます。 上位2ビットが前回の状態、下位2ビットが現在の状態だとします。回しはじめは 0b0010 ですね。 その後、 0b1011 0b1101 0b0100 と合計4回の状態遷移を見守ると、ひと目盛動いたと判定できます。

下図を理解の参考にしてください:

時計回りの状態遷移
前回A:    0->1->1->0
前回B:    0->0->1->1
現在A: 0->1->1->0->0
現在B: 0->0->1->1->0
       ^           ^
       スタート    終了


反時計回りの状態遷移
前回A:    0->0->1->1
前回B:    0->1->1->0
現在A: 0->0->1->1->0
現在B: 0->1->1->0->0
       ^           ^
       スタート    終了

これで晴れて、4ビットあればすべての状態遷移を表現できる、ということになりました。 0b0000から0b1111ですから、最大16通りです。 ただし、理論的には 0b1100 は起こり得ません。 両接点がオンだった前回状態から、両接点が一気にオフになることはないからです。 ほかにも 0b0011 0b1001 0b0110 は起こり得ないので、実際には12通りの状態遷移しかないはずです。

ただし16項による状態遷移テーブルをつくっておくのがプログラム上では扱いやすいので、下記のようなデータをつくっておきます:

前回A:    0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
前回B:    0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
現在A:    0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
現在B:    0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
16進表記: 0 1 2 3 4 5 6 7 8 9 a b c d e f
判定      0 - + _ + 0 _ - - _ 0 + _ + - 0

上の表の「判定」行は、 0 が停止、 + が時計回り、 - が反時計回り、 _ が起こり得ない遷移、です。 起こり得ない状態遷移は無視する(0と見做す)として、C言語ですと、以下のように16項の状態遷移テーブルを書くことができ、

int8_t table[] = {0, -1, 1, 0, 1, 0, 0 -1, -1, 0, 0, 1, 0, 1, -1, 0};

以下のように回転判定できます:

static uint8_t previout_status; /* 前回ABを表す2ビットデータ */
uint8_t current_status;         /* 現在ABを表す2ビットデータ */
table[previout_status << 2 | current_status]; /* 0または-1または+1を返す */

そんでもって、 + の数を数えていって +4 になったら時計回りにひと目盛、 -4 になったら反時計回りにひと目盛です! おめでとうございます🎊 しかし、

現実の自作キーボードでは、これだとまったくダメです

チャタリングです。 英語ではBounce(バウンス。はね返り)と言います。 電気接点がバタバタと暴れるので、理論どおりには状態遷移しません。 真面目に状態遷移を追いかけると、 +4-4 にはぜんぜんなりません。 バウンスのせいで不規則な状態遷移が発生して、時計回りなのにマイナスが積まれたりします。 酷いときには、時計回りのはずなのに判定合計値がマイナスになることさえあります。

ではどうするか

ハードウェア的に対策するなら、回路にコンデンサを入れて波形を鈍らせることで、マイコンの分解能的には理論どおりに動いているように見せかけることができるらしいです。

しかしロータリエンコーダにチャタリング防止(英語ではDebounce)回路をいれた自作キーボードは見たことがありません。 ありませんよね?

ですので、ソフトウェアでディバウンスします。 素早すぎる(たとえばミリ秒未満の)状態遷移を握りつぶしたり、判定間隔をマイクロ秒単位でチューニングしたりして、いろいろ試しました。

これで多少はよくなるのですが、誤判定を完全になくすことはできませんでした。 数パーセントくらいは誤判定してしまいます。 画面スクロールにエンコーダを使用するくらいなら、多少の誤判定は許容できるかもしれません。 しかし、レイヤの切り替えにエンコーダを使用するとなると、誤判定があってはストレスフルです。

ここで、うーんと唸りつつQMK Firmwareを焼いたマイコンにサシカエてみました。 すると、まったく誤判定しないのです! なぜだ! QMKは神か?! でもQMKのソースコードは闇が深くて読みたくない!(めんどくさい) と、まったく誤判定のないエンコーダの動作を繰り返し観察して、あることに気づきました。

16項の状態遷移テーブルなんて要らんかったんや!

(あしたにつづきます)