ロータリーエンコーダの使い方

主にPICで利用する場合の使い方をまとめたページです。他のマイコンでも同様だと思います。

ロータリーエンコーダの基本

光学式のものが主流のようですが、メカニカル型のものも流通しています。メカニカル型は安価で電源が不要なため、電子工作ではよく利用すると思いますが、チャタリングが発生する為、この対策が必要で、高速検出には向きません。
逆に、高速検出や、伝送距離が必要な用途の場合、内部にドライバーを持った製品もあります。
出力される信号は主にアブソリュート型とインクリメンタル型に大別されます。

アブソリュート型

absolute output

軸の絶対角度を検出する事を目的とした製品です。構造が比較的複雑なため、高価です。出力は分解能により異なりますが、数bitあり、グレイコードと呼ばれる、バイナリコードを並び替えたコードで出力される場合が多いです。
同時に複数のビットが変化するコードだと、タイミングのズレから、一時的に意図しないコードが出力される場合があるため、これを防止する為に考えられたコードです。

graycode binary converter

グレイコードからバイナリコードには左記のような回路で変換する事が出来ます。

インクリメンタル型

incremental output

回転方向や角速度の検出を目的とした製品です。A相,B相の位相から回転方向を、周波数から速度を割り出します。Z相と呼ばれる、軸の絶対位置を示す出力を持ったものもあり、この場合基準点を通過すれば、相対位置から絶対位置を算出する事も可能です。
AB相の出力は2bitのグレイコードと考える事も出来ますが、4値のコードが1周当たり複数回繰り返し出力されることになります。

マイコンでのデコード手法

wave form vs code

アブソリュート型は単に読み込んで、直読ないしはグレーコード-バイナリ変換をすれば良いだけなので、割愛します。
ここでは、ダイヤル入力やモーターの回転検出などで最も出番の多いと思われる、インクリメンタル型を使った場合について掘り下げていきたいと思います。

前述したとおり、前回の値と、今回の値を組み合わせて判断していく事が基本になり、2+2=4bitの値を扱う事になります。もっとも単純なのは、前述のグレイコード-バイナリ変換を行い、前回との差分を取る事ですが、2回XORを取って、1回差分を取るという演算が必要になります。これよりも、16値のテーブルを作って、テーブル参照をさせた方が、bit shift + メモリ参照で済むので、CLC等を利用してハードウェアに処理を肩代わりさせる場合を除き、テーブル参照の方が有利でしょう。
同様の方法が検索すると沢山出てきますが、基本に立ち返って、テーブルの作り方を一から見てみましょう。Lowを0,Hiを1と定義し、A相をbit0, B相をbit1とすると、時計回りで1,3,2,0のコードが得られることがわかります。(A,B相の逆接や、pull upして使う場合などは、回転方向が逆になるだけで、考え方は変わりません。)

transition
IndexIndex
0081
119+-2
2-1A0
3+-2B-1
4-1C+-2
50D-1
6+-2E1
71F0

これを循環図にして、前回の値を2bit shiftしたものに今回の値を加算したものを、それぞれの間に記述します。時計回り(青地)と反時計回り(緑地)が存在します。
図には記載していませんが、同じ考え方で、不変の場合、+-2変化した場合が存在するので、それら全てをテーブルに纏めると次のようになります。

+-2は、2つ移動した場合で、処理が追いつかない場合に発生します。この場合、回転方向の識別が出来ないので、エラーとして扱うか無視するなどの対策が必要です。

プログラム例

ローターリーエンコーダー(RB[5:6])に接続。各入力はpull upしてあり、ONでGNDに落ちる回路としています。入力変化割込みで割込みルーチンからcallしています。
short read_re(void){
   static unsigned short index;
   unsigned short current;
   const static short re_tbl[] = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};
   current = (PORTB>>5)&0x3;    // REが接続されているRB[5:6]を取り出す
   index <<= 2;         // 前回の値を2bit左シフト
   index += current;    // 現行値を加える
   index &= 0xf;        // 下位4bitを取り出す

   return(re_tbl[index]);
}

テーブルの圧縮

Index
01
11
2-1
3-1
4-1
5-1
61
71

前の図において、状態変化割り込みを使用する場合は、(チャタリングなどの誤動作を除き)不変という事はあり得ません。また、2つ移動するケースが発生しないか、無視できるならば、下位3bitで有効な値が表現できる事に気づきます。
ただ、プログラム的にはわかり易くはなるかもしれませんが、処理時間やリソースは大して節約にならないので、あまり意味はないかもしれません。
一応、問題なく動作する事は確認しています。

プログラム例

short read_re(void){
   static unsigned short index;
   unsigned short current;
   const static short re_tbl[] = {1,1,-1,-1,-1,-1,1,1};
   current = (PORTB>>5)&0x3;    // REが接続されているRB[5:6]を取り出す
   index <<= 2;         // 前回の値を2bit左シフト
   index += current;    // 現行値を加える
   index &= 0x7;        // 下位3bitを取り出す
   
   return(re_tbl[index]);
}

2つ増減した場合の工夫

16値の+-2のケースで、回転方向が急に変わることが無いなら、前回と同一方向に2つ増やす処理をする事で、処理が追いつかないケースをカバーする事が出来ます。
また、wait時間をうまく調整する事で、早く回した時は上位の桁を増減させるなどしてUIを向上させることが可能です。次の例では、read_re()のcall前に30msのwaitを入れてあります。

プログラム例

short read_re(void){
   static unsigned short index;
   static short direction;
   short current;
   // 2値変化した場合は、100が読み出されるようテーブルを作っておく。
   const static short re_tbl[] = {0,1,-1,100,-1,0,100,1,1,100,0,-1,100,-1,1,0};
   current = (PORTB>>5)&0x3;
   index <<= 2;
   index += current;
   index &= 0xf;
   
   if(re_tbl[index] == 100){	// 速く回した場合、+-100を返す。
       return(direction*re_tbl[index]);
   }
   direction = re_tbl[index];	// ゆっくり回した時の回転方向を保存
   return(re_tbl[index]);
}

クリック付の注意点

EC12 doc さて、今まではA相、B相の何れかが変化した場合には回転していると判断する前提で話を進めてきましたが、クリック付の場合はクリック安定点は何れかの相の0度の点にしか設けられていない場合があります。前述のようなプログラムでは1クリックで4増減してしまうためうまくありません。
左記のような例では、クリック安定点のB相の値は利用できず、A相がONになるタイミングで、B相の極性を見るだけで回転方向が判断できるので、プログラムは簡単で済みますが、分解能が下がるので注意が必要です。
クリック安定点が、立上り/立下がりからずらしてあるタイプもあり、この場合は4値のうち任意の点が利用できます。

プログラム例

short read_re(void){
   static unsigned short index;
   unsigned short current;
   
   if(RE_A != index && RE_A == 0){  // RE_Aが1から0に変化しているとき
       index = RE_A;
       current = RE_B ? 1 : -1;     // RE_Bの値で時計回り、反時計回りが判定できる
   }
   else{                            // RE_Aが0から1に変化しているとき、RE_Bのみ変化する場合は無視
       current = 0;
   }
   index = RE_A;
   return(current);
}

テスト回路とプログラム全文

回路図

プログラムは こちら
一番標準的な、入力を検知して割込みをかけ、16値のテーブルを参照する方法での例です。自作のLCDのライブラリ(LCDディスプレイの使いかたはこちらで説明しています。)を使っていますが、ピンアサインの関係で改造してあります。

タクトスイッチで2つの値の入力を切り換えられるようになっており、設定中の値の先頭には*が表示されます。
また、ロータリーエンコーダーの現在値が1行目の10文字目に常に表示されます。
チャタリング防止の為割込みが掛ってから20ms待たせていますが、この辺は誤動作がなければ短くしてもよいでしょう。(因みに、パーツの仕様には30ms MAXとありますw)

参考

オムロン 汎用センサの基礎知識 ロータリーエンコーダー編