デバッグの基本

マイクロコントローラベースのシステムのデバグの基本を学びます。


AUTHOR: José Bagur, Taddy Chung、LAST REVISION: 2022/08/31 18:54


組込みシステムは、マイクロプロセッサーやマイクロコントローラベースのシステムで、専用の操作機能を持っています。デスクトップPCやノートPC、ゲーム機が複数の要素から構成されているのとは違い、組込みシステムは特定の目的に必要となるすべてのハードウェアとソフトウェアを一つにまとめたものです。自動車やカメラ、家電、モバイル機器など、組込みシステムは今やどこででも使われています。

ハードウェア設計やファームウェア・ソフトウェア開発の全てを一つのデバイスにまとめ上げる必要があるので、組込みシステムの設計難易度は高いものです。特定のデバイスや製品向けに、高品質の組み込みファームウェアやソフトウェアを開発するには、開発工程においてデバグが必要不可欠な工程です。デバッグは、正しいと信じていることとコードの機能など多くのことをひとつずつ確認する工程です。一つ以上の仮定が正しくないとき、コード中に「バグ」を発見します。

i
Thomas Alva Edisonがその当時使ったように、世界中の人が「バグ」について長年議論しています。バグという単語は「モンスター」を意味する古い言葉です。モンスターは機械の中のグレムリンのようなもので、バグは悪いものです。

以下では、特にArduino®ハードウェアに基づくマイクロコントローラーベースのシステムでバグを見つけるための複数のデバッグツールや技術を議論します。

デバッグツールと技術

コードを検証するために利用・実装できる基本的なデバッグツールや技術があります。

  • コンパイラとシンタックスエラー
  • 古典的手法: トレースコードとGPIO
  • リモートデバッガー
  • シミュレーター
  • インサーキットエミュレータ―とインサーキットデバッガー
  • ハードウェアツール: マルチメーター、ロジックアナライザー、オシロスコープ、ソフトウェア無線

それぞれのデバッグツールと技術を見ていきましょう。

コンパイラとシンタックスエラー

コンパイルは、高級言語をマイクロコントローラのようなプロセッサが理解できる機械語に変換する操作です。この過程でコンパイラはシンタックスエラーの発見にも役立ちます。シンタックスエラーは、プログラムの文法に誤りがあることを意味します。例えば、プログラムの文末にセミコロンがない場合、コンパイラはシンタックスエラーを発生させます。

Arduino IDE 2.0でのスケッチのシンタックスエラーの表示

Arduino IDE 2.0でのスケッチのシンタックスエラーの表示

シンタックスエラーのデバッグにコンパイラを使うと、たまに奇妙なことが起こります。よくある2つの状況を見ていきましょう。

  • コンパイラが100個のエラーを表示する: 通常この場合、100個のエラーがあるわけではありません。コンパイラがエラーを見つけると、通常の動作から逸脱することがよくあります。コンパイラはエラーを回復しようとし、最初のエラーの後読み込み続けます。そして、コンパイラは誤ったエラーを報告します。最初のエラーメッセージだけが純粋に信頼できます。一度にひとつだけエラーを修正して、再コンパイルしてみてください。
  • 奇妙なコンパイラメッセージが出る: コンパイルエラーは短い専門用語で表示されるので、読みづらく理解しづらい場合がありますが、隠れた貴重な情報が含まれています。エラーメッセージを注意深く読んでください。コードのどこでエラーが発生したのかがわかります。

古典的手法: トレースコードとGPIO

組み込みシステムの設計で、トレースコードの追加は、もっとも簡単で基本的なデバッグ技術です。この手法では、プログラムにメッセージや変数の値を表示するトレースコード(例えばSerial.print()関数を使います)を記述し、プログラムの実行時に表示させます。例えば、ある関数が待ち状態にあるのか、フリーズしているのかを確認するために、以下の例のようなトレースコードを使うことができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Print a message if the execution gets here
Serial.println("Code got here");

// Try to execute myFunction1()
myFunction1(); 

// Print a message if the execution gets here
Serial.println("Code got here, myFunction1 executed"); 

// Try to execute myFunction2()
myFunction2(); 

// Print a message if the execution gets here
Serial.println("Code got here, myFunction2 executed");

デバッグのためのトレースコードは、通常組込みシステムのコード開発の初期段階でだけ利用できます。プログラムにトレースコードを追加すると、処理時間とリソースを大量に消費します。このため、プログラムでクリティカルなタイミングを必要とするタスクの実行を妨げます。また、UARTを他のタスクで使うと、トレースコードの表示に問題が起こります。

i
F()関数を使うことで、フラッシュメモリ上の文字列をSerial.print()に渡すことができます。例えば、Serial.println(F("Code got here"))は、フラッシュメモリ上の文字列を表示します。

もう一つのトレースコード技術は、重要な情報を実行時に配列に格納する方法です。例えば、プログラムの終了時など、後から配列の内容を参照することができます。この手法は、“dump into array"として知られています。goodbadが参照したい重要な情報と仮定します。最初のステップは、デバッグ情報をRAMに格納するためのデバッグバッファを定義することです。以下にコードを示します。

1
2
3
4
#define DUMP_BUFFER_SIZE 32
unsigned char goodBuffer[DUMP_BUFFER_SIZE];
unsigned char badBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;

変数countは、デバッグバッファ内の位置を示すために使います。デバッグプロセスが開始する前にゼロに初期化する必要があります。以下に示すコードは、goodbad変数という重要な情報をデバッグバッファに書き込みます。

1
2
3
4
5
6
7
void Save_Debug_Buffer(void) {
  if (count < DUMP_BUFFER_SIZE) {
    goodBuffer[count] = good;
    badBuffer[count] = bad;
    count++;
  }
}

GPIO(General Purpose Input/Output: 汎用入出力)ピンは、UARTが利用できないときやトレースコードが有用でないときに利用できるでバッグ手法です。例えば、以下に示すコードのように、digitalWrite(LED_BUILTIN, HIGH)というコードを、プログラムの疑わしい箇所の前後などに記述すると、Arduino®ボードの内蔵LEDを点灯させることができます。内蔵LEDが点灯しているときは、特定の行のコードが実行されているということがわかります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// Print a message if the execution gets here
Serial.println("Code got here");

// Try to execute myFunction1()
myFunction1(); 

// Turn on the built-in LED for one second to indicate that myFunction1 was executed
digitalWrite(LED_BUILTIN, HIGH); 
delay(1000);
digitalWrite(LED_BUILTIN, LOW); 

// Try to execute myFunction2()
myFunction2(); 

// Turn on the built-in LED for one second to indicate that myFunction2 was executed
digitalWrite(LED_BUILTIN, HIGH); 
delay(1000);
digitalWrite(LED_BUILTIN, LOW);

リモートデバッガー

組込みシステムのデバッグり利用されるもう一つの一般的な方法は、リモートでバッグです。リモートデバッガ―は、ある組込みシステムをホストコンピュータに接続し、ホストコンピュータ上のソフトウェアと組込みシステムのハードウェアとが通信することで動作します。リモートデバッガは、開発環境と実環境とが異なるアーキテクチャのときに有効です。例えば、WindowsベースのPCじょうでARMベースのマイクロコントローラシステムの開発を行う場合などです。

リモートデバッガーは、フロントエンドデバッガーとバックエンドデバッガーの2つの主要な要素から構成されます。

  • フロントエンドデバッガーは、GUIやCLIベースのユーザインターフェイスを含み、組込みシステムのハードウェア上でのコードの実行を選択する手段をプログラマに提供します。
  • バックエンドデバッガーはでバッグモニターとしても知られています。特定のプロセッサアーキテクチャやプロセッサファミリー向けで、通常、インサーキットエミュレータやインサーキットデバッガ―のような外部のハードウェアツールと組み合わせて動作します。プロセッサがリセットされると動作を開始し、フロントエンドデバッガーと組込みシステムのハードウェア間で実行時の命令を取り扱います。
i
デバッガーツールは、Arduino® IDE 2.0で新しく導入された、まだあまり知られていない機能です。サポートされているボードでのArduino® IDE 2.0のデバッガーの使い方を、このチュートリアルでは説明します。

シミュレータ

シミュレータは、ターゲットプロセッサの機能と命令セットを疑似するために使うツールです。このツールは、通常、ターゲットプロセッサの機能は疑似できますが、環境や外付けの部品、コンポーネントは疑似できません。ソフトウェアはあるがハードウェアはまだ実装されていない初期の段階で、シミュレータは便利です。。

i
Tinkercad Circuitsは、Arduino®エコシステムで利用できる初心者向けの優れたシミュレータです。このツールでは、Arduino® UNOボードの命令セットと、抵抗やLED、モーター、LCD、サーボモータなどの多くの電子部品の機能を疑似できます。
Tinkercad Circuitsでの回路のシミュレーション

Tinkercad Circuitsでの回路のシミュレーション

インサーキットエミュレータ―とインサーキットデバッガー

インサーキットエミュレータ―(In-Circut Emulator: ICE)は、プログラムの実行中に開発者がプロセッサの状態を調べることができる特別なツールです。ICEはそれ自身が組込みシステムで、ターゲットプロセッサとメモリ(RAMとROM)のコピーです。このため、ターゲットプロセッサでのコードのデバッグを簡単に行うことができます。歴史的には、ICEは組込みシステムの開発者が選択できるツールでしたが、プロセッサが複雑化し、クロックスピードが上昇すると、ICEは高価になり、入手することがとても難しくなりました。

1990年代初めの8086マイクロプロセッサ用Mikrotek ICE

1990年代初めの8086マイクロプロセッサ用Mikrotek ICE

著作権情報: Binarysequence, CC BY-SA 3.0, via Wikimedia Commons.

インサーキットデバッガ―(In-Circuit Debugger: ICD)も、ホストコンピュータとプロセッサをつなぐ特別なツールで、リアルタイムアプリケーションのデバッグを早く簡単に行うことができます。デバッグ中にターゲットとなるマイクロコントローラのメモリとGPIOピンを使います。ICDを使うと、開発者は、JTAGなどのインターフェイスを通じて、CPUに集積されたオンチップのデバッグモジュールにアクセスできます。このデバッグモジュールでは、ロードや実行、停止、プロセッサのステップ実行ができます。

SEGGER J-Link EDU JTAG/SWDデバッグプローブ

SEGGER J-Link EDU JTAG/SWDデバッグプローブ

著作権情報: SEGGER Microcontroller GmbH & Co, CC BY-SA 3.0, via Wikimedia Commons.

i
ICEとICDの本質的な違いは、デバッグ対象の制御に利用するリソースです。ICEでは、リソースはエミューレーションハードウェアが提供しますが、ICDでは、リソースはターゲットプロセッサが提供します。

Arduino® のICD サポート

SAMDマイクロコントローラを搭載したArduino®ボードは、ICDデバッグをサポートします。以下に示します。

Arduino® Zeroボードは、Atmel® Embedded Debugger (EDGB)というオンボードデバッガーを搭載しています。プログラムとデバッグのサポートに加え、EDGBはホストコンピュータとターゲットプロセッサ間でのデータストリーミング機能も提供します。Arduino® Zeroボードでの、Arduino® IDE 2.0のデバッガーの使い方を、このチュートリアルでは説明します。

Arduino® Zero EDGB

Arduino® Zero EDGB

SAMDマイクロコントローラを搭載したArduino®ボードはネイティブのオンチップデバッグ機能を提供します。このデバッグ機能は、JTAGもしくはSMDインターフェイス経由で外部のICDツールを使うことで利用できます。CMSIS-DAP準拠のデバッグプローブはArduino IDE 2.0で、設定ファイル無しでそのまま利用することができます。非標準のデバッグプローブを使うには、特別な設定が必要です。Arduino IDE 2.0でSAMDベースのArduinoボードの外部ICDツールを使うには、以下のチュートリアルを参照してください。

Proファミリーの、Arduino® Portenta H7H7 LiteH7 Lite Connectedボードも、ICDデバッグをサポートしています。これらのボードは、LauterbachのTRACE32デバッガーを利用します。TRACE32デバッガはプロセッサのインサーキットデバッグインターフェイスを使い、組込みハードウェアとソフトウェアのテストができます。

PortentaファミリーのボードでTRACE32デバッガーを使うには、以下のチュートリアルを参照してください。

Arduino® Portenta H7

Arduino® Portenta H7

ハードウェアツール

組込みシステム開発者と一般的なソフトウェア開発者とでは、ハードウェアとの「近さ」という点において、重要な違いがあります。通常、組込みシステム開発者のほうが一般的なソフトウェア開発者よりもハードウェアに近いところで開発しています。組込みシステムユーザが、ハードウェアに何が起こっているのかを知るためのさまざまなツールがあります。これらは下位レベルのソフトウェアのデバッグにとても有用です。マルチメーターやロジックアナライザー、オシロスコープ、ソフトウェア無線(SDR)などです。

それぞれのデバッグツールを見ていきます。これらのツールの基本的な理解は、組込みシステムの開発時のデバッグ技術を高めます。

i
マルチメーターやロジックアナライザー、オシロスコープ、ソフトウェア無線(SDR)は組込みシステムで、プロセッサと他の電子部品とのやりとりのデバッグに有用です。これらのツールは、組込みシステムの実行の流れを制御するわけではありません。

マルチメーター

デジタルマルチメーター(DMM)は、2つ以上の電気的な値を測定するためのハードウェアツールです。通常は電圧(ボルト)と電流(アンペア)、抵抗値(オーム)を測定します。DMMは、組込みシステムにおいて、電気的な問題をデバッグするために使われる、とても素晴らしい基本的なテスト器具です。

デジタルマルチメーター

デジタルマルチメーター

著作権情報: oomlout, CC BY-SA 2.0, via Wikimedia Commons.

ロジックアナライザ―

ロジックアナライザーは、デジタル回路で、電子信号の取得や表示、測定を行うために設計されたハードウェアツールです。このツールは、電気信号が特定のロジックレベル(1か0)かどうかを検出するための入力ピンを持っています。ロジックアナライザは、デジタル回路で、電気信号間の関係や時間差を表示することもでき、SPI通信プロトコルなどのデジタル通信プロトコルの解析ができるものもあります。

24MHz、8chロジックアナライザー

24MHz、8chロジックアナライザー

著作権情報: SparkFun Electronics, CC BY-SA 2.0, via Wikimedia Commons.

オシロスコープ

オシロスコープは、電気信号を視覚的に表示し電気信号が時間によってどのように変化するのかを表示するためのハードウェアツールです。オシロスコープは、センサーを使って電気信号を測定します。

50MHz、4chオシロスコープ

50MHz、4chオシロスコープ

著作権情報: Dave Jones from Australia, CC BY-SA 2.0, via Wikimedia Commons.

ソフトウェア無線(SDR)

ソフトウェア無線(SDR)は、無線信号の変調・復調にソフトウェアを利用する無線通信システムです。古い無線通信システムの処理はハードウェアコンポーネントに依存します。これは、下位レベルでの再プログラミングに制限があります。SDRはソフトウェアによって再構成できるので、より柔軟に対応することができます。

HackRFソフトウェア無線

HackRFソフトウェア無線

著作権情報: Alexander Neumann, Public domain, via Wikimedia Commons.

ハードウェアツールを使ってデバッグする

デバッグ技術はいくつかありますが、LDEを最低限のデバッグプロセスに利用することは、利用可能な最も単純で早い方法です。特定の異なる箇所で、タスクの実行の正しさを視覚的に観測できるように、インジケーターを設定します。例えば、複数の箇所に同時に配置し、一か所ずつLEDをオン・オフさせ、段階的に確認することができます。これは、動作をデバッグために、追加のコードレイヤを構築したり、次の構造箇所に進むために必要となる、充分な情報を提供します。LEDは、レジストリやデータ交換の精密で深い情報は取得できないので、アーキテクチャとして複雑でなく、主に分岐がなく実行されるコード構造で使われるツールです。デバッガーが利用できないときに、コードがどのように振舞うのかを知りたいときに手軽に利用できます。

LEDがなかったり、利用できなかったりするシステムでは、システムを視覚的に検査する方法がありません。しかし、この場合は、GPIOピンの状態を知るために、直接オシロスコープを使うことができます。この場合、GPIOピンを望むロジック状態に設定することで、コードが特定のフィードバック与えているかを確認するために、オシロスコープを使い、特定のGPIOピンを監視することができます。デジタルマルチメータも同じ用途に簡単に使うことができます。

オシロスコープとGPIOピンを最大限に利用するには、性能を測定する必要があります。これは、信号の電気的・時間的特性を決めることです。例えば、コード内の不要な遅延は、この情報で特定することができます。

1
2
3
4
5
6
void myFunction() {
  digitalWrite(LED_BUILTIN, HIGH); 
  Serial.println("Code got here");
  count++;
  digitalWrite(LED_BUILTIN, LOW); 
}

myFunction()の実行間隔は、処理開始時にHIGHレベルに設定し、myFunction()終了時に、LOWレベルにすることで、測定することができます。その後、オシロスコープで、関数の実行が正確に定義した時間だけかかっているのか、長いのか、短いのか、あるいは、想定した振る舞いを変更する説明できない電気的な振る舞いがあるのかを知ることができます。

次は無線通信についてです。異なる要件や仕様、目的の新しいIoTデバイスを開発するのに必要な重要な要素です。無線通信は多くの組込みシステムに備わっています。Arduino®ハードウェアも例外ではありません。次の問題は、デバイス間の無線通信をどのようにデバッグするかです。

2つ以上のデバイス間の無線通信をデバッグする簡単で共通する技術は、ACK(Acknowledge)フラグを使うことです。ACKフラグは、デバイス間での通信が確立しているときに現在の状態を与えることで、デバイスの動作を理解するのを助けます。この動作は、I2CやSPIといった物理通信プロトコルでも利用されています。無線通信によりプロトコル形式が異なるので、デバッグ手法としてACFフラグを使うことはできるが、個々の決まりによりルールが異なります。データ交換が成功したことを確認する一番簡単な方法は、それぞれの端末デバイスのデータログを確認することです。先ほど述べた、デジタルマルチメーターやオシロスコープ、ロジックアナライザーなどのハードウェアツールは、より詳細な情報を提供するので、デバッグ手法にさらなる価値を提供します。

しかし、すべてが物理レイヤで接続しているわけではなく、抽象レイヤで接続しているものもあります。無線通信での抽象レイヤは、電波が空中を伝播する箇所です。伝播が苦衷を伝播するところをデバッグする必要はあるのでしょうか? 組込みシステムの、送信出力のような無線通信設定が正しいかどうかを確認する必要があります。このような場合にSDRが有用です。SDRは安価なスペクトラムアナライザーとして使用することもできます。スペクトラムアナライザーは周波数と信号強度を測定するためのハードウェアツールで、信号のパワースペクトルを主に測定するために使われます。SDRをスペクトラムアナライザーとして使うことは、開発プロセスにおいては、通常オプションのデバッグ方法で、より強固なシステム設計を保証します。

i
多くのプログラムがSDRを利用できます。[GQRX](https://gqrx.dk/)は最も一般的なソフトウェアです。GQRXは、LinuxとmacOSをサポートするクロスプラットフォームのオープンソースです。[AirSpy](https://airspy.com/)と[CubcSDR](https://cubicsdr.com/)は、WindowsとLinux、macOSをサポートするクロスプラットフォームの一般的なソフトウェアです。

SDRソフトウェアで信号が視覚表現されることで、デバイスの送信出力や通信データ量が確認できます。これは、デバイスの無線通信のプロパティを視覚化するのに役立ちます。送信出力や受信電力、送信したバイト数、送信しているはずの周波数などを確認することもできます。これらのプロパティは、周波数スペクトラムを使ってデバッグすることができ、組込みシステム間の無線通信の性能を改善することができる。

スペクトグラムで表示されるLoRa信号。出展: The Tings Network.

スペクトグラムで表示されるLoRa信号。出展: The Tings Network.

デバッグ技術の例

これまでに議論したデバッグ技術のいくつかの実装を簡単な例を使って示します。Arduino®エコシステムでコードの開発がどのように簡単になるのかがわかると思います。Arduino® Nano 33 BLE Senseボードとオンボードの慣性計測装置(Inertial Measurement Unit:IMU)を使い、デバッグプロセスの重要性を示します。コード例では、加速度センサーとジャイロスコープ、磁気センサーを同時に使い、すべての値を取得します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
  Program:
  - Debugging_techniques_example.ino

  Description:
  - This example combines data from the LSM9DS1 accelerometer, gyroscope, and magnetometer into a single code. On top of it,
  it helps to understand different methods and techniques of debugging when the code structure combines multiple tasks.

  The circuit:
  - Arduino Nano 33 BLE Sense.

  Code based on examples created by Riccardo Rizzo, Jose García, and Benjamin Dannegård.
  Modified by Taddy Ho Chung and José Bagur (16/02/22).
*/

#include <Arduino_LSM9DS1.h>

#define DUMP_BUFFER_SIZE 32
unsigned char GoodBuffer[DUMP_BUFFER_SIZE];
unsigned char BadBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;
uint8_t good, bad = 0;

float x, y, z, ledvalue;
int degreesX = 0, degreesY = 0;
int plusThreshold = 30, minusThreshold = -30;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  Serial.println("- Started");

  if (!IMU.begin()) {
    Serial.println("- Failed to initialize IMU!");
    
    bad++;
    save_bebug_buffer();
    disp_debug_buffer();
    debug_stop();
  }

  accelermeter_setup();
  gyroscope_setup();
  
}

void loop() {
  for (int i = 0; i < 5; i++) {
    accelerometer_task();
    gyroscope_task();
    magnetometer_task();
  }
  
  save_debug_buffer();
  debug_stop();
}

// Accelerometer setup
void accelermeter_setup() {
  Serial.print(F("- Accelerometer sample rate: "));
  Serial.print(IMU.accelerationSampleRate());
  Serial.println(F(" Hz"));
}

// Read accelerometer data in all three directions task
void accelerometer_task() {
  if (IMU.accelerationAvailable()) {
    Serial.println(F("- Accelerometer data ready"));
    IMU.readAcceleration(x, y, z);
    good++;
  } else {
    Serial.println(F("- Accelerometer data not ready"));
    bad++;
  }

  if (x > 0.1) {
    x = 100 * x;
    degreesX = map(x, 0, 97, 0, 90);
    Serial.print(F("- Tilting up "));
    Serial.print(degreesX);
    Serial.println(F("  degrees"));
  }

  if (x < -0.1) {
    x = 100 * x;
    degreesX = map(x, 0, -100, 0, 90);
    Serial.print(F("- Tilting down "));
    Serial.print(degreesX);
    Serial.println(F("  degrees"));
  }

  if (y > 0.1) {
    y = 100 * y;
    degreesY = map(y, 0, 97, 0, 90);
    Serial.print(F("- Tilting left "));
    Serial.print(degreesY);
    Serial.println(F("  degrees"));
  }

  if (y < -0.1) {
    y = 100 * y;
    degreesY = map(y, 0, -100, 0, 90);
    Serial.print(F("- Tilting right "));
    Serial.print(degreesY);
    Serial.println(F("  degrees"));
  }
  
  delay(1000);
}

// Gyroscope setup
void gyroscope_setup() {
  Serial.print(F("- Gyroscope sample rate = "));
  Serial.print(IMU.gyroscopeSampleRate());
  Serial.println(F(" Hz"));
  Serial.println();
  Serial.println(F("- Gyroscope in degrees/second"));
}

// Read gyroscope data in all three directions task
void gyroscope_task( ) {
  if (IMU.gyroscopeAvailable()) {
    IMU.readGyroscope(x, y, z);
    Serial.println(F("- Gyroscope data ready"));
    good++;
  } else {
    Serial.println(F("- Gyroscope data not ready"));
    bad++;
  }

  if(y > plusThreshold) {
    Serial.println(F("- Collision front"));
    delay(500);
  }

  if(y < minusThreshold) {
    Serial.println(F("- Collision back"));
    delay(500);
  }

  if(x < minusThreshold) {
    Serial.println(F("- Collision right"));
    delay(500);
  }

  if(x > plusThreshold) {
    Serial.println(F("- Collision left"));
    delay(500);
  }
}

// Read magnetometer data in all three directions task
void magnetometer_task(){
  IMU.readMagneticField(x, y, z);
  
  if(x < 0) {
    ledvalue = -(x);
  }
  else {
    ledvalue = x;
  }
  
  analogWrite(LED_BUILTIN, ledvalue);
  delay(500);
}

// For debugging purposes
void save_debug_buffer(void) {
  if (count < DUMP_BUFFER_SIZE) {
    GoodBuffer[count] = good;
    BadBuffer[count] = bad;
    disp_debug_buffer();
    count++;
  }
}

// Debugging array buffer
void disp_debug_buffer() {
  Serial.println(F("\n Debugging array buffer result >>"));
  Serial.print(F("- Good marks: "));
  Serial.println(GoodBuffer[count]);
  
  Serial.print(F("- Bad marks: "));
  Serial.println(BadBuffer[count]);
}

void debug_stop() {
  Serial.flush();
  exit(1);
}

上記のコードでは、加速度センサーとジャイロスコープ、磁気センサーのタスクをひとつのコードに統合しています。異なるモジュールのタスクが含まれるので、異なる関数に分割し、わかりやすい方法で実行しています。コードには、デバッグのためのトレースコード技術(配列への格納)が含まれていて、コードがどのように動作するのか詳細に理解できるようになっています。コード内の知りたい場所でのgoodbadの状態が配置され、割り当てられた配列に格納します。配列はコードの最後で表示するようになっています。

コードのデバッグをいつやめるのかを知るのは重要です。上記のコードは実行時にデバッグするので、いつ辞めるのかやコードの操作をどのように止めるのかを知るのは容易です。例えば、最初の実行で止めると、以下の結果が得られます。

一回だけ実行したときのデバッグ配列の例

一回だけ実行したときのデバッグ配列の例

Arduinoのシリアルモニタで、1つの成功(good)と1つの失敗(bad)が表示されています。成功したのはジャイロスコープで、データが利用できる状態になっています。一方、失敗したのは加速度センサで、データが利用できる状態になっていません。つまり、測定タスクの前に、加速度センサのデータが利用できるための時間が不足しているということがわかります。配列の情報を表示する前に、何回か処理を実行することができます。その結果を以下に示します。

5回実行したときのデバッグ配列の例

5回実行したときのデバッグ配列の例

加速度センサは最初の1回を除いて問題なく動作し、9回の成功と1回の失敗が記録されています。Serial.println(F())命令のモジュール設定とタスク実行が実行されているので、このコードが問題なく通過していることも示しています。これにより、コード構造そのものは問題はありませんが、デバイスが開始した最初の1回は、加速度センサのデータが利用できるようになるための時間が不足していることがわかります。

さらに、タスクの実行前にdigitalWrite(12, HIGH)を、タスクの実行後にdigitalWrite(12, LOW)を呼び出すようにコードを修正し、ボード上のGPIO12とオシロスコープを使って、タスクの実行にかかる時間を測定することができます。これは、実行時にどのくらい電力を消費するかを知るためにも有用です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void loop() {
  for (int i = 0; i < 5; i++) {
    digitalWrite(12, LOW);
    accelerometer_task();
    gyroscope_task();
    magnetometer_task();
    digitalWrite(12, HIGH); 
  }
  
  save_debug_buffer();
  debug_stop();
}
オシロスコープとGPIOを使いタスクの実行時間を測定する

オシロスコープとGPIOを使いタスクの実行時間を測定する

デバッグに関する最後の見解

強固で信頼性の高い組込みシステムソフトウェアを開発するには、デバッグが必須です。Embedded Systems Programming magazineに掲載された、Robin Knokeによる、組込みC言語のデバッグに関するこの記事に記載された、デバッグで最も重要な4つのフェーズを紹介し、この記事を締めくくります。

  • テスト: さまざまな入力値や異なる環境で組込みソフトウェアを動作させ、能力を調べる段階。
  • 安定化: 特定のバグを発生させる条件を確認する段階。
  • 局所化: バグの存在する範囲を、組込みソフトウェアの特定のコードセグメントに狭める段階。
  • 修正: ソフトウェアからバグをなくす段階。

バグの潜在的な原因を知ることで、バグの発生を最小限にとどめるための戦略を採用することができます。これを実現するために、多くの異なるデバッグ技術と外部デバイスが存在します。例えば、ソフトウェア設計しだいでは、外部デバッガを利用する必要がないかもしれません。しかし、ソフトウェアに異なる要求条件、特にスケーラビリティが求められる場合、開発プロセスの状況は一変します。デバッグ技術と外部デバッガは、この開発プロセスを支援し、ソフトウェアが洗練されたものになっていきます。ほとんどの場合、ソフトウェアによってデバイスがどのように振舞うのかや計算性能を知ることができ、さらには、きれいなメモリ管理により省電力デバイスを実現できるようになります。

組込みシステムの開発では、デバッグは見過ごされやすいことですが、最も深刻で重要なツールです。強固で信頼性の高い組込みシステムを作りたければ、目的を達成するために、デバッグプロセスを一貫して行う必要があります。

その他の資料

デバッグは学習するには面白い題材です。デバッグツールや技術をもっと学びたければ、以下のリンクをチェックしてください。

  • デバッグとエンジニアリング技術を向上したければ、David J. AgansのDebugging: The 9 Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problemsを推薦します。
  • デジタルマルチメーターについて学びたければ、Fluke®のこの記事を読んでください。
  • オシロスコープについて学びたければ、Tektronix®のこの記事を読んでください。
  • ロジックアナライザーについて学びたければ、Saleae®のこの記事を読んでください。
  • スペクトラムアナライザーについて学びたければ、Tektronix®のこの記事を読んでください。
  • ソフトウェア無線について学びたければ、Great Scott Gadgets video seriesを見てください。Great Scott Gadgetsのビデオシリーズは、ソフトウェア無線の完全な講座です。ソフトウェア無線の基礎を学べ、GNU Radioを使って、柔軟なソフトウェア無線アプリケーションを作成できるようになります。

参考文献

[1] P. Koopman, Better Embedded System Software. S.L.: Drumnadrochit Press, 2010.

[2] D. J. Agans, Debugging: The Nine Indispensable Rules for Finding Even the Most Elusive Software and Hardware Problems. New York: Amacom, 2002.

[3] M. Barr and A. Massa, Programming Embedded Systems: with C and GNU Development Tools. Johanneshov: MTM, 2013.

[4] J. W. Valvano, Embedded Systems: Introduction to ARM® Cortex™-M Microcontrollers. United States: Self-published, 2015.

オリジナルのページ

https://docs.arduino.cc/learn/microcontrollers/debugging

最終更新日

October 23, 2022

inserted by FC2 system