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

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

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

【概要】

noInterrupts() は、Arduinoフレームワークにおいてすべての割り込みをグローバルに無効化する関数です。この関数が呼び出されると、タイマー割り込み、外部割り込み、シリアル通信割り込みなど、すべてのハードウェア割り込みが停止します。

主に「クリティカルセクション(Critical Section)」の構築に使用されます。クリティカルセクションとは、複数のプロセスや割り込みハンドラ間で共有されるデータ(共有変数など)を操作する際に、操作の途中で割り込みが入ることでデータの一貫性が破壊されるのを防ぐための領域を指します。

【仕様】

  • 引数: なし
  • 戻り値: なし(void)
  • データ型: 関数(内部的にはCPUのステータスレジスタを操作する命令へマッピングされる)
  • 初期状態: デフォルトでは割り込みは有効(Enabled)な状態。
  • 対応ハードウェア: AVR(Arduino Uno/Nano等)、ESP32、ARM Cortex-Mシリーズなど、Arduinoに対応するほぼすべてのマイクロコントローラで利用可能。

【技術的特徴】

  • ハードウェアへのマッピング:
    AVRマイコンにおいては、この関数はアセンブリ言語の cli()(Clear Interrupts)命令に直接対応します。CPUのステータスレジスタ内の「Global Interrupt Enable (GIE)」ビットをクリアすることで、割り込み要求を無視するように動作します。
  • 原子性(Atomicity)の保証:
    例えば、16ビット以上の変数を8ビットCPUで読み取る際、読み取り操作の途中で割り込みが発生すると、上位バイトと下位バイトが不整合を起こす可能性があります。noInterrupts() を使用することで、この一連の読み取り操作を「原子的な操作」として保証します。
  • 比較 (interrupts() 関数):
    noInterrupts() と対になる関数として interrupts() が存在します。これはすべての割り込みを再び有効にします。通常、この2つを組み合わせて、最小限の範囲のみ割り込みを停止させる実装が推奨されます。

【注意点】

  • 実行時間の制約:
    割り込みを停止している間、システムはリアルタイム性を失います。特に、シリアル通信(UART)やI2C/SPIのタイムアウト監視、タイマーによるPWM出力などのバックグラウンド処理が停止するため、可能な限り短いコードブロック内でのみ使用する必要があります。
  • ウォッチドッグタイマー (WDT):
    割り込みを長時間停止していると、ウォッチドッグタイマーがリセットを検知し、システムを強制再起動させる可能性があります。
  • 再入可能性と状態の復元:
    単純に noInterrupts() と interrupts() をペアで使う場合、ネストされた関数内で呼び出すと状態の管理が複雑になることがあります。より堅牢な実装では、現在の割り込み状態を保存して復元する save_interrupt_state() および restore_interrupt_state() の使用が推奨されます。

【サンプルコード】

共有変数(カウンター)を割り込みハンドラとメインループの両方から操作する際の正しい実装例です。

volatile int sharedCounter = 0; // 割り込み間で共有される変数

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT_PULLUP);
  // 2番ピンの立ち下がりで割り込み発生
  attachInterrupt(digitalPinToInterrupt(2), countISR, FALLING);
}

void countISR() {
  sharedCounter++; // 割り込みハンドラ内での操作
}

void loop() {
  // 共有変数を安全に読み取るためのクリティカルセクション
  noInterrupts(); 
  int currentCount = sharedCounter; 
  interrupts(); 

  // 読み取った値に基づいた処理を行う
  if (currentCount > 100) {
    Serial.print("Threshold reached: ");
    Serial.println(currentCount);
  }

  delay(100); 
}

【活用例】

  1. マルチバイト変数の同期:
    32ビット変数を操作する際に、8ビットや16ビットマイコンでデータの不整合を防ぐ。
  2. 通信バッファの操作:
    シリアル通信などで受信したデータをキュー(バッファ)に格納する際、ポインタの更新操作をアトミックにする。
  3. ハードウェアレジスタの直接操作:
    特定のハードウェアレジスタを複数のステップに分けて設定する際、その途中で別の割り込みが入ってレジスタ状態を破壊するのを防ぐ。

コメント

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