シュウジマブログ

Apple製品,技術系の話をするブログ

Arduino Due で高分解能なエンコーダを読み取ってみた

f:id:masa_flyu:20180729020326j:plain

概要

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でのタイマー割り込みメモ

//このプログラムは Arduino 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() {
  //エンコーダの2つのピンを入力に
  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);

  //シリアル通信(USB)の設定
  Serial.begin(115200);
  
  //入力ピンが変化した時に割り込みを入れる
  attachInterrupt(ENC_A, itr_A, CHANGE);  //Aピンが変化した時itr_A()を呼び出す
  attachInterrupt(ENC_B, itr_B, CHANGE);  //Bピンが変化した時itr_B()を呼び出す

  //タイマー(シリアル通信用)
  //http://jtakao.web.fc2.com/elec/due_timerinterrupt/index.html
  NVIC_SetPriority((IRQn_Type)TC3_IRQn,1);  //TC3タイマーの優先順位を1に設定(普通は0なので優先度を下げたことになる)。エンコーダ読み取りを優先させるため。
  startTimer(TC1, 0, TC3_IRQn, 100);  //TC3タイマーを100msecに設定

}


void loop() {
  //ループでは何もしない(割り込みが呼ばれるだけ)
}


/**********割り込み***************/

//Aピン割り込み関数
void itr_A(){
  count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;
}

//Bピン割り込み関数
void itr_B(){
  count += digitalRead(ENC_A) == digitalRead(ENC_B) ? 1 : -1;  
}

//タイマー割り込み関数 100msecごとに呼び出される
void TC3_Handler() {
  TC_GetStatus(TC1, 0);  //時間のカウントを0に戻す
  Serial.print(count);  //エンコーダのカウントをシリアル送信  
  Serial.print("[counts]\t\t");  
  Serial.print( (count - recentcount) * 10);
  Serial.println("[counts/s]");
  recentcount = count;
}


/**********タイマー用関数***************/

//http://jtakao.web.fc2.com/elec/due_timerinterrupt/index.htmlからのコピペ
//タイマーを使いやすくしてくれているらしい
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相の波形を示します. f:id:masa_flyu:20180729013801p:plain 縦軸はそれぞれの相の出力(0または5V),横軸は回転角度です. 正回転であれば横軸の右へ進み,負回転であれば横軸の左へ進むものとします.

18行目でA相の割り込みを以下のように設定しています.

  attachInterrupt(ENC_A, itr_A, CHANGE);  //Aピンが変化した時itr_A()を呼び出す

第3引数をCHANGEに設定することで,A相の入力になんらか変化が生じたときに割り込みが生じるように設定しています.

つまり,A相の割り込みが発生するのはA相に変化が生じる赤い線の場所になります. f:id:masa_flyu:20180729013833p:plain

割り込みが生じたすぐあとは,正回転であれば赤線の右側,負回転であれば赤線の左側にいるはずです.回転方向を判別してカウントを+1または-1するためには,左右のどちらにいるかをA相B相の値から判断しなければなりません. 赤線の左右でのA相B相の値を書き込みました. f:id:masa_flyu:20180729013952p:plain このとき,A相とB相の値が一致しているものを青,不一致のものを黄色で表しました.すると見事に,赤線の左は青,右は黄色に塗り分けられました. つまり, A=Bのとき-1,A≠Bのとき1のように判別してカウントすることができるわけです. f:id:masa_flyu:20180729014203p:plain それをC言語で表したものが

count += digitalRead(ENC_A) == digitalRead(ENC_B) ? -1 : 1;

です,三項演算子を使って,A==Bのとき-1を,A!=Bのとき1をcountに加算しています.

なお,B相のときは青と黄色の関係が真逆になるので,1と-1を反転しています.f:id:masa_flyu:20180827150756p:plain

測定結果

上記のプログラムを走らせた状態で,Arduinoのシリアルモニタを見ながらエンコーダを動かしてみました.下図は高速に往復させた時の値の変化です.

f:id:masa_flyu:20180729015203p:plain

最大で150000[counts/s]くらいの値が確認できますね.

一定回転を高速に往復させ続けたところ,150000[counts/s]程度であれば理論上のカウント数とおよそ一致しました. より高速に回転させたところ,最大で200000[counts/s]くらいを計測しましたが,その際には,累積のカウント数が理論上の値と大幅にずれてしまいました. 正直,計測方法がとても雑なので,自信を持っては言えないのですが,150000[counts/s]までなら測定できそうです. わりと実用的じゃないでしょうか?少なくともArduinoUnoよりはるかに実用的です.

今後の展望

以上のプログラムは,Arduino標準の関数を用いて実装しました.しかしながら,マイコンのレジスタを直接操作するなどすれば,より早い実装が可能かもしれません.

当ブログをご利用いただく際には免責事項をお読みください。