🦁
ロータリーエンコーダを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