回転角度や回転速度が知りたいとき、ロータリーエンコーダというセンサーが用いられます。そんなロータリーエンコーダですが、Arduinoで使うとき、どのように繋げばよいか、どのようにスケッチを書けばよいか初学者にとっては難しいと思います。そこで本記事では、ロータリーエンコーダの使い方を一から十まで説明します。
ロータリーエンコーダについて
ロータリーエンコーダとは
ロータリーエンコーダとは回転角度や回転速度を知るためのセンサです。ロータリーエンコーダの軸を回転させると、パルス(矩形)波の電気信号が出力され、それを電気回路やプログラムで処理することによって、回転角度や回転速度を得ます。市販されているロータリーエンコーダは主に、光センサ式と磁気式(ホール効果利用)が多く見られます。また、インクリメンタル型とアブソリュート型とがあり、本記事ではインクリメンタル型を説明します。
ロータリーエンコーダの出力信号
センサからは配線が3~5本出ています。一般的な4本の場合を説明します。模式図で示すと次のようになります。
A相だけあれば、パルス数を数えることで回転角度が分かると感じたと思います。しかし、回転方向が反対になったとしても、パルスの数が増えるだけで回転方向までは分かりません。パルスの増分しか知ることができないことを意味します。そのため図のようにB相があります。
A相とB相はセンサ軸の回転方向(時計回りcw/反時計回りccw)によって、次のようになります。横軸が時間です。
A相とB相は1/4パルス(90度)分ずれています。図のように、時計回りと反時計回りとではA相とB相のパターンが異なるため、これを利用して回転方向を知ります。つまり、A相とB相の2つのパルス波をもとに、パルス数をカウントして回転角が分かり、A相とB相のずれ方によって回転方向が分かります。
分解能
センサにはどれだけ細かく情報を得られるのか指標として分解能があります。ロータリーエンコーダは、主にPPR(pulses per revolution)、CPR(counts per revolution)の二種類で分解能が表記されます。
PPR:1回転あたりのパルス数
CPR:1回転あたりの最大の分解能
下の図はそれぞれの意味を時間軸で表現したものになります。CPRは、A相とB相により、HighとLowの組み合わせ方で1パルス中に4パターンあるためPPRの4倍となります。$$ \rm{CPR} = 4\times\rm{PPR}\tag{1} $$
配線方法
基本的に以下の表のように配線します。
ロータリーエンコーダ | … | Arduino |
A相 | ↔ | 割り込みピン[*1] |
B相 | ↔ | Digitalピン |
Vcc | ↔ | 5V[*2] |
GND | ↔ | GND |
[*1] 割り込みピンは以下の表のようにボードごとに異なります。(参考:attachInterrupt() – Arduino Reference)
ボード名 | 割り込みピンとして使用可能なピン |
Uno, Nano, Min | 2, 3 |
Uno WiFi Rev.2, Nano Every | 全てのデジタルピン |
Mega, Mega2560, MegaADK | 2, 3, 18, 19, 20, 21(ただし、20及び21ピンについては、I2C通信に使っている間は割り込みピンとして使用不可能) |
Micro, Leonardo, 他の32u4ベースのボード | 0, 1, 2, 3, 7 |
Zero | 4ピン以外の全てのデジタルピン |
Due | 全てのデジタルピン |
[*2] 使用するロータリーエンコーダの電源電圧が5Vの場合です。他の電圧を供給しなければならない場合、昇圧/降圧もしくは別電源を使用してください。
スケッチ
なるべくシンプルにしました。Arduino Unoを想定しています。また、分解能は割り込みピンを1つだけ使用しているため、外部割り込みのCHANGEを用いてPPRの2倍となります。
const int A_pin = 3; // 割り込みピン
const int B_pin = 4;
int count = 0;
void setup() {
pinMode(A_pin, INPUT_PULLUP);
pinMode(B_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(A_pin), pulse_counter, CHANGE);
Serial.begin(9600);
}
void loop() {
Serial.println(count);
delay(10);
}
void pulse_counter() {
if(digitalRead(A_pin) ^ digitalRead(B_pin)) {
count++;
} else {
count--;
}
}
1~3行
const int A_pin = 3; // 割り込みピン
const int B_pin = 4;
int count = 0;
変数を定義しています。int型(整数は英語でinteger)は整数を格納します。const(定数は英語でconstant)は変数の挙動を変える修飾子であり、定数を表します。型を持つ変数として使えますが、値は変更できません。つまり、宣言以降に代入しようとするとコンパイルエラーとなります。1行目がボードに割り当てられている割り込みピンの番号です。
5~10行
void setup() {
pinMode(A_pin, INPUT_PULLUP);
pinMode(B_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(A_pin), pulse_counter, CHANGE);
Serial.begin(9600);
}
setup()関数は、Arduinoの電源がオンになったときやリセットされたときに、最初の一度だけ実行されます。この関数の中で、pinMode()関数で使用するピンの入出力を設定します。ここでは、INPUT_PULLUPを指定し、入力ピンかつ内部プルアップ抵抗を有効にしています。また、attachInterrupt()関数で外部割り込みが発生したときに実行する関数(のちに定義するpulse_counter()関数です)を指定しています。言葉にすれば、「3番ピン(A相)に変化があったときpulse_counter()関数を実行する」となっています。Serial.begin()により、Arduinoとパソコンの間の通信回線を用意します。引数の9600という数字は1秒に9600ビット(9600 bps)という意味で、通信を行う速度に相当します。
12~15行
void loop() {
Serial.println(count);
delay(10);
}
setup()関数の実行が終わると、loop()関数が繰り返し実行されます。Serial.println()関数によって、情報をArduinoからパソコンに送信します。シリアルモニタから、その情報を見ることができます。delay()関数によって、指定した時間だけ止めます。引数は一時停止する時間(ミリ秒)です。
17~23行
void pulse_counter() {
if(digitalRead(A_pin) ^ digitalRead(B_pin)) {
count++;
} else {
count--;
}
}
オリジナルのpulse_counter()を定義しています。エンコーダの回転を読み取る本質になります。if()文をを用いて、回転方向により条件を分岐しています。if文がtrueであれば時計回りなので変数countに1を足し(count++)、falseであれば反時計回りなので変数countから1を引きます(count–)。if分の条件について説明します。A相が変化したとき、そのあとすぐのA、B相の値に着目しています。A相が変化したとき、時計回りであれば、10か01となっています。反時計回りであれば、11か00となっています。論理表にすると次のようになります。Xが1の時は時計回りを、0の時は反時計回りを表しています。表から分かるように排他的論理和(XOR)です。ですから、A相のピンの状態とB相のピンの状態の排他的論理和(^)をとれば、条件分岐できるわけです。
A | B | X |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
最後に
正直なところ、まだまだ書ききれていません。チャタリングやオープンコレクタ、Z相などロータリーエンコーダを用いるにあたり考えなければならないことがあります。基本的にデータシートを参照していただければ分かると思います。また、私が検索した中では、本記事のスケッチが一番シンプルかつ最低限のものとなっています。助けになればと思います。
コメント