ロータリーエンコーダとは
ロータリーエンコーダは機械的な回転を電気信号に変換するセンサの一種です。この電気信号を回路やプログラムで処理し、軸が回転した角度や回転速度を得ることができます。ロボットアームの角度検出・エレベーター・印刷機・ラジコン・アーケードゲーム機などの回転数検出に用いられます。

ロータリーエンコーダの仕組み
機械接点式と無接点式(光センサ方式・磁気センサ方式)があります。ここでは簡単のため、光センサ方式のロータリーエンコーダで説明します。
基本的な角度の検出方法は「LEDから出た光が円板上のスリッド(隙間)を透過しているか」で判断されます。円板上のスリッドは一定角度毎に配置され、光の通り道上にスリッドがあれば、光がその先のフォトトランジスタに到達してONし、光が遮られればフォトトランジスタはOFFします。
つまり仮に回転軸が一定速度で回転していれば、フォトトランジスタの出力はONとOFF時間が等間隔のパルス信号になります。(実際にパルス信号を生成するためには、フォトトランジスタの周辺回路が必要です)

ロータリーエンコーダの種類
インクリメンタル型
2相パルス出力のタイプで、回転方向とカウント数のみ分かります。絶対的な角度は分からないため、絶対角度を知りたい場合は初期位置を検出するための他のセンサ・スイッチを組み込むか、基準位置を得るための原点出力(Z相出力)を持つタイプを使います。構造が簡単なため、比較的安価です。
回転速度や角度を知るときは、パルスの変化を監視してプログラムや回路内で加算(インクリメンタル)して数えてやる必要があります。

アブソリュート型
10bit~20bit程度の絶対角度を出力するタイプです。いつでも絶対角度が分かるので、初期位置の設定が不用です。出力する信号は純二進コード(バイナリコード)とグレイコードのタイプがあります。インクリメンタル型と比較して構造が複雑なため、高価です。角度情報を絶対値で出力するため、マイコンや回路で角度や速度を得るための処理が簡単になります。

ブレッドボードに配線
Arduino nanoを使って、回転を検出してみます。
回路図
R1,R2でプルアップし、ロータリーエンコーダ出力を安定化させます。C1,C2はチャタリング防止で0.1μFを追加。R3,R4はマイコンのIOピン保護用の抵抗です。(回路接続を間違わなければなくても動きます)

ブレッドボードの配線


プログラム(クラスで実現)
今後もプログラムを再利用しやすいように、ロータリーエンコーダの処理はクラスにまとめました。インクリメンタル型のロータリーエンコーダの信号を処理し、カウントの更新を自動で行ってくれるクラスです。
※ロータリーエンコーダの信号を処理するプログラムはググれば効率的なものがいくらでも出てきますが、あえて自作してみました。車輪の再発明でも、自力で実装してみることで分かることは沢山あります・・・
RotaryEncoder2phase.ino(メインルーチン)
#include "RotaryEncoder2phase.h"
int encoder_pin1 = 2;
int encoder_pin2 = 3;
int old_counter = 0;
//ロータリーエンコーダ処理クラスのインスタンス化
RotaryEncoder2phase RE = RotaryEncoder2phase(2,3);
void setup() {
Serial.begin(9600); //デバッグ用シリアルポートオープン
}
void loop() {
RE.update();//ロータリーエンコーダクラスの更新
int new_counter = RE.get_counter(); //最新状態を取得
if(new_counter != old_counter){
Serial.println(new_counter); //値が変化していたらシリアル転送
}
old_counter = new_counter;
}RotaryEncoder2phase.h(ロータリーエンコーダ処理クラスの定義ファイル)
#include <Arduino.h>
/******************************************************/
//クラス名:RotaryEncoder2phase
//ver:1.0 2020/07/12
//処理内容:ロータリーエンコーダ(2相)の信号処理を行う。
/******************************************************/
class RotaryEncoder2phase{
public:
RotaryEncoder2phase(int pin1, int pin2);
void update(); //カウンタ値を最新の状態に更新するメンバ関数
int get_counter(); //最新のカウンタ値を取得するメンバ関数
void reset_counter(); //カウンタを初期化(ゼロクリア)するメンバ関数
private:
int pinNum1, pinNum2;
int nowSig1, nowSig2;
int oldSig1, oldSig2;
int counter; //カウンタ(範囲:-2147483648~2147483647)
int nowState, oldState; //現フレームの状態,前フレームの状態
};RotaryEncoder2phase.cpp(ロータリーエンコーダ処理クラスの実装ファイル)
※プラグインの不調で、比較条件で使っている「&」が「&」と表示去れているかもしれません。ごめんなさい・・・
#include "RotaryEncoder2phase.h"
/******************************************************/
//クラス名:RotaryEncoder2phase
//ver:1.0 2020/07/12
//処理内容:ロータリーエンコーダ(2相)の信号処理を行う。
/******************************************************/
/********** コンストラクタ **********/
RotaryEncoder2phase::RotaryEncoder2phase(int pin1, int pin2){
pinNum1 = pin1;
pinNum2 = pin2;
pinMode(pinNum1, INPUT);
pinMode(pinNum2, INPUT);
nowSig1 = 0;
nowSig2 = 0;
oldSig1 = 0;
oldSig2 = 0;
counter = 0;
nowState = 0;
oldState = 0;
}
//更新
void RotaryEncoder2phase::update(){
int nowSig1 = digitalRead(pinNum1);
int nowSig2 = digitalRead(pinNum2);
/*stateの更新*/
if(nowSig1 != oldSig1 || nowSig2 != oldSig2){
if (nowSig1 == 0 && nowSig2 == 0)nowState = 0;
else if(nowSig1 == 1 && nowSig2 == 0)nowState = 1;
else if(nowSig1 == 1 && nowSig2 == 1)nowState = 2;
else if(nowSig1 == 0 && nowSig2 == 1)nowState = 3;
}
/*========stateの定義========*//*
0:pin1=0, pin2=0
1:pin1=1, pin2=0
2:pin1=0, pin2=1
3:pin1=1, pin2=1
*//*==========================*/
if((oldState == 0 && nowState == 1) ||
(oldState == 1 && nowState == 2) ||
(oldState == 2 && nowState == 3) ||
(oldState == 3 && nowState == 0)){
counter++;
}
else if((oldState == 0 && nowState == 3) ||
(oldState == 3 && nowState == 2) ||
(oldState == 2 && nowState == 1) ||
(oldState == 1 && nowState == 0)){
counter--;
}
oldSig1 = nowSig1;
oldSig2 = nowSig2;
oldState = nowState;
}
//カウンタ値の取得
int RotaryEncoder2phase::get_counter(){
return counter;
}
//カウンタのリセット
void RotaryEncoder2phase::reset_counter(){
counter = 0;
}
ちょっとだけ解説
信号処理のミソは35行目から60行目で実行しています。処理の流れは以下の通りです。[L○○]は行番号です。
①二つの入力(nowSig1,nowSig2)の状態を取得[L30~L31]
②入力信号を元に、現在の状態(nowState)を決定(状態0~3)[L35~L40]
③過去の状態(oldState)と現在の状態(nowState)を比較[L49~L60]
④変化していれば、変化のパターンによりカウンタ(counter)のインクリメント/デクリメントを実行[L53, L59]
⑤過去の値(oldState, oldSig1, oldSig2)を更新[L62~L64]
実際に動かしてみた
シリアルモニタを起動し、ロータリーエンコーダを回すと・・・
ちゃんと値が変化した場合のみ表示されました!

逆にしたときも問題なくデクリメントされています。



