Arduinoに簡単なデバッグ機能をつけてみた


概要

 ArduinoのIDEにはデバッグ機能がない。
 機能がないのは開発元のポリシーかもしれないが、どつぼに嵌った時などは俗にいうprintf攻撃だけでは力不足を感じる。
 デバッグ機能付きのIDEもあるには有るが、趣味でそこまでやる踏ん切りはなかなかつかない。
 そこでprintf+α程度でデバッグ出来るものを考えた。

問題点の把握

 printf攻撃には次の弱点がある。
  (1)プログラムが走行している場所が判らないと仕掛けられない。
   場所が判らない場合、幅広く仕掛ける必要がある。
  (2)たくさん仕掛けると表示がウザくなる。
   確認完了部位のprint処理は削除が必要。
  (3)一時停止ができない
   特定の信号などを確認する場合に必要
  (4)データの書き換えができない。
   書き換えられればデバッグが進む

必要な機能

 問題点の把握より、次の機能が必要と考える。
  (1)トレース機能
   プログラムが何処を走っているのかを確認する。
  (2)ブレーク機能
   プログラムを一時停止させる。
  (3)データ書き換え機能
   プログラム動作中にデータを書き換えて、処理の流れを変える。

実現方法

 プログラム中にブレークポイント文言を追加し、ここを通過時にデバッグ機能に分岐させる。
 文言(trace(xx))をマクロ展開してデバックプログラムを呼び出す。
 プログラムの書き換えが必要となるので、printf攻撃の手間とどっこいどっこいという気もしないではない。

デバッグ記載例

 下記はBlinkプログラムにデバッグ文言を追加したものである。
 次の二つの文言をデバッグするプログラムに追加する事により、デバッグコマンドが使用可能となる。
  (1)trace_cmdloop(引数)
   デバック用コマンドを入力する為の文言。
   loop内への記載が必要。
   引数が'0'の場合、プログラムは停止させずにデバッグコマンドの処理を行う。
   setup内で使用する場合、引数を'1'とする事により、プログラムを一時停止させた状態でブレーク等の設定を行う。
  (2)trace(引数)
   この文言を挿入すると、ブレーク等が可能となる。
   引数はブレーク位置の識別の為に必要だが重複しても構わない。

 「uno_Blink.ino」被デバックプログラム


void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

int tct = 100;
void loop() {
  trace_cmdloop(0);
  trace(1);
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(tct);                       // wait for a second
  trace(2);
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(tct);                       // wait for a second
  trace(3) {tct = trace_data; Serial.print(tct); }
}

操作コマンド説明

  全てのコマンドはコマンド+改行
  (1)トレース
   tnn(nnは1-31の数値)入力で、プログラム通過毎に[nn]が表示される。
   t単独だと全てのトレースを表示。tの再入力で停止。
  (2)ブレーク
   bnn(nnは1-31の数値)入力で、プログラム通過時にbreak inを表示し停止。
   b単独だと最初のブレーク位置で停止。
  (3)再実行
   g入力でプログラム再開
  (4)ステップ実行
   次のブレークポイントでプログラム停止。
  (5)クリア
   cnn(nnは1-31の数値)入力で、nnのトレース/ブレークを解除
   c単独だと全てクリア。
  (6)追加プログラム実行
   enn(nnは1-31の数値)入力で、nnの右側の文言を実行。
  (7)データ書き換え
   wnn dddd(nnは1-31の数値、ddddはintの数値)入力で、変数のデータを書き換える。
   ※実際にはtrace_dataを設定して、ブレーク時に右辺を実行しているだけであり、書き換えのタイミングに注意が必要。

<Arduino MicroDebuger>
 txx trace
 bxx break
 exx exec
 cxx clear
 wxx yyy write
 g   go
 s   step
 l   list bp
t2
[2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2][2]c
b1

Break at 1
c
g
Go 
s
Next
Break at 1
s
Next
Break at 2
w3 100
100

デバッグ処理プログラム

 「uno_debug.ino」
 被デバッグプログラムと同一フォルダに置く事で、各コマンドの実行が可能となる。
 デバッグを無効にする場合は、#define trace(xx) if(false)とする

#define trace(xx) if(trace_break(xx))
//#define trace(xx) if(false)

#define trace_MAX 32
uint8_t trace_setting[trace_MAX] = {0};
char trace_cmdbf[16];
int trace_data;
uint8_t trace_brkall=0, trace_cmdbfp = 0;

void trace_cmdloop(uint8_t dmd){
   uint8_t i;
   if(trace_setting[0]==0){ // 初期化
     Serial.begin(115200); delay(100);  Serial.println("");
     Serial.println("<Arduino MicroDebuger>");
     Serial.println(" txx trace");
     Serial.println(" bxx break");
     Serial.println(" exx exec");
     Serial.println(" cxx clear");          
     Serial.println(" wxx yyy write");          
     Serial.println(" g   go");          
     Serial.println(" s   step");          
     Serial.println(" l   list bp");          
     for( i=0; i<trace_MAX; i++) trace_setting[i] = 0; trace_setting[0] = 0xff;
   } else{ // ユーザ入力確認 
      do{
        if(Serial.available()) dmd = trace_cmdset(dmd);
      } while(dmd);
   }
}
uint8_t trace_cmdset(uint8_t dmd){
  uint8_t i, kd, cmd, bno;
  int ndt;
  while(Serial.available()) { 
    kd = Serial.read();
    if (kd>=0x20) trace_cmdbf[trace_cmdbfp++] = kd;
    else {
      if(kd==0x08 && trace_cmdbfp!=0) trace_cmdbfp--;
      if(kd==0x0d){
        trace_cmdbf[trace_cmdbfp] = 0;
        Serial.println(trace_cmdbf);
        cmd = trace_cmdbf[0]; bno = atoi(&trace_cmdbf[1]); 
        if(cmd=='t') {// Trace 
          if(bno) trace_setting[bno] |= 0x01; 
          else    trace_brkall ^=0x01;
        }
        if(cmd=='b') {// Break
          if(bno) trace_setting[bno] |= 0x02; 
          else    trace_brkall ^=0x02;           
        }
        if(cmd=='e') {// Exec 
          if(bno) trace_setting[bno] |= 0x04; 
          else    trace_brkall ^=0x01;
        }
        if(cmd=='l') {// Trace
          Serial.print("Set [ ");
          for( i=1; i<trace_MAX; i++) 
            if( trace_setting[i] != 0) {Serial.print(i);Serial.print(":");Serial.print(trace_setting[i]);Serial.print(" ");}
          Serial.println(" ]");          
        }
        if(cmd=='c') {// Clear
          if(bno)trace_setting[bno] = 0x00;
          else {
            for( i=1; i<trace_MAX; i++) trace_setting[i] = 0;
            trace_brkall = 0;
          }
        }
        if(cmd=='w') { // Data
          i = 0; while(trace_cmdbf[i++]!= ' ') ; // ' 'を探す
          trace_data = atoi(&trace_cmdbf[i]); // 設定値 
          trace_setting[bno] |= 0x08; // Set oneshot
        }
        if(cmd=='g') { // Go
          trace_brkall = 0;
          dmd = 0; Serial.println("Go ");
        }
        if(cmd=='s') { // Step
          trace_brkall = 0x02;
          dmd = 0; Serial.print("Next");
        }
        trace_cmdbfp = 0;
      }
    }
  }
  return dmd;
}
uint8_t trace_tcnt = 0;
int trace_break(int dno){
  uint8_t bkf;
  bkf = trace_brkall | trace_setting[dno];
  if(bkf==0) return 0; // 何もしない
  if((bkf & 0x01)){ // Trace
    Serial.print("[");Serial.print(dno);Serial.print("]");
    if(trace_tcnt++ >32) {Serial.println(""); trace_tcnt = 0;}
  }
  if((bkf & 0x02)){ // Break
    Serial.print("\nBreak at ");Serial.println(dno);
    trace_cmdloop(1);
  }
  trace_setting[dno] &= 0x07;
  return (bkf & 0x0c);
}


その他

 上記記載のハード、ソフトは無保証であり、各自の責任においてご利用願います。