つづいて、割り込みを使ってLチカしたいと思います。最初に謝っておきますが、8bit タイマーを使ったため肉眼では点滅を確認できないと思います。
実際にLEDを点滅させたい場合は、16bit タイマーに変える等をしていただくと良いと思います。
今回はオシロによる波形を掲載したいと思います。
使用する AVR のクロックは 8MHz, Fuse Bit による8分周なしとします。
コード
早速コードです。前回のものよりはスッキリしています。
.include "../m328pdef.inc"
.device atmega328p
.def tmp = r16
.cseg
.org 0x0000
rjmp main
.org 0x001C
rjmp TIMER0_COMPA
tm0_irq_init:
cli ; global interrupt disable
ldi tmp, (2 << WGM00) ; CTC mode
out TCCR0A, tmp
ldi tmp, (2 << CS00) ; clk/8
out TCCR0B, tmp
ldi tmp, (1 << OCIE0A) ; compare match interrupt on A
sts TIMSK0, tmp
ldi tmp, 100 ; compare match value
out OCR0A, tmp
sei ; global interrupt enable
ret
main:
sbi DDRB, PB1
rcall tm0_irq_init
loop:
rjmp loop
TIMER0_COMPA:
in tmp, PORTB
ldi r17, (1 << PB1)
eor tmp, r17
out PORTB, tmp
reti
割り込みベクタ部分
先頭部分です。.cseg
は「ここからプログラム領域だよ」と明示するためのディレクティブで、特に無くても問題ありません。気分で付けました。
.cseg
.org 0x0000
rjmp main
.org 0x001C
rjmp TIMER0_COMPA
.org 0x001C
は、0x001C
に TIMER0 の A とのコンペアマッチ時割り込みのジャンプ命令を書いておきます。このアドレスはデータシート通りです。
TIMER0初期化部
tm0_irq_init:
cli ; global interrupt disable
ldi tmp, (2 << WGM00) ; CTC mode
out TCCR0A, tmp
ldi tmp, (2 << CS00) ; clk/8
out TCCR0B, tmp
ldi tmp, (1 << OCIE0A) ; compare match interrupt on A
sts TIMSK0, tmp
ldi tmp, 100 ; compare match value
out OCR0A, tmp
sei ; global interrupt enable
ret
この部分です。関数として用意しているので、最後は ret
としています。
まず、cli
ですが、これは割り込みを禁止する命令です(多分、gcc だと __disable_irq()
だった気が(きっとおそらく多分))。初期化直後なのでわざわざ禁止する必要はないと思うのですが、念の為。
out
は IO レジスタ(ただしアドレスが 0〜63 に限る)に対して、レジスタを第2オペランドに取って値を転送する命令です。即値は置けません。なので、直前で ldi
によって tmp
に即値をロードさせています。
sts
という命令がありますが、これは概ね out
と同じです。out
は転送先のアドレスが 63 までの IO レジスタ に対するストア命令でしたが、sts
はそれ以外の一般のストア命令です。TIMSK0
は 0x6E
であり、63 を超えてますので、out
は使えない、ということになります。
sei
は cli
と逆の命令で、すなわち割り込みを許可する命令です。これをしないと CPU コアレベルで割り込みが全てマスクされてしまい、一切割り込みが入りません。そして最後は ret
で抜けています。
ここで、reti
という命令もあります。これは割り込みを有効にしてリターンするというものです。sei
と ret
という2つの命令を1行で書けるので、これを使用してもいいと思います。実際に、下の割り込み関数の最後には使用しています。
ということで、設定した部分を列挙するとこんな感じです。
cli
で割り込みを禁止TCCR0A
に CTC モードを設定する(0CR0A
が周期レジスタ)TCCR0B
にクロックの分周を設定するTIMSK0
でコンペアマッチ時割り込みを有効化するOCR0A
にコンペアマッチ値を書き込むsei
で割り込みを有効化
割り込み関数
TIMER0_COMPA:
in tmp, PORTB
ldi r17, (1 << PB1)
eor tmp, r17
out PORTB, tmp
reti
ここでは LED を XOR によってチカチカさせているだけです。本当ならば割り込みフラグのクリアが必要になりますが、データシートによると関数が実行されたタイミングでハードウェアによってクリアされるということなので、今回は不要です。
AVR マイコンの仕様として、割り込みはレベルセンシティブで反応します。ですので、割り込みが入ると AVR は全割り込みを無効化します。無効化しないと、クロック毎に同じ割り込みが入りまくって無限ループになってしまいますね。割り込み処理が終わったら再度有効化する必要があります。なので、末尾には reti
としているわけです。
割り込み入って早々に sei
してしまえば、他の要因による多重割り込みも可能な気がしますね(多分)。
動作
ということで、まずは期待する動作を確認してみましょう。まず CPU クロックは 8MHz であり、タイマーへのクロック入力は8分周されているので、1MHz 入力となります。タイマー周期は 100 ですから、割り込みが入るのは 10kHz になるはずです(正確には、タイマー周期を99とすべき)。
割り込み毎にオンとオフを繰り返しているので、実質周期は半分の5kHzになっていて、期待通りの動作をしています。