電子工作

AVR Goes On

ようやく秋葉原でも入手しやすくなってきたAVR。ちょっと、触ってみて、気が付いたことをメモしようかと思います。


入手できるのは

秋葉原で簡単に入手できるチップを調べてみました。(2009/02/28)

一覧表にしてみると、意外に多くの種類が入手可能です。また、DIP品が多いのも嬉しい。AT90S…は旧品なので、これから使うのは避けたいところ。


開発環境

プログラムの開発環境は、メーカー供給のAVRSTUDIOが無償。

ライターは、簡易ライターが色々発表されているし、メーカー純正も比較的安価に手に入ります。そこで、安易に既製品のライターを入手しました。

  1. OPTIMIZE AVRライター

    EZ_USBに接続する小さなボードです。レパートリはOPTIMIZEさん取り扱いのもの。ISPのシリアル書き込みに限られます。

  2. ATMEL ISP MKII

    ほぼ、すべて書き込み可能。シリアル書き込みのみ。

  3. ATMEL AVR DRAGON

    レパートリは限定されているのですが、パラレル書き込みが出来るので、いざというときには便利。

いずれにしても、面倒なのは、書き込み対象に対応して配線をしなければならないこと。配線自体は簡単なんだけれど、プログラム開発中にこういうところで神経を使いたくない。結局、書き込みアダプタ基板を作ることになりそうです。

現在入手できるチップは限られているので、順次使いながら書き込み基板も作ってストックということ。とりあえず、一番安価な、TYNY2313あたりから手がけたいと思っています。


AVRのアドレス空間

AVRは、大まかには、実行命令語を格納するプログラム空間と、操作対象のデータ(メモリ)空間が分かれています。

プログラム空間は、一つのアドレスが1ワード(2バイト)。リニアに配置されていて、現在のところ、論理的には4Mワードが最大の大きさです。

データ空間は一つのアドレスが1バイト。基本的には、アドレスの大きさが16ビットで、64Kバイトの大きさです。しかし、IO領域(空間)にアドレッシングモードに対応して、1バイト(8ビット)分の上位アドレスがあるので、都合24ビット、4Mワードのデータ空間となります。

プログラム空間、データ空間ともに、その大きさは、チップによって異なります。

というように、プログラム空間と、データ空間が分離しているのがハーバード・アーキテクチャーというもので…と説明が続きそうですが、AVRはもう少し複雑な構造をしています。

データ空間に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バイト連結してインデックスレジスタとして使われるものがあり、これに対してのみ可能な演算もあります(これは、納得のいくものですが…)。

  1. インデックスレジスタとして使われるもの(2バイト連結)

    R27:R26(X)、 R29:R28(Y)、 R31:R30(Z)

    XYZはデータ空間とレジスタ間の転送(LDST)のインデックスとして使用されます。また、Zはプログラム空間のアクセスや、間接コール、間接ジャンプにも使用されます。

  2. ワード演算ができるレジスタ(2バイト連結)

    R25:R24R27:R26R29:R28R31:R30

    ワード(2バイト)に対して直値の加算、減算ができますが、その対象はこの4組に限られます。ちなみに、許される直値は0-63です。これは、インデックスレジスタの更新のためにあると考えれば、妥当な範囲で、R25:R24は、サービスで扱えるようになっているのでしょう。

  3. 実行できない命令が多いレジスタ

    R0 .. R15

    乗算関係や、LD/ST関係を除いても、レジスタの半分を占めるこれらのレジスタに実行できない命令があります。以下にあげる命令はR16..R31に対してのみ有効なものです。

    LDIANDICPIORISBCISUBICBRSBRSER

    直値が絡んだ演算関係ができません。また、SERは0xffをセットする命令で、CLRは実行できるのに…。

    LDI(直値読み込み)ができないのも痛い。

  4. 乗算関係のレジスタ制限

    乗算結果は、R1:R0にセットされます。これはいいのですが、乗算のオペランドになれるレジスタの制限は、かなり、奇怪です。乗算がインプリメントされていないAVRもあるぐらいですから、いたしかたないと思います。

    MULR0..R31符号なし
    MULSR16..R31符号付
    MULSUR16..R23符号/符号なし
    FMULR16..R23符号なし小数
    FMULSR16..R23符号付小数
    FMULSUR16..R23符号/符号なし
  5. プログラム書き込み/読み出し

    ちょっと特殊ですが、プログラム(フラッシュ)への書き込みには、R1:R0が使われます。また、プログラム(フラッシュ)から読む場合にオペランドを書かないと、R0が使われます。

特に、R0..R15の制限は、実際にプログラムを組んでみると、意外に厳しいので、R16..R31を中心に組むことになりそうです。まとめると下の表になります。

reg命令の制限乗算関係の制限ワード演算機能
R0 LDIANDICPIORI
SBCISUBICBRSBR
SER不可
MULSMULSUFMUL
FMULSFMULSU不可
ADIWSBIW不可 乗算結果を格納
プログラムアクセス
R1
R2
R3
R4
R5
R6
R7
R8
R9
R10
R11
R12
R13
R14
R15
R16
R17
R18
R19
R20
R21
R22
R23
R24 MULSUFMUL
FMULSFMULSU不可
R25
R26 インデックスX
R27
R28 インデックスY
R29
R30 インデックスZ
R31
命令の対称性

命令の並びからいって、ありそうなものがありません。

まず、二項の論理演算関係。ANDORXORが可能ですが、XORだけが、直値命令がありません。XORは、ビット反転など良く使うので、あると便利なもの。他に代替えといっても、ビット操作の反転命令もありません。

加減算。加算は、ADDADC。減算は、SUBSBCですが、減算には直値命令があるのに加算にはない。負の数を減算すれば、結果的に加算になるのですが、なんだか…。

実は同じ命令?

命令語を見ていると、これって、同じ命令じゃないか?というものがあります。

結局、アセンブラに内蔵されているマクロ命令のようなもののようです。

  1. ORISBR

    ともに直値をオペランドに取り、レジスタにOR演算する命令。機械語を見ると、両方とも、0110_kkkk_dddd_kkkkとなっていて、同じ命令。

  2. ANDICBR

    これも実はアセンブラが処理していて、CBRANDIそのもの。オペランドの値はアセンブラが0-kの演算をしてからコードを生成しているようです。

  3. SREGのビット操作、ブランチ関係。

    BCLRSREGのビットクリア)関係の機械語を調べると次のとおり。

    BCLR1001_0100_1sss_1000sssはビット位置
    CLC1001_0100_1000_1000sss=0
    CLZ1001_0100_1001_1000sss=1
    CLN1001_0100_1010_1000sss=2
    CLV1001_0100_1011_1000sss=3
    CLS1001_0100_1100_1000sss=4
    CLH1001_0100_1101_1000sss=5
    CLT1001_0100_1110_1000sss=6
    CLI1001_0100_1111_1000sss=7

    要するにBCLRを書きやすくした命令がCLC以降の命令ということになります。

    ちなみに、BSET命令の機械語は、1001_0100_0sss_1000。bit7が1ならBCLR命令、0ならば、BSET命令。SECSEZSENSEVSESSEHSETSEIBSETの簡略表記。

    同様にSREGのブランチ関係も、基本命令は、BRBSBRBC

    BRBSの機械語は、1111_00kk_kkkk_ksssで、BRBCの機械語が1111_01kk_kkkk_ksss。この二つがブランチ関係の基本命令。

    BRBS1111_00kk_kkkk_kssssssはビット位置
    BRCSBRLO1111_00kk_kkkk_k000sss=0
    BREQ1111_00kk_kkkk_k001sss=1
    BRMI1111_00kk_kkkk_k010sss=2
    BRVS1111_00kk_kkkk_k011sss=3
    BRLT1111_00kk_kkkk_k100sss=4
    BRHS1111_00kk_kkkk_k101sss=5
    BRTS1111_00kk_kkkk_k110sss=6
    BRIE1111_00kk_kkkk_k111sss=7
    BRBC1111_01kk_kkkk_kssssssはビット位置
    BRCCBRSH1111_01kk_kkkk_k000sss=0
    BRNE1111_01kk_kkkk_k001sss=1
    BRPL1111_01kk_kkkk_k010sss=2
    BRVC1111_01kk_kkkk_k011sss=3
    BRGE1111_01kk_kkkk_k100sss=4
    BRHC1111_01kk_kkkk_k101sss=5
    BRTC1111_01kk_kkkk_k110sss=6
    BRID1111_01kk_kkkk_k111sss=7
条件判断の制約

AVRでの条件判断は、通常使うときには、SREGの単一ビットを判定するもの、BRBS/BRBCのマクロ・バリエーションとなります。

スキップをする命令では、任意のレジスターに対するSBRS/SBRCと、IOポートに対するSBIS/SBICですが、こちらもビット判断です。

さて、問題は条件ブランチ命令。二つのレジスタ、あるいは、レジスタと直値の大小比較の話。符号付の場合と符号無しの場合で使用する命令が変わります。

符号付きの場合
条件命令
Rd > Rr
Rd >= RrBRGE
Rd = RrBREQ
Rd <= Rr
Rd < RrBRLT
Rd != RrBRNE
符号なしの場合
条件命令
Rd > Rr
Rd >= RrBRSH
Rd = RrBREQ
Rd <= Rr
Rd < RrBRLO
Rd != RrBRNE

左の表が条件ブランチをまとめたものです。空白の部分が、命令が存在していないところになります。

で、ない部分は、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を使えばラベルは省けるが…)。

RISCの呪い

AVRは、RISCらしく、SAVE/LOADアーキテクチャ、つまり、演算はレジスタ間で行い、メモリ空間とはLD/ST命令でやり取りするようになっています。実際、ARM7なども、同様の構造なのですが…。

例えば、汎用のメモリ上にある、1バイトをインクリメントする場合は、次のようになります。

	LDS	R16,XXX
	INC	R16
	STS	XXX,R16

これの実行サイクルを数えると、2 + 1 + 2 で、5サイクルかかります。さらに問題なのは、ここでR16が壊れるので、仮に汎用の処理ならば、前後にPUSHPOPがつく可能性があり、この場合は、さらに 2サイクルずつ追加されて、合計、9サイクルかかります。

これが、PICならば、INCF XXXで1命令、4サイクルで終わります。PICは、RISCといいながら、SAVE/LOADアーキテクチャではないので、メモリに対して直接演算できることが、こういう場合には優位に働きます。

もちろん、ちょっとしたサブルーチンなら、レジスタに入れておけば演算が速いので、PICに対して優位に立つのですが、この場合でも、大抵の場合、ルーチンの入り口、出口にレジスタの退避/復帰処理が入るので、その負担も考慮しなければなりません。

要は、単に実行サイクルの比較だけでは、速度の優劣の判断は難しいということです。コンピュータ的な処理ならば、AVRPICよりも、相当に優位だと思いますが、小さな制御処理だと、必ずしも優位とはいえないかもしれません。作業域がすべてレジスタに入ってしまうような、もっと、小さいサイズだと、AVRは、PICよりもメチャクチャ速いのですが。

アドレッシング・モード

SAVE/LOAD機構なので、結局、メモリとレジスタとのやり取りは、LD/ST類の命令に限られます。間接アドレッシングは、X(R27:R26)、Y(R29:R28)、Z(R31:R30)の三つのインデックスレジスタを使用します。64KBを超えるアドレスを扱う場合には、IO空間にある、RAMPXRAMPY,RAMPZという各Tバイトが上位アドレスとして連結され、24BITレジストして扱われます。

  1. 直接アドレス

    LDS Rd,XXX / STS XXX,Rr で直接任意のメモリから読み込みます。

    64KBを超える場合には、RAMPDと合わせた24ビットでアドレスを指定します。

  2. 間接アドレッシング

    X、Y、Zレジスタで示されたアドレスでの読み書き。

    256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。

    64KBを超えるシステムでは、RAMPXRAMPYRAMPZを併用します。

    LD R16,X など。

  3. 間接アドレッシング(ポストインクリメント)

    X、Y、Zレジスタでアクセスしたのち、X、Y、Zレジスタを +1。

    256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。

    64KBを超えるシステムでは、RAMPXRAMPYRAMPZを併用します。これらの上位アドレスバイトも連結してインクリメントされます。

    LD R16,X+ など。

  4. 間接アドレッシング(プリデクリメント)

    X、Y、Zレジスタを-1したのち、そのアドレスを対象に読み書き。

    256バイトより小さいシステムでは、X、Y、Zレジスタの上位バイトは使用されません。

    64KBを超えるシステムでは、RAMPXRAMPYRAMPZを併用します。これらの上位アドレスバイトも連結してデクリメントされます。

    LD R16,-X など。

  5. 間接アドレッシング(オフセット)

    Y、Zレジスタのアドレスにディスプレイスメント(オフセット)を加えてアドレスを読み書き。Xレジスタは使用できません(惜しい!)。オフセットは正の方向だけで、0-63。

    256バイトより小さいシステムでは、Y、Zレジスタの上位バイトは使用されません。

    64KBを超えるシステムでは、RAMPYRAMPZを併用します。

    LDD R16,Z+15 など。


スタック

多くのRISCマシンと異なり、スタックのメカニズムは、既定で用意されています。

スタック自体は、データ空間に置かれます。このため、独自のスタックメモリーを持っているPICのようなサイズの制限は無く、メモリの実装に合わせてサイズを適宜設定できます。

スタックポインタは、AVRのチップのデータ空間の広さに合わせてサイズが変わりますが、IO空間に置かれています。

スタックはアドレスの大きい方から小さい方へ伸びるタイプ(普通のスタック)。スタックポインタはスタックトップよりも一つ小さいアドレス、つまり、次にPUSHすると格納するべきアドレスを指しています。

メニューへ戻る
アルゴ算法堂へ戻る