マイコンカーラリー 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の中の呼び出しを削除するようにアドバイスしました。

 

マイコンカーラリー printfデバッグ (3) printf文を埋め込む

相談を受けたのは「マイコンカーラリーのプログラムでデータフラッシュにパラメータを保存しているが、最近パラメータが壊れるようになって困っている。」ということでした。

マイコンカーラリー競技は白線を引いたコースで走行タイムを競う競技で、現在は工業高校の先生・生徒が中心となって盛り上がっています。

プログラムは「JMCR実行委員会 &株式会社日立ドキュメントソリューションズ
」が提供しているテンプレートを基にして各参加者が改造・修正して作っています。

そのため、プログラム構造はしっかりしていて第三者が見ても判り易く出来ていますが、プログラムソースを見てもどうしてそのようなことが起きているのか原因を見つけることが出来ませんでした。

そこでprintfデバッグを使えるようにして問題の原因を探ることにしました。

まず、main関数の最初にUART0の初期化を挿入します。

void main( void )
{
 int i, ret;
 char fileName[ 8+1+3+1 ]; /* 名前+'.'+拡張子+'\0' */
 unsigned char b;

/* マイコン機能の初期化 */
 init(); /* 初期化 */
 setMicroSDLedPort( &p6, &pd6, 0 ); /* microSD モニタLED設定 */
 /* uart0 初期化 */
 init_uart0_printf(SPEED_9600);
 uart0_setbps(1250000);
 //for(;;) printf("Hello World. \r\n");
 /* 全体の割り込み許可 */
 asm(" fset I "); 
 ..
 ..

関数 init_uart0_printf(SPEED_9600)  はprintf_lib.cに含まれています。
この関数で設定できる通信レートは最高38400bpsなので、とりあえず9600bpsで初期化しておきます。
次にuart0_setbps(1250000)で設定できる最高スピードの1250000に再設定します。

uart0_setbps(long bps)は次のようになっています。

/*******************************************************************************
* uart0ボーレート設定
* init_uart0_printf()の後にこの関数を実行する
*******************************************************************************/
void uart0_setbps(long spd)
{
 int brg;
 
 brg = (1250000L * 10 / spd + 5) / 10 - 1; 
 /* UART0の設定 */
 u0sr = 0x05; /* P14=TXD0,P15=RXD0に設定 */
 u0c0 = 0x00; /* カウントソース=20MHz */
 u0c1 = 0x05; /* 送信、受信許可 */
 u0brg = brg; /* 通信速度 = brg */
 u0mr = 0x05; /* UART0 データ長8bit 1ストップビット */
}

uart0_setbps(long spd)の引数spdには任意の値を設定できますが通信相手が対応できる速度に対して誤差が大きくならないように注意する必要があります。

R8C/38Cマイコンのボーレートレジスタu0brgに設定した値をbrgとすると 通信速度は

20000000 / 16  / (brg  + 1)で求めることができます。

brgには0から255までの任意の値を設定可能ですが通信相手の速度に対して最大10%以内の誤差、実用的には5%以下の誤差でないと安定した通信ができません。

brg = 0 の時は 20000000 / 16 / 1 =1250000となり最高スピードでUSBシリアルのICも1250000bpsに対応していますからこの時は誤差0%となります。

printfをサポートする関数が printf_lib.c に用意されていますのでこれだけでprintf()からの出力がUART0から出力されUART0にUSBシリアルで接続したPCのターミナルソフトで表示できるようになります。

次にDataFlash書き込み関数にprintf文を埋め込みます。

/* DataFlash関連 */
#define DF_ADDR_START 0x3000 /* 書き込み開始アドレス */
#define DF_ADDR_END 0x33ff /* 書き込み終了アドレス */

#define DF_PARA_SIZE 32 /* DataFlashパラメータ数 */

#define DEBUG

/************************************************************************/
/* DataFlashへパラメータ書き込み */
/* 引数 なし */
/* 戻り値 なし */
/************************************************************************/
void writeDataFlashParameter( void )
{
  unsigned int st = DF_ADDR_START;
  signed char c;

  while( 1 ) {
    // 書き込む番地を探す
    readDataFlash( st, &c, 1 );
    if( c == -1 ) {
      writeDataFlash( st, data_buff, DF_PARA_SIZE );
#ifdef DEBUG
      printf("writeDataFlash.. st=%u, time=%d\r\n", st, iTimer1ms);
#endif
      break;
    }

    st += DF_PARA_SIZE;

    if( st > DF_ADDR_END ) {
      // すべて使用したら、イレーズして先頭に書き込み
      blockEraseDataFlash( DF_ADDR_START );
      writeDataFlash( DF_ADDR_START, data_buff, DF_PARA_SIZE );
#ifdef DEBUG
      printf("writeDataFlash.. (EraseData) st=%u, time=%d\r\n", st, iTimer1ms);
#endif
      break;
    }
  }
}

データを保存するDataFlashは0x3000から0x33FFの1Kバイトで書き込みはバイト単位でおこなえるのですが消去は1Kバイトブロックの一括消去になります。
それで32バイトづつ書き込んで一杯になったらブロック消去をおこない、また先頭から書き込んでいくという繰り返しをおこなっています。

データフラッシュの書き込み制限回数は10000回ですがこれは10000回の消去が可能という意味なのでこの関数のような使い方をすれば1024/32*10000の32万回のデータ保存が可能ということになります。

 

マイコンカーラリー printfデバッグ (2) デバッグの準備

printfデバッグに使うシリアルポート

printfデバッグをおこなうためにはマイコンボードの情報をPCに出力する仕組みが必要となります。
マイコンカーラリーに使うボードにはプログラム書き込みのためのシリアルコネクタがあり、そのシリアル経由で通信することが出来ます。

ボード上のprintfサポートライブラリ

PC上で走るアプリケーションの場合何も考えなくてもprintfの結果は画面に表示されますがボードで走るプログラムの場合はprintfの結果をシリアルポートから出力する機能をライブラリがサポートしていることが必要です。
マイコンカーラリーのライブラリにprintf_lib.cが用意されていてこの中に書き込みコネクタのUART0からデータを出力する機能がサポートされています。

通信速度について

printfデバッグで情報を取り出すときにまず考えておかなければならないのはシリアル送信に要する時間です。
シリアル通信ではスタートビット+8ビットデータ+ストップビットの10ビットでアルファベット一文字(1バイト)が送信できます。
例えば通信速度が9600bpsでは1秒に 最大(9600/10)=960文字送ることが出来ます。
マイコンカーラリーのプログラムは1ms毎の制御をしていますが1msに一文字も送ることが出来ないので9600bpsでは1ms毎の割り込みには対応できません。

マイコンカーラリーで使うR8C/38Aに設定できる最大通信速度は1250000bpsで、この場合1秒間に1250000/10=125000文字、1msあたり125文字の送信が可能です。

例えばデータとして次のように10個の16ビットデータを16進数で送る例では
1A80,334B,F7C3,78EF,224A,68C1,FF00,DE0A,1322,98B3[cr]

50文字の送信となり送信に必要な時間は1ms/125*50=0.4msで、プログラムループで送信待ちを行うと0.4msの間プログラムの処理時間を占有されてしまいます。
マイコンカーラリー用のライブラリでは送信割り込みを使った通信がサポートされていて、通信処理のプログラム占有時間を大幅に削減できるのでこれを使うことにします。

PC用のターミナルソフト

PC用ターミナルソフトとしては定番のTeraTermという良くできたソフトがあるのですが、私はDelphiで開発したJTW32という自作のソフトを使っています。
もとはH8シリーズのプログラム書き込みツールとして作ったものですが今ではデバッグ用ターミナルとして機能拡張して使っています。
(ここからJTW32がダウンロード出来ます)※インストーラはありません、任意のフォルダに解凍して実行するだけで使えます。

第一の理由は1.25Mbps,3Mbpsという通信速度をサポートしているターミナルソフトが無いことです。
最近のPCでは無くなったRS232Cポートは物理的にサポートされている最大速度が115200bps程度でしたが、USBシリアルコンバータを使う場合はFTDIのICを使ったもので最大3Mbpsの通信をおこなうことが出来ます。

第二の理由は例えばカンマ区切りデータをグラフ表示して簡易オシロスコープ的な使い方をするなど自分が欲しいと思う機能を追加して作業能率を上げることが出来るからです。
そのかわり機能がごちゃごちゃし過ぎて取っつきの悪いソフトになっていることは否めません。

サポートしている通信速度

カンマ区切りデータのグラフ化