Arduinoの割り込み処理とは?interruptの仕組みと使い方を解説

プログラミング・電子工作






Arduinoの割り込み(interrupt)とは




Arduino / 電子工作

Arduinoの割り込み(interrupt)とは――loop()を止めずに即時検知する仕組み

delay()で処理が止まっている間のボタン操作、取りこぼしていませんか。この記事では、attachInterrupt()の基本構文を解説します。あわせて、volatile修飾子の必要性や実践的な実装手順もご紹介します。


YOFUKASI LAB編集部 | 更新日:2026年8月18日 | 読了目安:約13分

Chapter 01 — Introduction

01なぜloop()だけでは反応が遅れることがあるのか

ポーリング方式の弱点

結論から言えば、「ポーリング」という方式には弱点がある。ポーリングとは、ある方式のことだ。loop()の中でdigitalRead()を繰り返し、状態を監視する方式である。なぜなら、loop()の途中には注意すべき点があるからだ。delay()や時間のかかる処理が入ることがあるためである。すると、その間に発生したボタン操作やセンサーの変化を見逃してしまう。

一方、Arduinoの割り込み(interrupt)を使えば話が変わる。処理の途中であっても、指定した条件が発生した瞬間に反応できるためだ。具体的には、別の処理(割り込みサービスルーチン、ISR)へ即座に切り替えられる。

チャタリングという落とし穴

実際に、外部割り込みを使ったプロジェクトでも注意すべき点がある。例えば、スイッチの状態変化を検知する際にも落とし穴がある。割り込みを使ったにもかかわらず、注意が必要な場合があるのだ。チャタリング(接点の跳ね返りによる誤検知)が発生することがある。実際に、ソフトウェア側の対策が必要になった事例がある。詳しくはArduinoの割り込み(interrupts)を使ってチャタリングが出た話【ソフトで解決】で紹介されている。機械式スイッチを使う以上、チャタリングは避けられない現象だという。そのため、割り込みを使う場合でも何らかの対策が必要になる。

編集部見解:割り込みは「反応の速さ」を手に入れられる機能だ。一方で、扱いを誤ると原因の特定が難しいバグを生みやすい。特にチャタリングや共有変数の扱いは、初めて使う際につまずきやすい。
― YOFUKASI LAB編集部

本記事の構成

そこで本記事では、Arduinoにおける割り込みの基本構文を解説する。さらに、ポーリングとの使い分けを取り上げる。加えて、緊急停止ボタンを例にした実装手順や注意点も紹介する。初心者から中級者まで、体系的に理解できる内容を目指す。



Chapter 02 — Background

02Arduinoの割り込み(interrupt)の基礎知識

割り込み(interrupt)とは、ある仕組みのことである。具体的には、通常のプログラムの流れを一時的に中断する仕組みだ。そして、あらかじめ指定しておいた処理を実行する。この際に呼び出される関数がある。それは割り込みサービスルーチン(ISR: Interrupt Service Routine)と呼ばれる。

Arduinoでは、ピンの電圧変化をきっかけに割り込みを発生させることができる。これを外部割り込みと呼ぶ。外部割り込みにはattachInterrupt()関数を使う。この関数は、割り込み発生時に呼び出すISRを指定する働きを持つ。Arduino公式 Language Reference「attachInterrupt()」でも、そう定義されている。

また、トリガーの条件には4種類の定数が用意されている。LOWはピンがLOWのときに発生する。CHANGEはピンの値が変化したときに発生する。さらに、RISINGはLOWからHIGHへの変化時に発生する。FALLINGはHIGHからLOWへの変化時に発生する。

用語の整理

  • ISR(割り込みサービスルーチン):割り込みが発生した際に呼び出される関数。引数を取らず、戻り値も返さない(void型)と定められている。
  • 外部割り込み(external interrupt):特定のピンの電圧変化をきっかけに発生する割り込み。
  • トリガーモード:割り込みを発生させる条件。LOW・CHANGE・RISING・FALLINGの4種類。

ただし、すべてのピンが外部割り込みに対応しているわけではない。使いたいピンが対応しているかは、確認する方法がある。digitalPinToInterrupt()関数を使えば確認できる。この関数の詳細はArduino公式 Language Reference「digitalPinToInterrupt()」にまとまっている。なお、Arduino UNOでは2本のピンのみが対応する。デジタルピン2番・3番だけである。この点には注意が必要である。

割り込みを学ぶ前に、基礎文法やボタン回路の基本を確認しておくとよい。なぜなら、先に基礎を押さえておくことで、この先の理解がスムーズになるからだ。

Chapter 03 — Mechanism

03割り込みの仕組みと技術的背景

サンプルスケッチとvolatile

外部割り込みを使う最小構成のスケッチは、次のような形になる。

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で実現するインタラクティブ花火演出システムの実例だ。タッチセンサーの入力をきっかけに演出を切り替える構成の参考になる。



Chapter 04 — Comparison

04ポーリング・外部割り込み・タイマー割り込みの比較

状態変化をどう検知するかは、用途によって最適な方式が変わる。以下に3つの選択肢を整理する。

項目 A. ポーリング(loop内でdigitalRead) B. 外部割り込み(attachInterrupt) C. タイマー割り込み(ライブラリ利用)
向いている用途 取りこぼしが許容できる一般的な入力 緊急停止など取りこぼし厳禁の入力 一定間隔での定期処理
反応速度 loop()の実行間隔に依存 ほぼ即時 設定した間隔で正確
実装の複雑さ 低い 中程度(volatile・チャタリング対策が必要) やや高い(ライブラリの知識が必要)
対応ピンの制約 制約なし ボードにより限定的(UNOは2本のみ) ピンではなくタイマー回路に依存

※各方式の適用範囲は編集部見解を含む。対応ピン・トリガーモードの定義は公式ドキュメントを参照してほしい。具体的にはArduino公式 Language Reference「attachInterrupt()」に記載がある。

迷った場合は、ある基準を使うとよい。それは「その入力を取りこぼした場合にどれだけ困るか」である。なぜなら、取りこぼしが安全上の問題につながるなら、外部割り込みを選ぶべきだからだ。逆に、多少の取りこぼしが許容できるなら話は別だ。ポーリングのままでも実用上問題ないことが多い。

Chapter 05 — Practice

05実践:緊急停止ボタンを割り込みで実装する5ステップ

ここでは、ある編集部想定シナリオを題材にする。それは緊急停止ボタンの例だ。モーターを動かしている最中でも、ボタンが押されたら即座に停止させる、というものである。この題材をもとに、割り込みを使った実装の手順を示す。

配線とコードの準備(STEP1〜3)

  1. STEP1. 対応ピンを確認し、プルアップ設定で配線する

    Arduino UNOの場合、外部割り込みに対応しているのはデジタルピン2番・3番のみである。ボタンの片側をこのピンに、もう片側をGNDに接続する。そして、内部プルアップ(INPUT_PULLUP)を使う配線にする。

    実例:緊急停止ボタンをピン2に接続した。そしてpinMode(2, INPUT_PULLUP);で内部プルアップを有効化した。ボタンを押すとピンがLOWになる配線にした。
  2. STEP2. 共有変数をvolatileで宣言する

    ISRの中で変更し、loop()側でも参照するフラグ変数を用意する。具体的には、volatile bool emergencyStop = false;のように宣言する。

    実例:volatile bool emergencyStop = false;を宣言。この変数だけをISRとloop()の橋渡しに使う設計にした。
  3. STEP3. ISRを短く、フラグを立てるだけの処理にする

    ISR内では時間のかかる処理を避ける。つまり、フラグを立てるだけにとどめる。モーターの停止処理そのものは、loop()側で行う。

    実例:void stopISR() { emergencyStop = true; }という1行だけのISRを定義した。モーター停止処理はloop()内の判定に任せた。

割り込みの登録と動作確認(STEP4〜5)

  1. STEP4. attachInterrupt()でISRを登録する

    setup()内でattachInterrupt(digitalPinToInterrupt(2), stopISR, FALLING);のように登録する。プルアップ配線の場合、ボタンを押した瞬間はHIGHからLOWへの変化になる。そのため、トリガーモードはFALLINGを指定する。

    実例:attachInterrupt(digitalPinToInterrupt(2), stopISR, FALLING);を追加した。これにより、ボタンを押した瞬間だけを検知するようにした。
  2. STEP5. loop()でフラグを判定し、チャタリング対策を加える

    loop()側でemergencyStopを判定し、trueであればモーターを停止する。あわせて、前回の割り込みからの経過時間をmillis()で確認する。そして、短時間の再発生を無視するデバウンス処理を加える。

    実例:ISR内にif (millis() - lastInterruptTime > 50) { emergencyStop = true; } lastInterruptTime = millis();という判定を追加した。すると、ボタンを1回押しただけで複数回反応していた誤検知が解消された(編集部想定シナリオ)。

あわせて確認したい基礎知識

ボタン回路の配線を、もう一度確認したい場合もあるだろう。プルアップ・プルダウンの基礎から見直したいときは、以下の記事も参考になる。

Arduinoとブレッドボードで学ぶタクトスイッチ入力の基礎で、応用まで解説している。

Chapter 06 — Pitfalls

06注意点・よくある誤解

!誤解1. ISR内にどんな処理を書いても問題ない

割り込みが発生している間はmillis()のカウントが止まる。そのため、ISR内でdelay()は正しく動作しない。また、シリアル通信中に割り込みが発生すると、受信データが失われる可能性がある。したがって、ISR内の処理はできるだけ短くするべきだ。具体的には、フラグを立てるだけの処理にとどめる。そして、実際の処理はloop()側で行うのが基本となる。

!誤解2. 割り込みを使えばチャタリングの心配はない

機械式スイッチである以上、接点の跳ね返り(チャタリング)は発生し得る。割り込みを使っても、この点は変わらない。つまり、1回のボタン操作で割り込みが複数回発生することがある。そのため、millis()を使ったデバウンス処理を組み合わせる必要がある。

!誤解3. どのピンでも割り込みを設定できる

外部割り込みに対応しているピンはボードによって異なる。Arduino UNOでは、デジタルピン2番・3番の2本のみである。しかも、対応していないピンをattachInterrupt()に指定しても、エラーにはならない。正常にコンパイルされてしまう場合があるのだ。事前にdigitalPinToInterrupt()で確認しておくとよい。この一手間が望ましい。



Chapter 07 — FAQ

07FAQ

基本を確認したい方向けの質問

Q.Arduinoの割り込み(interrupt)とは何ですか。
A.割り込みとは、ある仕組みのことです。通常のプログラムの流れ(loop()の実行)を一時中断する仕組みです。そして、あらかじめ指定しておいた処理(割り込みサービスルーチン、ISR)を即座に実行します。delay()で処理が止まっている間でも、割り込みは発生すればすぐに反応します。

Q.Arduino UNOで外部割り込みが使えるピンはどこですか。
A.Arduino UNOには、外部割り込みに対応するピンが2本あります。デジタルピン2番(割り込み番号0)と、デジタルピン3番(割り込み番号1)です。使いたいピンが対応しているかは、確認する方法があります。digitalPinToInterrupt()関数を使えば確認できます。

Q.割り込みで使う変数にvolatileを付ける必要があるのはなぜですか。
A.volatileを付けないと、注意が必要です。コンパイラが最適化の過程で、変数の値をレジスタにキャッシュしてしまうことがあります。すると、割り込み内で変更した最新の値が、loop()側に反映されないことがあります。ISRの中と外、両方からアクセスする変数には注意が必要です。そうした変数には、volatile修飾子を付けるのが基本です。

実装時の注意点に関する質問

Q.割り込みサービスルーチン(ISR)の中でdelay()やSerial.print()を使ってもよいですか。
A.推奨されません。割り込みが発生している間は、millis()のカウントが止まります。そのため、ISR内でdelay()は正しく動作しません。また、シリアル通信中に割り込みが発生すると、受信データが失われる可能性があります。そのため、ISR内の処理は短くするのが基本です。フラグを立てるだけの処理にとどめておく。

Q.割り込みを使うとチャタリングの影響を受けやすくなりますか。
A.はい。原因は、機械式スイッチの接点の跳ね返り(チャタリング)です。これにより、1回のボタン操作で割り込みが複数回発生してしまうことがあります。対策として、前回の割り込みからの経過時間をmillis()で計測します。そして、一定時間内の再発生を無視します。これがソフトウェア的なデバウンス処理であり、一般的な対策です。

Q.割り込みを使わずポーリング(loop内でdigitalReadを繰り返す)だけではダメですか。
A.ポーリングでも多くの場合は対応できます。ただし、loop()内に時間のかかる処理が含まれる場合は注意が必要です。delay()なども含め、その間のボタン操作を取りこぼす可能性があります。緊急停止のように「取りこぼしが許されない」処理では、割り込みを使う方が確実です。逆に、多少の取りこぼしが許容できる用途もあります。その場合は、コードがシンプルになるポーリングの方が適していることもあります(編集部見解)。

参考文献

関連記事(内部リンク)


関連記事

特集記事

コメント

この記事へのコメントはありません。

TOP
CLOSE