AVR内の汎用レジスタ・メモリ・I/Oレジスタ anchor.png

Page Top

AVRのメモリマップ anchor.png

AVRには,3タイプのメモリエリアがある。1つは,「プログラムメモリ」で,AVRに行わせる命令(プログラム)を記憶しておく場所になります。時には,定数データを納める場所としても使われます。通常は読み出し専用として使われます。容量は小さいAVRで1KB,大きいものでは256KBあります。書き込みは,そう煩雑にはできません。

もう1つが汎用レジスタ,SRAMメモリ,I/OレジスタなどがあるRAMエリアです。 汎用レジスタとSRAMメモリは,データの記憶に用いられます。
さらにもう1つ,EEPROMというメモリを持っていますが,これはI/O接続でアクセスできる様になっています。

Page Top

I/Oレジスタとは anchor.png

I/Oレジスタ(特殊機能レジスタ)とは,AVRの内蔵機能にアクセスするための特殊なレジスタです。 「内蔵機能」には,UARTやTIMER,汎用I/Oポートなどがある。

Page Top

入出力ポートの使いかた anchor.png

入出力ポートに関係するレジスタは,ポート毎に3つある。 ポートBについて記述するが,ポートA,C,Dなどでも同様になっている。

  • ポートB方向レジスタ DDRB  (ポートAならDDRA)
    DDRBにデータを書き込むと,1が書き込まれたbitに対応するピンが出力ポートになる。0が書き込まれたbitに対応するピンは入力ポートになる。
    DDRB = 0xF0;    // ポートBのpin0~3を入力に,pin4~7を出力に設定
    WinAVR上では,レジスタは8bit整数変数のように使えます。代入すれば値がセットされる。
  • ポートB出力レジスタ PORTB (ポートAならPORTA)
    このレジスタは,ポートBに対応する出力用データレジスタです。
    DDRB = 0xFF;       // ポートBを全て出力設定
    PORTB = 0xAA;      // PORTBのpin7,5,3,1をHigh,pin6,4,2,0をLowに
    char res = PORTB;  // PORTBを変数resに読み取る((ピンの入力情報を読むことではない。出力レジスタを読む。))
    なお,書き込んでからピンの電圧が変化するまでに最悪で1.5サイクルほどの遅れがある。ポートの先にコンデンサや長い配線などの容量性負荷がつながっていて遅延回路を形成している場合は,さらに遅れが生じる。
  • ポートB入力レジスタ PINB  (ポートAならPINA)
    これはポートBに対応する,入力用データレジスタです。入力設定されたピンに外部から与えられた電圧を読み取ります。*1
    WinAVR上では,レジスタは8bit整数変数のように使えます。変数を読み出すように値を読み出すことが出来る。
    DDRB = 0x00;       // ポートBを全て入力設定
    PORTB = 0xFF;      // PORTBの各ビットを全てセット
                       //   →対応する入力ポートのプルアップ
    char res = PINB;   // ポートBに与えられた電圧を変数resに読み取る。
                       // Highなら1,Lowなら0
    char res2 = PORTB; // これは、先にPORTBに書き込んだ値が得られる。
    入力ピンに対応するPORTBレジスタをセット(1)にすると,入力は,Vddに20から100kΩでプルアップされる。PORTBの対応ビットが0の場合は,プルアップされない入力(ハイインピーダンス)となります。
    外部につなぐ回路に応じて選択する。
Page Top

PINXへの書き込み→PORTXのトグル anchor.png

一部のAVR(*)には通常の入力レジスタのPINBのbit-nに1を書き込むと,ポートBのbit-nの値が反転するという機能が付加されている。

DDRB = 0xFF;       // ポートBを全て出力設定
PORTB = 0xF0;      // PORTB書き込み→ポートBのPB4からPB7がHighに、PB0からPB3がLowになる
PINB  = 0xFF;      // PINBのbit7-0に書き込む→ポートBのPB4からPB7がHigh→Lowに、PB0からPB3がLow→Highになる

*2

Page Top

ポート・メモリ・レジスタを操作するために便利なマクロなど anchor.png

Page Top

_BV(bit) anchor.png

ヘッダファイル<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))
Page Top

sbi(),cbi() -- 2005/02時点にて既に削除されました。 anchor.png

最新版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)
Page Top

ビットチェック bit_is_set(),bit_is_clear() anchor.png

  • bit_is_set()は指定レジスタの指定のビットがセット(1)されているかどうかチェックします
  • bit_is_clear()は指定レジスタの指定のビットがセット(1)されていないかどうかチェックします

条件分岐などと共に使うと便利です。

// example:
if (bit_is_set(PINB, PINB3)) {
    // ポートBの入力 PINB3が1(入力がHi)なら、if節内の処理を行う
}

if (bit_is_clear(PINB, PINB3)) { 
    // ポートBの入力 PINB3が0(入力がLo)なら、if節内の処理を行う
}
Page Top

loop_until_bit_is_set(),loop_until_bit_is_clear() anchor.png

名前の通り指定のビットがセットされるまで/クリアされるまでループします。

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の間この処理を行う
}
Page Top

I/Oポート(I/Oレジスタ)の使い方=入出力ポートと基本的には同じ anchor.png

Page Top

I/Oポートとは? anchor.png

入出力ポート以外にも多くの付加機能を持ったI/Oポートがあります。 あるものは内蔵機能の動きをコントロールし(コントロールレジスタ),あるものは内蔵機能の動作結果を読み込み・動作指定するための書き込み(データレジスタ)を行います。入出力ポートで言えば,DDRレジスタがコントロールレジスタ,PORTBやPINBがデータレジスタにあたります。
WinAVRでの扱いは入出力ポートと同じです。

タイマ0のコントロールレジスタ→TCCR0
タイマ0のデータレジスタ→TCNT0

これらのI/Oレジスタは "Special Function Registers (SFR) と呼ばれており,WinAVRでは,使用できるレジスタは<avr/io.h>ヘッダファイルで定義されています。
MPU名をmakefileで適切に指定してあれば,指定プロセッサにあった定義ファイルが読み込まれます。

各レジスタはデータシートにある名前と同じ名の変数として扱えます。

Page Top

ペアレジスタ(16bitレジスタ) anchor.png

レジスタや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など)

Page Top

レジスタの取扱で注意する点 anchor.png

Page Top

1を書き込むとビットクリアとなるレジスタがあります。 anchor.png

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);
Page Top

複数のbitに同時に書き込みをかける必要があるレジスタ anchor.png

一部のレジスタについては,同時に書き込みをかけないと機能しないようになっている物があります。
EEPROM制御アドレスEECRのEEPM1/EEPM0(mega/tinyシリーズ)などがそれにあたります。

Page Top

書き込みに時間制限があるレジスタ anchor.png

容易に変更されては困るビットの中には,ビットを鍵として,鍵になるビットを書き込んだ後すぐに目的のビットを書き込まないと鍵が閉じてしまうような仕掛けをしているレジスタがあります。
WDTCR(ウォッチドッグタイマコントロールレジスタ)などがそれにあたります。WDTCRの一部のビットは,WDTCRのWDCEビットを書き込んだ後4クロック以内に他のビットを書き込まない限りは書き換えられないようになっています。

Page Top

備考 anchor.png

PINxnのsbi命令でのトグル動作の不思議

  • 新しいMCUはPINXnを書き込むとPORTxnの値が反転になる仕様である
  • sbi命令はリードモデファイライト命令であるので,ポートの値を読み込んで必要なビットを書き換えて出力するというのを1命令で実行する。そのため実行には2クロックサイクル必要~
  • データシートの標準デジタル入出力回路構成を見ると,読み書きのストローブはポート内のピンで共通であるとなっている(重要)
    これは1ビット単位での書き換えは出来ないことを意味している。
    入出力回路構成図にもビット単位で処理できるような構造は描かれていない。
    sbi,cbiではポート読込⇒ビット処理⇒ポート書込とやっているはず。
  • DDRxやPORTxにsbi命令を使用してもMCU外部からの影響は受けないので,他のビットには影響はしないのは当然
  • PINxはピンの状態が反映されるのでPORTxnを出力にして1を出力していればPINxnを読めば1となるはずである(外部の負荷が軽い場合)
  • ここで疑問
    PINxnに1を書くとPORTxnが反転するのであるが,sbi命令で操作する時に他のPINxmが1になっていたらそのビットまで反転するのではないか?ということ。~
  • 確認してみました・・・(ATmega48)~ PORTB0,PORTB1を出力にしてともに1を出力しておく。
    sbi PINB,0を実行してみる
    PORTB0だけが反転しました(問題なし)
    これはPINBに0bxxxxxx01 を書き込んだことになる
  • ちなみにPINBを読み込んでBit0を1にしてPINBに書き込むというように命令を分けてみました
    PORTB0,PORTB1ともに反転しました
    これは,PINBに0bxxxxxx11を書き込んだことになる。~
  • いったいどうゆう機構になっているのでしょう? sbiは[ポート読込⇒ビット処理⇒ポート書込]とは違う?
    PINxに対してのsbiだけ特別な機構があるのでしょうか?
  • 結論~ PINxに対してsbi命令を使用しても問題なし(データシートにも書いてあるし)。~ パルスを高速に連続して出力するにはout命令でPINxに書き込むようにしたほうが速いです。
Page Top

付加機能 anchor.png

多くのポートは付加機能を持っている。
いくつかのピンはプロセッサ内の付加機能の入出力としても使われています。例えばPORTBのpin0はTimer1のキャプチャ入力を兼ねています。この辺はデータシートに詳しく載っています。


*1 PORTBの読み込みでは入力できないことに注意してください。
*2 PINxnに1を書き込むと,PORTxnの値が反転するMCU は,ATtiny13/2313以降のすべての新型MCUがそうなっているようです。

新しくコメントをつける

題名
ゲスト名
投稿本文
より詳細なコメント入力フォームへ

トップ   凍結 差分 バックアップ 複製 名前変更 リロード   ページ新規作成 全ページ一覧 単語検索 最新ページの一覧   ヘルプ   最新ページのRSS 1.0 最新ページのRSS 2.0 最新ページのRSS Atom
Counter: 908, today: 1, yesterday: 2
最終更新: 2020-12-26 (土) 16:07:52 (JST) (1214d) by yuji