概要
1周512[PPR](2048[counts/rev])のエンコーダを読み取って,シリアル通信で出力してみました.結論から言えば,150000[counts/s]くらいまでなら読み取れそうです.ただし,他になんの処理も挟んでいない上,1個だけの場合ですので,実際に使う場合には,10分の1くらいに抑えるのが無難じゃないでしょうか.
単位はこちらに準拠:エンコーダのPPR、CPR、LPRとは何ですか? | CUI Inc
環境
※カタログには512カウントとありますが1回転あたりのパルス数(PPR)が512です.なので,これ以降では512[PPR]=2048[Cycles/rev]として話を進めます.
高いですが,ごく普通の高分解能エンコーダです.A相B相の+側をArduinoのdigitalに,-側をGNDにつないで,電源として5Vを加えています.
Arduino DueのIOは3.3V入力ですが,エンコーダの5V出力を直接つなぎました.推奨しません.
プログラム
以下のプログラムは以下のURLにあるタイマ割り込みプログラムを使用しています.
DUEでのタイマー割り込みメモ
#define ENC_A 2
#define ENC_B 3
void itr_A();
void itr_B();
void TC3_Handler();
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t mSec);
long count=0;
long recentcount = 0;
void setup() {
pinMode(ENC_A, INPUT);
pinMode(ENC_B, INPUT);
Serial.begin(115200);
attachInterrupt(ENC_A, itr_A, CHANGE);
attachInterrupt(ENC_B, itr_B, CHANGE);
NVIC_SetPriority((IRQn_Type)TC3_IRQn,1);
startTimer(TC1, 0, TC3_IRQn, 100);
}
void loop() {
}
void itr_A(){
count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;
}
void itr_B(){
count += digitalRead(ENC_A) == digitalRead(ENC_B) ? 1 : -1;
}
void TC3_Handler() {
TC_GetStatus(TC1, 0);
Serial.print(count);
Serial.print("[counts]\t\t");
Serial.print( (count - recentcount) * 10);
Serial.println("[counts/s]");
recentcount = count;
}
void startTimer(Tc *tc, uint32_t channel, IRQn_Type irq, uint32_t mSec) {
pmc_enable_periph_clk((uint32_t)irq);
TC_Configure(tc, channel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK1);
uint32_t rc = (VARIANT_MCK/2/1000)*mSec;
TC_SetRC(tc, channel, rc);
TC_Start(tc, channel);
tc->TC_CHANNEL[channel].TC_IER=TC_IER_CPCS;
tc->TC_CHANNEL[channel].TC_IDR=~TC_IER_CPCS;
NVIC_EnableIRQ(irq);
}
プログラム解説
タイマー
DUEでのタイマー割り込みメモ
startTimer()関数はこちらのサイトをコピペして,タイマー割り込みを用いています.
setup()内でstartTimer(TC1, 0, TC3_IRQn, 100);
のように呼び出すことで,TC1ブロックの0番=TC3ユニットのタイマを用いて100msをカウントしています.カウント毎にシリアルを送っていてはエンコーダIOピン割り込みの時間が長くなってしまい,次の割り込みに間に合わなくなるので,100msに1回だけタイマーでシリアルを送るようにしました.
この割り込みが反応するとTC3_Handler()関数が呼び出されます.
また,NVIC_SetPriority((IRQn_Type)TC3_IRQn,1);
の1行を加えることで,タイマー割り込みの優先順位をエンコーダより下げているので,エンコーダIOピン割り込みには影響しないはずです.
エンコーダIOピン割り込み
attachInterrupt()関数を用いて,IOピン割り込みを入れています.その際,A相ピンとB相ピンで割り込み先の関数を変えています.
A相が0または1から1または0に変化するとitr_A()が,B相が変化するとitr_B()が実行されます.
方向の判断・加減算
ここが一番難解でしょう.
エンコーダのA相,B相の波形のずれを利用して回転方向を判断します.
正回転の場合にカウントを増加,負回転の場合にカウントを減少させます.
A相を例にとって説明します.
count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;
A相,B相の波形を示します.
縦軸はそれぞれの相の出力(0または5V),横軸は回転角度です.
正回転であれば横軸の右へ進み,負回転であれば横軸の左へ進むものとします.
18行目でA相の割り込みを以下のように設定しています.
attachInterrupt(ENC_A, itr_A, CHANGE);
第3引数をCHANGEに設定することで,A相の入力になんらか変化が生じたときに割り込みが生じるように設定しています.
つまり,A相の割り込みが発生するのはA相に変化が生じる赤い線の場所になります.
割り込みが生じたすぐあとは,正回転であれば赤線の右側,負回転であれば赤線の左側にいるはずです.回転方向を判別してカウントを+1または-1するためには,左右のどちらにいるかをA相B相の値から判断しなければなりません.
赤線の左右でのA相B相の値を書き込みました.
このとき,A相とB相の値が一致しているものを青,不一致のものを黄色で表しました.すると見事に,赤線の左は青,右は黄色に塗り分けられました.
つまり,
A=Bのとき-1,A≠Bのとき1のように判別してカウントすることができるわけです.
それをC言語で表したものが
count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;
です,三項演算子を使って,A==Bのとき-1を,A!=Bのとき1をcountに加算しています.
なお,B相のときは青と黄色の関係が真逆になるので,1と-1を反転しています.
測定結果
上記のプログラムを走らせた状態で,Arduinoのシリアルモニタを見ながらエンコーダを動かしてみました.下図は高速に往復させた時の値の変化です.
最大で150000[counts/s]くらいの値が確認できますね.
一定回転を高速に往復させ続けたところ,150000[counts/s]程度であれば理論上のカウント数とおよそ一致しました.
より高速に回転させたところ,最大で200000[counts/s]くらいを計測しましたが,その際には,累積のカウント数が理論上の値と大幅にずれてしまいました.
正直,計測方法がとても雑なので,自信を持っては言えないのですが,150000[counts/s]までなら測定できそうです.
わりと実用的じゃないでしょうか?少なくともArduinoUnoよりはるかに実用的です.
今後の展望
以上のプログラムは,Arduino標準の関数を用いて実装しました.しかしながら,マイコンのレジスタを直接操作するなどすれば,より早い実装が可能かもしれません.