仕様の説明についてはDataSheetの私的和訳要約です(笑)。
お品書き |
● AVRのメモリ構造と命令セット
● 割り込みプログラム
● EEPROMやプログラムメモリの使用
● 8515で拡張RAMをつなぐ
● シリアル通信
● AD変換
● AVR-GCCのインラインアセンブラ
メモリ構造と命令セットについて |
●AVRのメモリー構造
AVRのメモリ構造はProgramMemoryとDataMemoryが分かれているHarvard architectureとなっています。これにより命令のPreFetchと計算結果の書き込みをパイプライン化しています(16+8=24bitのバスを持っていることになります)。ProgramFlash,Register,I/O,SRAMの構造は次のようになっています。at90s2313の例です。
|
|
CPUレジスタ(32本)、I/O(64byte)、SRAM(128byte)が同じ空間に配置されています。ロードストア命令時にはこれらのレジスタにリニアにアクセス可能です。プログラムメモリは16bit境界でアドレッシングされています。
●命令セット(アセンブラ)
・General Purpose Registerについて
32本のレジスタはほぼ同等の機能を持っています。リトルエンディアンです。
(1) 即値計算、代入(LDI)、ビット操作(SBR,CBR)はR16-R31のみ。即値コードに値を含む。アドレス直接ロードストア(LDS,STS)、bit
skip(SBRC,SBRS)はすべてのレジスタで可能。
(2) 16bit即値計算はR24-R31のみ。ただし値は0-63まで。
(3) インデックスポインタはR26-R31を使ったX,Y,Zの3本。
(4) X,Y,Zを使ったロードストアはPostIncrement,PreDecriment可能(LD命令)。Y,Zのみdisplacementが使える(LDD命令)。ProgramMemoryロード(LPM)はZ使用のみ可能。
(5) 掛け算命令(MUL,MULS,MULSU,FMUL,FMULS,FMULSU)の結果はR0:R1に入る。FMUL系はDSPで使われる小数演算。
・I/O命令について
(1) IN,OUT命令によるI/OアクセスはI/Oアドレスで行う。I/O命令アドレスの$00はDataMemoryアクセス(ロードストア)時の$20と同じ。
(2) I/Oアドレスでのbit操作命令(CBI,SBI)、スキップ命令(SBIC,SBIS)はI/O$00-$1Fまでのみ可能。STATUSレジスタ(I/O$3F)は専用命令を持つ。
・STATUSについて
(1) STATUS(I/O$3F)レジスタのbit操作は専用命令がある(基本はBSET,BCLR)。
(2) STATUSによる分岐命令は前後64命令まで(基本はBRBC,BRBS)。
・比較分岐命令について
(1) 16bit以上に比較分岐に便利なように Z flag が伝播する引き算命令(CPC,SBC,SBCI)がある。おもにキャリーつき引き算で
Z flag が伝播します。
(2) 比較のみ行う命令(CP,CPC,CPI)がありさらに比較スキップ(CPSE)がある。
(3) 値が0あるいはマイナスであることを調べる命令(TST)がある。
(4) ジャンプ(RJMP,JMP,IJMP)、コール命令(RCALL,CALL,ICALL)はR系が前後2K, normal系は64Kword,
I系はZインデックス。
割り込みプログラム |
割り込みプログラムを書いてみます。今回はタイマー割り込みを使用したいと思います。
●AVRの割り込みシステムについて
AVRは割り込みベクタ形式で割り込みがかかります。ベクタの配置はAT90S2313の場合次のようになっています。AddressはAVRの場合2バイト境界となっています。通常はrjmp命令で埋めるようです。この順番は割り込みのpriority順となっています。at90s2313の例です。
|
ProgramAddress | Source | InterruptDifinition |
|
|
RESET | Hardware Pin,Power-on Reset and Watchdog Reset |
|
|
INT0 | External Interrupt Request 0 |
|
|
INT1 | External Interrupt Request 1 |
|
|
TIMER1 CAPT1 | Timer/Counter1 Capture Event |
|
|
TIMER1 COMP1 | Timer/Counter1 Compare Match |
|
|
TIMER1 OVF0 | Timer/Counter1 Overflow |
|
|
TIMER0 OVF0 | Timer/Counter0 Overflow |
|
|
UART,RX | UART,RX Complete |
|
|
UART,UDRE | UART Data Register Empty |
|
|
UART,TX | UART,TX Complete |
|
|
ANA_COMP | Analog Comparator |
割り込みは開始(ベクタのRJMP命令実行)までに4clock、割り込み終了後(RETI実行後)4clockかかります(スタックの復帰退避などのため)。もし同時に割り込みが起こった場合は優先順に従って次々と割り込み処理されていきます。
●割り込みに関するレジスタ
またそれらに付随して、割り込みマスク、割り込みフラグがあります。括弧内はI/Oアドレスです。
・割り込みマスクは1をセットすると割り込み可能になります。0でマスク状態です。
・割り込みフラグは割り込みが起こったときにセットされ、割り込みハンドリング時に自動でクリアされます。割り込みをマスクしていた場合にはフラグは立ったままとなります。
・SREG ($3F) : Status Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・GIMSK ($3B) : General Interrupt Mask
Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・GIFR ($3A) : General Interrupt FLAG
Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・MCUCR ($35) : MCU Control Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
効果 |
|
|
Low levelの間割り込みがかかります |
|
|
予約 |
|
|
falling edgeで割り込みがかかります |
|
|
rising edgeで割り込みがかかります |
・TIMSK ($39) : Timer/Counter Interrupt
Mask Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・TIFR ($38) : Timer/Counter Interrupt
FlLAG Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
●AVR-GCCで割り込みを掛ける
実際にAVR-GCCで割り込みプログラミングしてみます。回路はAVR-startのページのものを使用しました。
仕様は「タイマー割り込みを利用してLEDを点滅させる」です。at90s2313のTMR0でタイマー割り込みをかけてみました。
・プログラム
プログラム例です。ファイルはこれです。出力アセンブラリストはこれです。
avr-gcc -mmcu=at90s2313 inttimer.c -o inttimer.elf
avr-objcopy -O ihex inttimer.elf inttimer.hex
でromイメージができます。同じようにLEDが点滅すれば成功です。
/**** interrupt function ****/ SIGNAL(SIG_OVERFLOW0) { ・・・・ }
のように割り込みルーチンを記述します。
sig-avr.hに割り込みルーチンのSIGNAL定義があります。
interrupt.hにsei()命令(global interrupt enable flag set)があります。
プログラムではカウンタクロック源を内部CL/1024にセットし割り込みタイマー割り込みマスクをはずし(1をセットし)、グローバル割り込みを許可にするといった手順です。
・印象
できたコードが長い!!豪快にpush-popしてます。(アセンブラなら短いのに...)
PICと比べて・・・レジスタが多い分コンテクストの退避復帰が大変。けど割り込みシーケンスやCPU速度を考慮すれば実行時間はあまり変わらない?ただしアセンブラでは互角ではないでしょうか。
・追伸
大変なミスをしてました。最適化オプションをつけてませんでした。
avr-gcc -Os -mmcu=at90s2313 inttimer.c -S
としてコードサイズで最適化してみるとこのようになりました。かなり最適化できてるようです。(すげー。)
EEPROMとプログラムメモリの使用 |
AVRのEEPROM使用とプログラムメモリのconst dataとしての使用について書きます。
●EEPROMに関するレジスタ
EEPROMに対してはI/Oレジスタを通じでアクセスします。at90s8535の例ですがat90s2313の拡張版のようなものです(アドレス等は同じです)。
・EEARH,EEARL ($1F,$1E) : EEPROM Address
Register
EEARH ($1F)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・EEDR ($1D) : EEPROM Data Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・EECR ($1C) : EEPROM Control Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・Errata
書き込み中にリセットがかかったとき(BODなども含む)0x00番地にデータが書き込まれてしまいます。0x00番地は使わないようにとのことです。
●プログラムメモリのconstデータとしての使用
アセンブラではLPMを使います。
●AVR-GCCでプログラムメモリconstおよびEEPROMを使う
AVR-GCCにはEEPROMアクセスのための関数があります。それを利用しました。LCDでチェックしたので今回はat90s8535を使用しました。LCDとLEDの配線はlcd.hにあります。
・プログラムの例
例はこれです。またLCDライブラリとMakefileをまとめたものはこれです。
・PROGMEM プログラム
プログラムメモリconstを使うためにprogmem.hをincludeします。グローバルとして定義するときはPROGMEM修飾をつけます。
/*program memory constants*/ PROGMEM char hello[] = "Hello AVR world.";
読み出すときは
char c; c = PRG_RDB(&hello[i]);
のようにPRG_RDB(address)というマクロを使用します。またプログラム中にconst値を埋め込むときは、
/*pointer access*/ char *s; s = PSTR("EEPROM test"); /*get pointer for inline progmem const value*/ c = PRG_RDB(s++);
のようにポインタを取得して読み込みます。
・EEPROM プログラム
eepromライブラリを使うためにeeprom.hをincludeします。命令は
eeprom_is_ready() : 読み書きの準備ができているとき1そうでないとき0を返す。
unsigned char eeprom_rb(unsigned int addr) : バイト読み込み。
unsigned int eeprom_rw(unsigned int addr) : ワード読み込み(little endian)。
void eeprom_wb(unsigned int addr,unsigned char val) : バイト書き込み。
void eeprom_read_block(void *buf,unsigned int addr,size_t n) :
ブロック読み込み。
があります。まず読み書き準備ができているか確認して、読み書きします。
AT90S8515でSRAMを拡張する |
8515など一部のAVRはデータSRAMを拡張することができます。このような1chipマイコンで拡張RAMをつけることに意義があるかは別として、外部ペリフェラルなどもメモリマップドIOとして接続できるようになります。8515は8051の代替品として位置付けられているからでしょうか。
増設されたメモリは内蔵RAMの後ろにくっつき、間接アクセス(ポインタ)などで普通の命令でアクセスできるようになります。
●拡張SRAMに関するレジスタ
拡張RAMを有効にするためには最初にレジスタの設定をしなくてはいけません。といっても1つだけです。
・MCUCR ($35) : MCU Control Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
●拡張SRAMの接続
インテルの8086のようにアドレスバスとデータバスがマルチプレクスされていますので、トランスペアレントラッチで分離します。8086とおなじように74HC573を使用しました。回路図はこれです。SRAMは62256-70互換品(32KByte)を使用しました。こっちは田舎なので980円もしました。(そんなばかなっ。)
これがその写真です。一番下のAVRと同じくらい自己主張してるやつがSRAM(しつこいですが980円)です。AVRとの間にラッチがはさっまっています。シリアルポートはほとんどRS485状態なのでステレオジャックにしてしまいました。SRAMはMOSEL
VITELICとかのやつでスタンバイ時20uAです。かつ35ns品まであるそうな。
●AVR-GCCで拡張SRAMを認識させる
拡張SRAMをつないだらそれを使用できなくてはいけません。アセンブラでしたら単にMCUCRのフラグを立てるだけですが、Cコンパイラの場合は自動でメモリを割り振るので認識させないといけません。それには以下の3つの過程が必要です。これらはローダで使用されます。
(1) メモリの大きさを認識させる
まずメモリの大きさですが、/avr/lib/ldscriptsにローダスクリプトがありますのでavr85xx.xをカレントディレクトリに移動させ、たとえばex_avr85xx.xという名前に変更してこれを使用します。さいしょにMEMORYの指定がありますのでこれを
MEMORY { text (rx) : ORIGIN = 0, LENGTH = 8K data (rw!x) : ORIGIN = 0x800060, LENGTH = 32K eeprom (rw!x) : ORIGIN = 0, LENGTH = 512 }
のように大きくします(変更はこれだけ)。それをロードさせるようにします。avr-gccプリプロセッサから読み込むときは(これが推奨されているようです) -Wl,--script,ex_avr85xx.x オプションを加えます。
(2) スタートアップでSRAMを有効にする
スタートアップコード内(gcrt1.S)でMCU Control RegisterやWDTのレジスタ設定を行っていますが、これらは
.weak __init_mcucr__ で定義されていますのでプログラムのロード時にこれをオーバーライドできます。最後にスタートアップコード(crt8515.o)とオブジェクトファイルをまとめるときに(*.oから*.elfにまとめるときに)ローダオプションで指定しました。なぜか直接書き込んでもうまくいきませんでした。avr-gccプリプロセッサからまとめるときは
-Wl,--defsym,__init_mcucr__=0xC0 としてローダへ渡します。0x80にするとNO-Waitになります。
(3) スタックポインタの場所を拡張メモリの最後に置く
さらにスタックポインタも設定します。そのままでもグローバルとして領域を取ることはできますが、これをしないとローカル変数として大きな領域を取ることができません。ただしスタックが外部RAMになるとどうしてもアクセスが遅くなります。(2)と同じく
__stack が .weak として宣言されているのでオーバーライドします。たとえば -Wl,--defsym,__stack=0x7fff
のようにメモリの最後に指定しています。(この場合メモリの最後は0x825fのほうでしょうか?誰か教えてください)
●プログラム例
これらをまとめたものをここにおいておきます。ローダスクリプトとMakefileです。Makefile内で
#linker flags
LDFLAGS = -Wl,-Map=$(TARG).map,--cref -Wl,--script,ex_avr85xx.x
-Wl,--defsym,__init_mcucr__=0xC0 -Wl,--defsym,__stack=0x7fff
としています。プログラムはint型で8192個の配列を確保してチェックしています。
・追伸(2000/08/24)
よく考えると、せっかくバスがあるからLCDもそっちにぶら下げればよかったです。ぼけてました。
シリアル通信(UART) |
シリアル通信の方法はPICと同じように簡単です。IOのアドレスが各種AVR間で同じなので可搬性がよくて助かっています。どうでもいいからまず動作確認したい場合は以下の命令だけでOKです。IOの入出力なんかもオーバーライドされます。
/* enable RxD/TxD */ outp(BV(RXEN)|BV(TXEN),UCR); /*9600bps at 8Mhz*/ outp(51,UBRR); /*put char*/ outp('#',UDR);
これを焼いて走らせてtera termなんかでモニタしたら取り合えず#が画面に映るはずです。(BVはビット数を数値に直す命令です。たとえばBV(7)は0x80になります。)
●UARTに関するレジスタ
4つのレジスタがあります。
・UCR($0A) : UART Control Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・UBRR($09) : UART BAUD Rate Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Baud | 4800 | 9600 | 19200 | 38400 | 57600 | 115200 |
UBRR | 103 | 51 | 25 | 12 | 8 | 3 |
・UDR($0C) : UART I/O Data Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・USR($0B) : UART Status Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
●GCCによる実装
一つ前の増設SRAMのプログラムにポーリングによる送受信例があります。単純に受信したデータをエコーするだけです。LCDに表示するのも受信データをLCD_data(char
c)で表示するだけですので簡単にできると思います。普通のターミナルソフト(tera termなど)を意識して、リターンコードだけエコー時に行送りも追加するようにしています。tera
termはシリアルポートも使えるのでので便利です。Linuxだったらkermitでしょうか。
割り込み駆動の例はAVR-libcの中にprintfを送信するものが入っています。
AD変換 |
ADCのついているAT90S8535でテストします。このチップには8入力マルチプレクサつきの10bitADCがついています。またフリーランモードで1クロック早く変換することもできるようです。もともと消費電力が小さいので大して効果はありませんが、変換後の割り込みを利用して変換中にCPUをスリープさせ、ノイズを減らすこともできます。
●AD変換に関するレジスタ
AD変換に関してもI/Oレジスタを通してアクセスします。コントロール1本、チャンネル設定1本、16bitデータレジスタ1本のシンプルな構成です。
・ADCSR($06) : ADC Control and Status Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・ADMUX($07) : ADC Multiplexer Select Register
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
・ADCH,ADCL ($05,$04) : ADC Data Register
ADCH ($05)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
●AD変換のためのポートの準備と回路の準備
AD変換するためのポートは基本的に入力ポートにしてプルアップをoffにしておきましょう。DDRA($1A)の対応するビットを0にして入力にし、PORTA($1B)の対応するビットを0にしてプルアップをoffにします。
回路上では、AVCC,AGND,AREFをつなぎます。AREFの電圧の時最大値になります。
●GCCでADCを使用する例
AT90S8535でやってみました。探してみたのですが、変換を行う関数はlibcには入っていないようです。
初期化はたとえば
/*for ADC*/ outp(0,DDRA); /*all input pins*/ outp(0,PORTA); /*disable pull up*/ outp(BV(ADEN)|BV(ADSC)|0x06,ADCSR); /*clock 8MHz/64 = 125kHz*/
として行います。このときADCそのものの初期化シーケンスを行うためにADSCも立てています。例としてAD変換するルーチンは
/*AD conversion routine*/ int AD_get(char ch) { /*set channel*/ ch &= 0x07; outp(ch,ADMUX); /*start ad conversion*/ cbi(ADCSR,ADIF); sbi(ADCSR,ADSC); loop_until_bit_is_set(ADCSR,ADIF); /*bit treat special function*/ return __inw(ADCL); /*read ADCL first function from iomacros.h*/ }
のようになります。loop_until_bit_is_setは特定のビットが1になるのを待つ命令です。あと、__inwは下のバイトから読むためのルーチンです。16bitカウンタの読み込みなどにもつかえます。どちらもAVR-libcの中に入っています。これらの特殊命令を使うとコードサイズやスピードが小さくなるようです。
わりと変換特性はいいようです。そのままで10bit値で最下位が静止します。ただし同時に派手にI/OをばたばたさせるとAREFが揺らぐのか最下位ビットがゆれるようです。
●スリープモードを使用する例
スリープモードを使用するとよりノイズの少ない変換が可能となります。
プログラム例はこれです。nopやsleepはインラインアセンブラで入れています。
まず初期化の時にMCUCRを操作し、スリープを許可します。スリープを使用するときはまずADCSRレジスタのADIEを1にして割り込みを許可しておきます。その後SREGのIフラグを1にしてグローバル割り込みを許可しておきます。
スリープモードに入る前には、以前の割り込み時にクリアされているはずですが、一応ADCSRのADIFをクリアしておきました。その後おもむろにスリープします。このときADCSRのADSCbitを1にする必要はありません。sleep命令時に自動でスタートします。よってスリープ時には、ADCSRのビットを
ADEN = 1
ADSC = 0
ADIF = 0
ADIE = 1
の状態でsleep命令を実行します。変換後にスリープから起きて割り込みルーチンに飛びます。ただしerrataに出ているように割り込みにはいる前にsleepに続く2,3命令が実行されてしまうようですので、今回の場合にはなくてもいいのですが、ここにはnopを入れておきました。ただし、他の割り込みが入るとsleepから起きてしまうので割り込みを複数使用するときにはsleepは使えないでしょう。
でも、今回は回路的にもともと安定していたので、変換値にあまり効果がありませんでしたが、待ち命令を入れる必要がないのでコードがすっきりして短くなる効果はあります。
AVR-GCCのインラインアセンブラ |
Harald KippさんのAvr-gcc Inline Assembler Cook Bookを訳しました。これもあわせてどうぞごらんください。もしここは変ですというご意見がありましたらメールください。
AVR-GCCのインラインアセンブラの書き方について、述べます。AVR固有のものとGCC標準のものが混ざっていますが、ご容赦ください。2001/03ではまだ正式リリースされていませんがGCCはVer3を基準に書きます。
まずは、GCCのinfoファイルをご覧になるのが本筋と思います。あと一つ前のページのtipsの中にインラインアセンブラの書き方についてのあるMLでの記事がありますのでそれとあわせてみるといいのではと思います。またstring-avr.hの中に少しインラインアセンブラが入っているようですのでそれも参考になると思います。すべてを述べるためにinfoの和訳でもいいのですが、それだとmaroly自身が後から見たときわからなくなるので表と例題で書いていこうと思います。
例題は順次増やしていこうと思います。
●はじめに
gccのインラインアセンブラ内では、実際の値が何でどこに配置されているのかを推測する必要がないようになっています。しかしインラインアセンブラ内での変数の使われ方を制御する必要があります。よってオペランドの制御子と修飾子を知っておく必要があります。インラインアセンブラと単にアセンブラファイルとリンクする方法とは区別します。ここではインラインアセンブラのみ扱います。
アセンブラとリンクさせるときはcall convensionが重要になりますがこれはML上では実際にダミー関数をつくって調べるとよいということになっているようです(実際、アセンブラ出力を見ればすぐわかる)。その内容の概略は前のページの時系列情報の中のリンクに少しレポートしています。
●例題1−ただ値をインクリメントする関数
単なるお試しプログラムです。ISO99-C関連でアンダースコアを2つつけることにするそうです。
unsigned int IncFunc(unsigned int x) { __asm__ __volatile__("\n\t" "adiw %0,1\n\t" :"=&w"(x) /*outputs*/ :"0"(x) /*inputs*/ :"memory" /*write to phisical memory if remain in register*/ ); return x; }
\n\tは改行後タブ位置に移動します。命令はadiwだけでただ値をインクリメントします。最初のコロンのあとにインラインアセンブラから出力する値、次のコロンはインラインアセンブラへ引き渡すCの値、最後のmemoryはもしレジスタに値が残ったままだったらメモリに書き出す意味です。記号の意味は下をご覧ください。inputsのところの0は"=&w"で示した最初の値をさします。こうすることで入力値を出力値のレジスタを同じにすることができます。adiw %0,1の「%0」はoutputsとinputsのレジスタの通し番号です。この例では%0,%1が使えます。%1のほうは上記で説明したとおり通し番号0と同じものになります。
●例題2−CPUループによるウェイト
CPUループを使うときは最適化オプションで速度が変わってしまうことがあります。最適化オプションによらずいつも同じ速度でウェイトを掛けたいときはそこをインラインアセンブラにするのが確実だと思います。volatileをかけるのはインラインアセンブラそのものが最適化で削除されるのを防ぐためです。
void CPU_wait(unsigned int time){ register unsigned char lpcnt; __asm__ __volatile__("\n" "CPU_wait_entry:\n\t" "ldi %0,200\n" "CPU_wait_lp:\n\t" "nop\n\t" "dec %0\n\t" "brne CPU_wait_lp\n\t" "sbiw %1,1\n\t" "brne CPU_wait_entry\n\t" :"=&a"(lpcnt) :"w"(time) ); return; }
この例は0.1ms単位で設定しています。やはりCでコーディングするよりもHEXファイルは小さいです。内部ループ用のレジスタをregister修飾でlpcntを作っています。インラインアセンブラ内でラベルを使用しています。
●オペランド制御子
レジスタが完全に等価でない場合のレジスタの型(クラス)の定義です。ほとんどAVR特化のほうでいいと思いますが、gcc一般のものも挙げます。gcc一般のほうはアセンブラで受け入れてくれるものは一部でしょう。
これらは普通はコンパイラ内部で、あるopcodeに対して使用可能なoprandを制御するための定義に使用されるものです(gcc/config/avr/avr.mdを参照すると載っています)。つまりこのファイルで定義されているものがoprandとして使用可能です。
・AVR特化のオペランド制御子
制御子 | 意味 |
|
Register from r0 to r15. |
|
Register from r16 to r23. |
|
Register from r16 to r31. |
|
Register from r24 to r31. These register can be used in 'adiw' command. |
|
Pointer register (r26 - r31). |
|
Base pointer register (r28 - r31). |
|
Stack pointer register (SPH:SPL). |
|
Temporary register r0. |
|
Register pair X (r27:r26). |
|
Register pair Y (r29:r28). |
|
Register pair Z (r31:r30). |
|
Constant greater than -1, less than 64. |
|
Constant greater than -64, less than 1. |
|
Constant integer 2. |
|
Constant integer 0. |
|
Constant that fits in 8bits. |
|
Constant integer -1. |
|
Constant integer 8, 16, or 24. |
|
Constant integer 1. |
|
A floating point constant 0.0 |
・gcc一般のオペランド制御子
意味の方はかなり要約してるのですこしずれているかも知れませんが大体の意味です。IA32(i386)や68000系でも使うものです。もともとGCCはレジスタが等価なRISCとターゲットとしてるようです。その点AVRは向いてるかも。
制御子 | 意味 |
|
種類を問わずメモリオペランドを可能にする。 |
|
オフセット可能なメモリオペランド。 |
|
オフセットできないメモリオペランド。 |
|
preまたはpostデクリメントができるメモリオペランド。 |
|
preまたはpostインクリメントできるメモリオペランド。 |
|
汎用レジスタとして使えるレジスタオペランド。 |
|
シンボルも使える即値整数をオペランドとして可能にする。もちろんシンボル値はアセンブラ時に値がわかっていないといけない。 |
|
(アセンブラが)すでにわかっている即値を数字としてオペランドに可能にする。多くのアセンブラはこっちしか使えないそうなので、"i"より"n"を使うべきです。 |
|
マシン依存の即値。たとえばディスプレースメントは通常範囲があるのでそれ以外を受け付けないようにする。AVRでは上記のようになっている。 |
|
フロートのそくちを可能にする。ホストマシンとターゲットマシンのformatが同じでないとちゃんと動かない。 |
|
マシン依存のフロート即値。AVRでは"G"が一応定義されている。 |
|
整数の即値だけれども、ある範囲を除外するときに使うもの。 |
|
どんなレジスタ、即値、メモリも受け入れる。ただしレジスタが一般レジスタでないときは除く。 |
|
そんなオペランドでも受け付ける。一般レジスタでなくても。 |
|
特定の数字のみ受け入れる。r0とかで物理的なレジスタ指定できる。CISCマイコンでは特に必要性がある。 |
|
有効な範囲のみ受け入れるアドレスオペランド。 |
●レジスタ制御子の修飾子
制御子の修飾をします。特に書き込み可能にしたりする機能があります。基本はread onlyになっているためです。
修飾子 | 意味 |
|
write-onlyにする。前の値は捨てられて新しい値が入る。修飾の最初につける。 |
|
命令でread-write可能にする。修飾の最初につける。 |
|
レジスタ番号が重ならないようにする修飾子です。つまりインラインアセンブラ内では重なる可能性があるということです。その結果input valueが上書きされてしまうのを防ぎます。 |
|
命令に続く2つのオペランドを交換可能にすることで、制御子のフィット性を高める。 |
|
これに続く文字はオペランド制御を無視する。 |
|
これに続く文字はレジスタ選択からはずされる。 |
●アセンブラでのシンボル
リンク時にアンダースコア"_"を使わないで済む方法として
int foo asm("myfoo") = 2;
とすることができる。アセンブラ内ではアンダースコアなしのmyfooを使える。
関数の場合は
extern func() asm("FUNC");
func(x,y)
int x,y;
・・・・
とする。
●C変数のレジスタ化(Cでのregister修飾)
・グローバル変数の場合
すべてのプログラムを通してその変数のためにレジスタが保存される。
・ローカル変数の場合
必要ないとコンパイラが判断した時点で破棄される。しかしインラインアセンブラには役に立つ。つまりインラインアセンブラ内でダイレクトにレジスタ変数に書き込むことができる。