attachInterrupt() の解説と実装リファレンス

記事内には広告が含まれています。

attachInterrupt() の解説と実装リファレンス

【概要】

attachInterrupt() は、Arduino環境において特定のデジタルピンまたは内部イベントに、特定の関数(割り込みサービスルーチン:ISR)を紐付けるための関数です。この関数を使用することで、メインループ(loop())の実行状態を一時中断し、外部イベント(ボタンの押下、センサーの信号変化など)が発生した瞬間に指定した処理を即座に実行する「割り込み処理」を実装できます。

本関数は、イベント駆動型(Event-driven)のプログラム設計において、ポーリング処理(周期的な状態確認)よりも低遅延かつ高精度な応答を実現するために不可欠な技術要素です。

【仕様】

関数シグネチャ

void attachInterrupt(unsigned int pin, void (*func)(), int mode);

引数

  • pin (unsigned int): 割り込みを検知するピン番号。多くのマイコンでは特定のピンのみが割り込みに対応しているため、digitalPinToInterrupt(pin) を用いて変換して渡すことが推奨されます。
  • **func (void()())*: 割り込み発生時に実行される関数のポインタ。この関数(ISR)は、引数や戻り値を持たず、可能な限り高速に処理を完了させる必要があります。
  • mode (int): 割り込みを発生させる条件。以下の定数を使用します:
    • LOW: ピンの電圧がLOWになった時
    • HIGH: ピンの電圧がHIGHになった時
    • CHANGE: ピンの電圧が変化した時(LOW→HIGH、またはHIGH→LOW)
    • RISING: ピンの電圧がLOWからHIGHに変化した瞬間
    • FALLING: ピンの電圧がHIGHからLOWに変化した瞬間

戻り値

  • void(戻り値なし)

【技術的特徴】

  • コンテキストスイッチ: 割り込みが発生すると、CPUは現在のレジスタの状態をスタックに保存し、ISRを実行します。ISR終了後、保存されたレジスタを復元して元の処理を再開します。
  • ハードウェアレベルの動作: この関数はマイコン内部の割り込みコントローラ(Interrupt Controller)と、各ピンに紐付く割り込みベクトルテーブル(Interrupt Vector Table)への登録を制御します。
  • ポーリングとの比較:
    • ポーリング: CPUが常に digitalRead() を実行して状態を監視する。処理の欠落が発生しやすく、CPUリソースを消費する。
    • 割り込み: CPUは他の処理を行い、イベント発生時のみ割り込み処理へ遷移する。極めて高い応答性と効率を両立する。

【注意点】

シニアエンジニアとして、実装時に必ず遵守すべき制約事項を以下に記述します。

  1. ISRの実行時間: ISR内では極めて短い処理のみを行うこと。複雑な計算、delay() 関数、あるいは Serial.print() などの長時間ブロックを伴う関数を呼び出すことは厳禁です。これらはメインループの実行を阻害し、ウォッチドッグタイマーの作動や他の割り込みの欠落を招きます。
  2. volatileキーワード: ISR内で書き換え、かつメインループ内で参照する変数は、必ず volatile 修飾子を付与する必要があります。これはコンパイラの最適化による変数のレジスタへのキャッシュを防止するためです。
  3. アトミック操作: 32ビット以上の変数(long など)をISRとメインループの両方で操作する場合、メインループ側では割り込みを一時的に禁止(noInterrupts() / interrupts())して読み書きを行う必要があります。そうしないと、読み取りの途中で割り込みが発生し、データの不整合(競合状態)が生じる可能性があります。
  4. チャタリングの処理: 物理的なスイッチやリレーを使用する場合、接触不良によるチャタリング(短時間の微細なON/OFFの繰り返し)が発生します。ハードウェア的なRCフィルタの導入、またはソフトウェアによる一定時間内の無視処理を実装する必要があります。

【サンプルコード】

ボタン押下によってカウンターをインクリメントする実装例です。

// ISR内で共有する変数はvolatileを付与// volatile int buttonCount = 0;

const int buttonPin = 2; // 割り込み対応ピン(ボードにより異なる)

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);

  // 立ち下がり(FALLING)でISRを登録
  attachInterrupt(digitalPinToInterrupt(buttonPin), countButton, FALLING);
}

void loop() {
  // 割り込みによってカウントされた数値を定期的に表示
  static unsigned long lastDisplay = 0;
  if (millis() - lastDisplay > 500) {
    Serial.print("Count: ");
    Serial.println(buttonCount);
    lastDisplay = millis();
  }
}

// 割り込みサービスルーチン (ISR)
void countButton() {
  buttonCount++;
  // ここに複雑な処理やSerial.printは記述しない\n}

【活用例】

  • エンコーダのカウント: モーターの回転数や位置を測るためのパルス信号の計測。
  • 緊急停止ボタン: 装置の安全確保のため、即時反応が必要な信号の検知。
  • 高精度なタイミング計測: 外部信号の到達時間をマイクロ秒単位で計測するトリガー処理。
  • 通信プロトコルの実装: SPIやI2Cなどのカスタムプロトコルにおいて、データフレームの開始を示す信号のキャッチ。

コメント

タイトルとURLをコピーしました