主にPICで利用する場合の使い方をまとめたページです。他のマイコンでも同様だと思います。
光学式のものが主流のようですが、メカニカル型のものも流通しています。メカニカル型は安価で電源が不要なため、電子工作ではよく利用すると思いますが、チャタリングが発生する為、この対策が必要で、高速検出には向きません。
逆に、高速検出や、伝送距離が必要な用途の場合、内部にドライバーを持った製品もあります。
出力される信号は主にアブソリュート型とインクリメンタル型に大別されます。
軸の絶対角度を検出する事を目的とした製品です。構造が比較的複雑なため、高価です。出力は分解能により異なりますが、数bitあり、グレイコードと呼ばれる、バイナリコードを並び替えたコードで出力される場合が多いです。
同時に複数のビットが変化するコードだと、タイミングのズレから、一時的に意図しないコードが出力される場合があるため、これを防止する為に考えられたコードです。
グレイコードからバイナリコードには左記のような回路で変換する事が出来ます。
回転方向や角速度の検出を目的とした製品です。A相,B相の位相から回転方向を、周波数から速度を割り出します。Z相と呼ばれる、軸の絶対位置を示す出力を持ったものもあり、この場合基準点を通過すれば、相対位置から絶対位置を算出する事も可能です。
AB相の出力は2bitのグレイコードと考える事も出来ますが、4値のコードが1周当たり複数回繰り返し出力されることになります。
アブソリュート型は単に読み込んで、直読ないしはグレーコード-バイナリ変換をすれば良いだけなので、割愛します。
ここでは、ダイヤル入力やモーターの回転検出などで最も出番の多いと思われる、インクリメンタル型を使った場合について掘り下げていきたいと思います。
前述したとおり、前回の値と、今回の値を組み合わせて判断していく事が基本になり、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して使う場合などは、回転方向が逆になるだけで、考え方は変わりません。)
Index | 値 | Index | 値 |
0 | 0 | 8 | 1 |
1 | 1 | 9 | +-2 |
2 | -1 | A | 0 |
3 | +-2 | B | -1 |
4 | -1 | C | +-2 |
5 | 0 | D | -1 |
6 | +-2 | E | 1 |
7 | 1 | F | 0 |
これを循環図にして、前回の値を2bit shiftしたものに今回の値を加算したものを、それぞれの間に記述します。時計回り(青地)と反時計回り(緑地)が存在します。
図には記載していませんが、同じ考え方で、不変の場合、+-2変化した場合が存在するので、それら全てをテーブルに纏めると次のようになります。
+-2は、2つ移動した場合で、処理が追いつかない場合に発生します。この場合、回転方向の識別が出来ないので、エラーとして扱うか無視するなどの対策が必要です。
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 | 値 |
0 | 1 |
1 | 1 |
2 | -1 |
3 | -1 |
4 | -1 |
5 | -1 |
6 | 1 |
7 | 1 |
前の図において、状態変化割り込みを使用する場合は、(チャタリングなどの誤動作を除き)不変という事はあり得ません。また、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]); }
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]); }
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)