- Atmel(アトメル,現在のMicrochip)製の8ビットマイコン。
- RISC構造が特徴。命令を16ビットまたは32ビットの固定幅、かつシンプルな命令のみにして実行速度とプログラム効率の向上に努めている。
- マシン語レベルでの命令は16ビットまたは32ビットなので、プログラムメモリ(フラッシュROM)のバス幅は16ビットとなっている。ちなみに、データメモリ(RAM)やI/Oのバス幅は8ビット。
- データメモリとI/Oは同一のアドレス空間だが、プログラムメモリとは別空間になっている。
データメモリとプログラムメモリが同一空間で、I/Oが別というマイコンはあったが、プログラムメモリが別というのは珍しい?。ちなみに、H8マイコンやRXマイコンは、プログラムメモリ,データメモリ,I/Oのすべてが同じ空間になっている。
- 32本の8ビット幅汎用レジスタがあり、そのうちR26~R31を3組の16ビットポインタX,Y,Zとしても使用できる。
- ATmega328Pの場合、プログラムカウンタPCの有効ビット幅は14ビット。 アドレス範囲は0x0000~0x3FFFとなるが、ワード(16ビット)単位のためサイズは32Kバイトとなる。(データシートではプログラムカウンタについて解説が少ない)
- スタックポインタSPとステータスレジスタSREGが、標準I/Oレジスタの一部として構成されている。
- 割り込み発生時、プログラムカウンタPCは自動的にスタックにPUSH/POPされるが、ステータスレジスタSREGはPUSH/POPされないのでプログラムで行う必要がある。しかも、直接PUSH/POPできないので、一旦汎用レジスタにコピーして行う必要がある。(SREGは標準I/Oレジスタの一部だからと思われる)
- PUSH命令はポストデクリメントであり、スタックに書き込み後にスタックポインタSPが-1される。POP命令はプリインクリメントであり、SPが+1された後にスタックから読み出される。
- 出荷時のシステムクロックは内蔵RC発振器になっているが、ヒューズビットの書き換えによって外部水晶発振子などに変更できる。内蔵RC発振器は8MHzであるが、出荷時は8分周されて1MHzになっている。この8分周は、ヒューズビットの書き換えによって解除できる。
外部発振子を使えば、例えば20MHz(VCC5V), 10MHz(VCC3.3V)などの高速動作が可能。
- CPUコアにAVR,AVRe,AVRe+,AVRxm,AVRxt,AVRrcという種類があり、開発時期やシリーズによって異なる場合がある。このうち、AVR,AVRe,AVRrcというコアは乗算命令を持っていないので、乗算を含むプログラムを組むと、加算等の組み合わせで対応するため、プログラムサイズや実行時間が若干大きくなる。
ちなみに、megaシリーズはほとんどAVRe+コアなので問題ないようだが、tinyシリーズはAVReコアのものが多いので確認した方がよい。
- DIP(デュアル・インライン・パッケージ)の機種もあり、安価なものも多いので、趣味の電子工作で使いやすい。
■MicrochipStudio+GCC Cコンパイラ でのプログラミングに関わる点
- リセット直後、ステータスレジスタが0クリアされ、スタックポインタSPにデータメモリ(RAM)の最終アドレスがセットされる。
- プログラムで、定数データをconst~と定義してもプログラムメモリ(フラッシュROM)中に配置されない。プログラムメモリ中に配置させるには次のように属性'PROGMEM'を追記する。
例)const char xxxx[] PROGMEM = "String";
属性指定'PROGMEM'を使用するためには<avr/pgmspace.h>をインクルードする必要がある。
- 前記のように記述しても、データメモリ(RAM)中のデータのように、そのまま単純に読み込むことはできない。読み込むためには専用の命令(アセンブラでLPM命令)が必要。
GCCでは、マクロpgm_read_byte()またはpgm_read_word()を使用して1バイトまたは2バイトずつ読み込む。
このマクロを使用するためには<avr/pgmspace.h>をインクルードする必要がある。
ATmega328P-PU
注:「MicrochipStudio」とはPC用の統合開発環境で旧AtmelStudioのこと。「GCC」とは "GNU
Compiler Collection" のこと。
2.プログラミングの最初
ATmega328Pのプログラム作成で最初にすることを説明します。ただし、統合開発環境MicrochipStudio(旧AtmelStudio)を使ってC言語で作成するものとします。
まず、MicrochipStudioを起動して、下図の例のように新プロジェクトを作成します。
図中にある、プロジェクト名(Name, Location, Solution name)などは例です。好きな名称を入力してください。
プロジェクトを作成すると、メイン関数のファイル(main.c)が作成されます。ただし、空のファイルなので中身は自身で作ることになります。
C言語によるプログラムでは、リセット後すぐにメイン関数が実行されるわけではなく、前処理(準備)が必要です。しかし、MicrochipStudioを使うと、その部分を作成してくれます。ただし、ソースはないので編集はできません。
次のリストは、某プロジェクトで作成されたもので、リセットからメイン関数までに実行される部分です。
先頭0x0000番地から割り込みベクタがあります。ベクタと言ってもJMP命令の羅列です。ちなみに、その内容はどの割り込みを使っているかで異なります。
リセット後、0x0000番地にあるJMP命令、この例ではJMP 0x0068(JMP __ctors_end)から実行開始します。
0x0068番地(__ctors_end)からは、次のようなことを行っています。
①SREG(ステータスレジスタ)とSP(スタックポインタ)の初期化
②初期値付き変数の初期化(初期値コピー)
③初期値無し変数の初期化(ゼロクリア)
④メイン関数の実行
この例でもわかるように、SP(スタックポインタ)はSRAMの最終アドレスになっています。また、メイン関数から戻ってくると実行停止のエンドレスループに入ります(通常ならメイン関数から戻りません)。
3.I/Oポートを使う
I/Oポートとは、I/Oポートの機能を持つマイコンの端子(ピン)を指すものとします。
I/Oポートの特定のビットに'1'(High)や'0'(Low)を出力したり、また特定のビットの'0/1'状態を読み込んだりする方法を説明します。なおここでは、GCC(GNU Compiler Collection)Cコンパイラ を使うものとします(以降同じ)。
まず、ソースプログラムファイル(例えばmain.c)の始めの方で、次のようにヘッダファイル<avr/io.h>をインクルードしておきます※1。これはコンパイラに用意されているヘッダファイルで、これによってマイコンのハードウェアマニュアルにあるレジスタの名前が使えるようになります。
#include <avr/io.h>
※1.MicrochipStudioでプロジェクトを作成すると、ファイルmain.cには"#include <avr/io.h>"が記述されています。
マイコンのI/Oポートは、通常次の3種類のレジスタに結び付いていて、これらを操作することで入力したり出力したりできるようになっています。各レジスタは8ビット構成ですが、操作は1ビットごとにできます。
PORTn … ポートn出力レジスタ
DDRn … ポートn方向レジスタ
PINn … ポートn入力レジスタ
"n"の部分はポート番号で、アルファベット"A"~が入ります。ただし、どのポートが存在するかマイコンによって異なるので、使用するマイコンのハードウェアマニュアルで確認してください。ATmega328Pでは、"B","C","D"の3つのI/Oポートを使うことができます。
出力レジスタとは、I/Oポートに'1'や'0'を出力するためのレジスタです。
入力レジスタとは、I/Oポートの入力状態'1/0'を読み込むためのレジスタです。
方向レジスタとは、I/Oポートを出力として使うのか入力として使うのか決めるレジスタです。つまりI/Oポートは、使う前に出力か入力かどちらかに決めておかなければなりません。ちなみに、マイコンのリセット後は入力ポートになっています。
以降、ATmega328PのポートCを例に、指定のビットに'0'や'1'を出力する方法、また指定のビットの'0/1'状態を読み込む方法を説明します。
ATmega328PのポートCの制御レジスタは下記のようになっています。この図のように、ビット7は端子がないので使用できません。
注:ポートCのb6は、初期状態でRESET入力になっています。I/Oポートとして使用するには、ヒューズビットの上位バイトのb7(RSTDISBL)を'0'に書き換える必要があります。
ここでは、ビット6~4を出力に、ビット3~0を入力とするものとして、次のように初期化します。PORTC = 0x00; … 初期状態として'0'をセットしておく※2
DDRC = 0x70; … b6~b4を出力に設定
・ビット4に'1'を出力する例
PORTC |= 0x10;
・ビット6と5に'1'を出力する例
PORTC |= 0x60;
・ビット4に'0'を出力する例
PORTC &= 0xEF;
・ビット0が'0'か'1'か判断する例※3
if((PINC & 0x01) == 0){
… // '0'だった時の処理をここに書く
}
else{
… // '1'だった時の処理をここに書く
}
・ビット3~1が'101'か判断する例※3
if((PINC & 0x0E) == 0x0A){
… // '101'だった時の処理をここに書く
}
else{
… // '101'ではなかった時の処理をここに書く
}
※2.初期状態を'1'にしたいビットは、'1'をセットしておきます。
※3.入力設定/出力設定に関係なく、PINnで読み込むのは端子の状態です。つまり、出力設定では実際に出力している'0/1'状態を確認できます。
※3.入力設定/出力設定に関係なく、PINnで読み込むのは端子の状態です。つまり、出力設定では実際に出力している'0/1'状態を確認できます。
■_BV(※4)マクロを使って次のように書くこともできます。
どちらがわかりやすいか考え、好きな方を使えばいいと思います。
・前記と同じポートCの初期化
PORTC = 0x00;
DDRC = (_BV(DDC6) | _BV(DDC5) | _BV(DDC4));
・ビット4に'1'を出力する例
PORTC |= _BV(PORTC4);
・ビット4に'0'を出力する例
PORTC &= ~_BV(PORTC4);
・ビット0が'0'か'1'か判断する例
if((PINC & _BV(PINC0)) == 0){
… // '0'だった時の処理をここに書く
}
else{
… // '1'だった時の処理をここに書く
}
・ビット3~1が'101'か判断する例
if((PINC & (_BV(PINC3) | _BV(PINC2) | _BV(PINC1))) == (_BV(PINC3) | _BV(PINC1))){
… // '101'だった時の処理をここに書く
}
else{
… // '101'ではなかった時の処理をここに書く
}
※4._BV()のカッコの中は、ハードウェアマニュアルの各I/Oレジスタの説明にあるビット名(ビット番号)です。