SNSへはこちら

AVRでアセンブリ(2) – 割り込みによるLチカ

つづいて、割り込みを使って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 は、0x001CTIMER0 の 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それ以外の一般のストア命令です。TIMSK00x6E であり、63 を超えてますので、out は使えない、ということになります。

seicli と逆の命令で、すなわち割り込みを許可する命令です。これをしないと CPU コアレベルで割り込みが全てマスクされてしまい、一切割り込みが入りません。そして最後は ret で抜けています。
ここで、reti という命令もあります。これは割り込みを有効にしてリターンするというものです。seiret という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になっていて、期待通りの動作をしています。