LoginSignup
3
6

More than 5 years have passed since last update.

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

Posted at

概要

 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);
}


その他

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

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6