文書の過去の版を表示しています。
第7章 プログラミングのトラブルシューティング
電子工作にはトラブルがつきものです。
トラブルシューティング概説は「きぐるみギミック制作講座・電子編」をご参照ください。
こちらでは「電子編」ではカバーしきれなかったプログラミングのトラブルシューティングについて補完したいと思います。
トラブルシューティングの基本
電子工作で制作物のトラブルが起きた時は、問題の切り分けが肝心です。
その問題の原因が何かによって、対処法はまるで違います。
原因別・よくあるトラブルの例
電気的原因によるもの
テスターを使うと発見できるものが多いです。
- 電圧不足 → 部品の動作電圧に満たない電圧が印加されている
- 電力不足 → 部品の消費電力に対し供給電力が足りない
- 過電圧 → 動作電圧を超える電圧を部品に与えている
- 過電流 → 定格を超えた電流が部品に流れている
- 配線ミス → 配線が間違っている、抜けている
- 接触不良 → ワイヤやコネクタの接触不良や断線、コネクタの圧着不良、はんだ付けの失敗など
- 配線が長すぎる → メートル単位に及ぶ配線はNG。特にI2Cは20cm程度の長さでも通信不良を起こすことがある
- 逆接 → 極性のある部品(LEDやCRDなど)を逆に接続している
- ノイズ → 電気的ノイズが部品の動作に悪影響を及ぼす
- UARTの接続が逆
プログラムのミスによるもの
大概のプログラムの書きミスはコンパイラが指摘してくれますが、中にはコンパイラがキャッチすることができずに発生するミスもあります。
以下はその一例です。
- プログラムの動作的には至極正しいが、自分の意図通りのプログラムを書けていない
→例えばサーボを90度に動かしたいのに角度の数値に180と入れている
while文から抜け出すための処理を設定し忘れていて無限ループに陥っている 等
- ピン番号の指定間違え → ピン番号を変数化している場合に番号を間違えたり、部品の初期化時に別の変数を入れている
- 変数の指定間違え → ある変数を入れるべき部分に型が同じ別の変数を入れてしまっている
- デジタルピンのINPUT/OUTPUT/INPUT_PULLUPの指定が間違っている
- UARTを使う場合、繋いだもの同士のボーレートが一致していない
- I2Cを使う場合、I2Cアドレスが間違っている。もしくは同じアドレスの部品が複数ある。
- そもそもマイコンへのプログラムの書き込みに失敗している
その他の原因によるもの
- マイコンや部品の初期不良
- マイコンや部品が過電流や逆接、静電気などで既に壊れている
- 極端な環境(高温や低温)で動作させようとしている
- モータ系の部品で、トルク以上の力や重さが動作部にかかっていて動きが相殺されている
- 通信系の部品で、電波の状況がよくない、電波を遮るものがある
トラブル対応の基本のおさらい
作った電子回路が動かない・或いは動作がおかしい場合、まずはすぐに電源を切りましょう。
動作不良の原因にすぐに思い当たる点がない場合は、電気的原因 → プログラムの順番でミスを探るのがベターです。
プログラムのチェックに使える小技集
プログラミングの構文的なミスはコンパイルをすることである程度チェックすることができます。
構文的なミスは、例えば「変数を宣言しないで使おうとした」、「あるライブラリの関数を使おうとしたが、ライブラリがインクルードされていない」、「あるライブラリを使おうとしたが、そもそもインストールされていない」、「; を付け忘れている」、「{ }の括りがおかしい」といったものです。
(詳細は次項のエラーメッセージ集をご確認ください)
但し、コンパイラのチェックは万能ではありません。
特に「プログラムの動作的には至極正しいが、自分の意図通りのプログラムを書けていない」場合、コンパイラがそれを見抜くことは基本的にはできません。
「コンパイルは通ったしボード書き込みも無事完了した、電気的な問題や配線もチェックし問題なかったけど意図した動作にならない」
そんな時のセルフチェックに使える手法をいくつかご紹介します。
とにかくシリアルモニタに表示する
基本中の基本はSerial.printlnを使ってシリアルモニタに処理状況を表示することです。
- 特定のループ文や条件分岐がちゃんと実行されているのか?
→そのループや条件分岐の中にSerial.printlnを入れる。メッセージが表示されれば実行されている
- どこかで無限ループが発生していないか?
→setup部、loop部の冒頭にSerial.printlnを入れる。
無限ループがなければsetup部のメッセージは1回、loop部のメッセージは繰り返し表示される
- 計算式の計算結果、変数に入っている値、センサで拾った数値等の値がおかしくないか?
→値をSerial.printlnで表示する
コメントアウトを活用する
プログラムの特定の行をコメントアウト「//」、「/* */」で無効化し、動作確認をします。
- 不具合の原因っぽそうな怪しい行がある
→プログラムに差し障りがなければその行を無効化し、Arduinoに書き込んで動作がどう変わるかチェックする
- プログラムを上から順番にチェックする
→スケッチが膨大でダメ箇所の目星もつかない場合、特定の行以下すべてを「/* */」でまとめて無効化し、Arduinoに書き込む
(無効化はプログラムの動作に不具合が出ない箇所で区切ってください)
動作に問題がなければ無効化した行の一部を少しだけ有効にし、書き込み→チェックを繰り返してダメ箇所を特定する
- 部品ごとに動作チェックをする
→Arduinoに複数の部品を繋いでいてどれが不具合を起こしているのか見当がつかない場合、部品ごとに動作チェックをする
関係のない部品の動作箇所をコメントアウトし、ある部品の動作がOKならコメントアウト箇所を変え別の部品のチェックをする
部品の動作すべてに問題がなかった場合、各部品間の連携やそれ以外のプログラムに問題があるかもしれない
AIにプログラムを見てもらう
ChatGPTなどのAIツールに自分が書いたプログラムを投げてダメな箇所を教えてもらいます。
最近講師がよく利用している方法です。講師が思う、使用に際してのコツをいくつか列挙します。
- プログラムだけでなく、「そのプログラムを説明する情報」を与える
→プログラム本文のアップと併せて、
「これは何のためのプログラムか」
「このプログラムでどういう処理をしたいのか」
「このプログラムが望ましい動作をしたときの処理プロセス」
「このプログラムを実行している今、どういうエラーや問題が起きているのか」 といった情報をAIに与えます。
うまくいけば、AIはこちらの要求の観点に立った改善案を挙げてくれます
- どういう風に回答してほしいかを指示する
→回答の仕方を指示せずに専門的な質問を投げると、容赦なく専門用語を多用し難しい言い回しをしてくる可能性があります。
例えば「初心者向けに難しい言葉を避け、わかりやすく説明してください」等の指示を盛り込むと、AIは回答の仕方を工夫してくれます
- マニアックな情報は期待しない
→AIは有名な情報・サンプルが多い情報には強いですが、マニアックな情報やユーザー数が少なくネットに情報がないもの
…例えばレアな部品の仕様やライブラリの使い方といった質問には答えられない可能性が高いです
- 課金する
→講師はChatGPTしか使っていないので他のAIはわかりませんが、
ChatGPTの場合課金して利用できるツールと非課金ツールとでは精度に天地の差があります。
真にAIの力を借りたければ課金しましょう
2024年6月現在のAIはまだ完璧な存在とは言い難いです。
不正確な情報や最新ではないソースをもとにした情報、こちらの指示にそぐわない回答をすることも多く、人の目の校正やチェックを前提に使うべきであると強く思います。
ちなみにAIにプログラムを書いてもらうこともできます。
こちらもそのままでは使えないことも多いですが、プログラムを書き始める時の“たたき台”として使ったり、有名ライブラリの使い方を訊いてみるといった用途で非常に便利です。
ArduinoIDE・よくあるエラーメッセージ集
ArduinoIDEを使っているとお友達になれるエラーメッセージのうち、頻出と思しきものを集めました。
エラーメッセージが出て困ったときにはとりあえずここを確認してみるといいかもしれません。
PCとArduinoの接続に問題がありそう
No DFU capable USB device available
Failed uploading: uploading error: exit status 74
「USBデバイスがないためプログラムの書き込みに失敗しました」のエラーです。
ArduinoとPCの接続に何らかの不具合があるようです。例えば…
- ArduinoがUSBケーブルでPCに繋がっていない(大抵は挿し忘れ)
- 使っているUSBケーブルがデータ転送機能のないケーブル(=充電専用のケーブル)だ
- USBケーブルが壊れている → USBケーブルを“確実にデータ転送機能があるもので確実に壊れていないもの”に換え挿す
上3つに間違いがないことを確認してもまだエラーが出る場合は、下記を試してみてください。
- Arduinoがプログラム書き込み可能な状態になっていない
→Arduinoのリセットボタンを1回押してからリトライ、ダメなら2回押してからリトライ
- Arduinoのドライバが正しくインストールされていない
→こちら(https://roboin.io/article/2023/12/11/arduino-dfu-error/)をご参照ください
R4でよく起きるエラー
Failed to retrieve language identifiers
error get_status: LIBUSB_ERROR_TIMEOUT
Failed uploading: uploading error: exit status 74
R4でよく起きるエラーです。直前にシリアルモニタを使っていた時に起きる印象があります。
まずシリアルモニタを閉じ、Arduinoのリセットボタンを1回押してから再度書き込みリトライ。
それでもだめなら今度は2回素早く押してからリトライしてください。
「;」忘れてますよ
Compilation error: expected ';' before '}' token
非常によくあるエラーです。「“;”を付け忘れています」の意です。
;を付け忘れている行のすぐ後ろの行がうっすら赤く色付けされます。
もしくはメッセージウィンドウに何行目にエラーがあるかが表示されているので、それを頼りに修正してください。
(図では切れていますが、スケッチのあるフォルダ番地の後に(スケッチの名前).ino:XX:の(XX)が;を付け忘れている行の後ろの行です)
「( )」とじ忘れてますよ
「{ }」とじ忘れてますよ
Compilation error: expected '}' at end of input
「{ }をとじ忘れています」の意です。
whileやifなどをたくさん使っていると、どの“ { “がどの” } “に対応しているのか、こんがらがってきます。
間違って必要な” } “を消してしまったりすると、だいたいこのエラーが出ます。
コンパイラは対応がおかしそうな{ }の箇所を機械的に判定して示しますが、必ずしも正確ではないこともあるので頑張って自力で特定して直してください。
変数・関数・インスタンス周りに何らかの問題があるか、余計な文字がどこかにある
Compilation error: 'XXXXXXXXXX' was not declared in this scope
「(このスコープの中で)変数'XXXXXXXXXX'は宣言されていません」の意です。
このエラーが出てくる主なシーンは下記のようなものかと思います。
- 変数を宣言しないで使おうとした
→まさしくこのエラー文・どストライクのミスです。
自分の頭の中で「こういう変数を使うぞ」と思って使う文は書いたものの、その変数をプログラム内で宣言するのを忘れてたり、
プログラムの増改築を繰り返すうちに必要な変数の宣言文を消去やコメントアウトなどしたりして発生します。宣言してください。
- ある変数を、その変数が使える範囲(スコープ)の外側で使おうとした
→変数には使える範囲(スコープ)があります。
例えばif文の{ }内で宣言した変数はその{ }の中でしか使えません。
このスコープを誤って変数を使おうとした時にもこちらのエラーが出ます。
(参考:https://atcoder.jp/contests/apg4b/tasks/APG4b_i)
- 関数名や変数名に書き間違いがある
→やや変化球的なエラーです。
上の図がまさしくその例で、本来digitalWriteと書くべき関数を「d」が抜けてigitalWriteと書いてしまっているためエラーが出ています。
ちなみに(この図では切れてますが)このエラー文の上から3行目には
note: suggested alternative: 'digitalWrite'
つまり「'digitalWrite'じゃないっスかね?」と書かれています。親切ですね。
- インスタンスを生成せずに関数を使おうとした
→変数と同じように、インスタンスも生成しなければ使えません。生成してください。
- プログラムに関係ない文字を誤入力している
→タイプミスや消し忘れなどでプログラムのどこかに余計な文字があり、
それをIDEが「これは宣言されていない変数か何かかな?」という形で教えてくれます。
プログラムに不要な文字は消すか、コメントアウトしてください。
関数の呼び出しができないよ!
Compilation error: no matching function for call to 'XXXXXXXXXX'
「関数の呼び出しができません」というエラーです。
様々な原因により生じますが、だいたい引数の何かに問題があることが多いです。
- 引数が足りない
→引数は(一部の例外を除いて基本的に)省略不可です。
例えば上の図のdigitalWrite関数では、「ピン番号」と「出力状態」の2つの引数を書いておく必要があります。
しかし上の図では「出力状態」しか書いていないため、動作しません。
- 引数の型が間違っている
→例えばint型で指定しなければいけない引数に文字列を入れたりなどすると発生します。
UARTが定義されていませんが?
undefined reference to `_UARTXX_'
「UARTXXが定義されていません」というエラーです。
エラーメッセージの最終行だけでは何が何だかわからないのですが、上から2行目の末尾からこのメッセージが読み取れます。
ソフトウェアシリアルを使う場合は、ライブラリのインクルードやインスタンスの生成といった手続きが必要です。
ライブラリ周りがあやしいです
Compilation error: 'XXXXXXXXXX' does not name a type
ライブラリ周りでトラブルが起きている可能性が高いです。
- ライブラリをそもそもインストールしていない
→ネットからよさげなコードを部分的にコピペしてきたときに特に起こりがちです
そのコードがどんなライブラリを使っているのか、そのライブラリは自分のIDEにインストールされているのかを確認しましょう
- ライブラリをインストールしたけどインクルードしていない
→ライブラリはインクルードしなければ使えません。
あるライブラリの関数をコード内で使おうとして、しかしそのライブラリがインクルードされていないと当然ながらエラーが起きます
ちなみに上の図はServoライブラリをインクルードせずにServoインスタンスを宣言しようとして起きたエラーです
- ライブラリの名前が間違っている
→「ライブラリをインクルード」からではなく手打ちでインクルード文を打った場合や、ネットから文をコピペした場合によく起きます
- よく似た名前のまったく違うライブラリをインクルードしている
→ライブラリ周りで非常に厄介なのが、ある機能のライブラリは世の中にたくさん存在していて、
それらのライブラリ名やインクルード文が非常に似通っている場合があるということです
例えばWebSocketを扱うライブラリ一つ取っても
「ArduinoWebSockets(インクルードするのは<ArduinoWebsockets.h>)」
「WebSockets(<WebSocketsClient.h>)」
「mWebSockets(<WebSocketClient.h>)」 など、似たような名前が無数にあり頭痛がします
複数の似たようなライブラリをインストールする場合は使い分けに注意しましょう。
- まったく違うライブラリの関数を使っている
→一つ上の間違いと併せてよくあるミスです。
ネットから他の人のコードをコピペした時や、複数の似たようなライブラリを使い分けてる場合は注意しましょう
- インスタンス生成の文に何らかの間違いがある
→ライブラリのインストールやインクルードに問題がない場合、インスタンス生成の文に何らかの問題
(綴り間違いやそもそも正しくない名称や書き方をしている 等)があるかもです
呼び出しを指示された関数が存在しないのですが?
Compilation error: 'class YYYYYY' has no member named 'XXXXXXXXXX'
殆どの場合「呼び出しを指示された関数が存在しないのですが?」という解釈でOKです。
'XXXXXXXXXX'が存在しない関数の名前、'class YYYYYY'はその関数が属している大元の機能です。
画像の例では
「あなたが呼び出しを命令したServoの関数”attachchchachchch”なんてものは、Servoで使える関数の中に存在しないのですが?」
と指摘されています。
- 関数の綴りが間違っている
→凡ミスです。怒らないので修正してください。
ちなみに関数はアルファベットの大文字/小文字を間違えるだけでエラー対象となります。
digitalWriteをDigitalwriteと書いたりするだけでNGです。まあたまに間違えます。
- そのバージョンのライブラリには存在しない関数を使おうとしている
→ややレアケースですが、覚えておくと損はないです。むしろ中級者以上は頭の隅に置いておいた方がいいです。
ボード情報をインストールしたときに付属でついてくるライブラリの関数はそのボードのバージョン、
ライブラリ内の関数はそのライブラリのバージョンによって、存在したりしなかったりすることがあります。
例えば、世の中の辞書は改訂ごとに新しい単語が追加されたり、使われなくなった古い言葉が削除されたりしますよね?
それと同じように、ライブラリも更新によりバージョンが変わるタイミングで、
古い関数に修正が加えられて名前や引数などの書き方が変わったり、その関数自体が消滅したり、
或いは新しい関数が追加されることがあります。
よくあるトラブルは「ネットのサンプルコードと同じライブラリを入れコードをコピペしたがこのエラーが出る」というものです。
ネットのコードが古い場合、そのライブラリの現行のバージョンでは既に存在しない関数が使われている場合があります。
ライブラリのバージョンを下げるか、廃止された関数の代わりになる関数が現行バージョンにあれば、それで書き直します。
ちなみにボードのバージョンはボードマネージャから、ライブラリはライブラリマネージャから、
ダウングレード/アップグレードしたいボード名(ライブラリ名)を入れて、
ドロップダウンから任意のバージョンを選択、「インストール」を押すことでダウン/アップグレードができます。
変数、とにかく変数。たぶん型周りがなんかおかしい
Compilation error: cannot convert 'XXXXX' to 'YYYYY' in assignment もしくは
Compilation error: invalid conversion from 'XXXXX' to 'YYYYY' [-fpermissive] もしくは
Compilation error: no match for 'operator=' (operand types are 'XXXXX' and 'YYYYY') 等
変数周りに何らかの問題があるかもしれません。
例として3種類の文を上げましたが、これ以外の文もあるかもしれません。
どんな文であってもメッセージのどこかで変数の型についての言及があれば、変数の型にまつわるミスであることが多いです(適当)
- その変数の型には入れられない値を代入しようとした
→例えばint型の変数に「“ごっつあんです”」と入れようとしたり、
Stringに「775.44」という数値を入れようとしたりすると、もちろんエラーが出ます。
- 変数の型が違うもの同士を演算しようとした
→上の画像では整数しか入れられないint型の変数「muri」に、文字列を格納した「dame」とint型の「akan」を足して入れようとしています。
しかしintとStringを足し算することはできないので、エラーが起きています。
但し、変数の型が違っても演算できる場合もあります。例えば小数点型のfloatに整数型のintを足すことは可能です
(上の図の例だとakan = akan + muri;は399.99で成立する)
逆にintにfloatを足すことも(両者は数値型なので)可能です。
但し、intは整数型なので小数点以下の数値は格納できず切り捨てられます(muri = muri + akan;は399となる)
これらの場合、機械的には正しい動作をしているのでコンパイルエラーは起きません。
もしmuri = muri + akan;の結果が小数点を保持していないことでプログラムが意図した処理をできなくなるのであれば、
自力でそれに気付いて修正する必要があります。
- 変数の型変換がうまくいっていない
→既に宣言されている変数の型を変換し、別の型にすることも可能です。
但しその記法が間違っていたり、変換後の型にそぐわない演算をしようとした場合はエラーが出ます。