Arduinoの割り込み処理(interrupt)とは――loop()を止めずに即時検知する仕組み。
delay()で処理が止まっている間のボタン操作、取りこぼしていませんか。attachInterrupt()の基本構文から、volatile修飾子の必要性、実践的な実装手順までを解説します。
目次
01なぜloop()だけでは反応が遅れることがあるのか
結論から言えば、loop()の中でdigitalRead()を繰り返して状態を監視する「ポーリング」という方式には弱点がある。loop()の途中にdelay()や時間のかかる処理が入っていると、その間に発生したボタン操作やセンサーの変化を取りこぼしてしまう。割り込み(interrupt)を使えば、こうした処理の途中であっても、指定した条件が発生した瞬間に別の処理(割り込みサービスルーチン、ISR)へ即座に切り替えられる。
実例として、Arduinoの外部割り込みを使ってスイッチの状態変化を検知するプロジェクトで、割り込みを使ったにもかかわらずチャタリング(接点の跳ね返りによる誤検知)が発生し、ソフトウェア側の対策が必要になった事例がある。Arduinoの割り込み(interrupts)を使ってチャタリングが出た話【ソフトで解決】では、機械式スイッチを使う以上チャタリングは避けられない現象であり、割り込みを使う場合でも何らかの対策が必要になることが紹介されている。
編集部見解:割り込みは「反応の速さ」を手に入れられる一方で、扱いを誤ると原因の特定が難しいバグを生みやすい機能でもある。特にチャタリングや共有変数の扱いは、割り込みを初めて使う際につまずきやすいポイントだと考えられる。
― YOFUKASI AI編集部
本記事では、Arduinoにおける割り込みの基本構文から、ポーリングとの使い分け、そして緊急停止ボタンのような実践的な実装手順と注意点までを、初心者から中級者を対象に体系的に解説する。
02Arduinoにおける割り込みの基礎知識
割り込み(interrupt)とは、通常のプログラムの流れを一時的に中断し、あらかじめ指定しておいた処理を実行する仕組みのことである。この際に呼び出される関数は割り込みサービスルーチン(ISR: Interrupt Service Routine)と呼ばれる。
Arduinoでピンの電圧変化をきっかけに割り込みを発生させる(外部割り込み)には、attachInterrupt()関数を使う。公式リファレンスでは、Arduino公式 Language Reference「attachInterrupt()」において、この関数が割り込み発生時に呼び出すISRを指定する働きを持つと定義されている。トリガーの条件は、ピンがLOWのときに発生するLOW、ピンの値が変化したときに発生するCHANGE、LOWからHIGHへ変化したときに発生するRISING、HIGHからLOWへ変化したときに発生するFALLINGの4種類が定義済み定数として用意されている。
用語の整理
- ISR(割り込みサービスルーチン):割り込みが発生した際に呼び出される関数。引数を取らず、戻り値も返さない(void型)と定められている。
- 外部割り込み(external interrupt):特定のピンの電圧変化をきっかけに発生する割り込み。
- トリガーモード:割り込みを発生させる条件。LOW・CHANGE・RISING・FALLINGの4種類。
すべてのピンが外部割り込みに対応しているわけではない。使いたいピンが対応しているかどうかは、Arduino公式 Language Reference「digitalPinToInterrupt()」で確認できる関数を使って調べることができる。Arduino UNOではデジタルピン2番・3番の2本のみが外部割り込みに対応している点に注意が必要である。
割り込みを学ぶ前に、Arduinoの基礎文法やボタン回路の基本をまだ体系的に学んでいない場合は、先に基礎を押さえておくとこの先の理解がスムーズになる。
03割り込みの仕組みと技術的背景
外部割り込みを使う最小構成のスケッチは、次のような形になる。
const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}
void loop() {
digitalWrite(ledPin, state);
}
void blink() {
state = !state;
}
ここで注目すべきはvolatile byte stateという宣言である。volatileは変数修飾子の一種で、コンパイラに対して「この変数はレジスタにキャッシュせず、常にRAMから読み直すように」と指示するものである。公式リファレンスでは、Arduino公式 Language Reference「volatile」において、変数の値がコードのある区画の制御が及ばない何か(並行して実行されるスレッドなど)によって変更される可能性がある場合にvolatileを宣言すべきであり、Arduinoボードにおいてこれが起こり得る唯一の場所が割り込みサービスルーチンに関連するコード区画であると説明されている。
volatileを付け忘れた場合、コードは一見正常にコンパイルされ、実機でも「たまたま動いているように見える」ことがある。ところがコンパイラの最適化レベルやコードの微修正をきっかけに、突然ISR内での変更がloop()側に反映されなくなる、という再現性の低いバグとして表面化しやすい。原因が分かりにくいため、割り込みで使う共有変数には最初からvolatileを付けておく習慣が推奨される。
また、ISRの実行中はプログラムの他の割り込みが一時的に無効化される。手動で割り込みを禁止・再開したい場合はnoInterrupts()とinterrupts()を使う。Arduino公式 Language Reference「interrupts()」では、タイミングがシビアな処理区間の前後でこれらの関数を使い、一時的に割り込みを無効化・再開する使い方が紹介されている。
タッチセンサーを使ったインタラクティブな演出のように、割り込みに近い即時反応が求められるプロジェクトの実例は、以下の記事でも扱っている。
ArduinoとPythonで実現するインタラクティブ花火演出システムの実例はこちらの記事で紹介している。タッチセンサーの入力をきっかけに演出を切り替える構成の参考になる。
04ポーリング・外部割り込み・タイマー割り込みの比較
状態変化をどう検知するかは、用途によって最適な方式が変わる。以下に3つの選択肢を整理する。
| 項目 | A. ポーリング(loop内でdigitalRead) | B. 外部割り込み(attachInterrupt) | C. タイマー割り込み(ライブラリ利用) |
|---|---|---|---|
| 向いている用途 | 取りこぼしが許容できる一般的な入力 | 緊急停止など取りこぼし厳禁の入力 | 一定間隔での定期処理 |
| 反応速度 | loop()の実行間隔に依存 | ほぼ即時 | 設定した間隔で正確 |
| 実装の複雑さ | 低い | 中程度(volatile・チャタリング対策が必要) | やや高い(ライブラリの知識が必要) |
| 対応ピンの制約 | 制約なし | ボードにより限定的(UNOは2本のみ) | ピンではなくタイマー回路に依存 |
※各方式の適用範囲は編集部見解を含む。外部割り込みの対応ピン・トリガーモードの定義についてはArduino公式 Language Reference「attachInterrupt()」を参照。
迷った場合の目安として、「その入力を取りこぼした場合にどれだけ困るか」を基準にするとよい。取りこぼしが安全上の問題につながるなら外部割り込み、多少の取りこぼしが許容できるならポーリングのままでも実用上問題ないことが多い。
05実践:緊急停止ボタンを割り込みで実装する5ステップ
ここでは「モーターを動かしている最中でも、ボタンが押されたら即座に停止させる緊急停止ボタン」という編集部想定シナリオを題材に、割り込みを使った実装の具体的な手順を示す。
-
STEP1. 対応ピンを確認し、プルアップ設定で配線する
Arduino UNOの場合、外部割り込みに対応しているのはデジタルピン2番・3番のみである。ボタンの片側をこのピンに、もう片側をGNDに接続し、内部プルアップ(
INPUT_PULLUP)を使う配線にする。実例:緊急停止ボタンをピン2に接続し、pinMode(2, INPUT_PULLUP);で内部プルアップを有効化した。ボタンを押すとピンがLOWになる配線にした。 -
STEP2. 共有変数をvolatileで宣言する
ISRの中で変更し、loop()側でも参照するフラグ変数を
volatile bool emergencyStop = false;のように宣言する。実例:volatile bool emergencyStop = false;を宣言。この変数だけをISRとloop()の橋渡しに使う設計にした。 -
STEP3. ISRを短く、フラグを立てるだけの処理にする
ISR内では時間のかかる処理を避け、フラグを立てるだけにとどめる。モーターの停止処理そのものはloop()側で行う。
実例:void stopISR() { emergencyStop = true; }という1行だけのISRを定義した。モーター停止処理はloop()内の判定に任せた。 -
STEP4. attachInterrupt()でISRを登録する
setup()内でattachInterrupt(digitalPinToInterrupt(2), stopISR, FALLING);のように登録する。プルアップ配線の場合、ボタンを押した瞬間はHIGHからLOWへの変化になるため、トリガーモードはFALLINGを指定する。実例:attachInterrupt(digitalPinToInterrupt(2), stopISR, FALLING);を追加。ボタンを押した瞬間だけを検知するようにした。 -
STEP5. loop()でフラグを判定し、チャタリング対策を加える
loop()側で
emergencyStopを判定し、trueであればモーターを停止する。あわせて、前回の割り込みからの経過時間をmillis()で確認し、短時間の再発生を無視するデバウンス処理を加える。実例:ISR内にif (millis() - lastInterruptTime > 50) { emergencyStop = true; } lastInterruptTime = millis();という判定を追加したところ、ボタンを1回押しただけで複数回反応していた誤検知が解消された(編集部想定シナリオ)。
ボタン回路の配線や、プルアップ・プルダウンの基礎からもう一度確認したい場合は、以下の記事も参考になる。
06注意点・よくある誤解
割り込みが発生している間はmillis()のカウントが止まるため、ISR内でdelay()は正しく動作しない。またシリアル通信中に割り込みが発生すると受信データが失われる可能性がある。ISR内はできるだけ短く、フラグを立てるだけの処理にとどめ、実際の処理はloop()側で行うのが基本となる。
機械式スイッチである以上、接点の跳ね返り(チャタリング)は割り込みを使っても発生し得る。1回のボタン操作で割り込みが複数回発生してしまうことがあるため、millis()を使ったソフトウェア的なデバウンス処理を組み合わせる必要がある。
外部割り込みに対応しているピンはボードによって異なり、Arduino UNOではデジタルピン2番・3番の2本のみである。対応していないピンをattachInterrupt()に指定してもエラーにならず正常にコンパイルされてしまう場合があるため、事前にdigitalPinToInterrupt()で対応状況を確認しておくことが望ましい。
07FAQ
参考文献
- Arduino公式ドキュメント編集チーム「attachInterrupt()」Arduino Documentation, 2025年改訂. https://docs.arduino.cc/language-reference/en/functions/external-interrupts/attachInterrupt/
- Arduino公式ドキュメント編集チーム「digitalPinToInterrupt()」Arduino Documentation. https://docs.arduino.cc/language-reference/en/functions/external-interrupts/digitalPinToInterrupt/
- Arduino公式ドキュメント編集チーム「volatile」Arduino Documentation, 2024年改訂. https://docs.arduino.cc/language-reference/en/variables/variable-scope-qualifiers/volatile/
- Arduino公式ドキュメント編集チーム「interrupts()」Arduino Documentation, 2025年改訂. https://docs.arduino.cc/language-reference/en/functions/interrupts/interrupts/
- hobbyhappyblog「Arduinoの割り込み(interrupts)を使ってチャタリングが出た話【ソフトで解決】」hobbyhappyblog, 2023年頃. https://www.hobbyhappyblog.jp/arduino-interrupts-chattering/
- 本文中の具体的な数値・誤検知解消エピソードは、断りのない限り編集部想定シナリオに基づく試算・想定であり、実測データではありません。
関連記事(内部リンク)
- Arduinoのコードの書き方|基礎から応用まで全てがわかる初心者の手引き ― https://yofukasi-lab-wp.com/2025/03/07/arduino-cord/
- Arduinoとブレッドボードで学ぶ!電子工作におけるボタン回路の基礎から応用まで ― https://yofukasi-lab-wp.com/2025/03/08/button/
- ArduinoとPythonで実現するインタラクティブ花火演出システム:タッチセンサー活用の詳細手順&コード解説 ― https://yofukasi-lab-wp.com/2025/03/09/hanabi/
コメント