
マイコンでスイッチ入力を監視しているはずなのに、押した瞬間を取りこぼしてしまう。
モータ制御中にセンサ異常を検出したいのに、通常処理が長くて反応が遅れる。
このような「今すぐ対応したい出来事」に対して、CPUの処理を一時的に中断して優先処理へ切り替える仕組みが割り込み処理です。
割り込み処理を理解すると、スイッチ入力、タイマー周期処理、通信受信、異常停止などを、ポーリングより確実かつ素早く扱えるようになります。
本記事では、割り込み処理とは何かという基礎から、ポーリングとの違い、フローチャートの書き方、C言語・Arduino・ESP32・PICでの使用例、実務での注意点までを体系的に解説します。
- 1. 割り込み処理とは何か
- 2. ポーリングとの違いと使い分け
- 3. 割り込み処理の仕組み
- 4. 割り込み処理の種類
- 5. フローチャートの書き方
- 6. C言語での割り込み処理の書き方
- 7. Arduino・ESP32・PICでの使用例
- 8. 実務でのメリットと注意点
- 9. Python・VBAの割り込み的処理
- 10. よくある質問
- 11. まとめ
1. 割り込み処理とは何か
割り込み処理とは、プログラムの通常処理を一時的に中断し、発生したイベントに対応する処理を先に実行する仕組みです。
英語では、割り込みを interrupt、割り込み処理を interrupt handling または interrupt processing と呼びます。
たとえば、メイン処理で温度表示を更新している最中に、非常停止スイッチが押されたとします。
このとき、表示更新が終わるまで待ってから停止処理に入るのではなく、現在の処理を一時停止して、非常停止の処理へすぐに移るのが割り込み処理です。
割り込み処理のイメージ
日常業務でたとえるなら、通常作業中に電話が鳴ったときの対応に近いです。
資料作成をしている途中でも、重要な電話が来たらいったん手を止め、電話対応を済ませてから資料作成に戻ります。
このとき、資料作成が「通常処理」、電話が「割り込み要因」、電話対応が「割り込み処理」に相当します。
| 用語 | 意味 | 具体例 |
|---|---|---|
| 通常処理 | 普段から繰り返し実行しているメイン処理 | 表示更新、演算、状態監視 |
| 割り込み要因 | CPUに優先対応を要求する出来事 | スイッチ入力、タイマー満了、通信受信 |
| 割り込みサービスルーチン | 割り込み発生時に実行される専用処理 | フラグセット、カウンタ更新、受信データ退避 |
| 復帰 | 割り込み処理後に元の通常処理へ戻ること | 中断していたループ処理の続きから再開 |
割り込み処理の重要なポイントは、通常処理を完全に捨てるのではなく、一時停止して後で戻ることです。
このため、割り込み処理終了後には、原則として割り込み前に実行していた処理の続きへ復帰します。
関連記事
2. ポーリングとの違いと使い分け
割り込み処理を理解するうえで、必ず比較されるのがポーリングです。
ポーリングとは、CPUが一定間隔で入力や状態を確認しに行く方式です。
たとえば、スイッチが押されたかどうかをメインループの中で何度も確認する処理は、ポーリングに分類されます。
一方、割り込み処理では、スイッチが押された瞬間にハードウェア側からCPUへ通知が入り、専用の処理へ移ります。
ポーリングは「見に行く」、割り込みは「呼ばれる」
両者の違いは、情報を取りに行く方向で考えると理解しやすいです。
ポーリングはCPUが定期的に「変化はありますか?」と見に行く方式です。
割り込みは外部イベントが発生したときに「今すぐ対応してください」とCPUを呼び出す方式です。
| 項目 | ポーリング | 割り込み処理 |
|---|---|---|
| 動作 | CPUが定期的に状態を確認する | イベント発生時にCPUへ通知される |
| 応答性 | 確認周期に依存する | 発生直後に反応しやすい |
| プログラムの見通し | 比較的単純 | 処理順序が複雑になりやすい |
| 向いている用途 | 遅くてもよい状態監視 | 取りこぼしたくない入力、周期処理、通信 |
| 注意点 | 周期が長いと見逃す | ISRを長くすると全体が不安定になる |
どちらを使えばよいか
結論から言うと、すべてを割り込みにすればよいわけではありません。
LED表示の更新や低速な温度監視のように、数十msから数秒単位で十分な処理はポーリングでも問題ありません。
一方で、通信受信、エンコーダパルス、タイマー周期処理、非常停止入力のように、タイミングが重要な処理は割り込みが有効です。
実務では、時間に厳しい処理だけを割り込みにし、重い処理はメインループ側で実行する構成が基本です。
3. 割り込み処理の仕組み
割り込み処理は、単に別の関数を呼び出しているだけではありません。
CPU内部では、現在の実行状態を退避し、割り込み先の処理へジャンプし、終了後に元の処理へ戻るという手順が行われます。
基本的な流れ
一般的な割り込み処理の流れは、以下のように整理できます。
- 通常処理を実行している
- スイッチ入力やタイマー満了などの割り込み要因が発生する
- CPUが現在の実行位置や状態を一時的に退避する
- 割り込みサービスルーチンへ移動する
- 必要な処理を実行する
- 割り込み要因のフラグをクリアする
- 退避していた状態を復元し、通常処理へ戻る
ここで重要なのが、割り込み処理は「別世界で勝手に動く処理」ではないという点です。
CPUは基本的に一つの命令列を順番に実行しており、その実行順序を一時的に切り替えているだけです。
割り込みベクタとISR
マイコンでは、どの割り込みが発生したら、どの関数を実行するかがあらかじめ決められています。
この対応表のようなものを割り込みベクタと呼びます。
割り込み発生時に実行される関数は、ISR(Interrupt Service Routine)と呼ばれます。
C言語の開発環境では、専用の記法や属性を付けて「この関数は割り込み用です」とコンパイラに伝えることが一般的です。
割り込み処理終了後に元へ戻る理由
割り込み発生時、CPUはプログラムカウンタや一部のレジスタ情報を退避します。
プログラムカウンタとは、次に実行する命令の場所を示す情報です。
割り込み処理が終わると、この退避情報を復元するため、割り込み前に中断していた通常処理の続きへ戻れます。
この復帰処理が正しく行われることで、メイン処理と割り込み処理が見かけ上は同時に動いているように見えます。
関連記事
4. 割り込み処理の種類
割り込み処理には、発生源や用途によっていくつかの種類があります。
名前だけを見ると難しく感じますが、実務では「何をきっかけに処理を止めるか」で分類すると理解しやすくなります。
外部割り込み
外部割り込みは、マイコンの入力端子に入った信号変化をきっかけに発生する割り込みです。
スイッチ入力、センサ信号、エンコーダのパルス入力などが代表例です。
たとえば、非常停止スイッチが押された瞬間にモータ停止フラグを立てる処理は、外部割り込みの典型的な使い方です。
タイマー割り込み
タイマー割り込みは、一定時間ごとに発生する割り込みです。
1msごとの制御周期、10msごとのスイッチ監視、100msごとの表示更新など、周期的な処理に使われます。
Arduinoで「一定周期で処理したい」、ESP32で「タイマーごとにフラグを立てたい」といったニーズは、このタイマー割り込みに関係します。
通信割り込み
通信割り込みは、UART、SPI、I2C、CANなどの通信でデータを受信したときに発生する割り込みです。
通信は相手側のタイミングでデータが届くため、メインループの都合だけで確認していると取りこぼす可能性があります。
そこで、受信した瞬間に割り込みでデータをバッファへ退避し、後でメイン処理が読み出す構成にします。
ソフトウェア割り込み
ソフトウェア割り込みは、ハードウェア信号ではなく、プログラムから意図的に発生させる割り込みです。
OSのシステムコールやタスク切り替えなどで使われることがあります。
組み込み初心者が最初に扱う頻度は高くありませんが、割り込みは外部入力だけでなくソフトウェアからも発生させられると理解しておくとよいです。
| 種類 | 発生きっかけ | 用途例 |
|---|---|---|
| 外部割り込み | 端子の立上り、立下り、レベル変化 | スイッチ、センサ、エンコーダ |
| タイマー割り込み | タイマーのカウント一致、オーバーフロー | 周期制御、時間計測、チャタリング処理 |
| 通信割り込み | 受信完了、送信完了、エラー発生 | UART受信、CAN通信、SPI転送 |
| ソフトウェア割り込み | プログラム命令による発生 | OS機能呼び出し、タスク切替 |
5. フローチャートの書き方
割り込み処理は、通常処理と同じ一本線のフローチャートだけで描くとわかりにくくなります。
なぜなら、割り込みはメイン処理の途中に横から入ってくる処理だからです。
フローチャートを書くときは、メイン処理の流れと割り込み処理の流れを分けて表現するのが基本です。
メイン処理側の書き方
メイン処理側では、初期化、割り込み許可、メインループ、フラグ確認、実処理という流れを描きます。
割り込みの中で重い処理を直接行わず、フラグだけを立てる設計にすると、フローチャートも整理しやすくなります。
開始
↓
初期化
↓
割り込み設定
↓
割り込み許可
↓
メインループ
↓
割り込みフラグあり?
├─ あり:対応処理を実行 → フラグクリア
└─ なし:通常処理を継続
↓
メインループへ戻る
割り込み処理側の書き方
割り込み処理側では、割り込み発生、要因確認、最低限の処理、割り込みフラグクリア、復帰という流れを描きます。
フローチャート上でも、割り込み処理は短くまとめるのがポイントです。
割り込み発生
↓
割り込み要因を確認
↓
必要な情報を退避
↓
メイン処理へ渡すフラグをセット
↓
割り込み要因フラグをクリア
↓
割り込み前の処理へ復帰
「割り込み処理終了後に戻る」を図に入れる
検索ニーズとして多いのが、「割り込み処理の終了後にどうなるのか」という疑問です。
フローチャートでは、ISRの最後からメインループの先頭へ単純に戻すのではなく、「割り込み前の処理へ復帰」と書く方が正確です。
実際には、割り込みが発生した命令位置やCPU状態をもとに、元の通常処理へ戻ります。
6. C言語での割り込み処理の書き方
C言語で割り込み処理を書く場合、通常の関数と同じ感覚で長い処理を入れると失敗しやすくなります。
基本方針は、ISRでは最小限の処理だけを行い、重い処理はメインループへ任せることです。
基本パターン:ISRでフラグを立てる
次の例は、タイマー割り込みが発生したらフラグを立て、メインループ側で実処理を行う構成です。
実際のISR宣言方法は、使用するマイコンやコンパイラによって異なります。
#include <stdint.h>
#include <stdbool.h>
volatile bool timer_flag = false;
volatile uint32_t timer_count = 0;
void timer_interrupt_handler(void)
{
/* 割り込み要因フラグをクリアする処理をここに書く */
timer_count++;
timer_flag = true;
}
int main(void)
{
/* クロック、GPIO、タイマー、割り込みの初期化 */
while (1) {
if (timer_flag) {
timer_flag = false;
/* ここで周期処理を実行する */
/* 例:センサ値の取得、状態更新、LED表示など */
}
/* その他の通常処理 */
}
}
volatileを付ける理由
割り込み処理とメイン処理で共有する変数には、基本的に volatile を付けます。
volatile は、コンパイラに対して「この変数はプログラムの見た目以外の場所で変更される可能性がある」と伝える指定です。
割り込みで変更されるフラグに volatile がないと、最適化によってメインループ側が変化を正しく見ない場合があります。
そのため、ISRと通常処理で共有するフラグ、カウンタ、受信バッファ管理変数には注意が必要です。
ISRに書かない方がよい処理
割り込み処理の中では、時間がかかる処理や待ち処理を避けます。
特に、次のような処理はISRに直接書くとトラブルの原因になります。
printfなどの重い出力処理- 長いループ処理
- 通信完了を待つ処理
- ファイル操作や動的メモリ確保
- ミリ秒単位の待ち時間を作る
delay系処理
ISRが長くなると、他の割り込みが遅れたり、メイン処理が進まなくなったりします。
実務では、ISRは「記録する」「フラグを立てる」「最低限の退避をする」程度に留めるのが安全です。
7. Arduino・ESP32・PICでの使用例
検索ニーズでは、Arduino、ESP32、PICなど、具体的なマイコン名と一緒に割り込み処理が調べられています。
ここでは、考え方を理解するための代表例を示します。
実際に使用するときは、ボード、開発環境、ライブラリの仕様に合わせて調整してください。
Arduino:スイッチ入力の割り込み例
Arduinoでは、外部割り込みに attachInterrupt() を使うことが多いです。
以下は、スイッチ入力でフラグを立てる最小構成の例です。
volatile bool switch_flag = false;
void switchInterrupt()
{
switch_flag = true;
}
void setup()
{
pinMode(2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(2), switchInterrupt, FALLING);
}
void loop()
{
if (switch_flag) {
switch_flag = false;
/* スイッチが押された後の処理 */
}
/* 通常処理 */
}
ここで注意したいのは、スイッチにはチャタリングがあることです。
機械接点は一度押しただけでも短時間にON/OFFが複数回揺れるため、割り込みが連続発生することがあります。
対策として、タイマーで一定時間後に確定判定する、メインループ側で時間差を見て無視するなどの処理を入れます。
Arduino:タイマー割り込みの考え方
Arduinoでタイマー割り込みを使う場合は、使用するボードやライブラリによって書き方が変わります。
重要なのは、一定周期ごとにISRを呼び、ISR内ではフラグを立てる程度にすることです。
たとえば、1msごとにカウンタを増やし、10msごとにスイッチ処理、100msごとに表示更新をする構成が考えられます。
ESP32:外部割り込みの例
ESP32では、割り込み関数に IRAM_ATTR を付ける例がよく見られます。
これは、割り込み処理を高速に実行できるメモリ領域へ配置するための指定です。
volatile bool input_flag = false;
void IRAM_ATTR inputInterrupt()
{
input_flag = true;
}
void setup()
{
pinMode(25, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(25), inputInterrupt, FALLING);
}
void loop()
{
if (input_flag) {
input_flag = false;
/* 入力検出後の処理 */
}
}
ESP32は処理能力が高い一方、Wi-FiやBluetoothなどの処理も内部で動作します。
割り込み内で重い処理を行うと、システム全体の動作に影響する可能性があるため、基本はフラグ渡しに留めます。
PICマイコン:割り込みの考え方
PICマイコンでは、外部割り込み、タイマー割り込み、UART割り込みなどをレジスタ設定で有効化します。
代表的な流れは、割り込み許可ビットを設定し、割り込みフラグを確認し、要因に応じた処理を行い、最後にフラグをクリアする形です。
PICでは、割り込み要因フラグを消し忘れると、同じ割り込みへ何度も入り続けることがあります。
そのため、ISR内で「どの要因で呼ばれたか」を確認し、対応するフラグを確実にクリアすることが重要です。
関連記事
8. 実務でのメリットと注意点
割り込み処理の最大のメリットは、イベントに対する応答性を高められることです。
ただし、使い方を誤ると、原因を追いにくい不具合を生みやすい仕組みでもあります。
メリット1:入力の取りこぼしを減らせる
ポーリングでは、確認していない瞬間に短いパルスが入ると見逃す可能性があります。
割り込みを使えば、立上りや立下りの瞬間をきっかけに処理できるため、短い信号を扱いやすくなります。
エンコーダのパルスカウント、流量センサのパルス入力、近接センサの高速検出などで有効です。
メリット2:一定周期の処理を作りやすい
タイマー割り込みを使うと、メインループの処理時間に左右されにくい周期処理を作れます。
たとえば、1ms周期で制御演算、10ms周期で入力判定、100ms周期で表示更新といった階層的な時間管理が可能です。
制御周期を一定に保ちたい場合、単純なメインループ内の待ち時間よりもタイマー割り込みの方が扱いやすくなります。
注意点1:割り込み処理を長くしない
割り込み処理が長いと、その間は他の処理が待たされます。
特に、優先度の低い割り込みやメイン処理が遅れ、結果として制御周期の乱れや通信取りこぼしにつながります。
ISRは短くし、複雑な処理はメイン側へ渡す設計にします。
注意点2:共有変数の競合に注意する
メイン処理と割り込み処理が同じ変数を読み書きする場合、途中で割り込まれることで値が不整合になることがあります。
8bitマイコンで16bitや32bitのカウンタを読み出す場合などは、読み出し途中に割り込みで値が変わる可能性があります。
このような場合は、一時的に割り込みを禁止して読み出す、コピー用変数へ退避するなどの対策が必要です。
注意点3:優先順位を決める
複数の割り込みを使う場合、どの処理を最優先にするかを決める必要があります。
非常停止、過電流検出、通信受信、表示更新が同じ優先度では、重要な処理が遅れる可能性があります。
実務では、安全に関わる割り込みを最優先とし、表示やログのような処理は低優先度にします。
| トラブル | 主な原因 | 対策 |
|---|---|---|
| 割り込みが発生しない | 割り込み許可忘れ、端子設定ミス、エッジ設定ミス | 初期化順序、許可ビット、入力モードを確認する |
| 同じ割り込みに入り続ける | 割り込み要因フラグのクリア忘れ | ISR内で要因確認後にフラグを確実に消す |
| スイッチ入力が何度も反応する | チャタリング | 時間判定、RCフィルタ、ソフトウェアデバウンスを入れる |
| 処理が不安定になる | ISRが長い、共有変数の競合 | フラグ渡し、排他制御、割り込み禁止区間を設計する |
| 通信を取りこぼす | 受信バッファ不足、ISR遅延 | リングバッファ化、優先度調整、処理時間短縮を行う |
関連記事
9. Python・VBAの割り込み的処理
検索ニーズには、「Python 割り込み処理 キーボード」や「VBA 割り込み処理」のような言葉も含まれます。
ただし、マイコンのハードウェア割り込みと、PC上のPythonやVBAで扱うイベント処理は厳密には別物です。
考え方としては似ていますが、CPUの割り込みベクタへ直接入る処理ではなく、OSや実行環境が提供するイベント処理として理解する方が正確です。
Python:キーボード入力による中断
Pythonでは、実行中のプログラムをキーボードで止める代表例として KeyboardInterrupt があります。
通常は、コンソール上で Ctrl + C を押すと発生します。
try:
while True:
print("処理中...")
# ここに繰り返し処理を書く
except KeyboardInterrupt:
print("キーボード操作で終了しました")
これはマイコンの外部割り込みとは異なりますが、「通常処理の途中で特定イベントを受け、終了処理へ移る」という意味では割り込み的な考え方です。
VBA:イベントプロシージャの考え方
VBAでは、ボタンを押したとき、セルの値が変わったとき、ブックを開いたときなどにイベントプロシージャが実行されます。
たとえば、Excelでセル変更をきっかけに処理を走らせる場合は、ワークシートイベントを使います。
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Range("A1")) Is Nothing Then
MsgBox "A1セルが変更されました"
End If
End Sub
これもハードウェア割り込みではありませんが、ユーザー操作や状態変化に応じて処理が呼ばれる点では、割り込み処理に近い発想です。
ただし、VBAのイベント内で重い処理を走らせるとExcel操作が固まることがあります。
マイコンのISRと同じように、イベント処理も必要以上に長くしない設計が重要です。
10. よくある質問
Q1. 割り込み処理とは簡単に言うと何ですか?
通常処理を一時停止して、優先度の高い出来事へ先に対応する仕組みです。
スイッチ入力、タイマー周期、通信受信など、発生タイミングを逃したくない処理で使います。
Q2. 割り込み処理とポーリングの違いは何ですか?
ポーリングはCPUが状態を定期的に見に行く方式です。
割り込み処理は、イベント側からCPUへ通知して処理を呼び出す方式です。
応答性を重視する場合は割り込み、処理の単純さを重視する場合はポーリングが向いています。
Q3. 割り込み処理終了後はどこに戻りますか?
原則として、割り込みが発生する前に実行していた通常処理の続きへ戻ります。
CPUは割り込み発生時に実行状態を退避し、割り込み終了時にその状態を復元します。
Q4. C言語の割り込み処理でvolatileは必要ですか?
ISRとメイン処理で共有する変数には、基本的に volatile を付けます。
割り込みで変化するフラグやカウンタを、コンパイラ最適化によって見落とさないようにするためです。
Q5. 割り込み処理の中でdelayやprintfを使ってもよいですか?
原則として避けるべきです。
割り込み処理が長くなると、他の割り込みやメイン処理が遅れ、システム全体の不安定化につながります。
11. まとめ
割り込み処理とは、通常処理を一時的に中断し、スイッチ入力、タイマー満了、通信受信などのイベントへ優先的に対応する仕組みです。
ポーリングが「CPUから見に行く方式」であるのに対し、割り込みは「イベント側からCPUを呼び出す方式」と考えると理解しやすくなります。
実務では、すべてを割り込みにするのではなく、応答性が必要な処理だけを割り込みにすることが重要です。
C言語やArduino、ESP32、PICで割り込みを使う場合は、ISRを短くし、共有変数には volatile を付け、重い処理はメインループ側で実行します。
割り込み処理は、正しく使えば取りこぼしを減らし、一定周期の制御を安定させる強力な仕組みです。
一方で、長すぎるISR、フラグクリア忘れ、共有変数の競合、優先度設計の甘さは、不具合の原因になります。
まずは「ISRではフラグを立てるだけ」「実処理はメイン側で行う」という基本形から始めると、安全で見通しのよいプログラムを作りやすくなります。