マイコンカーラリーのアナログセンサ

マイコンカーラリーのアナログセンサについて

マイコンカーラリーのラインセンサ(写真で前方に伸びたアームの先についているセンサ)はコース中央の白線をトレースするためのセンサで、デジタルセンサとアナログセンサが使われています。

M-S199:アナログセンサー基板 TypeSとして提供されているセンサには5つのデジタルセンサと白線を挟む位置にある2つのアナログセンサがあります。

取り付け拡大部分(SCITECS_MCR日記)より

LEDとペアで取り付けられているのがデジタルセンサでセンサ取り付けネジの斜め前部分にあるのがアナログセンサとして使う反射型フォトインタラプタ(下図)です。


デジタルセンサはマーカー検出や誤動作防止に使い、アナログセンサで姿勢制御をおこなうことでスムーズな高速走行が可能になります。

アナログセンサの信号をADコンバータの数値で見てみる

アナログセンサの信号はR8CでAD変換して姿勢制御に使います
次のグラフはコースと同じ白線パターン上でアナログセンサを振ってみた時のAD値をグラフにしたものです。
縦軸はAD変換の値で0~1023、横軸は時間でmS単位です。

アナログセンサ回路は次のようになっています。

フォトトランジスタは光の反射が多いほど多く電流を流し出力電圧が下がるため、コースの黒い部分で電圧が高く、白線の部分で電圧が低くなります。

グラフは左右のアナログセンサ(a0,a1)とその差分(ad1-ad0)の値を表示しています。
色の変わり目でスムーズに変化していることが判りますad0とad1の最低値、最高値が違うのは素子特性のバラつきによるものです。

制御プログラムを効率よく開発するための秘訣

制御プログラムで難しいのは上のグラフにあるような素子特性のバラつきや機構の特性によって生じるセンサ信号や挙動の変化をいかにうまく補正するかということです。
ロボットを走らることを繰り返してパラメータを調整する前に、各センサがどのような特性を持つかを把握しておけば問題の解決がずっと容易になります。

上のグラフはCPUボードをUSBでPCに接続してリアルタイムデータをグラフ表示したものです、センサやステアリング部分の挙動を調べるときにはリアルタイムデータを観察する方がプログラムの修正が手早く出来るので便利です。

そして、実際にコースを走行する時のデータはmicroSDに記録して後から解析するという使い分けをすることで開発をより効率的におこなうことが出来ます。

 

 

マイコンカーラリー printfデバッグ (5) 割り込み周期の確認

printfデバッグで原因が判って問題は解決しましたが、制御ループが正確なタイミングで実行されていないと制御が不安定になる原因になりますので、タイマ割り込みと制御ループの実行間隔を調べて見ました。

(4)のtimeに表示されている数値は1ms毎の割り込み関数でインクリメントされているので正確に増えていますが実際には消去関数は130mS程度割り込みを禁止しているのでその間は実際の周期は狂っているはずです。

そこで今度はタイマーRFを使って割り込み関数の起動タイミングを調べてみます。

init()の最後に次の処理を追加してタイマRFを20MHz/32のクロックでインクリメントします。
本当ならばもっときりの良いクロックを使いたかったのですが空いているタイマーの制約上このようなクロックを使うことになりました。

 /* タイマ 時間 計測 */
 trfcr0 = 0x05; /* f32 フリーランカウント開始 */

タイマRFが0-0xFFFFまでカウントアップする時間は 約(1/9.5)秒、およそ0.1秒です。

次のようにタイマ関数の先頭にprintf文を入れてタイマRFの値を表示してみます。

/************************************************************************/
/* タイマRB 割り込み処理 */
/************************************************************************/
#pragma interrupt /B intTRB(vect=24)
void intTRB( void )
{
   static int line_no; /* 行番号 */
   unsigned int i;

   asm(" fset I "); /* タイマRB以上の割り込み許可 */

#ifdef DEBUG
   printf("%04X\n",trf); // TRF 表示 20/32 MHzクロック 
#endif
   uTimer1ms++; // 時間計測用タイマ
   cnt0++;
   cnt1++;
   cnt2++;
   cnt_lcd++;
   check_sen_cnt++;
   check_enc_cnt++;

   ...

実行結果は次のようになります

これをグラフ表示で見ると次のように周期的に間隔が乱れているのが判ります。

間隔の乱れているところを拡大してみると次のようになっています。

縦軸がタイマの値で横軸はデータの数です、タイマの値は16bitで65535を超えると0に戻るためノコギリ波のような形になります、真ん中の大きい段がDataFlashの消去で割り込みが止まっている時でおよそ(57+105×n)mS止まっています。
ここで57mSはグラフから読み取れる値で、105mSはTRFがタイムアウトする時間です。
DataFlash消去中はデータも出力されないためその間に何度TRFがタイムアウトしているかはわかりません。
止まっている時間を正確に知りたければ例えば10KHzクロックでカウントアップするタイマがあれば6.5秒までは測れることになります。
手がかりとしてdata_flash_lib.cのソースに次のような記述があります。

/************************************************************************/
/* モジュール名 blockEraseDataFlash */
/* 処理概要 ブロックイレーズ */
/* 引数 unsinged int アドレス */
/* 戻り値 1:エラーなし 0:エラーあり */
/* メモ 実測で135ms程度 */
/************************************************************************/
int blockEraseDataFlash( unsigned int address )
{
   volatile int ret = 1;
   volatile int block;

   block = checkBlockAddress( address );
   if( block == -1 ) return 0;

   asm( "FCLR I" ); /* 全体の割り込み禁止 */
   
   ...

これを考慮すれば n=1で 162mSの間割り込みが止まっていると考えるのが妥当でしょう。

両側の小さい段は書き込みの時で書き込みの時も割り込み禁止期間があり数ミリ秒は割り込みが止まるようです。

マイコンカーラリーではロボットが高速走行します。
例えば2m/秒の速度で走っているとすれば2mm / 1msの間隔で制御をしていますが、162mS制御不能になればその間に 2mm × 162 = 324mm 進んでしまうことになります。
カーブ等で324mmの間制御が止まればコースアウトは確実です。

 

次にメインループの先頭にtrfを表示するprintfを入れてメインループの実行間隔も確認してみます。

while( 1 ) {
   // デバッグ表示
#ifdef DEBUG
   printf("%04X\n",trf); // TRF 表示 20/32 MHzクロック
#endif
   // LCD表示、パラメータ設定処理
   lcdProcess();
 
   ...

結果のグラフ表示は次のようになりました

当然ですがこちらも同じように制御周期が乱れています。
拡大してみます。

メインループの周期はノコギリ波の周期とその間のデータ数から求められます。
ノコギリ波の1周期は105msでその間におよそ120のデータが出力されていますから、105/120=約0.9msとほぼ割り込みと同じ周期でメインループが繰り返されていることがわかります。。

 

 

 

 

 

マイコンカーラリー printfデバッグ (4) トラブル原因の特定

printfデバッグの実行

準備ができたのでデバッグ文を入れたプログラムをボードに書き込み実行してみると次のような結果になりました。

なんと50ms毎にwriteDataFlashが呼び出されて32回毎にDataFlashの消去が行われています。
データフラッシュの耐久性は1万回の消去までOKとなっています。
実際はかなりマージンがあるはずなので2万回が寿命と考えて2万回×32=32万回のデータ消去で寿命を迎えてデータエラーが発生するようになります。
時間にすると1.6秒×32万回=512000秒=142.2時間になり、如何に熱心に練習されているかがこのデータから読み取れます。

writeDataFlash関数が呼ばれているところをファイル検索で調べるとメインループの中とメインループから呼び出された関数lcdProcessの中でスイッチを押した時に呼ばれるようになっています。

で、次のようにしてメインループの最初でスイッチの値を表示してみました。

while( 1 ) {
   // デバッグ表示
#ifdef DEBUG  スイッチ状態の表示
   printf("pushsw_get()=%d, SW_1=%d, SW_2=%d, SW_3=%d, SW_4=%d\r\n",
          pushsw_get(), getSwFlag(SW_1), getSwFlag(SW_2), getSwFlag(SW_3),       getSwFlag(SW_4));
   // printf("%04X\n",trf); // TRF 表示 20/32 MHzクロック
#endif
   // LCD表示、パラメータ設定処理
   lcdProcess();  <== この中で呼ばれる

   ...
   ...
   switch( pattern ) {
     case 0:
       /* プッシュスイッチ押下待ち */
       servoPwmOut( 0 );
       if( pushsw_get() ) {
           writeDataFlashParameter(); // パラメータ保存  <== ここで呼ばれる

   ...

プログラムを実行すると次の結果が表示されました

SW_4だけが時々ONになっています、SW_4は同じ信号線を使ってLCDの表示とスイッチの読み込みを兼ねているのでハードウェアとの相性かソフト的な原因でこのようなことが起こっていると思われます。
SW_4の動作を修正することが目的ではなくpushsw_get()が押された時だけwriteDataFlash関数を呼び出すようにすれば実用上問題無いのでSW_4誤動作の件は無視してlcdProcessの中の呼び出しを削除するようにアドバイスしました。