「割り込み」の使い方とデバッグの基礎知識
組み込みソフトウェア開発において、「割り込み」は最も重要な基礎技術の一つです。
センサからのデータ受信、タイマーの満了、ボタンの押下など、外部からの信号に即座に対応するために、割り込みの活用は欠かせません。
本記事では、これからスキルアップを目指す組み込みソフトウェアエンジニアの方に向けて、割り込みの基本からデバッグのポイントまで、分かりやすく解説します。
「割り込み」とは何か? 「ポーリング」との違い
割り込み(インタラプト:Interrupt)とは、CPUが実行中の処理を一時的に中断して、優先度の高い別の処理を実行する仕組みのことです。「割り込む」という名の通り、今行っている作業に別の作業が「割り込んで」くるイメージです。
身近な例でいえば、スマートフォンの通知はまさに割り込みです。スマホでゲームをしている最中に、メッセージが届くと画面上部に通知が表示されます。この瞬間、スマホは「ゲームの処理」を一時中断して「通知を表示する処理」を実行し、その後ゲームに戻ります。
割り込みを理解するには、よく対比される「ポーリング(Polling)」という方式を知っておくと良いでしょう。ポーリングとは、「定期的に状態を確認しに行く方式」を指します。
例えば、洗濯機が洗濯を終えたかどうかを知りたい場合を考えてみましょう。
- ポーリング方式: 5分おきに洗濯機の様子を見に行く。洗濯が終わっていなければ、また5分後に確認しに行く。これを繰り返します。
- 割り込み方式: 洗濯機が終了したら「ピーッ」と音で知らせてくれる。人はその音を聞いてから洗濯機に向かえば良く、それまでは他のことをしていられます。
ポーリング方式では、CPUが定期的に状態をチェックする必要があり、無駄な処理が発生します。一方、割り込み方式では、イベントが発生したときだけCPUが動作するため、効率的です。また、割り込みは即座に対応できるため、応答性も高くなります。一方で、割り込みを多用したプログラムは動作が複雑になり、予期しない不具合や、デバッグの難しい不具合を埋め込んでしまう確率も高くなります。
組み込みシステムでは、限られたCPUリソースを有効活用し、かつリアルタイム性(決められた時間内に処理を完了すること)を確保するために、割り込みが多用されます。
割り込みが発生したら何が起きるのか? CPUの動きを理解する
割り込みが発生したとき、CPUの内部では次のような処理が行われます。
- 1. メイン処理の中断 CPUは現在実行中の命令を完了させた後、メイン処理を一時停止します。「命令の途中」では止まらず、必ず一つの命令が終わってから停止します。
- 2. レジスタやフラグをスタックに退避 中断した時点でのCPUの状態(レジスタの値、プログラムカウンタ、ステータスフラグなど)を、スタック(一時保存用のメモリ領域)に保存します。これにより、後で元の処理に戻ったときに、中断した時点から正確に再開できます。
-
3. 割り込みハンドラの実行
CPUは、割り込みベクタテーブル(割り込みの種類ごとに実行すべき関数のアドレスが登録されているテーブル)を参照して、対応する割り込みハンドラ(ISR: Interrupt Service Routine)にジャンプします。
割り込みハンドラとは、割り込みが発生したときに実行される専用の関数のことです。例えば、タイマー割り込みが発生したら「タイマー割り込みハンドラ」が、通信データ受信の割り込みが発生したら「受信割り込みハンドラ」が実行されます。割り込みの種類ごとに異なった処理を行うため、ソフトウェアを実装する際には、それぞれの割り込みハンドラを割り込みベクタテーブルに登録(初期化処理)しておく必要があります。 -
4. スタックから復帰
割り込みハンドラの処理が終わると、スタックに退避しておいたCPUの状態を復元し、中断していたメイン処理に戻ります。メイン処理は、割り込みが発生したことを意識することなく、中断した場所から処理を続けます。
この一連の流れは、すべてハードウェアとソフトウェアが自動的に処理するようにプログラミングされる必要があります。
割り込みプログラムのデバッグのコツ
割り込みを使ったプログラムのデバッグは、通常のプログラムより難しいことがあります。よくある失敗と対処法を紹介します。
-
よくある失敗1:割り込みが2回連続で来る
症状:ボタンを1回押しただけなのに、割り込みハンドラが2回呼ばれてしまう。
原因:割り込みフラグのクリア忘れ。多くのマイコンでは、割り込みハンドラ内で「割り込みが発生しました」という記録を消去しないと、割り込みハンドラから戻った瞬間に再び同じ割り込みが発生します。
対処法:割り込みハンドラの最初で、必ず割り込みフラグをクリアします。 -
よくある失敗2:優先度の間違い
症状:重要な割り込みが遅れて処理される、または処理されない。
原因:割り込みの優先度設定が適切でない。複数の割り込みがある場合、優先度を正しく設定しないと、重要度の低い割り込みが先に処理されてしまいます。また、割り込みをプログラム上で動的に禁止する場合、割り込み許可をするまで、割り込みの処理は待たされてしまいます。
対処法:人の安全に関わる処理や、リアルタイム性が厳しい処理には高い優先度を設定します。逆に、データ処理など多少遅れても問題ない処理には低い優先度を設定します。どの割り込みが重要かを整理してから、優先度を決めることが大切です。 -
よくある失敗3:スタックオーバーフロー
症状:プログラムが予期せず暴走したり、リセットがかかったりする。
原因:関数の中で別の関数を呼び出し、その関数の中でさらに別の関数を呼び出すというように、関数の呼び出しが連鎖することを「ネスト」と呼びます。割り込みハンドラ内で大きな配列を宣言したり、関数呼び出しを深くネストしたりすると、スタック(一時保存用のメモリ領域)を使い果たしてしまいます。これをスタックオーバーフローと呼びます。
対処法:割り込みハンドラ内では、できるだけ変数を使わないようにします。どうしても大きなデータが必要な場合は、グローバル変数(プログラム全体のどこからでも読み書きできる共有の変数)として確保します。また、マイコンの設定でスタックサイズを適切に設定することも重要です。 -
よくある失敗4:「volatile」の付け忘れ
症状:フラグを立てているのに、メインループで検出されない。不思議なことに、デバッグ実行では動くのに、通常実行では動かないことがある。
原因:コンパイラの最適化により、変数の読み取りが省略されてしまっている。コンパイラは「この変数は変化しない」と判断して、毎回読み取らずに前回の値を使い回してしまいます。特にソフトウェアが感知しないところで、ハードウェアが勝手に外部メモリを変更するようなケースで起こります。
対処法:割り込みとメイン処理で共有する変数には、必ず「volatile」(ボラタイル)というキーワードを付けます。volatileとは、「この変数の値はいつ変わるか分からないので、毎回メモリから読み取ってください」とコンパイラに指示するキーワードです(コンパイラとは、人間が書いたプログラム=ソースコードを、コンピュータやマイコンが理解できる機械語に翻訳するソフトウェアのこと)。volatileをつけることにより、コンパイラに「この変数はいつ変わるか分からないので、毎回読み取ってください」と伝えることができます。 -
よくある失敗5:多重割り込みのネストが深すぎる
症状:システムが不安定になる、スタックオーバーフローが発生する。
原因:多重割り込み(割り込みハンドラ実行中に別の割り込みが発生すること)を許可している場合、割り込みが何段も重なってスタックを使い果たすことがあります。例えば、割り込みAの実行中に割り込みBが発生し、その実行中にまた割り込みCが発生する、というような状況です。
対処法:必要がなければ多重割り込みは禁止にします。多重割り込みを許可する場合は、優先度を慎重に設定し、何段まで重なることがあるかを事前に計算しておきます。
まとめ
割り込みは、組み込みソフトウェア開発において最も基本的で重要な技術です。外部からのイベントに即座に対応し、CPUリソースを効率的に使うために、割り込みは欠かせません。一方で優先度や即応性の要否により、割り込みを使わないほうが動作が安定する場合もあります。割り込みを使用することは限られたリソースで性能を出すために必要なことですが、一方で割り込みに対する処理は、組み込みソフトウェアの開発において、バグを産み出す大きな要因となります。そのため、その機能を割り込みで確認するか、ポーリングで確認するかについても、組み込みソフトウェアを設計する上では重要な要素です。
まずはシンプルな割り込み処理から始めて、徐々に複雑なシステムに挑戦されていくとよいでしょう。

