Arduinoでロータリーエンコーダーを使ってゲームコントローラーを作る

マイコン/Arduino
ノブの動作をよくするためにソースコードを改良しました。改良版はこちら
改・Arduinoでのロータリーエンコーダーの使い方
ESP32でロータリーエンコーダーを使ってゲームコントローラー(ゲームパッド)を作る方法とその理屈を解説しました。

なお、上記改良版も含めてここで紹介する方法は高速で回転するエンコーダーの読み取りには向きません。あくまでも簡易的な方法です。

arduinoでゲームコントローラー(ジョイスティック)を作る4回目です。今回はArduinoでロータリーエンコーダーを使う方法を紹介します。

ロータリーエンコーダーのメリット

ロータリーエンコーダーを使うと、ホンモノの機械の操作ノブのようなゲームパッド(ゲームデバイスといった方がよいでしょうか?)を作ることができます。ただ、これまで説明してきたボタンなどと比べると、検出にタイミングなどが絡んでくるためにすこし面倒になります。今回は(おそらく)一番簡単な方法でロータリーエンコーダーを使う方法をご紹介します。特別なライブラリ等も使用しません。

完璧ではありませんが、チャタリング対策のハードウェア等もなしで、そこそこの精度で拾えます。いろいろ試した挙句、これに行きつきました。もっと良い方法は他にもあると思いますが(絶対にある)、とりあえず使えればOKという方向けの情報です。

フライトシミュレーターでオートパイロット関係の設定をいじったりする分には十二分に使えます。

本来は立ち上がりのエッジを検出する、というのが常套手段ですがHWにより使用できるピンが決まってしまうという問題があります。
ここで紹介する方法は、入力割り込みを使わずに、タイマー割り込みでロータリーエンコーダーを使用する方法をご紹介します。

回路図

Arduino ProMicro1個とロータリーエンコーダーを1個だけ使います。

配線は次の通りにします。

ProMiroとロータリーエンコーダーの配線図

ロータリーエンコーダーの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 追記)

ここで紹介した方法ではゆっくり動かした場合やはやく動かした場合などにバグることがちょいちょいあったので、プログラムを少し改良しました。

現在はこちらがおすすめです。

改・Arduinoでのロータリーエンコーダーの使い方
ESP32でロータリーエンコーダーを使ってゲームコントローラー(ゲームパッド)を作る方法とその理屈を解説しました。

おすすめのロータリーエンコーダー

ロータリーエンコーダーの定番は秋月の24クリックタイプ(100円)。

ロータリーエンコーダ(24クリックタイプ): パーツ一般 秋月電子通商-電子部品・ネット通販
電子部品,通販,販売,半導体,IC,マイコン,電子工作ロータリーエンコーダ(24クリックタイプ)秋月電子通商 電子部品通信販売

[P-06357]

Amazonでも類似品の取り扱いがあります。

Amazon.co.jp

おすすめのつまみ(ノブ)

サトーパーツのアルミ削り出しノブがオススメです。
ロータリーエンコーダー本体より高いですが(笑

メタルツマミ(ノブ) K−59−M Φ30mm×16mm アルミ削り出し: パーツ一般 秋月電子通商-電子部品・ネット通販
電子部品,通販,販売,半導体,IC,マイコン,電子工作メタルツマミ(ノブ) K−59−M Φ30mm×16mm アルミ削り出し秋月電子通商 電子部品通信販売

[P-12530]

※秋月電子へのリンクは、秋月電子通販サイトの利用規約に基づき秋月電子の通販コードを併記しています。

コメント