AVR における割り込みとは、 ハードウェア的な信号をもとにサブルーチン呼び出しを行う (CPU に call または rcall 相当の処理を起動させる) ための仕掛けです。
ハードウェアはCPUにレジスタを通じて値を伝えます。 同様に、ハードウェアはCPUに割り込みを通じてタイミングを伝えます。 CPUは周辺回路から割り込み要求(Interrupt Request; IRQ)という呼び鈴が押されると、 現在の命令の(次の)アドレスをスタックに積んで(現在の命令の処理を済ませて)から あらかじめ定められた割り込み処理ルーチン(Interrupt Service Routine; ISR) に制御を移します。 一般にソフトウェアは、ハードウェア割り込みを利用してハードウェアイベントの待ち合わせをします。 別の表現をすると、 何かしらの時間待ちの実装には必ずハードウェア割り込みを利用するのが常なのであり、 レジスタの退避時間をも惜しむほどの短時間でない限りビジーループは使用しないのが常なのです。 割り込みをうまく使えば、 空いている時間に不要な電源を切って寝て待つ (SLEEPする、他のCPUの命令に例えるならHLTする) こともできますし、 あるいは他のメインタスクを実行することもできるでしょう。
UNIX系統のlibcを使ってプログラミングをしたことがある方には、 割り込みはシグナルに似たものであるということができます。 UNIX libcのシグナルは、ハードウェア割り込みを模して設計されたものだからです。 しかし、これからAVR向けに記述しようとしている処理はカーネル内部であって、 OSに管理されたユーザランドではありません。 ハードウェア割り込みを理解するにあたり、 シグナル機構はあまり適切な教材ではありませんので、 UNIX libc風のシグナル機構(#include <signal.h>の類) のことは忘れてしまいましょう。
多用されるものの中からとりあえずの例を挙げると、 AVR には次のような割り込みがあります。
割り込み原因として利用できるイベントは他にも多数あります。 新しい品種では機能が増える分だけ、 割り込みの種類も必然的に増える傾向にあります。
CPUは1度に1つの処理しかできませんので、多数の割り込みを同時に捌くことはできません。 このため、原則的には複数ある割り込みに優先順位をつけ、 優先順位の高いものから受け付けて処理します。 AVR の割り込みでは、 ベクタ番号またはアドレスの小さいものが優先度の高いものとして扱われます。
割り込みの優先度を均等に回すための機能は備え付けられていません。 周辺回路に機能を割り当てるときは、 割り込みの優先度もそれなりに考慮する必要があります。
AVR の割り込みでは、呼び出し先のアドレスがフラッシュメモリ冒頭に固定されており、 割り込みベクタ領域と呼びます。 AVR の割り込みベクタ領域には、通常、jmp 命令または rjmp 命令が配置されます。
AVR では割り込みベクタに制御が移るときに無限ループしないよう、 いったん割り込みが禁止されます。 割り込みから戻るときは再び割り込みを許可する必要がありますが、 許可する動作は ret 命令の後で発生して欲しいものであるため、 ret 命令と sei 命令を組み合わせた動作をする専用の命令として reti 命令が用意されています。 AVR では、割り込み処理ルーチンは reti 命令で終わります。
AVR の割り込みでは、割り込み処理ルーチンで SREG を含めた各種レジスタの保存と復帰を行う必要があります。 このあたりは他の CPU と事情はさほど変わりません。
avr libc に便利なマクロが用意されていますので、マクロを使うのが定番です。 割り込みを処理する関数は、avr libc を用いて次のように記述します。
#include <avr/io.h> #include <avr/interrupt.h> volatile unsigned char timer0_cnt; ISR(TIMER0_OVF_vect) { timer0_cnt++; }
マクロ ISR() が割り込み関数の記述に必要な委細を包み隠しています。 同じくマクロ展開されるシンボル 〜_vect が、 どの種類の割り込み関数を定義するかを指定しています。
上記の例では、割り込み処理が書き換えている変数 timer0_cnt に volatile がついています。 これは、 「格納されている値がいつどのように変化するか予測してはいけない (最適化してはいけない) 変数である」 ということをコンパイラに対して指示しています。 メイン処理が同じ変数を操作して割り込み処理との間でデータを受け渡す場合に、 受け渡しの境界点となる変数に volatile を (必要に応じて) 使います。
AVR では割り込みに伴って発生するコンテキストスイッチがけっこう高くつきます。 どういうことかというと、SREG, R0からR31のすべてのレジスタを保存するのに 3 + 64 クロックほどかかります。 復元するのにも同数かかります。 AVR-GCC の呼び出し規約は、ある程度レジスタを破壊することを前提としています。 レジスタ保存と復元のコードがたっぷりと発生することは覚悟しておきましょう、 ということです。
割込みを捌くのに時間が掛かることは、それ自体が致命的な結果を招くことがあります。
ATtiny2313 は 128bytes の RAM しか内蔵していません。 メイン処理で 32bytes, 割り込みに 32bytes と考えると、 スタックで 50% 近いメモリが奪われる計算になります。 ATmega88 クラスになると 512bytes ほどありますので、 スタック領域についても余裕をもって対処できます。
AVR の割り込みは基本的にレベルトリガです。 割り込み関数の中で割り込みの発生原因を解消してから return しないと、 return した直後に再び割り込みが発生します。
上記の例に示したタイマ割り込みでは、 割り込みが発生し関数が呼び出された段階でハードウェア的にフラグが解除されますので、 何もしていません。
もし USART からの受信を割り込みで捌くことにしたのなら、 (ATmega88 の場合であれば) USART_RX_vect の中で UDR0 を必要な回数だけ読む必要があるでしょう。
今はもう過去のものになってしまいましたが、 AT90S1200 のように RAM を持たないデバイスのために割り込み処理を記述する場合、 主たるレジスタである R0〜R31 に必要なデータを退避します。 たとえば R0, R1, R16, R17 を割り込み処理に割り当て、 残りのレジスタをメイン処理で使うといった切り分けをします。
RAM のない AVR では、 関数呼び出しを管理するスタックがハードウェア的に実装されています。 当然のことながら使用できるスタックの段数には限りがあります。 AT90S1200 であれば、たった3段のスタックしかありません。 スタックが溢れないように注意してソフトウェアを設計する必要があります。
処理フローの中で変数を矛盾なく設定するために「割り込まれたくない」場合は、 cli 命令で割り込み処理の呼び出しを抑止し、 sei 命令で抑止を解除することができます。 avr libc では、 sei() マクロと cli() マクロがそのために用意されています。
AVR では割り込みが発生した時点で割り込みの発生が禁止されており、 reti 命令でメイン処理に復帰するまでこの状態は続きます。
割り込みの処理自体で長時間 CPU を占有してしまう場合など、 他のさらに優先度の高い割り込みを先に処理したい場合には、 割り込み処理の中で sei 命令を実行して割り込みを許可できます。
いくつかの条件を満たさないと割り込みは発生しません。
割り込みのマスクとは、 たとえるなら鳴っている電話機を布団のなかに押し込めてしまう操作に対応します。 単にマスクしたからといって、取るべき電話機が鳴り止むわけではありません。
Arduino でお馴染みの ATmega88 系統では次の割り込みがあります。 割り込みに用いるベクタ番号や処理の記述に用いるシンボルは AVR の種類によってマチマチです。 具体的なことは個別にデータシートを確認してください。
No. | Symbol | Name | Remarks |
---|---|---|---|
0 | - | リセット | 起動アドレス、または初期化ルーチン |
1 | INT0_vect | 外部割り込みピンINT0 | 1ピンのみなので高速なピン状態変化通知 |
2 | INT1_vect | 外部割り込みピンINT1 | 1ピンのみなので高速なピン状態変化通知 |
3 | PCINT0_vect | 汎用ピン変化0 | 最大8ピンまで集約される |
4 | PCINT1_vect | 汎用ピン変化1 | 最大8ピンまで集約される |
5 | PCINT2_vect | 汎用ピン変化2 | |
6 | WDT_vect | 番犬タイマ | 異常処理やハングアップの検出の試みに使われます |
7 | TIMER2_COMPA_vect | タイマ2 比較合致 A | PWMでのタイミング取得など |
8 | TIMER2_COMPB_vect | タイマ2 比較合致 B | PWMでのタイミング取得など |
9 | TIMER2_OVF_vect | タイマ2 オーバーフロー | |
10 | TIMER1_CAPT_vect | タイマ1 捕獲イベント | |
11 | TIMER1_COMPA_vect | タイマ1 比較合致 A | |
12 | TIMER1_COMPB_vect | タイマ1 比較合致 B | |
13 | TIMER1_OVF_vect | タイマ1 オーバーフロー | |
14 | TIMER0_COMPA_vect | タイマ0 比較合致 A | |
15 | TIMER0_COMPB_vect | タイマ0 比較合致 B | |
16 | TIMER0_OVF_vect | タイマ0 オーバーフロー | 比較的単純な時間待ちに使われます |
17 | SPI_STC_vect | SPI シリアル転送完了 | |
18 | USART_RX_vect | USART 受信完了 | 受信レジスタにデータが届いた |
19 | USART_UDRE_vect | USART 送信可能 | 送信レジスタが空になった |
20 | USART_TX_vect | USART 送信完了 | 送信シフトレジスタが空になった |
21 | ADC_vect | ADC 変換完了 | |
22 | EE_READY_vect | EEPROM 待機完了 | |
23 | ANALOG_COMP_vect | アナログコンパレータ | |
24 | TWI_vect | TWI 待機完了 | |
25 | SPM_READY_vect | SPM 待機完了 |