AVRGCCメモ

割り込み処理中で操作される変数の定義
sbi/cbi関数

 割り込み処理中で操作される変数の定義
 割り込み処理の中で、グローバル変数の値を変えるようなプログラムを組んだときは注意が必要。
 たとえば

    uint16_t timer_count;

    timer_start(msec);
    while(timer_count > 0);
 というプログラムを書いたとする。
 グローバル変数timer_countの値はtimer_startルーチンの中で設定され、同時にタイマ割り込みが有効になる。timer_countの値はタイマ割り込みルーチンの中で減算される。
 whileループはtimer_countがゼロになった時にループを脱出するように思えるが、実際にはそうはならない。
 何故かはコンパイル結果を見ればすぐ分かる。

    119 001c 8091 0000             lds r24,timer_count
    120 0020 9091 0000             lds r25,(timer_count)+1
    121                    .L33:
    122 0024 0097                  sbiw r24,0
    123 0026 F1F7                  brne .L33
 timer_countの値をレジスタにロードして、あとはレジスタに対してゼロチェックを行っている。
 timer_countの値が割り込み中で変化しても、それはレジスタには反映されないので、この条件チェックは永久に成立しない事になる。
 これを避けるためには、timer_count変数の宣言にvolatileを付け加えれば良い。

    volatile uint16_t   timer_count;
 変更後のコンパイル結果はこうなる。

    119                    .L33:
    120 001c 8091 0000             lds r24,timer_count
    121 0020 9091 0000             lds r25,(timer_count)+1
    122 0024 892B                  or r24,r25
    123 0026 D1F7                  brne .L33
 一見すると変わってないように見えるが、ラベルの位置が違うので、これなら毎回timer_countの値がチェックされる。

 sbi/cbi関数
 AVRアセンブラではビット制御命令sbi/cbiはI/Oポートレジスタにしか使用できず、制御レジスタに対して使おうとするとアセンブルエラーになってしまうが、AVR-GCCのsbi/cbi関数は制御レジスタに対しても使用することができる。 

    sbi(PORTD, 1);
    sbi(TIMSK, 1);
 実際にはsbi命令にコンパイルされるのは前者だけで、後者は複数の命令で同じ動作を行うようなオブジェクトが出力される。
 コンパイル結果を見れば違いは一目瞭然。

    361:avrusb.c      ****         sbi(PORTD, 1);
        870 021a 919A                  sbi 18,1

    362:avrusb.c      ****         sbi(TIMSK, 1);
        874 021c 82E0                  ldi r24,lo8(2)
        876 021e 09B6                  in __tmp_reg__,57
        877 0220 082A                  or __tmp_reg__,r24
        878 0222 09BE                  out 57,__tmp_reg__
 制御レジスタに対するsbi/cbi関数は、ポートの値を読み込んでからビットをセット(またはクリア)して、同じポートへ出力するという命令にコンパイルされている。

戻る


mailto:[tokoya][at][mars.dti.ne.jp]