トップ 新規 編集 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Getting Started Notes - Ports

最終更新時間:2010年11月06日 04時03分23秒

I/Oレジスタ、入出力ポートの使い方

 AVR内の汎用レジスタ、メモリ、I/Oレジスタ

AVRのメモリマップ

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

 もう1つが汎用レジスタ、SRAMメモリ、I/Oレジスタなどがある場所です。 さらにもう1つEEPROMというメモリを持っていますが、これはAVRコンピュータの外にある記憶装置を1つのパッケージに収めたものと言った方がよいでしょう。

 汎用レジスタとSRAMメモリはデータの記憶に用いられます。

  • 汎用レジスタ(32バイト):数は少ないが読み書きが高速でいろんな計算動作ができる
  • SRAMメモリ:読み書きは遅く演算対象にはできないが、数が多い

を使い分けてAVRは仕事を行います。

レジスタはどのAVRでも32個で同じですが、SRAMは少ないものでは0、多いものでは数KBあります。

I/Oレジスタとは

 I/Oレジスタ(特殊機能レジスタ)とは、ある値を書き込むと、内蔵されたさまざまな装置に何らかの動作を行わせたり、内蔵された様々な装置に外部から与えられた情報を読み取ることができる仕掛けがなされた特殊なレジスタです。 「内蔵された装置」には、通信機構やタイマーなどがあります。CPU部と外の付加機能部のやりとりを行う港のような場所ということで「ポート」とも呼ばれます。

 その中でも特に、データを書き込むと書き込んだデータのビットに対応したピンの電圧が変化するものを「出力ポート」、外部から与えられた電圧に応じてレジスタのビット値が変化し、読み取ることができる物を「入力ポート」と呼んでいます。まとめて「入出力ポート」と呼びます。

 入出力ポートの使いかた

入出力ポートに関係するレジスタはポート毎に3つあります。たとえば、ポートBに対応するものは以下の3つです。以下、わかりやすくするためポートBについて書きますが、ポートA,C,Dなどでも同様です。

  • ポートB方向レジスタ DDRB  (ポートAならDDRA)
  • ポートB出力レジスタ PORTB (ポートAならPORTA)
  • ポートB入力レジスタ PINB  (ポートAならPINA)

ポートB方向レジスタ DDRB

 DDRBにデータを書き込むと、書き込んだデータについて、1が書き込まれたbitに対応するピンが出力ポートになります。0が書き込まれたbitに対応するピンは入力ポートになります。ピンの対応についてはデータシート冒頭のピン対応図をご覧ください。

DDRB = 0xF0;    // ポートBのpin0〜3を入力に、pin4〜7を出力にする。

WinAVR上では、レジスタは8bit整数変数のように使えます。代入すれば値がセットされます。

ポートB出力レジスタ PORTB

 これはポートBに対応する出力用データレジスタです。入出力ポートの「ポート」と紛らわしいので注意してください。

ここでは「ポートB」と書けば入出力機構としてのポートB、「PORTB」とすればポートBの出力レジスタを指すものとします。

DDRB = 0xFF;       // ポートBを全て出力設定
PORTB = 0xAA;      // PORTBのpin7,5,3,1をHigh、pin6,4,2,0をLowに
char res = PORTB;  // PORTBを変数resに読み取る
                   // (ポートBに与えられた電圧の読み取りではない)

なお、書き込んでからピンの電圧に現れるまで最悪で 1.5 サイクルほどの遅れがあります。ポートの先にコンデンサや長い配線などの容量性負荷がつながっていて遅延回路を形成している場合は当然さらに遅れが生じます。

ポートB入力レジスタ PINB

 これはポートBに対応する入力用データレジスタです。入力設定されたピンに外部から与えられた電圧を読み取ります。PORTBの読み込みでは入力できないことに注意してください。

WinAVR上では、レジスタは8bit整数変数のように使えます。変数を読み出すように値を読み出すことができます。

DDRB = 0x00;       // ポートBを全て入力設定
PORTB = 0xFF;      // PORTBの各ビットを全てセット
                   //   →対応する入力ポートのプルアップ
char res = PINB;   // ポートBに与えられた電圧を変数resに読み取る。
                   // Highなら1,Lowなら0
char res2 = PORTB; // これは、先にPORTBに書き込んだ値が得られる。

入力ピンに対応するPORTBレジスタをセット(1)にすると、入力はVccに20〜100kΩでプルアップされます。PORTBの対応ビットが0の場合はプルアップされない入力(ハイインピーダンス)となります。外部につなぐ回路に応じて選択してください。

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

一部のAVR(*)には、PINBのbit-nに1を書き込むとPORTBの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になる

*PINxnに1を書き込むとPORTxnの値が反転するMCU (2005-09-23現在) ATtiny13/2313以降のすべての新型MCUがそうなっているようです。

AT90PWM2/3 
ATmega48/88/168 
ATmega165/169 
ATmega164/324/644 
ATmega325/3250/645/6450 
ATmega329/3290/649/6490 
ATmega406 
ATmega640/1280/1281/2560/2561 
ATtiny13 
ATtiny24/44/84 
ATtiny25/45/85 
ATtiny2313 

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

 _BV(bit)

 ヘッダファイル<avr/io.h>で、AVR各レジスタのビット名とビット番号(0〜7)の対応が定義されています。たとえば、PORTBのbit0〜bit7に対応して、PB0〜PB7という名前が定義されています。入力ポートPINBについては、PINB0〜PINB7の名前で定義されています。同じ0〜7なんですけど、わかりやすいように使い分けた方がいいでしょう。

 ビット位置から簡単にレジスタに出力すべき値を記述できるように、便利なマクロ _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  = ~_BV(PB1);         /* PB1ピンだけをLowにし、残りをHighにする */
PORTB  = PORTB & ~_BV(PB1); /* _BV(PB1)のビット反転とandする。PB1ピンをLowにし、残りは変化させない */
PORTB &= ~_BV(PB1);         /* こう書いてもいい */
PORTB ^= _BV(PB1);          /* PB1ピンだけを反転し、残りは変化させない */
PORTB ^= ~_BV(PB1);         /* 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))

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

最新版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(3);      // PORTBのbit3をクリア

しかしこのマクロ、葬り去るには惜しいほど使いやすいです。マクロを作って利用するのも良いかも知れません。

#define cbi(addr,bit)     addr &= ~(1<<bit)
#define sbi(addr,bit)     addr |=  (1<<bit)

 ビットチェック bit_is_set() , bit_is_clear()

  • 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節内の処理を行う
}

 loop_until_bit_is_set() , loop_until_bit_is_clear()

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

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ポート(I/Oレジスタ)の使い方=入出力ポートと基本的には同じ

 I/Oポートとは?

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

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

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

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

 ペアレジスタ(16bitレジスタ)

 レジスタや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レジスタという仕掛けがあります。詳細は[Getting Started Notes - Timer1]の、「タイマ関連2バイトレジスタの注意点」を読んでください。ペアレジスタを参照するために定義された16bitシンボル(上記のTCNT1の他、OCR1A,ICR1など)を使う場合は書き込み時は上位から、読み出し時は下位からの原則が守られます。*1

 たいていの16bitレジスタは、上記のTCNT1のように、hogehogeL,hogehogeHというレジスタなら「hogehoge」がペアレジスタ名になります。
例外もあります。AD変換のデータレジスタADCL,ADCHはADCではなくADCWというペアレジスタが用意されています。

また、連続したアドレスにないためペアレジスタの形で扱えないものもあります。(一部デバイスのUBRRL/UBRRHなど)

*1:ただし、(volatile uint16_t *)で宣言したポインタを介してレジスタを参照する場合、現行WinAVR(20050214版)にはバグがあり、書き込み時上位バイトから書き込んでくれません。詳細は[WinAVRバグ情報]参照

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

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

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

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

 一部のレジスタについては、同時に書き込みをかけないと機能しないようになっている物があります。EEPROM制御アドレスEECRのEEPM1/EEPM0(mega/tinyシリーズ)などがそれにあたります。データシートでレジスタの仕様を確認してご使用ください。

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

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

  備考

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に書き込むようにしたほうが速いです

 付加機能

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