クレア工房 | 電子工作 | AVR | AVR の 割り込み

AVR の 割り込み

割り込みってなに?

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-GCC は割り込み処理の記述がちょっとばかり不得意

AVR では割り込みに伴って発生するコンテキストスイッチがけっこう高くつきます。 どういうことかというと、SREG, R0からR31のすべてのレジスタを保存するのに 3 + 64 クロックほどかかります。 復元するのにも同数かかります。 AVR-GCC の呼び出し規約は、ある程度レジスタを破壊することを前提としています。 レジスタ保存と復元のコードがたっぷりと発生することは覚悟しておきましょう、 ということです。

割込みを捌くのに時間が掛かることは、それ自体が致命的な結果を招くことがあります。

ATtiny2313 は 128bytes の RAM しか内蔵していません。 メイン処理で 32bytes, 割り込みに 32bytes と考えると、 スタックで 50% 近いメモリが奪われる計算になります。 ATmega88 クラスになると 512bytes ほどありますので、 スタック領域についても余裕をもって対処できます。

AVR の基本はレベルトリガ

AVR の割り込みは基本的にレベルトリガです。 割り込み関数の中で割り込みの発生原因を解消してから return しないと、 return した直後に再び割り込みが発生します。

上記の例に示したタイマ割り込みでは、 割り込みが発生し関数が呼び出された段階でハードウェア的にフラグが解除されますので、 何もしていません。

もし USART からの受信を割り込みで捌くことにしたのなら、 (ATmega88 の場合であれば) USART_RX_vect の中で UDR0 を必要な回数だけ読む必要があるでしょう。

AVR に RAM がないとき

今はもう過去のものになってしまいましたが、 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 待機完了

Copyright © 2012, 2014 clare. All rights reserved.