
なお、上記改良版も含めてここで紹介する方法は高速で回転するエンコーダーの読み取りには向きません。あくまでも簡易的な方法です。
arduinoでゲームコントローラー(ジョイスティック)を作る4回目です。今回はArduinoでロータリーエンコーダーを使う方法を紹介します。
ロータリーエンコーダーのメリット
ロータリーエンコーダーを使うと、ホンモノの機械の操作ノブのようなゲームパッド(ゲームデバイスといった方がよいでしょうか?)を作ることができます。ただ、これまで説明してきたボタンなどと比べると、検出にタイミングなどが絡んでくるためにすこし面倒になります。今回は(おそらく)一番簡単な方法でロータリーエンコーダーを使う方法をご紹介します。特別なライブラリ等も使用しません。
完璧ではありませんが、チャタリング対策のハードウェア等もなしで、そこそこの精度で拾えます。いろいろ試した挙句、これに行きつきました。もっと良い方法は他にもあると思いますが(絶対にある)、とりあえず使えればOKという方向けの情報です。
フライトシミュレーターでオートパイロット関係の設定をいじったりする分には十二分に使えます。
回路図
Arduino ProMicro1個とロータリーエンコーダーを1個だけ使います。
配線は次の通りにします。
ロータリーエンコーダーのA相とB相をそれぞれArduinoの20番と21番に接続します。
これだけです。チャタリング防止用のコンデンサが無くてもそれなりに動きます。
ソースコード
Arduinoのソースコードは次の通りです。
クリックタイプのロータリーエンコーダーの場合、クリックから次のクリックまでの間に4回状態が変化しますが、感覚的には1クリックで1操作、というのがわかりやすいので、今回はそうしています。
#include <Joystick.h> #include <Wire.h> #include <MsTimer2.h> Joystick_ Joystick = Joystick_( 0x06, // reportid JOYSTICK_TYPE_GAMEPAD, // type 2, // button count 0, // hat switch count false, // x axis enable false, // y axis enable false, // z axis enable false, // right x axis enable false, // right y axis enable false, // right z axis enable false, // rudder enable false, // throttle enable false, // accelerator enable false, // brake enable false // steering enable ); volatile byte enc1c; volatile byte enc1p; volatile int Counter1 = 0; const int PinEnc1A = 20; const int PinEnc1B = 21; void setup() { pinMode(PinEnc1A, INPUT_PULLUP); pinMode(PinEnc1B, INPUT_PULLUP); Joystick.begin(); MsTimer2::set(1, ROTALY); // 1msごとに割り込み MsTimer2::start(); } void loop() { if(Counter1 > 0) { Joystick.pressButton(0); delay(40); Joystick.releaseButton(0); Counter1--; delay(10); } else if(Counter1 < 0) { Joystick.pressButton(1); delay(40); Joystick.releaseButton(1); Counter1++; delay(10); } delay(1); } void ROTALY() { enc1c = (digitalRead(PinEnc1A)<<1) | (digitalRead(PinEnc1B)); if(!(enc1c == enc1p)) { byte enc1 = enc1p << 2 | enc1c; if( enc1==0b0001) //0b0001 { Counter1++; } else if(enc1 == 0b0100)//0b0100 { Counter1--; } enc1p=enc1c; } }
やっていること
1m秒ごとにエンコーダーがつながれたピンの状態を取得します。
変化がなければなにもしません。変化があった場合には次の規則に従って4bitのデータに格納します。
前のA相の状態、前のB相の状態、今のA相の状態、今のB相の状態
enc1c = (digitalRead(PinEnc1A)<<1) | (digitalRead(PinEnc1B)); enc1 = enc1p << 2 | enc1c;
このようにすると、ロータリーエンコーダーを回したとき、(ノイズが一切なければ)格納されるデータは次のようになります。
<時計回り>
0010
1011
1101
0100
<反時計回り>
0001
0111
1110
1000
時計回りと反時計回りで同じデータの並びは生じないことがわかります。
上のなかからそれぞれ適当に1つ選んで(※)、データがその並びになるかどうかで時計回り/反時計回りを検出し、カウンターを回します。これが1m秒ごとに呼び出されるROTALY()関数の役目です。
この手順にしておけば、取りこぼしはそれなりの確率であり得ますが、時計回り/反時計回りを間違って検出してしまうことは少なくなるはずです。
そして、メインループの中で、ROTALY()関数が回したカウンターに応じてジョイスティックのボタンを押します。
ちなみに、割り込みのサブルーチンの中で使われる変数にはvolatileをつけておく必要があるのでご注意を。
※ここの選び方でエラーを少なくできると聞いたことがあるようないような…
この方法の問題点
この方法では、
カウンターが消費される速度 < カウンターを回す速度
となる条件では入力に対して遅延が発生します。
また、ボタンを押して離すまでの時間は”待ち”になってしまって他の処理が止まってしまうのにも注意です。
実際にはさほど問題にはなりませんが、割り込み間隔やPress/Releseにかける時間のチューニングは必要です。
改良版
(2022.06.13 追記)
ここで紹介した方法ではゆっくり動かした場合やはやく動かした場合などにバグることがちょいちょいあったので、プログラムを少し改良しました。
現在はこちらがおすすめです。

おすすめのロータリーエンコーダー
ロータリーエンコーダーの定番は秋月の24クリックタイプ(100円)。
[P-06357]
Amazonでも類似品の取り扱いがあります。
おすすめのつまみ(ノブ)
サトーパーツのアルミ削り出しノブがオススメです。
ロータリーエンコーダー本体より高いですが(笑
[P-12530]
※秋月電子へのリンクは、秋月電子通販サイトの利用規約に基づき秋月電子の通販コードを併記しています。
コメント