🦁

ロータリーエンコーダをArduinoで試してみる

に公開

背景

最近、風洞のファン制御に使っている PWM ジェネレータの入力を見直しました。
これまではポテンショメータをアナログ入力で読んで Duty 比を調整していたのですが、
温度ドリフトが地味に効いてきて、同じノブ位置でも出力が微妙にズレる問題がありました。

そこで今回は、アナログではなくロータリーエンコーダで値を増減させる方式に変更してみます。

まずは Arduino での動作確認をしてみました。


使用したエンコーダ

秋月でよく見るこのタイプです
EC12PLRGBSDVBF-D (押し込みスイッチ・RGB LED付き)

A/B の2相出力に加えて、
押し込みスイッチ(タクト)と RGB LED が内蔵されています。
LEDは共通アノードなので、LOWで点灯するタイプ。

このピン配はノブを上から見たときの位置になります。

LEDとタクトスイッチの接続は下記のようになっています。


配線

機能 Arduino 備考
A D2 割り込み入力
B D3 割り込み入力
C GND 共通
PIN3 A0 押し込みSW、外部 10kΩ プルダウン(押下=HIGH)
PIN5 +5V 共通アノード
PIN1 A1 LED R、220Ω直列、LOWで点灯
PIN2 A2 LED G、220Ω直列、LOWで点灯
PIN4 A3 LED B、220Ω直列、LOWで点灯

LED は各色ごとに抵抗を入れています。
押し込みスイッチは GND 側プルダウン構成で、押されると A0 が HIGH。


サンプルスケッチ

エンコーダの回転でカウントが増減し、押し込みで LED の色を切り替えるだけのテストコードです。

#define PIN_ENC_A   2
#define PIN_ENC_B   3
#define PIN_BTN     A0
#define PIN_LED_R   A1
#define PIN_LED_G   A2
#define PIN_LED_B   A3

// ---- オプション:エッジの最短間隔(チャタ対策用)----
#define USE_TIME_GATE  1       // 0なら無効
#define EDGE_MIN_US    300     // これ未満のエッジは無視(必要に応じて調整)

// 回転方向反転(実機に合わせて)
#define INVERT_DIR     1

// 4遷移で1クリックにするためのしきい
#define DETENT_STEPS   4

volatile int32_t encClicks = 0;   // 1ノッチ=±1 で増減
volatile int8_t  encQ      = 0;   // 遷移の積算(-4..+4でクリア)
volatile uint8_t prevAB    = 0;   // 直前のAB (00/01/11/10)
volatile unsigned long lastEdgeUs = 0;

static const int8_t qtab[16] = {
  //  prevAB(2bit) << 2 | ab(2bit)
  //   00 01 11 10
      0, -1, +1,  0,   // 00 -> 00/01/11/10
     +1,  0,  0, -1,   // 01 -> 00/01/11/10
     -1,  0,  0, +1,   // 11 -> 00/01/11/10
      0, +1, -1,  0    // 10 -> 00/01/11/10
};

inline void enc_isr_core(){
#if USE_TIME_GATE
  unsigned long now = micros();
  // 極端に短いエッジを弾く(機械チャタ対策)
  if ((now - lastEdgeUs) < EDGE_MIN_US) return;
  lastEdgeUs = now;
#endif

  // D2(D)とD3(C)はPINDのbit2/3。まとめて読むことで位相ズレを避ける
  uint8_t ab  = (PIND >> 2) & 0x03;   // 0b00..0b11
  uint8_t idx = (prevAB << 2) | ab;
  prevAB = ab;

  int8_t step = qtab[idx];
#if INVERT_DIR
  step = -step;
#endif
  if (!step) return;

  int8_t q = encQ + step;
  encQ = q;
  if (q >= DETENT_STEPS){
    encQ = 0;
    encClicks++;
  } else if (q <= -DETENT_STEPS){
    encQ = 0;
    encClicks--;
  }
}

void isrEncA(){ enc_isr_core(); }
void isrEncB(){ enc_isr_core(); }

void setup(){
  Serial.begin(115200);

  pinMode(PIN_ENC_A, INPUT_PULLUP);
  pinMode(PIN_ENC_B, INPUT_PULLUP);
  pinMode(PIN_BTN,   INPUT);     // 外付けプルダウン前提(押下=HIGH)
  pinMode(PIN_LED_R, OUTPUT);
  pinMode(PIN_LED_G, OUTPUT);
  pinMode(PIN_LED_B, OUTPUT);

  // 共通アノードLED → 初期は消灯(LOWで点灯なのでHIGH=消灯)
  digitalWrite(PIN_LED_R, HIGH);
  digitalWrite(PIN_LED_G, HIGH);
  digitalWrite(PIN_LED_B, HIGH);

  // 初期AB
  prevAB = (PIND >> 2) & 0x03;

  // A/B 両方でCHANGE割り込み
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_A), isrEncA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(PIN_ENC_B), isrEncB, CHANGE);

  Serial.println(F("[ENC] ready (1 detent = ±1)"));
}

int32_t fetchDelta(){
  static int32_t last = 0;
  noInterrupts();
  int32_t c = encClicks;
  interrupts();
  int32_t d = c - last;
  if (d) last = c;
  return d;
}

void loop(){
  // 1ノッチ=±1で出てくる
  int32_t d = fetchDelta();
  if (d){
    Serial.print(F("detent: "));
    Serial.println(d > 0 ? "+1" : "-1");

    // 変化確認:青LEDをチカッと
    digitalWrite(PIN_LED_B, LOW);
    delay(30);
    digitalWrite(PIN_LED_B, HIGH);
  }

  // 押し込み(簡易)
  if (digitalRead(PIN_BTN) == HIGH){
    static bool locked=false;
    locked = !locked;
    Serial.print(F("Button -> "));
    Serial.println(locked ? F("LOCK") : F("UNLOCK"));
    digitalWrite(PIN_LED_R, locked ? LOW : HIGH);  // 赤=LOCK
    digitalWrite(PIN_LED_G, locked ? HIGH : LOW);  // 緑=UNLOCK
    while (digitalRead(PIN_BTN) == HIGH) delay(5); // リリース待ち
  }
}

実行結果

シリアルモニタを 115200bps で開くと、
エンコーダを回すたびにカウント値が増減します。
押し込みを検出すると LED の色が赤↔緑で切り替わります。
この時点で、回転・押し込みの両方が安定して検出できました。
回転の取りこぼしも若干はありますが、とりあえずサンプルとしてはこんな感じでいいです。

まとめ

ロータリーエンコーダを試してみましたが、LEDもついているし、色々なものに使えそうです。

Discussion