ようやく秋葉原でも入手しやすくなってきたAVR。ちょっと、触ってみて、気が付いたことをメモしようかと思います。
秋葉原で簡単に入手できるチップを調べてみました。(2009/02/28)
一覧表にしてみると、意外に多くの種類が入手可能です。また、DIP品が多いのも嬉しい。AT90S…は旧品なので、これから使うのは避けたいところ。
プログラムの開発環境は、メーカー供給のAVRSTUDIOが無償。
ライターは、簡易ライターが色々発表されているし、メーカー純正も比較的安価に手に入ります。そこで、安易に既製品のライターを入手しました。
EZ_USBに接続する小さなボードです。レパートリはOPTIMIZEさん取り扱いのもの。ISPのシリアル書き込みに限られます。
ほぼ、すべて書き込み可能。シリアル書き込みのみ。
レパートリは限定されているのですが、パラレル書き込みが出来るので、いざというときには便利。
いずれにしても、面倒なのは、書き込み対象に対応して配線をしなければならないこと。配線自体は簡単なんだけれど、プログラム開発中にこういうところで神経を使いたくない。結局、書き込みアダプタ基板を作ることになりそうです。
現在入手できるチップは限られているので、順次使いながら書き込み基板も作ってストックということ。とりあえず、一番安価な、TYNY2313あたりから手がけたいと思っています。
AVRは、大まかには、実行命令語を格納するプログラム空間と、操作対象のデータ(メモリ)空間が分かれています。
プログラム空間は、一つのアドレスが1ワード(2バイト)。リニアに配置されていて、現在のところ、論理的には4Mワードが最大の大きさです。
データ空間は一つのアドレスが1バイト。基本的には、アドレスの大きさが16ビットで、64Kバイトの大きさです。しかし、IO領域(空間)にアドレッシングモードに対応して、1バイト(8ビット)分の上位アドレスがあるので、都合24ビット、4Mワードのデータ空間となります。
プログラム空間、データ空間ともに、その大きさは、チップによって異なります。
というように、プログラム空間と、データ空間が分離しているのがハーバード・アーキテクチャーというもので…と説明が続きそうですが、AVRはもう少し複雑な構造をしています。
64バイトの大きさ。アドレス一つが1バイトです。CPUの基本機能であるステータスレジスタなどや、入出力関係のレジスタが割り振られています。IO関係の命令でアクセスします。
この空間は、データ空間にIO領域としてマップされるので、データ空間からもアクセス可能です。PICではIO空間がなく、すべてデータ空間でした。
特に名称がないのですが、プログラム空間のデータにアクセスする場合の空間です。プログラム空間(フラッシュメモリー)に定数やテーブルを置いたときに読み出すための命令が、特別に用意されています。一つのアドレスが1バイト。基本的には16ビットアドレスで、64Kバイト。IO領域の拡張アドレスを使えば、8ビット増えて、24ビットで4Mバイトの大きさ。
空間全体で言うと、プログラム空間の半分のサイズですが、現実的には問題ないでしょう。
PICでは、プログラム空間を読み出せないか、あるいは、IOポートを通してのアクセスになっていました。
EEPROMを搭載しているチップでは、EEPROMを、IOポートを通してアクセスします。いわば外部デバイスのような扱いですが、EEPROMの内容は、プログラムファイルの中に記述できます。EEPROMの扱いはPICと殆ど同じです。
32バイトの大きさ。アドレス一つが1バイトです。特にアドレス空間として取り上げるものではないのですが…。小さなチップでは32バイトは、大きい。実質的に通常のデータ領域として使うこともあります。
普通のCPUでの内部レジスタに相当するもの。RISCチップを唄っているのにふさわしく、基本的に演算は、レジスタを対象にのみ行います。
前述のとおりです。
アドレスの若い方に、リセットや割り込みの固定の飛び込み先が並びます。これらの固定位置にはベクター(飛び込み先アドレス)ではなく、それぞれの実行命令を書きます。通常は、それぞれの処理先に分岐するジャンプ命令を書きます。飛び込み先の数は、チップによって異なります。また、個々の飛び込み先の大きさは、規模の小さいチップではrjmp(相対ジャンプ)で十分なため、1ワード。大きなチップではjmp命令が必要なので、2ワードの大きさとなっています。
データ空間には、いわゆるRAMだけでなく、レジスタ空間、IO空間がマッピングされています。
先頭の0x00-0x1Fの32バイトがレジスタ領域でレジスタ空間そのもの。
次の0x20-0x5Fの64バイトがIO領域で、IO空間をマッピング。あるIOポートのIO空間のアドレスに0x20を加えると、データ空間上でのアドレスになります。
0x60以降に通常のRAMが、と行きたいところですが、64バイトのIO空間に入りきれないIOポートは、ここにマップされます。メモリマップドIOポートというわけです。
この後に、通常のRAMがマップされます。上の事情で、チップによってこの領域の開始位置は異なります。
データ空間にIO空間がマッピングされているので、IO空間は不要な感じがしますが、IO空間は無駄ではありません。
AVRは、演算をレジスタに対してのみ行います。データ空間のデータは一度レジスタに読み込まなければ、演算が行えません(ロード・ストア・アーキテクチャ)。IO領域も例外でなく、例えば、ポートのあるビットを立てるには、一度レジスタに読み込み(lds r16,xxx 2w/2cycle)、ビットを立てた(ori r16,bit 1w/1cycle)後、ポートに書き込む(sts xxx,r16 2w/2cycle)処理が必要になります。この処理には、5ワード(10バイト)のプログラムメモリと、5サイクルの実行時間が必要な上、作業用のレジスタも用意しなければなりません。
制御用のCPUでは、このIOポートのビット操作が頻繁に行われるため、パフォーマンスを要求される処理となっています。このために、IO命令が用意されたわけです。同じ作業を、IO空間で行うと、ポートにビットセット(sbi xxx,bit 1w/2cyle)だけでできます。わずか、1ワード。2サイクルで実行できる上に、作業用のレジスタが不要です。
IO空間用には、このほかに、1w/1cycle で動作するIN/OUT命令や、SBIC/SBISのビット判定スキップ命令がサポートされています。
ロード・ストア・アーキテクチャを取っていないPICの場合は、データ空間に対して演算ができるので、IO空間は不要です。
PICと比較して、すっきりしているとの印象のあるAVRの命令語ですが、実際にプログラムを組もうとすると、結構、癖が強いものです。
AVRのレジスタはR0..R31の32個。それぞれ、1バイトです。これの使い方が意外に難しい。データシートに掲載されているインストラクション表には書いてないのですが、レジスタによって実行できる命令と、実行できない命令があります。
また、2バイト連結してインデックスレジスタとして使われるものがあり、これに対してのみ可能な演算もあります(これは、納得のいくものですが…)。
R27:R26(X)、 R29:R28(Y)、 R31:R30(Z)
X、Y、Zはデータ空間とレジスタ間の転送(LD、ST)のインデックスとして使用されます。また、Zはプログラム空間のアクセスや、間接コール、間接ジャンプにも使用されます。
R25:R24、R27:R26、R29:R28、R31:R30
ワード(2バイト)に対して直値の加算、減算ができますが、その対象はこの4組に限られます。ちなみに、許される直値は0-63です。これは、インデックスレジスタの更新のためにあると考えれば、妥当な範囲で、R25:R24は、サービスで扱えるようになっているのでしょう。
R0 .. R15
乗算関係や、LD/ST関係を除いても、レジスタの半分を占めるこれらのレジスタに実行できない命令があります。以下にあげる命令はR16..R31に対してのみ有効なものです。
LDI、ANDI、CPI、ORI、SBCI、SUBI、CBR、SBR、SER
直値が絡んだ演算関係ができません。また、SERは0xffをセットする命令で、CLRは実行できるのに…。
LDI(直値読み込み)ができないのも痛い。
乗算結果は、R1:R0にセットされます。これはいいのですが、乗算のオペランドになれるレジスタの制限は、かなり、奇怪です。乗算がインプリメントされていないAVRもあるぐらいですから、いたしかたないと思います。
MUL | R0..R31 | 符号なし | |
MULS | R16..R31 | 符号付 | |
MULSU | R16..R23 | 符号/符号なし | |
FMUL | R16..R23 | 符号なし小数 | |
FMULS | R16..R23 | 符号付小数 | |
FMULSU | R16..R23 | 符号/符号なし |
ちょっと特殊ですが、プログラム(フラッシュ)への書き込みには、R1:R0が使われます。また、プログラム(フラッシュ)から読む場合にオペランドを書かないと、R0が使われます。
特に、R0..R15の制限は、実際にプログラムを組んでみると、意外に厳しいので、R16..R31を中心に組むことになりそうです。まとめると下の表になります。
reg | 命令の制限 | 乗算関係の制限 | ワード演算 | 機能 |
---|---|---|---|---|
R0 | LDI、ANDI、CPI、ORI SBCI、SUBI、CBR、SBR、 SER不可 |
MULS、MULSU、FMUL、 FMULS、FMULSU不可 |
ADIW、SBIW不可 | 乗算結果を格納 プログラムアクセス |
R1 | ||||
R2 | ||||
R3 | ||||
R4 | ||||
R5 | ||||
R6 | ||||
R7 | ||||
R8 | ||||
R9 | ||||
R10 | ||||
R11 | ||||
R12 | ||||
R13 | ||||
R14 | ||||
R15 | ||||
R16 | ||||
R17 | ||||
R18 | ||||
R19 | ||||
R20 | ||||
R21 | ||||
R22 | ||||
R23 | ||||
R24 | MULSU、FMUL、 FMULS、FMULSU不可 |
|||
R25 | ||||
R26 | インデックスX | |||
R27 | ||||
R28 | インデックスY | |||
R29 | ||||
R30 | インデックスZ | |||
R31 |
命令の並びからいって、ありそうなものがありません。
まず、二項の論理演算関係。AND、OR、XORが可能ですが、XORだけが、直値命令がありません。XORは、ビット反転など良く使うので、あると便利なもの。他に代替えといっても、ビット操作の反転命令もありません。
加減算。加算は、ADD、ADC。減算は、SUB、SBCですが、減算には直値命令があるのに加算にはない。負の数を減算すれば、結果的に加算になるのですが、なんだか…。
命令語を見ていると、これって、同じ命令じゃないか?というものがあります。
結局、アセンブラに内蔵されているマクロ命令のようなもののようです。
ともに直値をオペランドに取り、レジスタにOR演算する命令。機械語を見ると、両方とも、0110_kkkk_dddd_kkkkとなっていて、同じ命令。
これも実はアセンブラが処理していて、CBRはANDIそのもの。オペランドの値はアセンブラが0-kの演算をしてからコードを生成しているようです。
BCLR(SREGのビットクリア)関係の機械語を調べると次のとおり。
BCLR | 1001_0100_1sss_1000 | sssはビット位置 |
CLC | 1001_0100_1000_1000 | sss=0 |
CLZ | 1001_0100_1001_1000 | sss=1 |
CLN | 1001_0100_1010_1000 | sss=2 |
CLV | 1001_0100_1011_1000 | sss=3 |
CLS | 1001_0100_1100_1000 | sss=4 |
CLH | 1001_0100_1101_1000 | sss=5 |
CLT | 1001_0100_1110_1000 | sss=6 |
CLI | 1001_0100_1111_1000 | sss=7 |
要するにBCLRを書きやすくした命令がCLC以降の命令ということになります。
ちなみに、BSET命令の機械語は、1001_0100_0sss_1000。bit7が1ならBCLR命令、0ならば、BSET命令。SEC、SEZ、SEN、SEV、SES、SEH、SET、SEIはBSETの簡略表記。
同様にSREGのブランチ関係も、基本命令は、BRBSとBRBC。
BRBSの機械語は、1111_00kk_kkkk_ksssで、BRBCの機械語が1111_01kk_kkkk_ksss。この二つがブランチ関係の基本命令。
BRBS | 1111_00kk_kkkk_ksss | sssはビット位置 |
BRCS、BRLO | 1111_00kk_kkkk_k000 | sss=0 |
BREQ | 1111_00kk_kkkk_k001 | sss=1 |
BRMI | 1111_00kk_kkkk_k010 | sss=2 |
BRVS | 1111_00kk_kkkk_k011 | sss=3 |
BRLT | 1111_00kk_kkkk_k100 | sss=4 |
BRHS | 1111_00kk_kkkk_k101 | sss=5 |
BRTS | 1111_00kk_kkkk_k110 | sss=6 |
BRIE | 1111_00kk_kkkk_k111 | sss=7 |
BRBC | 1111_01kk_kkkk_ksss | sssはビット位置 |
BRCC、BRSH | 1111_01kk_kkkk_k000 | sss=0 |
BRNE | 1111_01kk_kkkk_k001 | sss=1 |
BRPL | 1111_01kk_kkkk_k010 | sss=2 |
BRVC | 1111_01kk_kkkk_k011 | sss=3 |
BRGE | 1111_01kk_kkkk_k100 | sss=4 |
BRHC | 1111_01kk_kkkk_k101 | sss=5 |
BRTC | 1111_01kk_kkkk_k110 | sss=6 |
BRID | 1111_01kk_kkkk_k111 | sss=7 |
AVRでの条件判断は、通常使うときには、SREGの単一ビットを判定するもの、BRBS/BRBCのマクロ・バリエーションとなります。
スキップをする命令では、任意のレジスターに対するSBRS/SBRCと、IOポートに対するSBIS/SBICですが、こちらもビット判断です。
さて、問題は条件ブランチ命令。二つのレジスタ、あるいは、レジスタと直値の大小比較の話。符号付の場合と符号無しの場合で使用する命令が変わります。
| ||||||||||||||
|
左の表が条件ブランチをまとめたものです。空白の部分が、命令が存在していないところになります。
で、ない部分は、RdとRrを入れ替えれば、書けるので、RdとRrを入れ替えなさいということですね。
CP R16,R17 BRGT XXXX |
ならば、
CP R17,R16 BRLT XXXX |
と書けばよいわけです。でも、これは、判りにくいし、次の例
CPI R16,40 BRGT XXXX |
は、素直に書き換えができない。
CPI R16,40 BREQ SKIP BRGE XXXX SKIP: |
という風になってしまいます(SBRSを使えばラベルは省けるが…)。
AVRは、RISCらしく、SAVE/LOADアーキテクチャ、つまり、演算はレジスタ間で行い、メモリ空間とはLD/ST命令でやり取りするようになっています。実際、ARM7なども、同様の構造なのですが…。
例えば、汎用のメモリ上にある、1バイトをインクリメントする場合は、次のようになります。
LDS R16,XXX INC R16 STS XXX,R16 |
これの実行サイクルを数えると、2 + 1 + 2 で、5サイクルかかります。さらに問題なのは、ここでR16が壊れるので、仮に汎用の処理ならば、前後にPUSH、POPがつく可能性があり、この場合は、さらに 2サイクルずつ追加されて、合計、9サイクルかかります。
これが、PICならば、INCF XXXで1命令、4サイクルで終わります。PICは、RISCといいながら、SAVE/LOADアーキテクチャではないので、メモリに対して直接演算できることが、こういう場合には優位に働きます。
もちろん、ちょっとしたサブルーチンなら、レジスタに入れておけば演算が速いので、PICに対して優位に立つのですが、この場合でも、大抵の場合、ルーチンの入り口、出口にレジスタの退避/復帰処理が入るので、その負担も考慮しなければなりません。
要は、単に実行サイクルの比較だけでは、速度の優劣の判断は難しいということです。コンピュータ的な処理ならば、AVRはPICよりも、相当に優位だと思いますが、小さな制御処理だと、必ずしも優位とはいえないかもしれません。作業域がすべてレジスタに入ってしまうような、もっと、小さいサイズだと、AVRは、PICよりもメチャクチャ速いのですが。
SAVE/LOAD機構なので、結局、メモリとレジスタとのやり取りは、LD/ST類の命令に限られます。間接アドレッシングは、X(R27:R26)、Y(R29:R28)、Z(R31:R30)の三つのインデックスレジスタを使用します。64KBを超えるアドレスを扱う場合には、IO空間にある、RAMPX、RAMPY,RAMPZという各Tバイトが上位アドレスとして連結され、24BITレジストして扱われます。
LDS Rd,XXX / STS XXX,Rr で直接任意のメモリから読み込みます。
64KBを超える場合には、RAMPDと合わせた24ビットでアドレスを指定します。
X、Y、Zレジスタで示されたアドレスでの読み書き。
256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。
64KBを超えるシステムでは、RAMPX、RAMPY、RAMPZを併用します。
LD R16,X など。
X、Y、Zレジスタでアクセスしたのち、X、Y、Zレジスタを +1。
256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。
64KBを超えるシステムでは、RAMPX、RAMPY、RAMPZを併用します。これらの上位アドレスバイトも連結してインクリメントされます。
LD R16,X+ など。
X、Y、Zレジスタを-1したのち、そのアドレスを対象に読み書き。
256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。
64KBを超えるシステムでは、RAMPX、RAMPY、RAMPZを併用します。これらの上位アドレスバイトも連結してデクリメントされます。
LD R16,-X など。
Y、Zレジスタのアドレスにディスプレイスメント(オフセット)を加えてアドレスを読み書き。Xレジスタは使用できません(惜しい!)。オフセットは正の方向だけで、0-63。
256バイトより小さいシステムでは、Y、Zレジスタの上位バイトは使用されません。
64KBを超えるシステムでは、RAMPY、RAMPZを併用します。
LDD R16,Z+15 など。
多くのRISCマシンと異なり、スタックのメカニズムは、既定で用意されています。
スタック自体は、データ空間に置かれます。このため、独自のスタックメモリーを持っているPICのようなサイズの制限は無く、メモリの実装に合わせてサイズを適宜設定できます。
スタックポインタは、AVRのチップのデータ空間の広さに合わせてサイズが変わりますが、IO空間に置かれています。
スタックはアドレスの大きい方から小さい方へ伸びるタイプ(普通のスタック)。スタックポインタはスタックトップよりも一つ小さいアドレス、つまり、次にPUSHすると格納するべきアドレスを指しています。