Release 2017.5.20 / Update 2017.6.19

スケッチのトラブルシューティング

Arduinoでスケッチをいじっていると、どっかでコードを書き間違え、「あれ?動かない…」と右往左往することがあります。大抵ちょっとした書き間違いだったりするんですが、それが複数のタスクが絡んだ長いプログラムになってくると、何処をどうしていいか分からなくなります。

そんな時に自分がやっている対処法を紹介してみたいと思います。デバッグって言うんでしょうか。まあ、そんな大それたことでもないので、デバッグ的なことをするためのヒントです。参考までに。

問題箇所をあぶり出す

分かりやすいエラーだとコンパイル時にダメ出ししてくれますが、それがないととりあえず動いてしまうので、何処がうまく行ってないのか判別しづらくなります。

プログラムが上手く動かないのは大抵、以下の2点に集約できると思います。

  • 命令が思ったように流れていない
  • 変数のやり取りができていない

結論から言うと、一個一個のコードを確認していくしかないんですが、シリアルモニタに値や状態を返し、あぶり出していくと比較的楽に答えへ近づけます。

回路図とサンプルスケッチ

Arduino UNOならOLEDの結線はA4(SDA)、A5(SCL)でも一緒です

OLEDは128*64のI2C接続の物です。

OLEDで可変抵抗器の変化を、スイッチの切替を画面の右上に表示するスケッチです。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
short   vr_val;
boolean sw_status;

void setup() {
  u8g.setFont(u8g_font_6x10);   // OLEDでの文字フォント指定
  u8g.setColorIndex(1);         // OLEDでの描画色を白に
  pinMode(VR, INPUT);           // 可変抵抗器のピン設定
  pinMode(SW, INPUT_PULLUP);    // タクトスイッチのピン設定
}

void loop() {
  vr_val = VR_READ(vr_val);   // 可変抵抗器の読み取り値を変数に代入
  BUTTON();                   // タクトスイッチ状態を確認
  DRAW();                     // OLEDへ描画
}

void BUTTON() {
  unsigned int gauge = 0;                      // ゲージ変数
  while (digitalRead(SW) == true) gauge++;     // ゲージ判定
  if (gauge > 700) sw_status = !sw_status;     // 判定に沿って変数sw_statusを変更
}

short VR_READ(short tmp) {
  short read_val = tmp * 0.9 + analogRead(VR) * 0.1;     // 可変抵抗器読み取り
  return tmp;                                            // 値の返し
}

void DRAW() {
  byte gauge_x = map(vr_val, 0, 1013, 0, 127);  // 可変抵抗器の値をOLEDサイズにリマップ

  u8g.firstPage();
  do {
    u8g.setPrintPos(0, 10);                     // 可変抵抗の数値表示の位置指定
    u8g.print(vr_val);                          // 可変抵抗の数値を表示

    u8g.drawBox(0, 30, gauge_x, 10);            // 可変抵抗の値に相対したゲージバー表示
    if (sw_status) u8g.drawStr(100, 10, "ON");  // タクトスイッチのON表示

    vr_val = VR_READ(vr_val);                   // 可変抵抗器読み取り(while内更新用)
    BUTTON();                                   // タクトスイッチ状態を確認(while内更新用)
  } while ( u8g.nextPage() );
}

本来は可変抵抗器を回すことで、数値とバーが、スイッチを押せば“ON”がOLEDに表示されます。

でも、そうはなりません。

とりあえず表示は出るものの、可変抵抗器を回しても全く反応しません。「コンパイルも書き込みも出来たが、狙い通りの反応をしてくれない」、そんな状態です。

こういった原因を探るためにシリアルモニタを利用すると便利です。

フリーズする行を探す

Arduinoはどんなスケッチであっても、基本的にSetup()関数を経て、loop()関数を繰り返すようになっています。そこで、まずはSerial.printlnを使い、ナンバリング表示をさせて、どこで止まっているかを探ります。

Serial通信を有効にして、コードの目安になる行に目印になるナンバリング表示命令を書きます。分かり易いようアルファベット表記にしていますが、自分で分かれば何でもかまいません。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
short   vr_val;
boolean sw_status;

void setup() {
  Serial.begin(38400);
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
  Serial.println("A");
}

void loop() {
  Serial.println("B");
  vr_val = VR_READ(vr_val);
  Serial.println("C");
  BUTTON();
  Serial.println("D");
  DRAW();
  Serial.println("E");
}

シリアルモニタで確認すると、Dの手前で止まっています。

BUTTON()関数に問題があることが分かります。同様の手口で、次はBUTTON()関数内の止まっている部分を探ります。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
short   vr_val;
boolean sw_status;

void setup() {
  Serial.begin(38400);
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
}

void loop() {
  vr_val = VR_READ(vr_val);
  BUTTON();
  DRAW();
}

void BUTTON() {
  Serial.println("A");
  unsigned int gauge = 0;
  Serial.println("B");
  while (digitalRead(SW) == true) gauge++;
  Serial.println("C");
  if (gauge > 700) sw_status = !sw_status;
  Serial.println("D");
}

Cの手前で止まっていることから、

while (digitalRead(SW) == true) gauge++;

このコードが悪さをしていると判断できます。

後は、何がどう間違っているのかを自力で探っていくしかありませんが、こうやって場所の特定が出来るだけでも、楽になります。ちなみに、この場合、内部プルアップをしているので、

while (digitalRead(SW) ==false) gauge++;

が正解です。これで、スイッチで“ON”表示が切替出来るようになります。

ただ、今度は可変抵抗器をいじっても値が動きません。

B~Eが問題なく繰り返されるので、どこかで引っかかっているわけではないことが分かります。

変数を常に確認

問題がフリーズではない場合、変数の扱いが上手くいっていない事が多いです。そこで、扱っている変数をSerial.printでモニタリングします。このスケッチでは可変抵抗器に関わる変数は「vr_val」なので、それを確認します。場所はとりあえず、loop関数最後に書きます。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
short   vr_val;
boolean sw_status;

void setup() {
  Serial.begin(38400);
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
}

void loop() {
  vr_val = VR_READ(vr_val);
  BUTTON();
  DRAW();
  Serial.println(vr_val);
}

シリアルモニタで確認すると、値が全く変化していないことが分かります。

そこで、他のコードの前後にもSerial.printを置いてみます。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
short   vr_val;
boolean sw_status;

void setup() {
  Serial.begin(38400);
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
}

void loop() {
  Serial.println(vr_val);
  vr_val = VR_READ(vr_val);
  Serial.println(vr_val);
  BUTTON();
  Serial.println(vr_val);
  DRAW();
  Serial.println(vr_val);
}

VR_READ()関数自体が上手く動いていないのかな、と目星がつきます。

short VR_READ(short tmp) {
  short read_val = tmp * 0.9 + analogRead(VR) * 0.1;
  return tmp;
}

後は自力で原因を探っていきます。

ここでの正解は関数の返り値が間違っています。return read_val;にするか、

short VR_READ(short tmp) {
  return tmp * 0.9 + analogRead(VR) * 0.1;
}

左上の数値は正常に表示されるようになりました。

こんな感じでシリアルプリントを間に挟んで、工程を確認していくことで、おかしい部分の目星がつけられ、効率的にトラブルシューティングできるかと。

関数化とプリプロセッサ

正直、今回のサンプルスケッチのサイズだと、このような探し方はほぼ必要ないです。ただし、大きなプログラムになってくると、一つの間違いが仇となって全く機能しなくなってしまうこともあります。自作フォーローフォーカスの製作でも、そこに大分苦労しました。

ある程度、規模が大きなプログラムを組む予定なら、こういったデバッグ用のコードを書いておいて、コメントアウトしておくと便利かと思います。

void loop() {
  //Serial.println(vr_val);
  vr_val = VR_READ(vr_val);
  //Serial.println(vr_val);
  BUTTON();
  DRAW();
  //Serial.println(vr_val);
}

ただ、いちいち一個ずつ書き換えるのが面倒なので、もしグローバル変数を使って色々値を変更していくのであれば、予め見たいものを集約して関数を作ってしまうと楽です。

void DEBUG(char num) {
  Serial.print(num);
  Serial.print(  "VR:");
  Serial.print(vr_val);
  Serial.print("  SW:");
  Serial.print(sw_status);
  Serial.println();
}

後は、プリプロセッサ#defineを使い、表示したいときと、したくないときをtrue・falseで定義し、if文で振り分けてやれば、コンパイル時に一発で再設定できるようになります。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
#define debug_on true

short   vr_val;
boolean sw_status;

void setup() {
  if (debug_on) Serial.begin(38400);
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
}

void loop() {
  if (debug_on) DEBUG('A');
  vr_val = VR_READ(vr_val);
  if (debug_on) DEBUG('B');
  BUTTON();
  if (debug_on) DEBUG('C');
  DRAW();
}

void BUTTON() {
  unsigned int gauge = 0;
  while (digitalRead(SW) == false) gauge++;
  if (gauge > 700) sw_status = !sw_status;
}

short VR_READ(short tmp) {
  return tmp * 0.9 + analogRead(VR) * 0.1;
}

void DRAW() {
  byte gauge_x = map(vr_val, 0, 1013, 0, 127);

  u8g.firstPage();
  do {
    u8g.setPrintPos(0, 10);
    u8g.print(vr_val);
    u8g.drawBox(0, 30, gauge_x, 10);
    if (sw_status) u8g.drawStr(100, 10, "ON");
    vr_val = VR_READ(vr_val);
    BUTTON();
  } while ( u8g.nextPage() );
}

void DEBUG(char num) {
  Serial.print(num);
  Serial.print("  VR:");
  Serial.print(vr_val);
  Serial.print("  SW:");
  Serial.print(sw_status);
  Serial.println();
}

なんですが、実はこのような書き方だと、とりあえずif判別式は実行してしまうので、命令の進行を遅らせることになってしまいます。その命令さえさせたくない場合はプリプロセッサのif文を書いてしまうほうが良いです。

#if(判定式)
  命令文~
#endif

プリプロセッサのif文は判定式に合致しなかった場合、その内容はコンパイル前にスケッチ上から抹消してくれるので、スケッチ容量と動作スピードを、より節約できます。

気をつけないといけないのはプリプロセッサでは「;」をつけないということ。#defineと一緒です。そして、入れ子にはしないということです。#ifと#endifは必ず対のセットで使います。

上記サンプルスケッチを#ifで書き換えるならこうなります。

#include "U8glib.h"
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0); // I2C / TWI
#define VR A0
#define SW 7
#define debug_on true

short   vr_val;
boolean sw_status;

void setup() {
#if (debug_on)
  Serial.begin(38400);
#endif
  u8g.setFont(u8g_font_6x10);
  u8g.setColorIndex(1);
  pinMode(VR, INPUT);
  pinMode(SW, INPUT_PULLUP);
}

void loop() {
#if (debug_on)
DEBUG('A');
#endif
  vr_val = VR_READ(vr_val);
#if (debug_on)
DEBUG('B');
#endif
  BUTTON();
#if (debug_on)
DEBUG('C');
#endif
  DRAW();
}

公開しているフォローフォーカスでも#if文は多用しているので、使ってみようと思う方は良かったら参考にしてみてください。

自作フォローフォーカス Sketch