AVRには,3タイプのメモリエリアがある。1つは,「プログラムメモリ」で,AVRに行わせる命令(プログラム)を記憶しておく場所になります。時には,定数データを納める場所としても使われます。通常は読み出し専用として使われます。容量は小さいAVRで1KB,大きいものでは256KBあります。書き込みは,そう煩雑にはできません。
もう1つが汎用レジスタ,SRAMメモリ,I/OレジスタなどがあるRAMエリアです。
汎用レジスタとSRAMメモリは,データの記憶に用いられます。
さらにもう1つ,EEPROMというメモリを持っていますが,これはI/O接続でアクセスできる様になっています。
入出力ポートに関係するレジスタは,ポート毎に3つある。 ポートBについて記述するが,ポートA,C,Dなどでも同様になっている。
DDRB = 0xF0; // ポートBのpin0~3を入力に,pin4~7を出力に設定
DDRB = 0xFF; // ポートBを全て出力設定 PORTB = 0xAA; // PORTBのpin7,5,3,1をHigh,pin6,4,2,0をLowに char res = PORTB; // PORTBを変数resに読み取る((ピンの入力情報を読むことではない。出力レジスタを読む。))
DDRB = 0x00; // ポートBを全て入力設定 PORTB = 0xFF; // PORTBの各ビットを全てセット // →対応する入力ポートのプルアップ char res = PINB; // ポートBに与えられた電圧を変数resに読み取る。 // Highなら1,Lowなら0 char res2 = PORTB; // これは、先にPORTBに書き込んだ値が得られる。
ヘッダファイル<avr/io.h>で,AVR各レジスタのビット名とビット番号(0から7)の対応が定義されている。
たとえば,PORTBのbit0からbit7に対応して,PB0からPB7という名前が定義されている。入力ポートPINBについては,PINB0からPINB7の名前で定義されている。
ビット位置から簡単にレジスタに出力すべき値を記述できるように,便利なマクロ_BV()が用意されている。
これを使って特定のビットを読み取る処理をわかりやすく書けます。
たとえば,_BV(PINA6)は (1<<PINA6)を返し,これはPINA6に対応するビット(bit6)をONとした時のPINAの値 64 (二進数で0b00100000)を返す。
// _BVの使用例 PORTB = _BV(PB1); // PB1ピンだけをHighにし,残りをLowにする PORTB = PORTB | _BV(PB1); // PB1ピンをHighにし,残りは変化させない PORTB |= _BV(PB1); // こう書いてもいい PORTB = PORTB & ~_BV(PB1); // _BV(PB1)のビット反転とandする。PB1ピンをLowにし,残りは変化させない PORTB &= ~_BV(PB1); // こう書いてもいい uint8_t result; result = PINB; result = result & _BV(PINB1); // 変数に対して使ってもいい uint16_t tm; tm = _BV(15); // 16bit整数のbit15を1にする
なお,_BV()はそのままでは16bitにしか対応できません。32bitとして使いたいときは以下のような_BV32()をマクロ定義して使ってください。
#define _BV32(bit) (1L << (bit))
最新版WinAVR(20050214)で削除されました。
void sbi (uint8_t port, uint8_t bit): void cbi (uint8_t port, uint8_t bit): sbi()関数で指定レジスタの指定のビットをセット(1)にすることができます。 cbi()関数で指定レジスタの指定のビットをクリア(0)にすることができます。 sbi(PORTB, 3); // PORTBのbit3をset sbi(PORTB, PB3); // こちらでもいいです。 sbi(GIFR, INT0); // bit名に意味のある名前が付いている場合は // それを使った方がわかりやすい
以下のように書いてください。
PORTB |= _BV(3); // PORTBのbit3をセット PORTB &= ~_BV(4); // PORTBのbit3をクリア
しかしこのマクロ,削除するには惜しいほど使いやすいです。マクロを作って利用するのも良いかも知れません。
#define cbi(addr,bit) addr &= ~(1<<bit) #define sbi(addr,bit) addr |= (1<<bit)
条件分岐などと共に使うと便利です。
// example: if (bit_is_set(PINB, PINB3)) { // ポートBの入力 PINB3が1(入力がHi)なら、if節内の処理を行う } if (bit_is_clear(PINB, PINB3)) { // ポートBの入力 PINB3が0(入力がLo)なら、if節内の処理を行う }
名前の通り指定のビットがセットされるまで/クリアされるまでループします。
loop_until_bit_is_set(PINB,PINB3); // ポートBの入力 PINB3が1(入力がHi)になるまで待つ
↓この形式では利用できません。
loop_until_bit_is_set(PINB,PINB3) { // PINB3が1になるまでこの処理を行う・・・コンパイルエラー }
↓このように使ってください。
while( bit_is_clear(PINB,PINB3) ) { // PINB3が0の間この処理を行う }
入出力ポート以外にも多くの付加機能を持ったI/Oポートがあります。
あるものは内蔵機能の動きをコントロールし(コントロールレジスタ),あるものは内蔵機能の動作結果を読み込み・動作指定するための書き込み(データレジスタ)を行います。入出力ポートで言えば,DDRレジスタがコントロールレジスタ,PORTBやPINBがデータレジスタにあたります。
WinAVRでの扱いは入出力ポートと同じです。
タイマ0のコントロールレジスタ→TCCR0 タイマ0のデータレジスタ→TCNT0
これらのI/Oレジスタは "Special Function Registers (SFR) と呼ばれており,WinAVRでは,使用できるレジスタは<avr/io.h>ヘッダファイルで定義されています。
MPU名をmakefileで適切に指定してあれば,指定プロセッサにあった定義ファイルが読み込まれます。
各レジスタはデータシートにある名前と同じ名の変数として扱えます。
レジスタやIOレジスタの中には,値が8bitに収まらないため上位下位バイトセットで意味を成すレジスタもあります。
そのうちTimer1の16biレジスタなどについては,2バイトいっぺんに操作がしやすいように16bit参照ができるシンボルが定義されています。以下は同じアドレス0x2C(2313の場合)を指し示しますが,得られる値が異なります。
a=TCNT1L; //TCNT1Lの値だけを取得 a = *(volatile uint8_t *)(0x2C); a=TCNT1; //TCNT1H*256+TCNT1Lを取得 a = *(volatile uint16_t *)(0x2C);
AVRは8bitCPUで,1度に上位下位16bit分のレジスタ値を取得することはできませんので,上記のa=TCNT1; では実際には,TCNT1L/TCNT1H2つのレジスタの読み込みによって成されています。
1バイトずつアクセスする方法と対比させてみると以下のようになります。
従来の方法 新しい方法 uint16_t tm; uint16_t tm; uint8_t tm_l,tm_h; tm_l = TCNT1L; tm_h = TCNT1H; tm = tm_h<<8 + tm_l; tm = TCNT1; //TCNT1の性質に基づき下位から読み出す TCNT1H = tm>>8; TCNT1L = tm&255; TCNT1 = tm; //TCNT1の性質に基づき上位から書き込む
タイマ関連レジスタについては,それ自身の値や比較対象の値が絶えず動いているので,同じ瞬間に上位下位2バイトの値を変更する必要があります。このためにTEMPレジスタという仕掛けがあります。ペアレジスタを参照するために定義された16bitシンボル(上記のTCNT1の他、OCR1A,ICR1など)を使う場合は,書き込み時は上位から,読み出し時は下位からの原則が守られます。
たいていの16bitレジスタは,上記のTCNT1のように,hogehogeL,hogehogeHというレジスタなら「hogehoge」がペアレジスタ名になります。
例外もあります。
AD変換のデータレジスタは,ADCL,ADCHはADCではなく,ADCWというペアレジスタが用意されています。
また,連続したアドレスにないためペアレジスタの形で扱えないものもあります。(一部デバイスのUBRRL/UBRRHなど)
GIFR(一般割り込み要求フラグレジスタ)やTIFR(タイマ割り込み要求フラグレジスタ)など,割読み要求フラグを提示するレジスタでは,bitに1を書き込むとクリア(0になる)され,bitに0を書き込むと不変のような動作をします。
GIFR=0xC0(INTF1,INTF0が1)の時、GIFR=_BV(INTF1);を実行
→GIFRは0x40になる(bit7(INTF1)のみクリア)
間違いの例
× GIFR = GIFR | _BV(INTF1); × GIFR |= _BV(INTF1); × sbi(GIFR,INTF1); →まちがい。これをやると、たとえばINTF0もセットされている場合、 INTF0に1を書き込むことになり、INTF0までクリアしてしまう! (read-modify-write動作の落とし穴) ■■□□□□□□ GIFR INTF1(bit7)とINTF0(bit6)が立っている ■■□□□□□□ GIFR値|_BV(INTF1) □□□□□□□□ ↑を書き込むと、INTF0もクリア対象になる!
通常のビットと,1書き込みでクリアとなるビット両方が混在しているレジスタもあります。
この場合は,通常ビットの部分はこれまでの値を,1書き込みでクリアとなるビットについてはクリアしたいビットだけを1にするようにした値を書き込むことになります。
TWI(I2C)のTWCRレジスタなどがそれに該当します。
以下のように通常ビットを他の変数や定数に保持しておくと便利です。
#define TWCR_value _BV(TWEN); TWCR = TWCR_value | _BV(TWINT);
PINxnのsbi命令でのトグル動作の不思議
新しくコメントをつける