AVRメモ

多重割り込みの許可
タイプによる割り込みベクタの違い
ポートの入力と出力
ADDI命令
.DBで確保されるメモリ
プログラムメモリのアクセス
SRAMのオリジンアドレス
タイマオーバーフロー割り込み使用時の設定値
ビット操作
内蔵RCオシレータの発振周波数
内蔵EEPROMのRead/Write

 多重割り込みの許可
 割り込み処理中は自動的に他の全ての割り込みが禁止状態になる。割り込み処理中に他の割り込みを許可したい場合は、割り込み処理中でSEIを実行する。
 CLI/SEIを内部で行っているサブルーチンを、割り込み処理中からCALLした場合、意図せずに多重割り込みを許可した事になってしまうので要注意。

 タイプによる割り込みベクタの違い
 同じAVRでも、割り込みベクタのアドレスはタイプ毎に異なる。
 例えばAT90S1200とAT90S2313を比べた場合、ぞれぞれのベクタアドレスは以下のようになる。リセットとINT0割り込みのアドレスは同じだが、それ以降の割り込みはベクタアドレスがずれている。
AT90S1200 AT90S2313
$0000 リセット割り込み $0000 リセット割り込み
$0001 INT0割り込み $0001 INT0割り込み
$0002 タイマ/カウンタ0
オーバーフロー割り込み
$0002 INT1割り込み
$0003 アナログコンパレータ
出力遷移割り込み
$0003 タイマ/カウンタ1
キャプチャ発生割り込み
$0004 タイマ/カウンタ1
コンペア一致割り込み
$0005 タイマ/カウンタ1
オーバーフロー割り込み
$0006 タイマ/カウンタ0
オーバーフロー割り込み
$0007 UART 受信完了割り込み
$0008 UART 送信バッファ空き割り込み
$0009 UART 送信完了割り込み
$000A アナログコンパレータ
出力遷移割り込み
 昔作ったソースファイルの一部をコピーして、新しいプログラムを作る場合、同一のAVRチップなら割り込みベクタ部分もコピーして問題ないが、それ以外の時は要注意。

 ポートの入力と出力
 AVRは一つのポートが入力と出力に分かれている。出力ポートから入力することもできるが、この場合は出力した内容がそのまま読み出されてくるだけで、実際の入力は行われていない。
 入出力を間違えても、一見するとプログラムは正常に動いているように見えてしまうため、要注意。

 ADDI命令
 AVRには何故かADDI(即値加算)命令が無い。
 SUBI(即値減算)命令はあるので、SUBI r16,-10のように補数を即値減算する事でADDIの代わりにすることができる。しかし、この場合キャリーフラグが立たないため、複数バイト長の加算には使用できない。
 例外としてX,Y,Zの各レジスタペアに対してはADIW命令で即値加算を行うことができるが、加算できる値が0〜63の範囲内に限られる。

 .DBで確保されるメモリ
 AVRのプログラムメモリは必ずワード単位でアクセスされるので、.DBでプログラムメモリ上に定数を確保しようとしたときに、書き方によって確保されるワード数が異なる場合がある。

    .DB "ABCDEFGHIJ"
 と書いた場合は

    000016 4241
    000017 4443
    000018 4645
    000019 4847
    00001a 4a49
 のように5ワード確保されるが、これを

    .DB "ABCDE"
    .DB "FGHIJ"
 と書いた場合は

    000016 4241
    000017 4443
    000018 0045
    000019 4746
    00001a 4948
    00001b 004a
 のように6ワード確保される。更に


    .DB "A"
    .DB "B"
    .DB "C"
    .DB "D"
    .DB "E"
    .DB "F"
    .DB "G"
    .DB "H"
    .DB "I"
    .DB "J"
 と書いた場合は

    000016 0041
    000017 0042
    000018 0043
    000019 0044
    00001a 0045
    00001b 0046
    00001c 0047
    00001d 0048
    00001e 0049
    00001f 004a
 となって10ワード確保される。文字列を.DBでプログラムメモリ上に確保する時に、途中で分けて宣言してしまうと、思わぬ結果を招く場合がある。

 プログラムメモリのアクセス
 AVRのプログラムメモリはワード単位でアクセスされるが、レジスタは基本的に8ビットなので、ワード単位でデータを読むことができない。
 そこで、プログラムメモリを読むときは必ずZレジスタペアによる間接アドレッシングを使い、ZLレジスタの下位1ビットでワードの上位8ビットと下位8ビットのどちらを読み込むのかを指定する。ちょっと分かりづらいが、要するにプログラムメモリのワードアドレスをZレジスタペアに1ビットシフトした状態で格納して、空いた1ビットでワードの上位(1)と下位(0)のどちらを読み込むのか指定すれば良いだけ。
ZH ZL
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
ワードアドレス 上位/下位
バイト指定
 この場合ワードアドレスは15ビット(32Kワード)しか指定できないが、よく使われるAVRは1K〜4Kワードのプログラムメモリしか内蔵していないので、特に問題にはならない。
 ちなみに32Kワード以上のプログラムメモリを内蔵するATmegaシリーズでは、拡張間接アドレッシング命令がサポートされている。
 実際にプログラムメモリへアクセスする時のプログラムは、以下のようになる。

    ldi     ZL,low(DATA*2)
    ldi     ZH,high(DATA*2)
    ldi     YL,low(BUF)
    ldi     YH,high(BUF)
    lpm
    st      Y+,r0
    adiw    ZL,1
    lpm
    st      Y,r0
 Zレジスタペアに、プログラムメモリアドレスを2倍(1ビットシフト)した値を格納する事と、lpm命令で読み出したデータは、常にR0に格納されるという点がポイント。
 ATmegaではlpm命令が拡張されて、読み出したデータを格納するレジスタや、ポストインクリメントの指定ができるようになっている。

おまけ:
 レジスタペアにアドレスポインタを格納するときは、以下のようなマクロを定義して使うと便利。

    .MACRO LDICA
        ldi @0L,low(@1*2)
        ldi @0H,high(@1*2)
    .ENDMACRO

    .MACRO LDIDA
        ldi @0L,low(@1)
        ldi @0H,high(@1)
    .ENDMACRO
 このマクロを使うと、上のプログラムは以下のようになる。

    LDICA   Z,DATA
    LDIDA   Y,BUF
    lpm
    st      Y+,r0
    adiw    ZL,1
    lpm
    st      Y,r0

 SRAMのオリジンアドレス
 開始アドレスを指定しない場合の、プログラムメモリとEEPROMメモリのオリジンは$00からになるが、SRAMの場合は$60からになる。
 これは何故かというと、下のようにSRAMアドレスの$00〜$1Fに汎用レジスタ、$20〜$5FにI/Oレジスタがマッピングされているため。
汎用レジスタ R0 $00
R1 $01
R2 $02
| |
R29 $1D
R30 $1E
R31 $1F
I/Oレジスタ $00 $20
$01 $21
$02 $22
| |
$3D $5D
$3E $5E
$3F $5F
SRAM $60 $60
$61 $61
$62 $62
| |
$DD $DD
$DE $DE
$DF $DF
 SRAMのアドレス指定で、汎用レジスタ及びI/Oレジスタ領域を強制的に指定することもできる。アセンブルすると警告が出るが、エラーにはならない。
 レジスタアドレスに重ねてメモリ領域を確保すると、その領域に対する読み書きは普通のレジスタアクセスと同じになる。

    .DSEG
    .ORG    $0
    BUF:    .byte  32

    .CSEG
    ldi     R16,0xaa
    sts     BUF+17,R16
 このコードは

    ldi     R16,0xaa
    mov     R17,R16
 と同じ結果になる。これを利用してX/Y/Zレジスタペアによる間接アドレッシングを、連続した汎用レジスタに対して行うことができる。

    ldi     XL,low(BUF+17)
    ldi     XH,high(BUF+17)
    ldi     R16,0xaa
    st      X+,R16
    st      X+,R16
    st      X+,R16
 このコードを実行すると、R17〜R19にR16の内容が格納される。やや危険だが、使い方次第では便利な場合もある。
 でもやらないほうが無難。

 タイマオーバーフロー割り込み使用時の設定値
 AVRの内蔵タイマは、システムクロックを設定値で割った時間毎にカウントアップされる。たとえばシステムクロックが4MHzで、分周比が1/256の時、4000000Hz/256->15625Hzなので、0.000064秒毎にカウントアップされることになる。

 目的とする時間から分周比と、タイマの初期値を算出する場合。以下のようにして行う。

 1).システムクロック値を目的の時間で割り、分周比を決める
 たとえば200mSecの場合、200mSecは5Hzだから、4000000Hz/5Hz=800000Hzとなる。この結果を設定可能な分周比(1/8/64/256/1024)で割り、8ビットまたは16ビットレジスタで設定可能な値を決める。
 ちなみにこの場合、最大の分周比1/1024で設定しても8ビットタイマに設定できる値にはならないので、16ビットタイマを持たないAVRでは、割り込み処理内で別のカウンタを使う等の処理を行う必要がある。
 2).分周後のクロック値を目的の時間で割り、カウンタ値を計算する
 分周比が決まったら、分周クロック/目的時間を計算する。目的時間を200mSec、分周比を1/1024とした場合、4000000Hz/1024/5Hz=781.25となる。
 分周比を1/256とした場合は、4000000Hz/256/5Hz=3125となり、どちらの場合でも16ビットタイマで設定可能だが、この場合なるべく誤差の少ない方、つまり1/256を選択したほうが良い。
 3).タイマレジスタに設定する値を計算する
 タイマはカウントアップタイマで、オーバーフロー時に割り込みが発生する。従って実際にタイマレジスタへ設定する値は、レジスタの最大値からカウンタ値を引いた値となる。
 システムクロック4MHzの時、200mSecのタイマを設定する場合は、分周比1/256、カウンタ値3125となるが、実際にレジスタへ設定する値は65536-3125=62411にしなくてはならない。
 これを忘れると想定していないタイミングでタイマ割り込みが発生して、悩む原因となる。
おまけ:
タイマ/カウンタ0制御レジスタ
TCCR0
7 6 5 4 3 2 1 0
- - - - - CS02 CS01 CS00

タイマ/カウンタ0入力クロック選択
CS02 CS01 CS00 動作
0 0 0  停止
0 0 1  システムクロック/1
0 1 0  システムクロック/8
0 1 1  システムクロック/64
1 0 0  システムクロック/256
1 0 1  システムクロック/1024
1 1 0  PD4の立ち下がり
1 1 1  PD4の立ち上がり

タイマ/カウンタ0
TCNT0
7 6 5 4 3 2 1 0
タイマ/カウンタ0初期値

タイマ/カウンタ1制御レジスタA
TCCR1A
7 6 5 4 3 2 1 0
COM1A1 COM1A0 - - - - PWM11 PWM10

コンペア1出力選択
COM1A1 COM1A0 意味
0 0  OC1(PB3)ピン未使用
0 1  OC1ピントグル動作
1 0  OC1ピンLowレベル出力
1 1  OC1ピンHightレベル出力

PWM動作選択
PWM11 PWM10 意味
0 0  PWM動作禁止
0 1  8ビットPWM
1 0  9ビットPWM
1 1  10ビットPWM

タイマ/カウンタ1制御レジスタB
TCCR1B
7 6 5 4 3 2 1 0
ICNC1 ICES1 - - CTC1 CS12 CS11 CS10

ICNC1  キャプチャノイズ除去
ICES1  キャプチャトリガ入力エッジ選択
CTC1  一致クリア許可

タイマ/カウンタ1入力クロック選択
CS12 CS11 CS10 動作
0 0 0  停止
0 0 1  システムクロック/1
0 1 0  システムクロック/8
0 1 1  システムクロック/64
1 0 0  システムクロック/256
1 0 1  システムクロック/1024
1 1 0  PD5の立ち下がり
1 1 1  PD5の立ち上がり

タイマ/カウンタ1
TCNT1H TCNT1L
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
タイマ/カウンタ初期値(上位) タイマ/カウンタ初期値(下位)

タイマ/カウンタ1 コンペアレジスタ
OCR1AH OCR1AL
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
コンペア値(上位) コンペア値(下位)

タイマ/カウンタ1 キャプチャレジスタ
ICR1AH ICR1AL
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
キャプチャ値(上位) キャプチャ値(下位)

タイマ/カウンタ割り込みマスクレジスタ
TIMSK
7 6 5 4 3 2 1 0
TOIE1 OCIE1A - - TICIE1 - TOIE0 -

TOIE1  タイマ/カウンタ1オーバーフロー割り込み許可
OCIE1A  タイマ/カウンタ1コンペア割り込み許可
TICIE1  キャプチャ割り込み許可
TOIE0  タイマ/カウンタ0オーバーフロー割り込み許可

タイマ/カウンタ割り込み要求フラグレジスタ
TIFR
7 6 5 4 3 2 1 0
TOV1 OCF1A - - ICF1 - TOV0 -

TOV1  タイマ/カウンタ1オーバーフロー割り込み要求フラグ
OCF1A  コンペア割り込み要求フラグ
ICF1  キャプチャ割り込み要求フラグ
TOV0  タイマ/カウンタ0オーバーフロー割り込み要求フラグ

 ビット操作
 I/Oレジスタに対するビット操作命令と、汎用レジスタ、制御レジスタに対するビット操作命令は、オペランドの形式が異なる。I/Oレジスタのビット操作命令は、1ビットのみのON/OFFなのでビット位置を指定するが、汎用レジスタ、制御レジスタに対するビット操作命令は、ビットパターンを指定する。
 たとえば5ビット目をONにしたい場合、対象がI/Oレジスタの場合は

    sbi     PORTB,5
 と指定するが、対象が汎用レジスタの場合は

    sbr     R16,0b00100000
 というふうに指定する。AVRアセンブラはオペランドの定数に数式やシフト演算子が使用できるので、汎用レジスタに対するビット操作命令は

    sbr     R16,(1<<5)
 のように書くのが普通。
 この命令はori命令(論理OR)と全く同じなのでは?と思った人は正解。AVRアセンブラでアセンブルするとsbroriは全く同じバイナリが出力される。
 同様に汎用レジスタのビットクリアを行うcbrandi(論理AND)も同じコードとしてアセンブルされるが

    cbr     R16,(1<<5)
    andi    R16,(1<<5)
 は以下のようにオペランド部分のアセンブル結果が異なる。

    +0000000F: 7D0F ANDI    R16,0xDF     ; 0xDF = 0b11011111 = 223
    +00000010: 7200 ANDI    R16,0x20     ; 0x20 = 0b00100000 = 32
 cbr命令の時は、AVRアセンブラがオペランドの定数をビット反転して、バイナリコードを出力するようになっている。つまり、cbr命令は以下のマクロ定義と、全く同じと言うことになる。

    .MACRO cbr
        andi    @0,~(@1)
    .ENDMACRO
 ちなみにビット操作命令が使えるのは、PORTB/PORTD等が含まれるI/Oレジスタの前半(レジスタ番号$00〜$1F/アドレス$20〜$3F)のみで、後半のGIMSK/TIMSK等のレジスタに対して行うことはできない(アセンブルエラーになる)。

 内蔵RCオシレータの発振周波数
 AT90S1200の内蔵RCオシレータは1MHz固定と思われている事が多いが、これはVccとして+5Vを供給した時に限っての話。実際の発振周波数は、電源電圧によってかなり変動する。
 3.3V動作時には、ほぼ半分の500KHz動作となってしまう。
 ワンダースワンの拡張端子から供給される電圧は3.3Vなので、スワンとAVRを直接接続する時には注意が必要。外部クロックで動作させる時は問題ない。

 AT90S1200はUARTを内蔵していないため、シリアル通信はソフトウェアUARTで行う事になる。内蔵RCオシレータ動作時にソフトウェアUARTを行う場合、電源電圧と温度によってクロックが無視できないほど変動するので、高いボーレートでは正しく通信が行えなくなる可能性が高い。
 内蔵RCオシレータを使うのは、精度を必要としない用途のみと考えた方が良い。

 内蔵EEPROMのRead/Write
 AVRの内蔵EEPROMへのアクセスは、通常のメモリアクセスとは異なり、制御レジスタを介して1バイトずつ読み書きする方式になっている。
 読むときは簡単で、EEARレジスタに読みたいEEPROMのアドレスを入れ、コントロールレジスタEECRのEEREビット(EEPROM読み込み許可ビット)をオンにすると、内部でEEPROMリードストローブ信号が発生してデータがEEDRレジスタに読み出されてくる。
 EEPROMを読むときのプログラムは、以下のようになる。

EEPROM_READ:
    sbic    EECR,EEWE
    rjmp    EEPROM_READ
    out     EEAR,R16
    sbi     EECR,EERE
    in      R17,EEDR
    ret
 最初に制御レジスタEECRのEEWEビットを監視しているのは、書き込み動作中はデータが正しく読み出せないため。このビットがゼロになっている時だけ、読み出しが行える。
 R16にアドレスを入れてこのサブルーチンをCALLすると、R17にそのアドレスの内容が格納される。

 EEPROMに書き込みを行うときも同じように、EEARレジスタにアドレス、EEDRレジスタにデータを格納するが、コントロールレジスタEECRの使い方がちょっとだけ異なる。
 アドレスとデータをそれぞれのレジスタに入れたあと、EECRのEEMWEビット(マスタ書き込み許可ビット)をオン、EEWEビット(書き込み許可ビット)をオフに設定(同時に)し、そのすぐ後でEEWEビット(書き込み許可ビット)をオンすることで書き込み動作が行われる。
 EEWEビットのオフとオンの間は4クロック以内と定められているため、この間で割り込みが発生しないようにする必要がある。
 EEPROMに書き込むときのプログラムは、以下のようになる。

EEPROM_WRITE:
    sbic    EECR,EEWE
    rjmp    EEPROM_WRITE
    out     EEAR,R16
    out     EEDR,R17
    cli
    sbi     EECR,EEMWE
    sbi     EECR,EEWE
    sei
    ret
 EERE、EEWE、EEMWEの各ビットは、処理終了後に自動的にゼロクリアされる。

戻る


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