この実験で使用するロボットカーのセンサーラボには,光距離センサが3つ搭載されていて,前と左右それぞれ独立に障害物までの距離を調べられる.
この光距離センサは,赤外線を発射して障害物からの反射光の強さを調べることで距離を測定する.センサ内部のフォトトランジスタによって反射光の強さに応じて電圧が増幅され,その電圧をA/D変換回路によってデジタル値として読み出す.
A/D変換(Analog/Digital)とは,アナログの値(この場合は電圧値)を,デジタル値として読み込む機能のことをいう.(逆の機能のことをD/A変換という.)
M16Cには,A/D変換を行うための機能があり,次のように使用することができる.
赤外線は常に照射しつづけるわけではなく,一定間隔のパルス光として照射する.その間隔はセンサーラボの仕様では800Hz(1/800秒=1.25ミリ秒間隔)と定められている.このパルスはハードウェアでは生成してもらえず,プログラム的にポート0番のbit4へ1.25ミリ秒の半分の間隔で「1」「0」を書き込む必要がある(※「1」「0」の繰り返し周期全体として800Hzのパルスになる).
ポート番号 | ビット番号 | 内容 |
---|---|---|
0 | 4 | 800Hz用出力,ここに800Hzの半分の間隔で0と1を書き込む |
このため,1.25ミリ秒の半分の時間間隔として625マイクロ秒をタイマ機能で測定する.タイマA1を使用する場合,次の設定となる.最後の割り込み処理については別ページを参照すること.この他のudfとtabsrの設定は,その他タイマー機能と同様である.
ta1mr = 0x40; //8分周を使用 ta1 = 1250-1; //8分周でちょうど625マイクロ秒分 ta1ic = 0x4; //割り込みレベルとして4を設定
このタイマ機能を利用して,625マイクロ秒ごとに割り込み処理を行う.つまり625マイクロ秒で1回の割合で,自動的に何か処理を行わせることができる.その割り込み処理の中では,次の動作を行う.
p0buf = p0buf^0x10; p0 = p0buf;
演算子「^」は,XOR演算を意味する.このため下のように,p0bufのビット4が0のとき1に,1のとき0に変化させることになる.つまりビット4だけ反転させるわけである.これにより,625マイクロ秒に1回の割合で0と1を反転させ,ちょうど800Hzのパルスを生成していることになる.
bit4が0の場合 1の場合 bit7 6 5 4 3 2 1 0 X X X 0 X X X X ( = ポート0番の中身) X X X 1 X X X X 0 0 0 1 0 0 0 0 ( = 0x40) 0 0 0 1 0 0 0 0
X X X 1 X X X X X X X 0 X X X X
M16CにはA/D変換機能がある.M16Cに内蔵されているA/D変換機能では,8種類の入力チャネルが用意されており,8個まで(センサなど)入力装置を接続することができるようになっている.センサーラボでは,前述のとおり3個の光センサが搭載されており,それぞれポート10番のビット0,ビット1,ビット2に入力されている.これをA/D変換器側から見て,0チャネルから2チャネルと呼ぶ.
ポート番号 | ビット番号(チャネル番号) | 対応センサ |
---|---|---|
10 | 0 | 右センサ(AN0) |
10 | 1 | 前センサ(AN1) |
10 | 2 | 左センサ(AN2) |
このようにポート10番は入力ポートなので,方向レジスタpd10には,入力(0x00)を設定しておく必要がある.
このチャネルごとに,次の制御レジスタを適切に設定してA/D変換を行い,センサの値を取り出すことになる.今まで見てきたタイマ機能などと異なり,A/D変換はかなり複雑な機能なため設定項目が多く,機能設定用のレジスタadconが3つ存在する.(※この3つ全部がひとつのA/D変換機能のためにあり,A/D変換機能が3個搭載されているわけではない.またチャネル番号とも勘違いしないこと.)
また,adcon0のビット6だけは,AD変換を開始させるフラグであり,単独で設定することが多いため,sfr62a.hの中で特別にadstとして定義されている.
他には,adicとして割り込み制御や,結果を格納するためのad0などがある.
レジスタ | 機能 |
---|---|
adcon0 | A/D制御レジスタ0(チャネル選択,動作モード,トリガ,変換開始,周波数選択) |
adcon1 | A/D制御レジスタ1(繰り返しモード,分解能,周波数,基準電圧,外部オペアンプ接続) |
adcon2 | A/D制御レジスタ2(サンプル&ホールド設定) |
adst | adcon0のビット6だけ.A/D変換開始フラグ |
adic | A/D変換用割り込み制御レジスタ(割り込みレベル) |
ir_adic | adicのビット3だけ.割り込み要求フラグ(A/D変換が終了したかどうか) |
ad0 | チャネル0の変換結果 |
ad1 | チャネル1の変換結果 |
ad2 | チャネル2の変換結果 |
A/D変換機能の設定例は次のようになる.それぞれどんな意味があるか,ちゃんと調べてみよ.
pd10 = 0x00; adst = 0; adic = 0x00; adcon2 = 0x01; adcon0 = 0x80;
適切な設定が終わった後に,A/D変換をスタートさせる.
adcon0 = 0x80 | チャネル番号; //変換するチャネルを指定 adst = 1; //開始
A/D変換は複雑な処理のため,変換が完了するのに時間がかかる.変換が完了すると,割り込みフラグが立つようになっている.割り込みを使用するか,このフラグをポーリングすることで完了後の値を取り出す.例えばこれまでのタイマ機能と同様にフラグをポーリングするには次のように書く.
for(;;){ if(ir_adic == 1){ //フラグが立っていたら(A/D変換が完了していたら) if_adic = 0; //とりあえず,フラグをおろしてから 結果を格納するための変数 = ad0; //0チャネルの結果を取り出す(※0チャネルの場合) /* 何か結果を使った処理 */ } }
A/D変換機能は,その他のデジタル回路部分とは異なりアナログ電圧を使用するアナログ回路のため,電源電圧の変動に影響を受けやすい.このため次の点に注意を必要とする.
サンプルとして,3個のセンサのどれかに反応があれば,CPUボード上のLEDを点灯する例を示す.関数としてmainの他に,センサを初期化するためのinitSensor(),そしてセンサ値を使用するためのreadSensor(),最後に800Hzを出力するための割り込み関数ta1int()が存在する.
#include "sfr62a.h" void main( void ); /* メインプログラム */ void initPort( void ); /* ポート初期化関数 */ void initTimer( void ); /* タイマ初期化関数 */ void initSensor( void ); /* AD入力初期化関数 */ unsigned int readSensor( unsigned char sens_num ); /* センサ出力読み込み関数 */ void ta1int( void ); /* タイマ割込み関数 */ /* 割込み処理の宣言 */ #pragma INTERRUPT ta1int /* ta1intが割込み処理であることを宣言する。 */ /* マクロ定義 */ #define PORTOUT 0xff /* ポート方向レジスタを出力に設定する為のデータ */ #define OFF 0x00 /* フラグのOFFを示すデータ */ #define ON 0x01 /* フラグのONを示すデータ */ #define CNT_TA0 (2500-1) /* タイマA0カウンタ値 fck=f1/32→2μs */ #define CNT_TA1 (1250-1) /* タイマA1カウンタ値 fck=f1/8→500ns */ /* 外部変数宣言 */ unsigned char p0outbuf; /* ポート0出力バッファ */ unsigned int ledoutbuf; /* LED出力バッファ */ void main( void ) { unsigned int right_sensor; /* 右センサAD変換値格納変数 */ unsigned int center_sensor; /* 中央センサAD変換値格納変数 */ unsigned int left_sensor; /* 左センサAD変換値格納変数 */ initPort(); /* ポート初期化関数呼出 */ initTimer(); /* タイマ初期化関数呼出 */ initSensor(); /* AD入力初期化関数呼出 */ /* タイマの起動 */ tabsr = 0x03; /* カウント開始:タイマA0、A1 */ _asm("\tFSET I"); /* 割り込み許可 */ /* メインループ */ for( ; ; ) { /* メインループ */ if( ir_ta0ic == 1 ) { /* 5ms経過? */ ir_ta0ic = 0; /* AD変換処理 */ right_sensor = readSensor( 0 ); /* 右センサの読み込み */ center_sensor = readSensor( 1 ); /* 中央センサの読み込み */ left_sensor = readSensor( 2 ); /* 左センサの読み込み */ /* AD変換値の判定 */ if( right_sensor > 150 /* 右、中央、左のいずれかの */ || center_sensor > 150 /* センサに接近していれば */ || left_sensor > 150 /* LEDを点灯する。 */ ) { ledoutbuf = 0x00; /* 接近しているのでP00のLEDを点灯するデータをセット */ } else { ledoutbuf = 0x01; /* P00に"H"出力してLEDを消灯するデータをセット */ } } } } /*---------------------------------------------------------------------- initPort(),initTimer()省略 タイマa0はこれまで同様にモータ用の5ミリ秒間隔 タイマa1は上記の625マイクロ秒間隔で,800Hzパルス出力用 ----------------------------------------------------------------------*/ void initSensor( void ) /* AD入力初期化関数 */ { pd10 = 0x00; /* AD入力ポート方向レジスタ初期化 */ adst = 0; /* AD変換停止 */ adic = 0x00; /* AD 割り込みレベルの設定 */ adcon2 = 0x01; /* AD制御レジスタ2の設定 */ adcon0 = 0x80; /* AD制御レジスタ0の設定 */ adcon1 = 0x28; /* AD制御レジスタ1の設定 */ } unsigned int readSensor( unsigned char sens_num ) /* センサ出力読み込み関数 */ { unsigned int result; /* 戻り値を格納する変数。 */ if( sens_num > 2 ) { result = 0; /* センサ指定が誤りなので"0"を返す */ } else { adcon0 = 0x80 | sens_num; /* AD制御レジスタ0で変換するADチャネルを指定 */ adst = 1; /* AD変換開始 */ for( ; ; ) { if( ir_adic == 1 ) /* AD変換完了? */ { /* yes */ ir_adic = 0; /* AD変換完了割り込み要求ビットクリア */ adst = 0; /* AD変換停止 */ switch( sens_num ) { case 0: result = ad0; /* 右センサAD変換データ読み込み */ break; case 1: result = ad1; /* 中央センサAD変換データ読み込み */ break; case 2: result = ad2; /* 左センサAD変換データ読み込み */ break; default: break; } break; /* forループを脱出する。 */ } } } return result; } void ta1int( void ) { /* 800Hzパルス出力 */ p0outbuf = p0outbuf ^ 0x10; /* ポート0のbit4: パルス出力ポートビット位置 */ p0outbuf = p0outbuf & 0xfe; p0outbuf = p0outbuf | ledoutbuf; p0 = p0outbuf; }