113.ロータリーエンコーダを使う


2002/11/10 【ソフトウエア編TOPに戻る】

今回はロータリーエンコーダを使ってみましょう。そもそもエンコーダとは電気の世界では一般的に広く使われている言葉で、コード化(符号化)するといった意味になるかと思います。逆にコード化した信号を元に戻すものをデコーダなどとも言います。では、今回のロータリーエンコーダとはなにをコード化するものかというと、ロータリーつまり回転をコード化するものということになるでしょう。

今まで回転角度の検出にはポテンショメータ(可変抵抗器)を用いてきました。ポテンショメータの軸が回転することによって抵抗値が変わり、それによる電圧の変化をA/Dして10ビット程度の数値に変換し、プログラムの処理に利用していました。これも立派なコード化ですが、ロータリエンコーダでは直接デジタル信号で回転の偏移量が出力されます。また、ポテンショメータのように回転できる角度などが機械的に決まっておらず、何回転でも回すことができます。

形は用途によって様々ですが、基本的にはポテンショメータなどと類似した形をしています。ただし、精度(分解能)が大きいものほど大型になってしまうようです。


113−1.ロータリーエンコーダとは

 

ロータリーエンコーダにはインクリメンタル方式と、アブソリュート方式の2種類があります。

 

■インクリメンタル方式

軸が一定量回転するごとにパルスを出力します。1回転でいくつのパルスを出力するかを分解能と呼び、[パルス数/回転]という単位で表します。安いものは10〜20パルス高価なものは5000パルス程度まで出力するものがあります。軸が回転するたびにパルスが出るので、軸が何度動いたか、何回転廻ったかといった情報は、パルスの数を数えることによって得ることができます。しかし、ただ単にパルスが出るだけでは、例えば時計周りに廻ったのか、反時計回りに廻ったのかの判断が付きません。そこで、以下に示すような2相のパルスを出力するように作られています。

ロータリーエンコーダからは、回転に合わせて上図のようなA相B相のパルスが出力されます。ふたつのパルスはタイミング(位相)がずれて出力されるように作られており、時計廻りと反時計廻りとで、その出力タイミングを逆の関係にしています。

時計廻りで軸が回転している場合、先にA相のパルスが出力され、その途中でB相のパルスが出力されます。逆に反時計廻りで回転している場合、先にB相のパルスが出力され、その途中でA相のパルスが出力されます。つまり、これらの関係を用いて、軸は現在どちら方向にどれだけ回転しているのか、といった情報を得ることができるのです。

パルスの出力方法については、大体以下の種類があります。

 

【1.オープンコレクタ出力】

トランジスタのコレクタ端子がそのまま外部に出力されます。ちょうどスイッチ接点と同じように扱うことができます。回転角度の検出方法としては、フォトインタラプタを使った光学式が多いようです。

 

【2.TTL互換電圧出力】

5Vの電圧でパルスを出力します。こちらもフォトインタラプタを使った光学式が多いようです。光学式は非常に高分解能なものがありますが、その分高価で、ちょっと大きめになります。

 

【3.無電圧接点出力】

摺動(しゅうどう)式で、機械的な接点が回転によってON/OFFすることでA相とB相を出力します。非常に安価ですが、分解能は限られます。今回の実験ではこのタイプを使ってみます。

 

インクリメンタル方式は、その名前の通りマイコン側でパルスの数を数えるだけで回転数や回転角度などを検出することができますが、回転軸の絶対的な位置を検出することはできません。回転角度を正確に検出するためには、回転軸を機械的に基準位置に合わせ、そこから数え始めるというイニシャル処理が必要になります。

 

■アブソリュート方式

それに対してアブソリュート方式では、回転軸の絶対的な位置を検出することが可能です。回転軸の現在位置にあわせて、グレイコードと呼ばれる何ビットかの絶対的な2進データを出力します。従って、機械的な位置合わせでカウントリセットするといったイニシャル処理は必要ありません。分解能は、高価なものであれば2000[分割/回転]程度までとれるものもあります。

 

インクリメンタル方式とアブソリュート方式のどちらが良いかについては、用途と精度と値段によって自由に決めればよいでしょう。個人的な感覚では、普通ロータリーエンコーダといえばインクリメンタル方式のほうが一般的である気がします。

 

【先頭に戻る】


113−2.今回の実験回路

 

では、早速ロータリーエンコーダをH8/3048Fに接続して動かしてみましょう。H8/3048Fでは、上でご紹介したロータリーエンコーダの2相の出力パルスを受けて、ITU2チャンネルTCNT2タイマーカウンタアップカウンタ/ダウンカウンタとして動作させる機能がありますので、試してみることにします。

 

で、今回実験に使用するロータリーエンコーダは、秋月電子通商200円で販売されているインクリメンタル方式の摺動式のものです。

端子はA相、B相、GNDの3本からなっており、外観上では普通のポテンショメータと区別がつきません。

秋月から購入すると写真のように取り扱い説明書が付いて来ます。それによると、1回転で24パルス/24クリック付きとあります。クリックとは軸を廻すたびにカチカチという手ごたえがあるもので、位置決めを行う際には操作感が良くなります。ここでは、1クリックごとにA相とB相それぞれ1パルスずつ出力されることになります。

なので、単純計算すると360度/24パルスで、15度ごとにパルスが出力されるということになります。回転角度の測定を目的とするならば、全然使い物にならないほどの精度です。

ところが、このロータリーエンコーダをマイコンに繋げると、3.75度まで測定できる精度を得ることができるようになります。ちょうど15度の4倍の分解能ということになります。なぜでしょう?

上の図は前にご紹介したタイミングチャートと同じものですが、A相B相の信号の立上がり立下りを強調して書いています。ここでタイミングチャートの1周期分を良くみて頂くと、A相とB相の間で、一方が1または0のときの他方の信号の立上がりまたは立下りの推移が、全て違う組合せになっていることが分かると思います。つまり、A相とB相の両方の信号の立上がりと立下りのタイミングで、回転しているということと、回転方向が分かるのです。なので、1周期分のパルス出力で4個の分解能が取れるのです。

3.75度という精度で十分か不十分かは用途によるところですが、15度の精度よりは使えそうですね。(^^;

 

で、今回はAKI-H8と接続するため、以下のような回路を組みます。H8/3048Fでは、ITU2チャンネルがロータリーエンコーダ入力に対応しており、PA-0端子がTCLK-A(A相入力)、PA-1端子がTCLK-B(B相入力)と共用端子になっています。

ロータリーエンコーダの出力は接点ON情報なので、10KΩの抵抗でプルアップしてOFFの時に5V、ONの時に0(ゼロ)VがAKI-H8に入力されるようにします。しかし、この信号のタイミングだと、H8/3048Fが欲しいA相とB相の信号と逆の関係になってしまうのです。なので、ロータリーエンコーダのA相とB相はH8/3048FのB相とA相になるように逆に接続してあげると、時計廻りでアップカウント、反時計廻りでダウンカウントという本来の動作をしてくれるようになります。

【先頭に戻る】


113−3.基本動作のためのプログラム enctest1.c

 

では、H8/3048Fでインクリメンタル方式のロータリーエンコーダを使うための基本プログラムをご紹介しましょう。ロータリーエンコーダを廻すと、TCNT2タイマーカウンタレジスタのカウント動作が開始されます。時計回りに廻すとカウント値が大きくなり、反時計回りでは小さくなります。その様子を見るために、TCNT2の下位8ビットをポート1に出力して、LEDに2進表示させます。

ソースプログラムは、ここをクリックするとダウンロードできます。(enctest1.c) プログラムは非常に短いものになっています。

なお、スタートアップオブジェクトは、今回割込みを利用していないので、startup.marで結構です。(ちなみに割込み対応のstartup2.marなどでも問題なく動作します)

/****************************************/
/* ロータリエンコーダ  RIKIYA 2002/11/01*/
/*                           enctest1.c */
/*                             H8-3048F */
/*ITU2を位相計数モードにして、          */
/*インクリメンタル型ロータリエンコーダで*/
/*TCNT2をカウント動作させる。           */
/****************************************/

#include <3048f.h>

/* メイン関数 ********************************************/

void main(void){

        P1.DDR = 0xff;                  /* portB出力に設定 表示LED */
        ITU.TMDR.BIT.MDF = 1;           /* ITU2 位相係数モード     */
        ITU.TSTR.BIT.STR2 = 1;          /* カウント開始            */

        while(1){
                P1.DR.BYTE = ITU2.TCNT; /* P1にカウント状態を出力  */ 
        }
}

プログラムはたったのこれだけです。表題のコメント分の方が多いくらいですね。    

 

        P1.DDR = 0xff;                /* portB出力に設定 表示LED */

まず、TCNT2のカウント値を表示させるために、P1を全ビット出力設定にしています。

 

        ITU.TMDR.BIT.MDF = 1;         /* ITU2 位相係数モード     */

次に、ITUのTMDR(タイマーモードレジスタ)のMDFビットを1に設定しています。MDFビットが1になるとITU2チャンネルは位相計数モードとして働き、TCLK-A端子(PA-0)とTCLK-B端子(PA-1)はロータリーエンコーダのA相入力とB相入力の端子に自動的に設定されます。

 

        ITU.TSTR.BIT.STR2 = 1;        /* カウント開始            */

位相計数モードに設定したら、TCNT2のカウントを開始します。これによってA相とB相のパルス入力によりTCNT2のカウント値が変化するようになります。

 

        while(1){
                P1.DR.BYTE = ITU2.TCNT; /* P1にカウント状態を出力 */ 
        }

ここではTCNT2の値をP1に出力しています。ただし、TCNT2は16ビットカウンタなので、表示は下位8ビットのみになります。

 

どうですか?非常に簡単ですね。どんなに分解能が高いロータリーエンコーダを持ってきても、インクリメンタル方式であればこのプログラムで動作します。なので、ロボットの関節角度を測定したり、車型ロボットのタイヤの回転数(移動距離)を測定したりする用途には、これを基本としてそのまま利用できるでしょう。

なんだか前置きばかりが長いくせに、プログラムが簡単すぎて申し訳ないような気がしてきました。(^^;  では、もう少しだけ発展させて、ロータリーエンコーダの別の使い道をご紹介することにしましょう。液晶表示器を使った電子レンジ式操作パネルです。

【先頭に戻る】


113−4.ロータリーエンコーダと液晶表示器による操作パネル例 enctest2.c

 

電子レンジの操作部分には、クリクリ廻るツマミとメニューを表示させる液晶表示器が付いています。最近の電子レンジになると、もっと高級な操作パネルになっているみたいですが、基本的なところは変わらないでしょう。メニューの中から選びたい事柄をツマミと押しボタンで選択して行く操作方法は、沢山の選択肢を体系立てて表示させることによって分かり易くし、なおかつ小さなスペースで済ませることができるため、非常に有用です。

この電子レンジについているツマミが、まさにロータリーエンコーダです。また、AKI-H8開発キットで扱うことができる液晶表示器もありますので、環境は既に整っています。

今回は、以下のようなメニュー展開のある操作パネルを作ってみます。先ず乗り物を選び、次にメーカーを選びます。メーカーを選んだら、そのメーカー名をSCI1のシリアルポートから出力します。それをパソコンのハイパーターミナルで受け、選ばれた乗り物のメーカー名が表示されるという仕組みです。メーカー名のページでは、選ばれた乗り物のメーカー名しか表示しません。つまり階層型のメニューということになります。

各ページでは、ロータリーエンコーダを廻すたびに、1から順に選択肢が入れ替わり表示されて行きます。好きな選択肢が表示されているときに押しボタンを押すとそれが選択されたことになり、次の階層のページに移って行ったり、選ばれたメーカー名をシリアルで出力したりします。

この操作パネルではロータリーエンコーダのほかに、選択肢決定のための押しボタンスイッチが必要になりますので、以下の回路としておきます。押しボタンスイッチはポートAのビット4に接続します。

ソースプログラムは、ここをクリックするとダウンロードできます。(enctest2.c)

なお、スタートアップオブジェクトは、今回も割込みを利用していないので、startup.marで結構です。

/**************************************************/
/* エンコーダメニュー展開         RIKIYA 2002.11.7*/
/*                                      enctest2.c*/
/*                                       H8-3048/F*/
/* ロータリエンコーダと液晶表示器を使って         */
/* メニュー選択の操作パネルを作る                 */
/**************************************************/

#include <3048f.h>
#include <myfunc.h>

/* メニュー文字列の設定 */
static char *menu[4][9] = /*乗り物を選ぶためのメニュー*/
                         {{"Select Vehicle      ", /* 表題 */
                           "CAR                 ", 
                           "BIKE                ",
                           "MTB                 "},

                          /*自動車メーカーを選ぶためのメニュー*/
                          {"Select CAR Maker    ", /* 表題 */
                           "TOYOTA              ",
                           "NISSAN              ",
                           "HONDA               ",
                           "MITSUBISHI          ",
                           "BMW                 ",
                           "VOLVO               "},

                          /*バイクメーカーを選ぶためのメニュー*/
                          {"Select BIKE Maker   ", /* 表題 */
                           "HONDA               ",
                           "YAHAMA              ",
                           "KAWASAKI            ",
                           "SUZUKI              ",
                           "DUCATI              "},

                          /*自転車メーカーを選ぶためのメニュー*/
                          {"Select MTB Maker    ", /* 表題 */
                           "TREK                ", 
                           "SPECIALIZED         ",
                           "GT                  ",
                           "Cannondale          ",
                           "MONGOOSE            ",
                           "SCHWINN             ",
                           "GIANT               ",
                           "PEUGEOT             "}};

/*メイン関数 *******************************************/
void main(void){

     timer_init();                 /* タイマーの初期化     */
     lcd_init();                   /* 液晶表示器の初期化   */
     sci1_init();                  /* シリアル通信の初期化 */

     PA.DDR = 0x00;                /* PA全BIT入力モード    */
     P1.DDR = 0xff;                /* P1全BIT出力モード    */
     ITU.TMDR.BIT.MDF = 1;         /* ITU2 位相係数モード  */
     ITU.TSTR.BIT.STR2 = 1;        /* カウント開始         */
     ITU2.TCNT = 0;                /* TCNTを0にクリア      */

     while(1){
     /*乗り物を選ぶ処理*/
          switch(menu_sel(0,3)){
               case 1: /* 自動車メーカーを選ぶ処理            */
                       /* 選んだメーカー名をシリアルで出力する*/
                       sci1_strtx(menu[1][menu_sel(1,6)]);
                       sci1_tx('\r');
                       sci1_tx('\n');
                       break;
               case 2: /* バイクメーカーを選ぶ処理             */
                       /* 選んだメーカー名をシリアルで出力する */
                       sci1_strtx(menu[2][menu_sel(2,5)]);
                       sci1_tx('\r');
                       sci1_tx('\n');
                       break;
               case 3: /* 自転車メーカーを選ぶ処理             */
                       /* 選んだメーカー名をシリアルで出力する */
                       sci1_strtx(menu[3][menu_sel(3,8)]);
                       sci1_tx('\r');
                       sci1_tx('\n');
                       break;
               }
        }
}

/* メニュー表示/選択関数 ***************************/
/* menu_no メニューページの番号 0始まり            */
/* menber メニューに含まれる選択肢の数 1始まり     */
/* PAのbit4が0で、表示中の選択肢に決定する。       */
/* 選択された選択肢の番号を返す                    */
/***************************************************/
int menu_sel(int menu_no,int menber){

     int count;
     int end_no;

     while(PA.DR.BIT.B4 == 0){}       /* 選択スイッチを離すまで停止       */
     end_no = (menber + 1) * 4;       /* エンコーダカウント値オーバー領域 */

     ITU2.TCNT = 0;                   /* エンコーダカウント値クリア       */
     lcd_locate(0,0);                 /* カーソル位置の指定               */
     lcd_print(menu[menu_no][0]);     /* メニュータイトルの表示           */
 
     while(1){
          P1.DR.BYTE = ITU2.TCNT;     /*デバッグ用カウント値表示          */

/*アップカウント中に選択肢の上限を超えた場合に初めの選択肢にジャンプする処理*/
          if((ITU2.TCNT >= (end_no - 4)) && (ITU2.TCNT < end_no)){
               count = 1;
               ITU2.TCNT = 0;

/*ダウンカウント中に選択肢の下限を超えた場合に最終の選択肢にジャンプする処理*/
          }else if((ITU2.TCNT >= 0x20) && (ITU2.TCNT <= 0xFFFF)){
               count = menber;
               ITU2.TCNT = end_no - 5;

/* 以下、順次選択肢を切り替えて行く処理 */
          }else if((ITU2.TCNT >= 0) && (ITU2.TCNT < 0x04)){
               count = 1;
          }else if((ITU2.TCNT >= 0x04) && (ITU2.TCNT < 0x08)){
               count = 2;
          }else if((ITU2.TCNT >= 0x08) && (ITU2.TCNT < 0x0C)){
               count = 3;
          }else if((ITU2.TCNT >= 0x0C) && (ITU2.TCNT < 0x10)){
               count = 4;
          }else if((ITU2.TCNT >= 0x10) && (ITU2.TCNT < 0x14)){
               count = 5;
          }else if((ITU2.TCNT >= 0x14) && (ITU2.TCNT < 0x18)){
               count = 6;
          }else if((ITU2.TCNT >= 0x18) && (ITU2.TCNT < 0x1C)){
               count = 7;
          }else if((ITU2.TCNT >= 0x1C) && (ITU2.TCNT < 0x20)){
               count = 8;
          }

/* 対象となる選択肢を表示器に表示させる処理 */
          lcd_locate(0,1);   /* カーソル位置の指定 */
          lcd_print(menu[menu_no][count]);

/* 選択ボタンが押されたら選択肢番号countを戻り値として関数を終了する*/
          if(PA.DR.BIT.B4 == 0){
               return count;
          }
     }
}

 

今回、このプログラムについての詳しい解説は省略させて頂きますが、要点は以下の通りです。

 

■myfunc.hの利用

 

シリアル通信やlcd液晶表示器のドライブなど、基本的なI/F部分の関数をまとめたmyfunc.hというヘッダファイルを読み込んでいます。myfuncについては、151.自分の関数をヘッダファイルにするのページを参照して下さい。

 

■メニュー表示/選択関数 menu_sel(int menu_no,int menber)

 

メニュー展開をページごとという区切りで考え、1ページの中に選択肢を表示させ、ロータリエンコーダで表示を切り替え、押しボタンが押されたところの表示内容に相当する番号を戻り値として返すという機能を持った関数を作りました。それがmenu_sel()関数です。

選択肢として表示させる文字列は、プログラムの先頭部分でstaticなグローバル変数として宣言している*menu[A][B]という文字配列変数に格納しています。*menu[A][B][B]の配列の大きさ(ここでは9)は各ページで持たせることができる選択肢の数+1を指定します。+1は配列の最初をページのタイトルとなる文字列としているためです。[A]の部分(ここでは4)は登録するページの数を指定します。展開するメニューの階層などはここでは関係なく、あくまでダーっと文字列を記憶させておくための文字配列です。

menu_sel()関数の引数であるmenu_noには、表示させたいページの配列番号を0(ゼロ)始まりで渡します。ここでは、メインメニューが0、自動車メーカーのメニューが1、バイクメーカーが2、マウンテンバイクのメーカーが3という数字になります。

同様に引数menberでは、それぞれのページが持つ選択肢の数を渡します。これによってmenu_sel( )関数の内部では、何個の選択肢で表示をグルグル廻せばよいかを判断しています。

そして、while(1){  }文の中でいくつものif文が連続する部分で、ロータリーエンコーダのカウント値に対応して現在表示すべき選択肢の番号を制御しています。ただし、ここでは1ページあたり8個の選択肢を上限としたプログラムにしてあります。

最後にPAのビット4が押されていることを検出し、選択肢の番号を戻り値として関数を終了させます。

今回利用しているロータリーエンコーダは、1回転あたり24クリックあり、マイコン上は1クリックごとに4カウント分アップ/ダウンします。なので、1クリックごとに選択肢が切り替わる操作性を確保するため、4カウントごとに選択肢の番号が更新されるようにしています。

 

■メイン関数

 

メイン関数では、イニシャル処理の後はmenu_sel( )関数を使ってメニュー展開を行うためのプログラムとなっています。switch case文の部分がそれになります。menu_sel( )関数は戻り値として選択された番号を戻すため、直接switchの分岐条件に関数ごと入れてしまっています。こういった柔軟なプログラミングができるところがC言語の魅力ですね。

で、case文の中身で実際に行いたい動作を実行しています。ここではsci1_strtx( )関数を使ってシリアルポートからメーカー名を出力する処理がそれに当たります。

【先頭に戻る】


113−5.enctest2.cの実験風景と動作結果

 

下の写真がメニュー動作の実験風景です。

左がテスト用のジャンク基板、真中がH8用マザー基板、そして右がテスト用のI/O基板です。

ジャンク基板は赤い枠で囲んでいる部分にロータリエンコーダと押しボタンスイッチの配線をしてあります。赤い枠以外のIC類は、別の実験の残骸なので気にしないで下さい。

H8のマザーとジャンク基板(ロータリエンコーダとスイッチ)は、H8のポートAに接続し、テスト用I/O基板の液晶とはポート3と接続しています。

そして、実際に動作させると以下のようになります。なお、パソコンのハイパーターミナルの設定については、109.パソコンとシリアル通信するのページを参照して下さい。

まず、H8の電源を入れると液晶表示器には左のような画面が表示されます。ここで、まだスイッチを押さずにロータリーエンコーダを時計廻りに1クリック分動かすと、下のような表示に変わります。

これが変わった表示です。CARからBIKEに表示が変わりました。ここで、ロータリーエンコーダを更に時計廻りに1クリック分動かすと、今後は下のような表示に変わります。

これが変わった表示です。BIKEからMTBに表示が変わりました。更に時計廻りに動かすと、CAR→BIKE→MTB→CARの変化を繰り返します。逆に反時計廻りに動かすと、BIKE→CAR→MTB→BIKEと逆の順に変化します。

では、CARと表示されているときにスイッチを押してみましょう。左のように今度は自動車メーカーTOYOTAと表示されます。ここで、またロータリーエンコーダを時計廻りに動かすと、以下のように表示が変わります。

これが変わった表示です。TOYOTAからNISSANに表示が変わりました。更に動かすと、設定されている自動車メーカー名が順々に表示されて行きます。もちろん反時計廻りに回転させると、逆の順に表示されて行きます。

では、液晶にTOYOTAと表示されているときにスイッチを押してみましょう。液晶画面は初めの乗り物を選択するメニューに切り替わり、パソコンのハイパーターミナル上には、今選択したTOYOTAという文字が表示されます。つまり、メニューによって選んだ動作が実行されたということになります。あとは、BIKEMTBについても同様の動作をします。え?自分が持っている乗り物のメーカー名が出てこない?そうしたら、自分でプログラムを書き換えて表示させるようにしてみましょう!(^^;

【先頭に戻る】


インクリメンタル方式のロータリーエンコーダは、実際の軸の現在の角度とは無関係に、偏移量をパルスで出力するだけなので、上記のようなメニュー展開のためのツールとして簡単に利用できます。これがアブソリュート方式のような、軸の現在の角度と出力データが密接な関係にあったりすると、逆に扱いづらくなります。

力弥としては、インクリメンタル方式のロータリーエンコーダを、ロボットの関節角度の検出に使いたいと思っています。今までのポテンショメータを利用した方法でも良いのですが、アナログ信号を扱わなくても良いという点で、外来ノイズに強いシステムが組めるのではないかと期待しています。ただし、高精度のものはそれなりに大きく、小型のロボットに搭載するにはちょっと都合が悪い点もあります。

いずれにしても、こんなに簡単に回転状態を検出できるロータリーエンコーダを、何かに使わない手はないですね。

 

【ソフトウエア編TOPに戻る】


【表紙に戻る】