Arduinoの関数ポインター割り当てはアトミックですか?


11

次のスニペットは、TimerOneライブラリのソースコードからの抜粋です

// TimerOne.h:
void (*isrCallback)();

// TimerOne.cpp:
ISR(TIMER1_OVF_vect) // interrupt service routine that wraps a user defined function supplied by attachInterrupt
{
  Timer1.isrCallback();
}

// TimerOne.cpp:
void TimerOne::attachInterrupt(void (*isr)(), long microseconds)
{
  if(microseconds > 0) setPeriod(microseconds);
  isrCallback = isr; // register the user's callback with the real ISR
  TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
  resume();                                                                                            
}

質問:タイマーが既に実行されていて、メインプログラムがを呼び出しているattachInterrupt()場合、関数ポインターの割り当て中にタイマー割り込みが発生する可能性がありisrCallback = isr;ますか?次に、幸運なタイミングで、Timer1.isrCallback();関数ポインタは古いアドレスと新しいアドレスの一部で構成され、ISRは偽の場所にジャンプしますか?

関数ポインターは確かに1バイトよりも広く、1バイトを超えるデータへのアクセスはアトミックではないため、これが当てはまる可能性があると思います。可能な回避策は次のとおりです。

  • を呼び出す前detachInterrupt()に、必ずを呼び出して、タイマーが実行されていないことを確認してattachInterrupt()ください。つまり、Timer1のドキュメントを明確にします。
  • または、Timer1を変更して、直前に一時的にタイマーオーバーフロー割り込みを無効にします isrCallback = isr;

これは理にかなっていますか、それともTimer1ソースや関数ポインタの割り当てに私が見逃した何かがありますか?

回答:


7

でattachInterrupt()とdetachInterrupt()のコードを見てください /Applications/Arduino.app/Contents/Resources/Java/hardware/arduino/cores/arduino/WInterrupts.c(まあ、とにかく、ここがMacの場所です。他のOSのArduinoファイル構造は、パスの下位レベルでもおそらく似ています)。

attachInterrupt()は、問題の割り込みが予防策なしで関数ポインターを書き込むため、まだ有効になっていないと想定しているようです。detachInterrupts()は、NULLポインターをベクターに書き込む前に、ターゲット割り込みを無効にすることに注意してください。したがって、少なくともdetachInterrupt()/ attachInterrupt()ペアを使用します

私自身、クリティカルセクションでそのようなコードを実行したいと思います。それはあなたの最初の方法(切り離し、次に接続)が機能するように見えますが、残念ながら時間制限された割り込みを見逃すことができなかったとは確信できません。ご使用のMCUのデータシートには、それについてさらに多くのことが記載されている場合があります。しかし、現時点では、グローバルcli()/ sei()がそれを見逃すこともないと確信しています。ATMega2560データシートのセクション6.8には、「SEI命令を使用して割り込みを有効にすると、この例に示すように、SEIに続く命令は保留中の割り込みの前に実行される」とあり、割り込み中に割り込みをバッファできることを示唆しているようです。オフです。


ソースに飛び込むことは確かに便利です:) TimerOneのアタッチ/デタッチ割り込みメカニズムは標準(WInterrupt)と同様に行われるようで、同じ "機能"を持っています。
Joonas Pulakka 14年

0

ポイントがあるようです。論理的には、最初に無効にした割り込みを再度有効にしないように、割り込みを無効にすることになります。例えば:

  uint8_t oldSREG = SREG;  // remember if interrupts are on
  cli();                   // make the next line interruptible
  isrCallback = isr;       // register the user's callback with the real ISR
  SREG = oldSREG;          // turn interrupts back on, if they were on before

「SEI命令を使用して割り込みを有効にする場合、この例に示すように、保留中の割り込みの前にSEIに続く命令が実行されます。」

この目的は、次のようなコードを記述できるようにすることです。

  sei ();         // enable interrupts
  sleep_cpu ();   // sleep

その規定がないと、これらの2つの行の間で割り込みが発生し、無期限にスリープ状態になる可能性があります(スリープ状態になる前に、起こしていた割り込みが発生したためです)。プロセッサで次の命令が前に有効化されていなかった場合に割り込みが有効化された後、常に実行されるという規定は、これを防ぎます。


それはより効率的ではないでしょうTIMSK1=0; TIFR1=_BV(TOV1); isrCallback=isr; TIMSK1=_BV(TOIE1);か?1つのCPUレジスタを節約し、割り込みレイテンシの原因にはなりません。
Edgar Bonet、2016年

ICIE1、OCIE1B、OCIE1Aなどの他のビットはどうですか?レイテンシについては理解していますが、割り込みは、使用できない2クロックサイクルに対処できるはずです。競合状態の可能性があります。割り込みはすでにトリガーされている可能性があります(つまり、CPUのフラグが設定されています)。TIMSK1をオフにしても、処理が停止しない場合があります。これが起こらないようにするには、TOV1をリセットする必要があります(TIFR1に1を書き込むことによって)。不確実性があるため、割り込みをグローバルにオフにする方が安全なコースだと思います。
Nick Gammon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.