Arduino Unoおよび同様のボードで割り込みはどのように機能しますか?


11

ATmega328Pプロセッサを使用するArduino Unoおよび関連するボードでの割り込みの仕組みを説明してください。次のようなボード:

  • うの
  • ミニ
  • ナノ
  • プロミニ
  • リリーパッド

特に議論してください:

  • 割り込みを使用する目的
  • 割り込みサービスルーチン(ISR)の作成方法
  • タイミングの問題
  • 重要なセクション
  • データへのアトミックアクセス

注:これは参考質問です。

回答:


25

TL; DR:

割り込みサービスルーチン(ISR)を作成する場合:

  • 短くしてください
  • 使わない delay ()
  • シリアルプリントを行わない
  • メインコードと共有する変数を揮発性にする
  • メインコードと共有される変数は、「クリティカルセクション」で保護する必要がある場合があります(以下を参照)
  • 割り込みをオフまたはオンにしないでください

割り込みとは何ですか?

ほとんどのプロセッサには割り込みがあります。割り込みを使用すると、他のことを実行しながら「外部」イベントに応答できます。たとえば、夕食を調理している場合は、ジャガイモを20分間調理することができます。時計を20分間見つめるのではなく、タイマーを設定してからテレビを見に行くこともできます。タイマーが鳴ると、ジャガイモで何かをするためにテレビの視聴を「中断」します。


割り込みの例

const byte LED = 13;
const byte SWITCH = 2;

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  if (digitalRead (SWITCH) == HIGH)
    digitalWrite (LED, HIGH);
  else
    digitalWrite (LED, LOW);
}  // end of switchPressed

void setup ()
{
  pinMode (LED, OUTPUT);  // so we can update the LED
  pinMode (SWITCH, INPUT_PULLUP);
  attachInterrupt (digitalPinToInterrupt (SWITCH), switchPressed, CHANGE);  // attach interrupt handler
}  // end of setup

void loop ()
{
  // loop doing nothing
}

この例は、メインループが何も実行していない場合でも、ピンD2のスイッチが押された場合に、ピン13のLEDをオンまたはオフにできることを示しています。

これをテストするには、D2とグラウンドの間にワイヤー(またはスイッチ)を接続するだけです。内部プルアップ(セットアップで有効)により、ピンは通常強制的にHIGHになります。接地するとLOWになります。ピンの変化はCHANGE割り込みによって検出され、割り込みサービスルーチン(ISR)が呼び出されます。

より複雑な例では、メインループが温度の読み取り値を取得するなどの有用な処理を実行し、割り込みハンドラーがボタンの押し下げを検出できるようにします。


ピン番号を割り込み番号に変換する

割り込みベクトル番号をピン番号に簡単に変換するには、関数を呼び出してdigitalPinToInterrupt()ピン番号を渡します。適切な割り込み番号、またはNOT_AN_INTERRUPT(-1)を返します。

たとえば、Unoでは、ボードのピンD2は割り込み0です(下の表のINT0_vect)。

したがって、これらの2行は同じ効果があります。

  attachInterrupt (0, switchPressed, CHANGE);    // that is, for pin D2
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);

ただし、2つ目は読みやすく、さまざまなArduinoタイプに移植可能です。


利用可能な割り込み

以下は、Atmega328の割り込みの優先順のリストです。

 1  Reset
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

内部名(ISRコールバックの設定に使用できます)は括弧内にあります。

警告:割り込みのベクトル名のスペルを間違えると、大文字を間違えただけでも(簡単に行うことができます)、割り込みルーチンは呼び出されず、コンパイラエラーは発生しません。


割り込みを使用する理由

割り込みを使用する主な理由は次のとおりです。

  • ピンの変化を検出するには(例:ロータリーエンコーダー、ボタンの押下)
  • ウォッチドッグタイマー(例:8秒経っても何も起こらない場合は、私に割り込みます)
  • タイマー割り込み-タイマーの比較/オーバーフローに使用
  • SPIデータ転送
  • I2Cデータ転送
  • USARTデータ転送
  • ADC変換(アナログからデジタル)
  • すぐに使えるEEPROM
  • フラッシュメモリ対応

「データ転送」を使用して、シリアルポート、SPIポート、またはI2Cポートでデータが送受信されている間に、プログラムに何か他のことをさせることができます。

プロセッサを起動します

外部割り込み、ピン変更割り込み、およびウォッチドッグタイマー割り込みも、プロセッサを起動するために使用できます。これは非常に便利です。スリープモードでは、プロセッサを使用して消費電力を大幅に削減できるためです(たとえば、約10マイクロアンペア)。立ち上がり、立ち下がり、または低レベルの割り込みを使用して、ガジェットをウェイクアップできます(たとえば、ボタンを押した場合)。または、「ウォッチドッグタイマー」割り込みが定期的にウェイクアップする場合があります(たとえば、時間を確認するために、または温度)。

ピン変更割り込みは、キーパッドなどでキーが押された場合にプロセッサをウェイクアップするために使用できます。

プロセッサは、タイマー割り込み(たとえば、タイマーが特定の値に到達するか、オーバーフローする)や、着信I2Cメッセージなどの他の特定のイベントによって起動することもできます。


割り込みの有効化/無効化

「リセット」割り込みは無効にできません。ただし、他の割り込みは、グローバル割り込みフラグをクリアすることで一時的に無効にすることができます。

割り込みを有効にする

次のような関数呼び出し「interrupts」または「sei」で割り込みを有効にできます。

interrupts ();  // or ...
sei ();         // set interrupts flag

割り込みを無効にする

割り込みを無効にする必要がある場合は、次のようにグローバル割り込みフラグを「クリア」できます。

noInterrupts ();  // or ...
cli ();           // clear interrupts flag

どちらの方法でも使用して、同じ効果を持っているinterrupts/ noInterrupts彼らは周りのどの方法を覚えて少し簡単です。

Arduinoのデフォルトでは、割り込みが有効になっています。長期間無効にしないでください。無効にしないと、タイマーなどが適切に機能しません。

なぜ割り込みを無効にするのですか?

タイマー割り込みなど、割り込みを望まないタイムクリティカルなコードがある場合があります。

また、マルチバイトフィールドがISRによって更新されている場合は、データを「原子的に」取得するために、割り込みを無効にする必要がある場合があります。さもなければ、あなたが他の1を読んでいる間、1バイトがISRによって更新されるかもしれません。

例えば:

noInterrupts ();
long myCounter = isrCounter;  // get value set by ISR
interrupts ();

割り込みを一時的にオフにすると、isrCounter(ISR内に設定されたカウンター)が値を取得している間は変更されません。

警告:割り込みが既にオンになっているかどうかわからない場合は、現在の状態を保存して後で復元する必要があります。たとえば、millis()関数のコードは次のようにします。

unsigned long millis()
{
  unsigned long m;
  uint8_t oldSREG = SREG;    // <--------- save status register

  // disable interrupts while we read timer0_millis or we might get an
  // inconsistent value (e.g. in the middle of a write to timer0_millis)
  cli();
  m = timer0_millis;
  SREG = oldSREG;            // <---------- restore status register including interrupt flag

  return m;
}

示されている行は、割り込みフラグを含む現在のSREG(ステータスレジスタ)を保存することに注意してください。タイマー値(4バイト長)を取得したら、ステータスレジスタを元の状態に戻します。


チップ

関数名

関数cli/ seiおよびレジスタSREGは、AVRプロセッサに固有です。ARMプロセッサなどの他のプロセッサを使用している場合、機能は少し異なる場合があります。

グローバルでの無効化と1つの割り込みの無効化

使用cli()する場合は、すべての割り込み(タイマー割り込み、シリアル割り込みなどを含む)を無効にます。

ただし、特定の割り込みを無効にするだけの場合は、その特定の割り込みソースの割り込み有効フラグをクリアする必要があります。たとえば、外部割り込みの場合はを呼び出しますdetachInterrupt()


割り込み優先度とは何ですか?

(リセット以外の)25個の割り込みがあるため、複数の割り込みイベントが同時に発生するか、少なくとも前のイベントが処理される前に発生する可能性があります。また、割り込みが無効になっているときに割り込みイベントが発生する場合があります。

優先順位は、プロセッサが割り込みイベントをチェックする順序です。リストの上位にあるほど、優先順位が高くなります。したがって、たとえば、外部割り込み要求0(ピンD2)は、外部割り込み要求1(ピンD3)の前に処理されます。


割り込みが無効になっているときに割り込みが発生する可能性はありますか?

割り込みイベント(つまり、イベントに気づく)はいつでも発生する可能性があり、ほとんどはプロセッサ内の「割り込みイベント」フラグを設定することで記憶されます。割り込みが無効になっている場合、その割り込みは、再び有効になったときに優先順位に従って処理されます。


割り込みをどのように使用しますか?

  • ISR(割り込みサービスルーチン)を記述します。これは、割り込みが発生したときに呼び出されます。
  • 割り込みを発生させるタイミングをプロセッサに通知します。

ISRの作成

割り込みサービスルーチンは、引数のない関数です。一部のArduinoライブラリは独自の関数を呼び出すように設計されているため、(上記の例のように)通常の関数を指定するだけです。

// Interrupt Service Routine (ISR)
void switchPressed ()
{
 flag = true;
}  // end of switchPressed

ただし、ライブラリがまだISRに「フック」を提供していない場合は、次のようにして独自に作成できます。

volatile char buf [100];
volatile byte pos;

// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;
    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

この場合、 "ISR"マクロを使用して、関連する割り込みベクトルの名前を指定します(前の表から)。この場合、ISRはSPI転送の完了を処理しています。(一部の古いコードはISRの代わりにSIGNALを使用していますが、SIGNALは非推奨です)。

ISRを割り込みに接続する

ライブラリによってすでに処理されている割り込みの場合、文書化されたインターフェイスを使用するだけです。例えば:

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
    {
    char c = Wire.receive ();
    // do something with the incoming byte
    }
}  // end of receiveEvent

void setup ()
  {
  Wire.onReceive(receiveEvent);
  }

この場合、I2Cライブラリは、着信I2Cバイトを内部で処理し、着信データストリームの最後に指定された関数を呼び出すように設計されています。この場合、receiveEventは厳密にはISRではありません(引数があります)が、組み込みのISRによって呼び出されます。

別の例は、「外部ピン」割り込みです。

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
}  // end of switchPressed

void setup ()
{
  attachInterrupt (digitalPinToInterrupt (2), switchPressed, CHANGE);  // attach interrupt handler for D2
}  // end of setup

この場合、attachInterrupt関数は関数switchPressedを内部テーブルに追加し、さらにプロセッサに適切な割り込みフラグを構成します。

割り込みを処理するようにプロセッサを構成する

ISRを取得したら、次のステップは、この特定の条件で割り込みを発生させたいことをプロセッサに通知することです。

例として、外部割り込み0(D2割り込み)の場合、次のようなことができます。

EICRA &= ~3;  // clear existing flags
EICRA |= 2;   // set wanted flags (falling level interrupt)
EIMSK |= 1;   // enable it

より読みやすくするには、次のように定義された名前を使用します。

EICRA &= ~(bit(ISC00) | bit (ISC01));  // clear existing flags
EICRA |= bit (ISC01);    // set wanted flags (falling level interrupt)
EIMSK |= bit (INT0);     // enable it

EICRA(外部割り込み制御レジスタA)は、Atmega328データシートのこの表に従って設定されます。それはあなたが望む割り込みの正確なタイプを定義します:

  • 0:INT0の低レベルは、割り込み要求(LOW割り込み)を生成します。
  • 1:INT0の論理変更は、割り込み要求(CHANGE割り込み)を生成します。
  • 2:INT0の立ち下がりエッジで割り込み要求が発生します(FALLING割り込み)。
  • 3:INT0の立ち上がりエッジで割り込み要求が発生します(RISING割り込み)。

EIMSK(外部割り込みマスクレジスタ)は、実際に割り込みを有効にします。

幸い、attachInterruptが代わりに行うので、これらの番号を覚えておく必要はありません。しかし、それが実際に起こっていることであり、他の割り込みについては、割り込みフラグを「手動で」設定する必要があるかもしれません。


低レベルISRとライブラリISR

生活を簡素化するために、いくつかの一般的な割り込みハンドラーは実際にはライブラリコード内にあり(たとえば、INT0_vectおよびINT1_vect)、さらにユーザーフレンドリーなインターフェイスが提供されます(たとえば、attachInterrupt)。attachInterruptが実際に行うことは、必要な割り込みハンドラーのアドレスを変数に保存し、必要に応じてINT0_vect / INT1_vectから呼び出します。また、必要に応じてハンドラーを呼び出すための適切な登録フラグを設定します。


ISRを中断できますか?

一言で言えば、そうではありませんか。

ISRに入ると、割り込みが無効になります。当然、最初から有効にしておく必要があります。そうしないと、ISRに入ることができません。ただし、ISR自体が中断されるのを避けるため、プロセッサは割り込みをオフにします。

ISRが終了すると、割り込みが再び有効になります。また、コンパイラーはISR内にコードを生成してレジスターとステータスフラグを保存します。これにより、割り込みが発生したときに行っていたことが影響を受けなくなります。

ただし、絶対に必要な場合は、ISR内で割り込みをオンにすることができます。

// Interrupt Service Routine (ISR)
void switchPressed ()
{
  // handle pin change here
  interrupts ();  // allow more interrupts

}  // end of switchPressed

通常、これを行うにはかなりの理由が必要です。これは、別の割り込みがpinChangeの再帰呼び出しを引き起こし、非常に望ましくない結果になる可能性があるためです。


ISRの実行にはどのくらい時間がかかりますか?

データシートによると、割り込みを処理するための最小時間は4クロックサイクル(現在のプログラムカウンターをスタックにプッシュする)であり、その後に割り込みベクトルの場所でコードが実行されます。これには通常、割り込みルーチンが実際にある場所へのジャンプが含まれています。これは、さらに3サイクルです。コンパイラーによって生成されたコードを調べると、「ISR」宣言を使用して作成されたISRの実行には、約2.625 µsと、コード自体が実行するすべての処理にかかることがわかります。正確な量は、保存および復元する必要があるレジスタの数によって異なります。最小量は1.1875 µsです。

外部割り込み(attachInterruptを使用する場合)はもう少し多く、合計で5.125 µs(16 MHzクロックで実行)かかります。


プロセッサがISRに入るのはどのくらい前ですか?

これは多少異なります。上記の数値は、割り込みがすぐに処理される理想的な数値です。いくつかの要因がそれを遅らせるかもしれません:

  • プロセッサがスリープしている場合、指定された「ウェイクアップ」時間が指定されます。これは、数ミリ秒になる可能性がありますが、クロックは高速にスプールされます。この時間は、ヒューズの設定、およびスリープの深さに依存します。

  • 割り込みサービスルーチンが既に実行されている場合、それが終了するか、割り込み自体を有効にするまで、さらに割り込みを入力することはできません。これは、各割り込みサービスルーチンを短くしておく必要がある理由です。1秒間に費やすマイクロ秒ごとに、別のルーチンの実行が遅れる可能性があります。

  • 一部のコードは割り込みをオフにします。たとえば、millis()を呼び出すと、割り込みが一時的にオフになります。したがって、割り込みが処理される時間は、割り込みがオフになった時間の長さによって延長されます。

  • 割り込みは命令の最後でのみ処理できるため、特定の命令が3クロックサイクルかかり、開始したばかりの場合、割り込みは少なくとも数クロックサイクル遅れます。

  • 割り込みをオンに戻すイベント(たとえば、割り込みサービスルーチンからの復帰)は、少なくとももう1つの命令を実行することが保証されています。そのため、ISRが終了し、割り込みが保留されている場合でも、サービスが提供される前に、さらに1つの命令を待機する必要があります。

  • 割り込みには優先順位があるため、関心のある割り込みの前に優先順位の高い割り込みが処理される場合があります。


パフォーマンスに関する考慮事項

割り込みを使用すると、スイッチが押されているかどうかを常にテストする必要がなく、プログラムの「主な作業」を続行できるため、多くの状況でパフォーマンスが向上します。とはいえ、前述のように、割り込みを処理するオーバーヘッドは、実際には単一の入力ポートをポーリングする「タイトループ」を実行する以上のものになります。たとえば、マイクロ秒以内にイベントにかろうじて応答することはできません。その場合は、割り込み(タイマーなど)を無効にして、変更するピンを探してループするだけです。


割り込みはどのようにキューに入れられますか?

割り込みには次の2種類があります。

  • 一部のユーザーはフラグを設定し、それらを引き起こしたイベントが停止した場合でも、優先順位に従って処理されます。たとえば、ピンD2での立ち上がり、立ち下がり、またはレベルの変化の割り込み。

  • その他は、「今」発生している場合にのみテストされます。たとえば、ピンD2の低レベル割り込み。

フラグを設定したものはキューに入っていると見なすことができます。割り込みフラグは、割り込みルーチンが開始されるまでセットされたままであり、その時点でプロセッサはフラグをクリアします。もちろん、フラグは1つしかないため、最初のフラグが処理される前に同じ割り込み条件が再度発生しても、2回処理されることはありません。

知っておくべきことは、割り込みハンドラをアタッチする前にこれらのフラグを設定できることです。たとえば、ピンD2のレベルの上昇または下降の割り込みに「フラグが立てられる」可能性があり、attachInterruptを実行するとすぐに、イベントが1時間前に発生した場合でも、割り込みがすぐに発生します。これを回避するには、手動でフラグをクリアします。例えば:

EIFR = bit (INTF0);  // clear flag for interrupt 0
EIFR = bit (INTF1);  // clear flag for interrupt 1

ただし、「低レベル」の割り込みは継続的にチェックされるため、注意しないと、割り込みが呼び出された後でも割り込みが発生し続けます。つまり、ISRが終了し、割り込みがすぐに再び発生します。これを回避するには、割り込みが発生したことがわかったらすぐにdetachInterruptを実行する必要があります。


ISRを作成するためのヒント

簡単に言えば、短くしてください!ISRの実行中は、他の割り込みを処理できません。そのため、やり過ぎると、ボタンを押したり、シリアル通信を受信できなくなったりする可能性があります。特に、ISR内で「印刷」のデバッグを行わないでください。それらを行うのにかかる時間は、彼らが解決するよりも多くの問題を引き起こす可能性があります。

1バイトのフラグを設定してから、メインループ関数でそのフラグをテストするのが妥当です。または、シリアルポートからの着信バイトをバッファに格納します。内蔵のタイマー割り込みは、内部タイマーがオーバーフローするたびに発生することで経過時間を追跡します。したがって、タイマーがオーバーフローした回数を知ることで、経過時間を計算できます。

ISR内部では、割り込みが無効になっていることに注意してください。したがって、millis()関数呼び出しによって返される時間が変わることを期待すると、失望につながります。この方法で時間を取得することは有効ですが、タイマーが増分しないことに注意してください。また、ISRに長時間を費やしすぎると、タイマーがオーバーフローイベントを見逃して、millis()から返される時間が正しくなくなる可能性があります。

テストは、16 MHz Atmega328プロセッサで、micros()の呼び出しに3.5625 µsかかることを示しています。millis()の呼び出しには1.9375 µsかかります。現在のタイマー値を記録(保存)することは、ISRで行うには妥当なことです。経過ミリ秒の検索は、経過マイクロ秒よりも高速です(ミリ秒カウントは変数から取得されるだけです)。ただし、マイクロ秒カウントは、Timer 0タイマーの現在の値(増加し続ける)を保存された「Timer 0オーバーフローカウント」に追加することによって取得されます。

警告: ISR内では割り込みが無効になっているため、Arduino IDEの最新バージョンはシリアルの読み取りと書き込み、および「millis」と「delay」で使用されるカウンターのインクリメントに割り込みを使用しているため、これらの関数を使用しないでください。 ISR内。別の言い方をすると:

  • 遅延させようとしないでください。例: delay (100);
  • 呼び出しからミリ秒までの時間を取得できますが、時間は増分されないため、時間の増加を待たずに遅延させないでください。
  • シリアルプリントをしないでください(例。Serial.println ("ISR entered");
  • シリアル読み取りを行わないでください。

ピン変更割り込み

ピンの外部イベントを検出するには2つの方法があります。1つ目は、特殊な「外部割り込み」ピン、D2およびD3です。これらの一般的なディスクリート割り込みイベントは、ピンごとに1つです。各ピンにattachInterruptを使用することでそれらに到達できます。割り込みの立ち上がり、立ち下がり、変化、または低レベルの条件を指定できます。

ただし、すべてのピンの「ピン変更」割り込みもあります(Atmega328では、必ずしも他のプロセッサのすべてのピンではありません)。これらはピンのグループ(D0〜D7、D8〜D13、およびA0〜A5)に作用します。また、外部イベント割り込みよりも優先順位が低くなっています。ただし、バッチにグループ化されているため、外部割り込みよりも使用するのが少し面倒です。したがって、割り込みが発生した場合は、独自のコードでどのピンが割り込みを引き起こしたかを正確に把握する必要があります。

コード例:

ISR (PCINT0_vect)
 {
 // handle pin change interrupt for D8 to D13 here
 }  // end of PCINT0_vect

ISR (PCINT1_vect)
 {
 // handle pin change interrupt for A0 to A5 here
 }  // end of PCINT1_vect

ISR (PCINT2_vect)
 {
 // handle pin change interrupt for D0 to D7 here
 }  // end of PCINT2_vect


void setup ()
  {
  // pin change interrupt (example for D9)
  PCMSK0 |= bit (PCINT1);  // want pin 9
  PCIFR  |= bit (PCIF0);   // clear any outstanding interrupts
  PCICR  |= bit (PCIE0);   // enable pin change interrupts for D8 to D13
  }

ピン変更割り込みを処理するには、次のことを行う必要があります。

  • グループ内のピンを指定します。これはPCMSKn変数です(nは以下の表の0、1、または2です)。複数のピンに割り込みを設定できます。
  • 割り込みの適切なグループを有効にする(0、1、または2)
  • 上記のように割り込みハンドラを提供する

ピンの表->ピンの変更名/マスク

D0    PCINT16 (PCMSK2 / PCIF2 / PCIE2)
D1    PCINT17 (PCMSK2 / PCIF2 / PCIE2)
D2    PCINT18 (PCMSK2 / PCIF2 / PCIE2)
D3    PCINT19 (PCMSK2 / PCIF2 / PCIE2)
D4    PCINT20 (PCMSK2 / PCIF2 / PCIE2)
D5    PCINT21 (PCMSK2 / PCIF2 / PCIE2)
D6    PCINT22 (PCMSK2 / PCIF2 / PCIE2)
D7    PCINT23 (PCMSK2 / PCIF2 / PCIE2)
D8    PCINT0  (PCMSK0 / PCIF0 / PCIE0)
D9    PCINT1  (PCMSK0 / PCIF0 / PCIE0)
D10   PCINT2  (PCMSK0 / PCIF0 / PCIE0)
D11   PCINT3  (PCMSK0 / PCIF0 / PCIE0)
D12   PCINT4  (PCMSK0 / PCIF0 / PCIE0)
D13   PCINT5  (PCMSK0 / PCIF0 / PCIE0)
A0    PCINT8  (PCMSK1 / PCIF1 / PCIE1)
A1    PCINT9  (PCMSK1 / PCIF1 / PCIE1)
A2    PCINT10 (PCMSK1 / PCIF1 / PCIE1)
A3    PCINT11 (PCMSK1 / PCIF1 / PCIE1)
A4    PCINT12 (PCMSK1 / PCIF1 / PCIE1)
A5    PCINT13 (PCMSK1 / PCIF1 / PCIE1)

割り込みハンドラー処理

マスクが複数を指定している場合(たとえば、D8 / D9 / D10で割り込みが必要な場合)、割り込みハンドラーはどのピンが割り込みを引き起こしたかを調べる必要があります。これを行うには、そのピンの以前の状態を保存し、この特定のピンが変更された場合は(digitalReadなどを実行して)解決する必要があります。


おそらくとにかく割り込みを使用しています...

「通常の」Arduino環境では、個人的に試みなくても、すでに割り込みを使用しています。millis()およびmicros()関数呼び出しは、「タイマーオーバーフロー」機能を利用します。内部タイマーの1つ(タイマー0)は、1秒あたり約1000回割り込みを行うように設定されており、内部カウンターをインクリメントして、効果的にmillis()カウンターになります。正確なクロック速度に合わせて調整が行われるため、それだけではありません。

また、ハードウェアシリアルライブラリは、割り込みを使用して受信および送信シリアルデータを処理します。これは、割り込みが発生している間にプログラムが他のことを行ったり、内部バッファーをいっぱいにしたりする可能性があるため、非常に便利です。次に、Serial.available()を確認すると、そのバッファーに何が配置されているかを確認できます。


割り込みを有効にした後で次の命令を実行する

Arduinoフォーラムで少しの議論と調査を行った後、割り込みを有効にした後に何が起こるかを明確にしました。以前は有効でなかった割り込みを有効にできると私が考えることができる主な方法が3つあります。

  sei ();  // set interrupt enable flag
  SREG |= 0x80;  // set the high-order bit in the status register
  reti  ;   // assembler instruction "return from interrupt"

すべての場合において、プロセッサは、割り込みイベントが保留中であっても、割り込みが有効になった後の次の命令(以前に無効にされていた場合)が常に実行されることを保証します。(「次へ」とは、プログラムシーケンスの次の命令を意味します。必ずしも物理的に続くものではありません。たとえば、RETI命令は、割り込みが発生した場所に戻り、さらに1つの命令を実行します)。

これにより、次のようなコードを記述できます。

sei ();
sleep_cpu ();

この保証がない場合、プロセッサがスリープする前に割り込みが発生し、その後、起こされない可能性があります。


空の割り込み

割り込みでプロセッサをウェイクさせたいだけで、特に何もしない場合は、EMPTY_INTERRUPT定義を使用できます。

EMPTY_INTERRUPT (PCINT1_vect);

これは単に「reti」(割り込みからの復帰)命令を生成します。レジスタを保存または復元しようとしないので、これは割り込みを取得してそれをウェイクアップする最も速い方法です。


クリティカルセクション(原子変数アクセス)

割り込みサービスルーチン(ISR)とメインコード(つまり、ISRにないコード)の間で共有される変数に関する微妙な問題があります。

ISRは、割り込みが有効になっているときはいつでも起動できるため、そのような共有変数は、アクセスした瞬間に更新される可能性があるため、アクセスに注意する必要があります。

最初に...いつ「揮発性」変数を使用しますか?

変数は、ISRの内部と外部の両方で使用される場合のみ、揮発性としてマークする必要があります。

  • ISRの外部でのみ使用される変数、揮発性であってなりませ
  • ISR内でのみ使用される変数、揮発性であってなりませ
  • ISRの内部と外部の両方で使用される変数、揮発性でなければなりません。

例えば。

volatile int counter;

変数を揮発性としてマークすると、コンパイラーは変数の内容をプロセッサー・レジスターに「キャッシュ」せずに、必要に応じて常にメモリーから読み取ります。これにより、処理が遅くなる可能性があります。そのため、必要のないときにすべての変数を揮発性にするだけではありません。

揮発性変数にアクセスしている間は割り込みをオフにします

たとえば、countある数値と比較するにcountは、ISRによって1バイトが更新され、他のバイトは更新されていない場合は、比較中に割り込みをオフにします。

volatile unsigned int count;

ISR (TIMER1_OVF_vect)
  {
  count++;
  } // end of TIMER1_OVF_vect

void setup ()
  {
  pinMode (13, OUTPUT);
  }  // end of setup

void loop ()
  {
  noInterrupts ();    // <------ critical section
  if (count > 20)
     digitalWrite (13, HIGH);
  interrupts ();      // <------ end critical section
  } // end of loop

データシートをお読みください!

割り込み、タイマーなどの詳細については、プロセッサーのデータシートを参照してください。

http://www.atmel.com/images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet_Complete.pdf


さらなる例

スペースに関する考慮事項(ポストサイズの制限)により、サンプルコードをさらにリストできなくなります。その他のコード例については、割り込みに関する私のページを参照してください


非常に有用な参考文献-それは印象的な素早い回答でした。
Dat Han Bag

参考質問でした。私は答えを準備しましたが、答えが長すぎなければそれはさらに速くなったので、それを切り捨てる必要がありました。詳しくはリンク先をご覧ください。
Nick Gammon

「スリープモード」について、Arduinoを500msの間スリープさせるのは効率的ですか?
Dat Ha

@Nick Gammon CPUの電源をオンまたはオフにする(自動化を使用するかどうかにかかわらず)とは、従来とは異なる割り込みとして定義できます。「私は答えを用意しました」-あなたはそれが持っていると思ったその瞬間のすべての魔法を取り除いただけです。
Dat Han Bag

1
それは本当ではないようです。私が持っているパワーダウン・モードから復帰するためにピン変化割り込みを使用しています。また、割り込みに関する私のページで言及したように Atmelは、外部割り込みがプロセッサをウェイクアップすることを確認しました(つまり、上昇/下降/変化および低)。
Nick Gammon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.