2002. 1. 30
ロータリー・エンコーダとは、回転軸に取りつけてその動きを検出するセンサのことです。身近なところではマウス(ボール式)やジョグダイアル等に使われています。産業用としては、サーボ・モータに内蔵されてモータの角度位置の検出に使用されます。汎用ロータリー・エンコーダの出力形式には次に示すように主に2つのタイプがあります。
図のような直角2相パルスの出力で、回転方向とカウント数だけ検出できるタイプ。構造が簡単なので安価。基準位置を得るため原点出力(1回転に1パルス)のあるものもある。機械接点式と無接点式(光学センサ、磁気センサ)がある。廉価品は波形整形されていないのがあるので注意。
絶対角度を十数ビットのグレイコードで出力する。出力コードがダイレクトに絶対角度を示すので、どんなときも位置を見失わない。構造が複雑でけっこう高価。グレイコードを使うのは、各ビットのアライメント・エラーの影響を排除するため。
変化 | 方向 |
---|---|
0→0 | 静止 |
0→1 | +1 |
0→2 | -1 |
0→3 | 無効 |
1→0 | -1 |
1→1 | 静止 |
1→2 | 無効 |
1→3 | +1 |
2→0 | +1 |
2→1 | 無効 |
2→2 | 静止 |
2→3 | -1 |
3→0 | 無効 |
3→1 | -1 |
3→2 | +1 |
3→3 | 静止 |
今回は構造が単純で一般的に広く用いられているインクリメンタル型の使い方を紹介してみます。モーター制御の場合、普通はバッファ・カウンタを設けたり、2相パルスを入力できるメカ制御向けマイコンを使いますが、この例は汎用PIOに直結して使う場合のコツです。
まず、右の図のように A相と B相をバイナリ・コードとして見ると、その値は回転に伴い 0→1→3→2→0(正転の場合)を繰り返すのが分かります。したがって、定期的に出力をサンプリングして前回の値と比較することによりどちらへ回転したのかが分かります。変化のパターンは16通りになり、それぞれの対応は右の表のようになります。
無効コードは回転速度(カウント・レート)がサンプリング周波数以上になると発生します。この場合、回転方向の情報が失われるので、エラーとなります。通常、このエラーは位置情報喪失としてシステム・エラーとなります。ダイアル(ユーザI/F)なら無視しても特に問題はないでしょう。また、無効コードに対して特別な処理(前回と同じ方向に2カウントとする)をすることによって、サンプリング周波数の2倍未満までのオーバー・スピード耐性を持たせることもできます。
エンコーダ出力の変化のパターンから回転方向を判定するには次のような方法が考えられます。
まず最初にこの方法を思いつきますが、少なくとも8パターンを判定しなければならないので、処理が煩雑になります。
「(前回値 << 2) + 今回値」で全ての変化の組み合わせを示す4bitのインデックス値としてテーブル変換する。殆どのマイコンではこの方法が一番簡単でしょう。
インデックス値のパリティが偶数なら変化なしか無効、奇数なら変化あり。変化なしの場合 0b0101で ANDをとってパリティ偶数で静止、奇数で無効。変化ありの場合 0b0110で ANDをとってパリティ偶数で+方向、奇数で-方向。この方法は2回の判定で済みますが、パリティフラグのあるプロセッサである必要があります。
エンコーダ出力は(バイナリで)0→1→3→2と変化するのでそのままでは使いにくいものです。これは2ビットのグレイコードと言えますが、これをバイナリ・コードに変換すれば0→1→2→3の順番になり、前回との差をとることにより回転方向を検出できます。バイナリ変換するには、サンプリングした値のbit0にbit1でXORをとることにより2と3が入れ替わり、シーケンシャルなバイナリ・コードとなります。2bit値なので、結果は-2~+1の範囲で得られ、-2はエラーです。
動作の判定にテーブル変換を使用した例です。sample_encoder()は、タイマー割り込みなどのバックグランドプロセスで一定時間毎に呼び出します(最大カウント率 < 呼び出し率)。ロータリー・エンコーダの回転量はEncoder構造体のpositionにリアルタイムで反映され、動きを検出するたびにmovedを立てます。
volatile struct { long position; /* 軸位置 */ int moved; /* 移動フラグ */ } Encoder; void sample_encoder(void) { static const int dir[] = { 0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0 }; /* 回転方向テーブル */ static int i; /* インデックス */ int n; i = (i << 2) + (PA.PIN.BYTE & 3); /* 前回値と今回値でインデックスとする */ n = dir[i & 15]; /* 変化パターンから動きを得る */ if (n) { /* 動きがあったら位置更新 */ Encoder.position += n; Encoder.moved = 1; } }
ユーザI/Fによく使われるジョグダイアルのカウント処理の例です。1クリック動きがあるたびにコマンドコードを発生します。
デテント型ジョグダイアルの場合、デテント位置が2または4カウント飛ばしの位置にあるものがあります。つまり、1ステップ当たり2または4カウントとなるので、扱い難いかもしれません。この場合は、不要な遷移分を変換テーブルから除いてやればOKです。カウント条件が減るので、テーブル変換よりも比較・分岐命令で飛ばす方が良いでしょう。ジョグダイアル相手なら数百~1kHz程度のサンプリング・レートが適当です。
u_char CMD; /* キー・コマンド */ void sample_encoder() { static u_char i; /* インデックス */ i = (i << 2) + (PA.PIN.BYTE & 3); /* 前回値と今回値でインデックスとする */ i &= 15; switch (i) { /* デテント位置は"0"を想定 */ case 0x7: /* 1→3 */ CMD = FORWARD; break; case 0xd: /* 3→1 */ CMD = BACKWARD; break; } }