DCモーターのエンコーダーを使ってみる


今回使用するモーターから6本のコードが出ている。このうち1と6がモーター駆動用。2と5がエンコーダーの電源用、3と4からエンコーダのパルスが出力される。ところがコードの説明が見当たらない。以前使ったGA12-N20 6V 60RPMのエンコーダーのコードと色と並びが同じだったが違っていた。Encorderの+と-が下の図とは逆だった。
今回、エンコーダーを使うに当たっては、レマの研究室さんの記事を全面的に参考にさせて頂いた。

エンコーダーの出力

モーターの回転はパルス数に比例する。回転の向きはEncoder A とEncoder B のパルスの関係で決まる。Encoder AとEncoder Bからは出力されるパルスは、1/4サイクルずれている。下図のようにEncoder A のパルスが立ち上がるときにEncoder B のパルスがLOWならば反時計回り、HIGHならば時計回りになる。

環境

マイコンESP32 Dev MOdule(38pin)
DCモータードライバDRV8871搭載 DCモータードライバ
DCモーター12V60RPM
Arduino IDE動作PCRaspberry Pi 5
Arduino IDバージョン1.8.19

RPMを表示する

モーター回転のRPM(revolutions per minute)を表示するプログラムを作る。このプログラムを起動すると、モーターが一定のスピードで回転し、その時のRPMが下図のようにOLEDに表示される。1行目:プログラム名, 2行目:プログラム名(つづき), 3行目:0.5秒毎のパルスの数, 4行目:0.5秒毎のRPM。

エンコーダーからのパルスの立ち上がりを確実にとらえるために、割り込み処理を使っている。割り込み処理の設定はattachInterrupt関数で行う。

attachInterrupt(digitalPinToInterrupt(ENCO_B), encoder_func, RISING);
    # digitalPinToInterrupt(ENCO_B) : ピン番号から変換した割り込み番号を指定する
    # encoder_func : 割り込み発生時に呼び出す関数
    # RISING : ピンの状態がLOWからHIGHに変わったときに割り込みが発生

割り込み処理関数は、フラグ処理やカウントアップなど極力短時間で終了する処理にする。ここでは排他は行っていない。また変数や関数をメモリに常駐し、優先して処理ができるようにする。参照

volatile long encoderValue = 0; // volatile : 変数をRAM からロードする
void IRAM_ATTR encoder_func() { // IRAM_ATTR : 関数をメモリに常駐する
  // 処理はパルスカウンターのインクリメントかデクリメントだけ
  int A_state = digitalRead(ENCO_A);
  int B_state = digitalRead(ENCO_B);
  if (A_state == B_state) {
    encoderValue--; // clockwise
  } else {
    encoderValue++; // counter clockwise
  }
}
ESP32_DRV8871_encoder

/* ESP32_DRV8871_encoder       2025.1.26
【概要】ESP32でモータードライバーDRV8871を使ってDCモーターのエンコーダーからrpmを表示する簡単な例

【環境】  ESP32は38pin
         モータードライバーはDRV8871
         モーターは、12V60RPM DCギアモーター、エンコーダー付き

【OLED表示】
    1行目 プログラム名
    2行目 プログラム名(つづき)
    3行目 INTERVAL間隔のパルス数
    4行目 INTERVAL間隔のRPM

*/

const int MOTOR_SPEED = 150; // モーターの出力

// ESP32のpin設定
const int motor1Pin1 = 12;  // ドライバーDRV8871のIN1へ   
const int motor1Pin2 = 13;  // ドライバーDRV8871のIN2へ 
const int ENCO_A = 27;      // エンコーダー A のコードへ 
const int ENCO_B = 26;      // エンコーダー B のコードへ  

const int INTERVAL = 500;   // RPM計測の間隔(ms)
const int PPR_VAL = 11;     // Pulses per Revolution
const int GEAR_RATIO = 168; // 減速比

#include <Wire.h>               // SSD1306 --- OLED用
#include "SSD1306.h"            // SSD1306用ライブラリを読み込み
SSD1306  display(0x3c, 21, 22); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)
//----- グローバル変数に行ごとの内容を保持
String line1 = "";            // 1行目
String line2 = "";            // 2行目
String line3 = "";            // 3行目
String line4 = "";            // 4行目

volatile long encoderValue = 0; // エンコーダーのパルスカウンター
long previousMillis = 0;

// エンコーダーのパルス発生割り込み処理
void IRAM_ATTR encoder_func() {
  // A_val and B_val are HIGH / LOW
  int A_state = digitalRead(ENCO_A);
  int B_state = digitalRead(ENCO_B);
  if (A_state == B_state) {
    encoderValue--; // clockwise
  } else {
    encoderValue++; // counter clockwise
  }
}

void setup() {

  // 初期画面表示
  display.init();    //ディスプレイを初期化
  display.setFont(ArialMT_Plain_16);    //フォントを設定
  display.flipScreenVertically();       // 表示反転(ボードにLCDを固定する向きに応じて)
  line1 = "ESP32_DRV8871_";  // プログラム名表示
  line2 = "encoder";
  display.drawString(0, 0, line1);      // 1行目の表示
  display.drawString(0, 17, line2);     // 2行目の表示
  display.drawString(0, 34, line3);     // 3行目の表示
  display.drawString(0, 49, line4);     // 4行目の表示
  display.display();                    // 画面を更新

  // DC motor設定
  pinMode(motor1Pin1 , OUTPUT);
  pinMode(motor1Pin2 , OUTPUT);
  analogWrite(motor1Pin1, 0);
  analogWrite(motor1Pin2, 0); 

  // エンコーダー設定
  pinMode(ENCO_A, INPUT_PULLUP);
  pinMode(ENCO_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENCO_B), encoder_func, RISING);

  // とりあえず低速回転
  analogWrite(motor1Pin1, 0);
  analogWrite(motor1Pin2, MOTOR_SPEED); 
  delay(1000);
}
 
void loop() {
  int currentMillis = millis();
  // INTERVALが経過したかチェック
  if (currentMillis - previousMillis > INTERVAL) {
    previousMillis = currentMillis;

    // INTERVAL間隔のパスる数からRPMを求める。
    float rpm_val = (float)encoderValue * 120 / PPR_VAL / GEAR_RATIO;

    char buf_l3[32];
    sprintf(buf_l3, "enc: %ld", encoderValue);
    line3 = buf_l3;

    char buf_l4[32];
    sprintf(buf_l4, "rpm: %.2f", rpm_val);
    line4 = buf_l4;

    //----- OLED表示
    display.clear();                      // 画面をクリア
    display.drawString(0, 0, line1);      // 1行目の表示
    display.drawString(0, 17, line2);     // 2行目の表示
    display.drawString(0, 34, line3);     // 3行目の表示
    display.drawString(0, 49, line4);     // 4行目の表示
    display.display();                    // 画面を更新
    
    encoderValue = 0;
  }
}