AVR Libc Home Page | ![]() |
AVR Libc Development Pages | ||
Main Page | FAQ | Library Reference | Additional Documentation | Example Projects |
uint8_t flag; ... ISR(SOME_vect) { flag = 1; } ... while (flag == 0) { ... }
最適化オプションを使っている場合、上記のようなループにおいて、コンパイラがループ内部で flag を書き換えていないと判断してしまい、(flagは0のままでこのwhile節の中身は無条件に実行されると判断して) 最適化を行い flag の参照を完全に除去してしまいます。(残念ながら割り込みルーチンの中身まではチェックしてくれない)
コンパイラに、値が(割り込みなどにより)文法解析のスコープ外※から変更されうると知らせるためには、以下のような宣言が必要です。
※ここでいうスコープはC言語の変数有効範囲のことではなく(グローバル変数だし)、gccの文法解析がチェックする範囲のことを言っているっぽいです。
volatile uint8_t flag;
Back to FAQ Index.
<math.h>
で宣言されている数学的関数はリンカに対しライブラリlibm.a もリンクするよう知らせてやらないと使えません。
特にlibm.aのようなシステムライブラリは、リンクステップの指示をするCコンパイラのコマンドラインの最後でフラグ-lmを付加することでリンクされます。 (-lの後にある文字に、頭にlib、末尾に.a をつけたライブラリがリンクされます。たとえば libfoo.a をリンクするには、-lfoo を付加します。) これを受けてリンカはシステムで決められたpathに指定されたライブラリを捜しに行きます。
もう一つの方法は、すべてのオブジェクトファイル(*.o)指定の後に、 libm.a ファイルのフルパス名をコマンドラインで指定することです。しかしながら、これはライブラリの置いてある場所を正しく知っていないといけないので、推奨されません。
Back to FAQ Index.
このようにして実現できます:register unsigned char counter asm("r3");
一般的にはr2〜r15レジスタを利用すべきです。
詳しくは、インラインアセンブラ−アセンブラコードで使われるCの名前も参照ください。
Back to FAQ Index.
できるだけ早期に初期化を行う(MCUCR、WDTCRなどスタートアップルーチンが実行される前に設定する)方法は、ちょっと変わっていて、しかし柔軟な方法があります。
基本的には以下のような小さなアセンブラファイルを書くことになります。
これをアセンブルし、できたxram.o を他のファイルと共にリンクしてください。このコードは初期化コードの中でもリセット直後に実行されます。リンカスクリプトの新しい.initN セクションについてのコメント(どれがを使うかなど)をご覧ください。??
※.initN セクションも参照。
このやり方の利点は、どんな初期化コードでも挿入できることです。
これは超早期スタートアップなので、スタックも、__zero_reg__も用意されていないことにご注意ください。
使用しない場合はプログラムメモリが消費されることはありません。??
いくつかのとても特殊なケースを除き、リンカスクリプトを変更する必要はありません。
__stack(内蔵RAMの終端、スタック開始端) はデフォルト値のままにしておく方がよいです。その方がスタックに高速な内蔵メモリを使えるため、より高速だです。またATmega161など一部のデバイスではバグのためスタックを動かせません。
データセクションをスタックより上位アドレスに置くには、-Wl,-Tdata,0x801100 のようにアドレス指定を加えてください。
Cからメモリセクションを利用するためのより詳しい情報(Cコードからの利用法など)は、 メモリセクションもご覧ください。There is also an example for Cでセクションを扱うには、用例も紹介しています。 Cコードでは、どの関数もセクション.init3
に置かれるべきです。というのは、セクション.init2
内のコードで内部レジスタ__zero_reg__
をクリアしているからです。??
(Note that in C code, any such function would preferrably be placed into section .init3 as the code in .init2 ensures the internal register__zero_reg__
is already cleared.)
Back to FAQ Index.
マイクロプロセッサのプログラミングとしては重要なローレベルな処理を行い場合、レジスタの特定のビットをセットしたりクリアしたりする必要があることがよくあります。デバイスの説明書にはIOレジスタ内の多種のビットについて名前が定義されており、AVR デバイス独自のI/O定義ではこれらの名前をビット番号を表す定数として定義しています。
これらビット番号(通常はバイトレジスタ内)をレジスタとやりとりできるバイト値に変換する必要があります。(※PORTBの第5ビットだけを立てる→PORTB=0x20など)。しかしながら、時にダイレクトなビット番号もまた使われることがあるので、(sbi()マクロなど) バイト値での定義はあまり使いやすいとは言えません。
そこで、ビットナンバーをバイト値に変換するため、_BV()
マクロを使ってください。このマクロは単なるビットシフトにより実現されています。(この計算はコンパイラで行われるので、コードやランタイムの増加をもたらすことはありません)。ce.
_BV(3) → 1 << 3 → 0x08
しかしながら、このマクロを使うことでプログラムが読みやすくなります。
"BV" は "bit value" を意味します。誰かに質問されたらそう答えてください。 :-)
例:
タイマ2が フルIO clockで動作 (プリスケーラCK/1、CS2x=0b001)で、
コンペアマッチ時OC2出力はトグル動作指定(COM21-COM20 = 0b01)で、
CTC動作でタイマもクリアする(CTC2 = 1) 。OC2 (PD7) を出力に指定する。
※訳注:_BV()は、デフォルトの16bit演算を行っているので、32bit変数などと共に_BV(16)のようなものを使おうとすると破綻します。その場合は(1UL<<16)のようにして使ってください。However, using the macro often makes the program better readable.
Back to FAQ Index.
基本的には「イエス」です。 C++ もサポートされています(もちろん、コンパイラをそのように設定しコンパイルした場合)。 .cc 、.cpp、.Cで終わるソースファイル名二より、コンパイラフロントエンドは自動的にC++コンパイラを呼び出すします。明示的にavr-c++を直接呼び出すこともできます。
しかしながら、C++実装に必要なライブラリlibstdc++ のサポートがなく、C++でプログラムするには多くの制限があります。
extern "C" { . . . }
コンストラクタとデストラクタはサポートされています。グローバルについても両者ともサポートされています。
スペース制限やマイクロコントローラのようにランタイムに依存する制限がある環境では、C++呼び出し慣習に伴う副作用の回避に格別な注意が必要です。(関数起動時に起こる意図しないコンストラクタのコピーなど)。これらの事態はあっという間にかなりの実行時間とプログラムメモリを浪費してしまいます。時々コンパイラオプション-Sでアセンブラコードを出力させてコードを点検する方がよいでしょう。
※訳注:C++のコメント "//" はとっても便利なのでついつい使ってしまいますが、本当はこれをCのプログラムに混在させてはならないそうです・・でも使っちゃいますけど。
Back to FAQ Index.
グローバル変数とスタティック変数は標準のCでは 0 で初期化されていることが保証されています。avr-gccでも section .init4 ( initN セクション参照)に適切なコードを置くことでこれを実現しています。標準を尊重しつつも、この手法はいくらか簡略化されています。標準では、実際に使用するビットパターンは全ビット0でないものも許可するようになっていますが、AVRターゲットではすべての整数型変数は 0 で埋められ、すべてのポインタはNULLポインタに、すべての浮動小数点型は0.0に初期化されます。
変数が(明示的に)初期化されない限り(変数名の右側に
'=' と初期値を表す式が書かれていない)、これらの変数はファイルの
.bss
セクションに配置されます。このセクションは単純に変数のサイズを記録しますが、オブジェクトファイルにもフラッシュメモリにも場所を取りません。(もちろん、変数であるからには、ターゲットのSRAMは消費します。)
これに対し、初期化指定を持つグローバル変数とスタティック変数は
.data
セクションに置かれます。オブジェクトファイルにも、ターゲットのFLASH
ROM内にも場所をとります(初期値を記憶するため)。FLASH
ROMはコンパイラが初期化値をターゲットデバイスに引き渡すことができる唯一の方法なので、ROMを消費することは必要なことです。
さて、あるプログラマーがプログラムスタートアップで変数に確実に0をセットすることを "重ねて確実にしたい" と思い、右辺が0である初期化( = 0;)を付加した場合、これはスペースの無駄遣いになってしまいます。この無駄なスペースはどのC言語が実装されているどのプラットホームにもあることなのですが、PCのような大きなメモリを持つ機械では通常は気にされません。しかしAVRのような小さなマイクロコントローラでは、このようなFLASH ROMストレージの浪費は非常に痛いものとなります。
そういうわけで、(AVRGCCでは)一般的には、0以外の値を初期値にしたい場合のみ変数を明示的に初期化すべきです。
いくつかのタイマ関連16-bit-IOレジスタのアクセスには、テンポラリレジスタ(AtmelのデータシートではTEMPと呼ばれています)が使われます。これは、16bitデータ移動に実際には2つの8-bit IO転送命令を要するAVRで、"atomic access"(分割されないアクセス=上位下位とも同時アクセスの意味)を保証するためのものです。典型的なものとしては、現在のタイマ/カウンタ値レジスタ(TCNTn)、インプットキャプチャレジスタ(ICRn)からの読み出し、アウトプットコンペアレジスタ(OCRnA/OCRnBなど)への書き込みなどがあります。TEMPレジスタを介してアクセスされるレジスタについてはデータシートを参照ください。
※ADCH/ADCLレジスタの読み出しもこれにあたります
アプリケーション側からTEMPを使うレジスタをアクセスしながら、割り込みルーチンで同じTEMPレジスタを共用する別のレジスタをアクセスする可能性がある場合、割り込み処理が、他のどこかで進行中のTEMPレジスタを使う処理をぶちこわすことがないよう注意しなければなりません。
割り込みルーチン内の処理を他の割り込みから守るには、割り込みの記述に ISR()マクロ (旧SIGNAL()) マクロを使うのがよいです。これなら割り込みルーチン実行中(16-bitタイマレジスタ使用中)割り込みが禁止されています。
メインプログラムでこれらの16bitレジスタを使う場合は、これらのレジスタへのアクセスはcli() とsei() マクロで囲んでやると良いでしょう。
割り込み禁止状態(割り込みルーチン内など)から呼ばれるかもしれない関数については、これらレジスタへのアクセス時、グローバル割り込みフラグの状態が特定できないので(割り込み禁止かどうか分からない)、以下の例のようにコードしてください。
Back to FAQ Index.
asm volatile("sbi 0x18,0x07;");
↑上記のようなことを試みた場合:これは動作します。
しかし、以下↓のようにポートアドレスをマクロ名で置き換えると・・
asm volatile("sbi PORTB,0x07;");
コンパイルエラーに出会います。 Error: constant value required
PORTB はプリコンパイラ宣言で、avr/io.h にインクルードされるプロセッサ依存ヘッダファイル内に含まれています。C言語ではなくアセンブラに渡す場合はプリコンパイラは文字列に手を触れず、アセンブラに文字列 "PORTB" をそのまま渡します。そのため、アセンブラはPORTBというオペランドを知らないため面食らってしまいます。
これを回避する方法は以下のようになります。_SFR_IO_ADDR()はマクロで、アセンブラに渡す前にPORTBのIOアドレスに置換されます。
asm volatile("sbi %0, 0x07" : "I" (_SFR_IO_ADDR(PORTB)):);
asm volatile("sbi %0, 0x07" :: "I" (_SFR_IO_ADDR(PORTB)));
※訳注:オリジナルは asm volatile("sbi %0, 0x07" : "I" (PORTB):);ですが、これだと%0と"I"が出力オペランドとなり、実際コンパイルエラーとなります。
PORTB |= (1 << 7)
と記述できます。対象アドレスがSBI命令の範囲内なら、コンパイラ最適化機構がこれらを単一のSBI命令に変換してくれます。プログラムを,avr-gccで利用可能な最適化オプション(-O)とデバッグ情報オプション(-g)両方をつけてコンパイルするとき(幸運なことにこれはavr-gccでは可能です)、デバッガでWatchされるコードは最適化されたコードです。無保証ながら、このコードはまれに-gスイッチを外して同じ最適化で走らせることが出来ます。
これは望まれない副作用を持ちます。コンパイラには意味が変わらない限り、自由にコードを並べ替えることが許されている、条件付き命令に対し、可能な限りsingle branch を使うためにコードを並べ替えることがあります。分岐命令は現在のPC位置から短い範囲(-63〜+64ワード)しかカバーできません。分岐命令が直接使えない場合、コンパイラは条件付きスキップ命令と相対ジャンプ命令(rjmp)の組み合わせを使おうとします。これはROMを1ワード余計に消費します。
もう1つの最適化の副作用は、変数使用が実際に使用されているコードの範囲に限らてしまうことです。もし関数の先頭で変数がレジスタに割り当てられた場合で、その変数がその関数の中ではもう使われないとコンパイラが気づいたら、変数がまだスコープ内にあっても、同じレジスタを別の用途に再利用してしまいます。その変数をavr-gdbで追っかけると、表示されている値は壊れてしまったように見えます。
これらの副作用を回避するためには、デバッグの間は最適化をOffにするのもよいでしょう。しかしながらこれらの最適化のいくつかは、発見できずカバーできないバグを生み出すことがあります。最適化Offはバグのパターンを変えてしまいます。多くの場合、デバッグ中も最適化オプションは有効なままにしておく方がいいでしょう。
※訳者はgdbを使っていませんので、よくわかりません。原文も読んでください・・・・
Back to FAQ Index.
-g コンパイラオプションを使用している場合、avr-gcc はコンパイラに手渡すために、行番号およびC(とC++)のためのその他のデバッグ情報を生成します。行番号情報を持たない関数は、gdbのシングルステップコマンドで完全にスキップされてしまいます。これは標準ライブラリの関数でも同様で、アセンブラソースファイルによる関数でも同様です。-g コンパイラスイッチはアセンブラには適用されません。
そういうわけで、アセンブラ入力ファイル(Cプリプロセッサを介して)をデバッグするには、アセンブラに行番号情報を出力ファイルに含ませる必要があります。これはGNU アセンブラオプション --gstabs で実現できます。(その他のデバッグ情報、データタイプや変数位置情報などは生成されません。コンパイラと違ってアセンブラは基本的にはこれらの情報を知りません)
Example:
$ avr-as -mmcu=atmega128 --gstabs -o foo.o foo.s
アセンブラが直接呼び出されず、Cコンパイラフロントエンド経由で呼ばれた場合(ソースファイルの拡張子が .S の場合、明示的に -x assembler-with-cppオプションをつけた場合)は、コンパイラフロントエンドは --gstabs オプションをアセンブラに手渡す必要があります。これは -Wa,--gstabs オプションで実現できます。アセンブラ入力ファイルをコンパイルする場合のみ、このオプションをつけてください。さもないと、Cコンパイル途上で生成されたアセンブラコードにも行番号がふられ、これはデバッガを混乱させます。
- Note:
- -Wa,-gstabs としてもよいです(gstabsの前の'-'が1つ)。 コンパイラは自動的に '-' を付加します。
Example:
$ EXTRA_OPTS="-Wall -mmcu=atmega128 -x assembler-with-cpp" $ avr-gcc -Wa,--gstabs ${EXTRA_OPTS} -c -o foo.o foo.S
ローカルでない前方参照ラベルを持つコード片が挿入されると、デバッガは混乱するかも知れません。ラベル名を新しい関数の入り口と受け取るからです。この混乱を回避するために、新しい関数を宣言するときだけローカルでないラベルを使用し、他の用途には使用しないで下さい。これらのラベルへの参照は数値に続き文字b(backward)またはf(forward)からなります。これらのローカルラベルはソースファイル内で再使用されます。参照は与えられた方向(forward/backward)で最も近い同じ番号のラベルを採用します。
Example:
myfunc: push r16 push r17 push r18 push YL push YH ... eor r16, r16 ; start loop ldi YL, lo8(sometable) ldi YH, hi8(sometable) rjmp 2f ; loop test 末尾にジャンプ 1: ld r17, Y+ ; ループ継続 ... breq 1f ; myfuncを終え、リターンする(前方(下方)の1:) ... inc r16 2: cmp r16, r18 brlo 1b ; loopのトップへ(後方(上方)の1:) 1: pop YH pop YL pop r18 pop r17 pop r16 ret
Back to FAQ Index.
この例題をごらんください:
#include <inttypes.h> #include <avr/io.h> void set_bits_func_wrong (volatile uint8_t port, uint8_t mask) { port |= mask; } void set_bits_func_correct (volatile uint8_t *port, uint8_t mask) { *port |= mask; } #define set_bits_macro(port,mask) ((port) |= (mask)) int main (void) { set_bits_func_wrong (PORTB, 0xaa); set_bits_func_correct (&PORTB, 0x55); set_bits_macro (PORTB, 0xf0); return (0); }
最初の関数は期待したコードを生成できません。関数が呼ばれるときに大きな問題があります。コンパイラはこのコールを見て、PORTBレジスタ位置のアドレス(メモリアドレス0x38、ポートアドレス0x18.mega128の場合)ではなく、レジスタの値をin命令で読み取ってそれを関数に渡します。
これはディスアセンブル結果を見れば明白です。
set_bits_func_wrong (PORTB, 0xaa); 10a: 6a ea ldi r22, 0xAA ; 170 10c: 88 b3 in r24, 0x18 ; 24 10e: 0e 94 65 00 call 0xca
関数が呼ばれたとき、関数はレジスタの値だけを知り、それがどのポートからきたのかという情報は知り得ません。この時点で、関数でどんなコードが生成されようが的はずれな結果に終わります。興味のある方は関数全体のディスアセンブル結果を見れば、関数本体が完全に大ボケに終わっていることを確認できます。
2番目の関数は、どうすればIOポートのメモリマップアドレスを関数内で読み書きできるように(参照)渡しできるかを示します。
この関数のオブジェクトコードを見てみましょう
set_bits_func_correct (&PORTB, 0x55); 112: 65 e5 ldi r22, 0x55 ; 85 114: 88 e3 ldi r24, 0x38 ; 56 116: 90 e0 ldi r25, 0x00 ; 0 118: 0e 94 7c 00 call 0xf8
IOポートのアドレス 0x0038が正しく渡されているのが確認できます。
関数本体のディスアセンブルコードもご覧ください。関数が期待した動作が出来ることを確認できます。
IOポートへのアクセスは ld/st命令で行われていることにご注意下さい。void set_bits_func_correct (volatile uint8_t *port, uint8_t mask) { f8: fc 01 movw r30, r24 *port |= mask; fa: 80 81 ld r24, Z fc: 86 2b or r24, r22 fe: 80 83 st Z, r24 } 100: 08 95 ret
※関数は値maskが定数か変数か、1bitだけビットが立っているかどうかなんてわからないので、sbi命令を使うわけにはいきません。また、portがメモリアドレス0x5f以下である保証もないので、out命令も使えません。
引数 port はコンパイラ警告を回避するために volatile していしなければなりません。
- Note:
- IOアドレスを渡す今回のような用途ではIN/OUT命令はその性格上利用できません。。興味を持たれた方は詳細をInstruction Set data sheetで確認して下さい。
※in/outは命令語に埋めこまれた定数値でしかポートアドレスを指定できない。また、0x20-0x3F(IOアドレス0x00-0x1F)の範囲しか対象にできない。
最後に、この操作をマクロで実現してみましょう。この工夫されたマクロは実行速度とコードサイズ、両方の要求を満たす最も効率的な方法です。
set_bits_macro (PORTB, 0xf0); 11c: 88 b3 in r24, 0x18 ; 24 11e: 80 6f ori r24, 0xF0 ; 240 120: 88 bb out 0x18, r24 ; 24
もちろん、実際のアプリケーションでは渡されたポートアドレスに対する関数内の処理はもっとたくさんあることでしょう。その場合はマクロを関数で置換する方がコード効率はよくなります。しかし、実行スピードは犠牲になります。
このような間接的ポートアクセスで、書き込み順序が重要な16-bit IOレジスタ(タイマ関連レジスタなど)をアクセスしようととする場合はご注意ください。バージョン3.3までのavr-gccは、このような場合に誤ったアクセス順序をとるコードを生成します。通常の順序を考慮しないメモリをオペランドとする場合はこちらの方が小さいコードを生成できるからです。
※アドレス値は下位バイトのものなので、上位バイトから先に操作する場合は不要なポインタインクリメント・デクリメント操作が必要になる。アドレス値がZレジスタにある場合は、adiw R31,1/st Z,R16/sbiw R31,1/st Z,R17という操作より、st Z++,R17/st Z,R16の方が短い
http://mail.nongnu.org/archive/html/avr-libc-dev/2003-01/msg00044.htmlもご覧ください。
avr-gcc のversion 3.3 以降は、各々のポインタ値が volatile として宣言されている場合はこの最適化を切るという方法でこの問題を回避しています。この場合は16-bit IOポートに対する正しい操作を強制することができます。
Back to FAQ Index.
変更されることはない文字列の配列 (文字charの配列ではないではない) が必要な時、SRAMを浪費したくないので、これら文字定数をFLASH-ROMだけに置きたいと考えることがあります。最も考えがちな(しかし間違った)やり方は以下のようなものです。
この結果はあなたが望んだものにはなりません。文字列の配列 (=文字列へのポインタの配列) はROM内に置かれますが、個々の文字列自身はSRAM( .data section)に置かれてしまいます。
うまくやるには下記のようにもう一工夫必要です。
できたオブジェクトのディスアセンブル結果を見れば、配列も文字列もFLASHにあることがわかります。
00000026 <array>: 26: 2e 00 .word 0x002e ; ???? 28: 2a 00 .word 0x002a ; ???? 0000002a <bar>: 2a: 42 61 72 00 Bar. 0000002e <foo>: 2e: 46 6f 6f 00 Foo.文字列 foo はアドレス 0x008c にあります。
文字列 bar はアドレス 0x0090 にあります。
配列 array はアドレス 0x0094 にあります。
main() からは、このようにアクセスできます
memcpy_P(&p, &array[i], sizeof(PGM_P)); 70: 66 0f add r22, r22 72: 77 1f adc r23, r23 74: 6a 5d subi r22, 0xDA ; 218 76: 7f 4f sbci r23, 0xFF ; 255 78: 42 e0 ldi r20, 0x02 ; 2 7a: 50 e0 ldi r21, 0x00 ; 0 7c: ce 01 movw r24, r28 7e: 81 96 adiw r24, 0x21 ; 33 80: 08 d0 rcall .+16 ; 0x92
このコードはROMのテーブル array からレジスタペアに読み込まれ、要求された文字列へのポインタを(ポインタ変数 pに)読み取ります。
r22:r23 にある i の値は、ワードオフセットをarray[]へのアクセスに使用するため倍にされ、arrayのアドレス値(0x26)が付加されます。(AVRには即値加算命令がないので-0x26=0xffdaを引く)。変数pのアドレスはスタックフレーム内オフセット(33)をYポインタレジスタに加えることで計算されます、そして memcpy_P がコールされます(rcall +16)。
ここで最終的にROM文字列をローカルバッファ buf にコピーしています。strcpy_P(buf, p); 82: 69 a1 ldd r22, Y+33 ; 0x21 84: 7a a1 ldd r23, Y+34 ; 0x22 86: ce 01 movw r24, r28 88: 01 96 adiw r24, 0x01 ; 1 8a: 0c d0 rcall .+24 ; 0xa4
変数 p (Y+33に位置する)が読み込まれ、buf のアドレス (Y+1)と共にstrcpy_P に渡されます。これはROM内文字列を buf にコピーします。
コンパイル時定数になるのインデックス(添え字、ここでは i )を使う場合は、最初のステップ(memcpy_PによりROMからポインタを読み出す)は省略され、それは知らされません。コンパイラは array をアクセスするコードをコンパイル時に最適化するからです。
※上記例でmemcpy_Pコール時必ずi=1;
となるコードなら、memcpy_Pの返す値は bar に確定されるので、strcpy_P(buf, bar)だけに最適化される。
Back to FAQ Index.
さて、この質問に対する一般的な答えはありません。これは外部RAMが何に使われるかによるからです。
基本的には、外付けメモリインタフェースを有効にするために、MCUCRレジスタのビットSRE(外付けSRAM有効)をセットしなければなりません。お使いのデバイスや作成するアプリケーションの詳細に依存しますが、さらにいくつかの外部メモリ操作に関与するレジスタ (XMCRAやXMCRBや、MCUCRのその他のビット) が設定されなければいけない場合もあります。詳しくはデータシートをご覧ください。
外部RAMがC言語の変数の保存のために使われる場合は( .dataや.bss セグメント)外部メモリインタフェースをデバイス初期化の初期段階で設定変更しなければなりません。初期化段階でこれらの変数がRAMに配置されるからです。数行のアセンブラコードでこれは実現できます。詳細は FAQ内の MCUCRやWDTCRを早期に設定するには? 、又は メモリセクション-Cコードでセクションを扱う.を参照ください。
malloc()の解説文には、内蔵・外部RAM使用時の何通りかのヒープ( malloc() で予約されたエリア) の置き方についての 議論が書かれています。ここにはメモリ領域を各々の内蔵RAM位置から外に移動する場合のリンカオプションについても解説されています。
最後に、アプリケーションが追加RAMを、Cコンパイラ管理領域とは関係ない単なるプライベートなデータ保存域として利用する (特定のアドレスに初期化された char * ポインタ変数を介してアクセスする) ならば、外部RAMインタフェースを main() の先頭で初期化するだけで事足ります。.init1をいじってスタートアップルーチン前に設定する必要もありません。
同様なことがheapだけを移動する場合にも言えます。スタートアップルーチンはheapにはなにも影響しないからです。
※C言語に関係ない領域(変数でもmalloc()管理の領域でもない)についてはC言語の設定は不要。ハード設定さえすればアクセス自体は可能で、ポインタを介して読み書きできる。
※スタートアップルーチンが設定するのは .data と .bss (定数と変数)なので、これを動かさないならmain()でゆっくり設定変更してもOK。
スタックを外部RAMに置くのはお勧めできません。一般的に、外部RAMアクセスは内蔵RAMより遅く、いくつかのAVRデバイスではバグによりこの設定では完全な動作ができない場合があります。
Back to FAQ Index.
-O オプションの後の番号が大きいほど無条件に「よい」最適化ができるという誤解がまかり通っています。まず最初に、すべての場合に通用する「よい」という言葉の定義はありません。最適化はしばしば実行速度とコードサイズの駆け引きとなります。どのオプションがコード生成のどの部分に影響するかについて、"using tools" の章内にある詳細なdiscussionを参照ください。
ライブラリそれ自体を異なる最適化レベルでコンパイルする効果を判定するテストをATmega128上で行いました。下記の表がその結果です。テストはおよそ2KBytesの文字列(複数)のソートで行いました。Test #1は 標準ライブラリ内関数 strcmp() を使っている qsort() を用いています。test #2は文字列をそのサイズでソートしています。比較のたびに strlen() が2度呼ばれています。
結果のコードサイズを比較する上で、実行時間を表示するために浮動小数点版のfvprintf()がリンクされていることに注意してください。fvprintf()のサイズは最適化レベルの違いに全く影響されず、常に2.5KBの追加をもたらします。
(955 μsec と972 μsecの違いはちょうどタイマーの一針分です。塩粒ほどの違いと考えてください)
Optimization flags Size of .text Time for test #1 Time for test #2 -O3 6898 903 μs 19.7 ms -O2 6666 972 μs 20.1 ms -Os 6618 955 μs 20.1 ms -Os -mcall-prologues 6474 972 μs 20.1 ms
ざっと見て、 -Os -mcall-prologues オプションが一般的には "ベストな"最適化レベルと言えそうです。最後の数%の実行速度向上を大きな問題とするようなアプリケーションだけが -O3を必要とすることになるでしょう。
Back to FAQ Index.
最初に、コードは新しい、名前を付けられたセクションに置かれなければなりません。これは section属性で指定できます。
この例では、 ".bootloader" が新しいセクションの名前です。__attribute__ ((section (".bootloader")))
この属性は関数プロトタイプ宣言の後に置き、関数を新しいセクションに置くよう強制します。
void boot(void) __attribute__ ((section (".bootloader")));
セクションを固定されたアドレスに再配置するには、リンカフラグ --section-start を使います。このオプションはリンカに -Wl コンパイラオプション を使って渡すことができます。
-Wl,--section-start=.bootloader=0x1E000section-start=の後の名前は再配置するセクションの名前です。セクション名の後の数値は配置される場所の先頭アドレスです。
Back to FAQ Index.
本当におかしな問題というのは、しばしば、Atmelから出荷されるAVRデバイスがユーザーの期待と異なる設定がなされている ことから引き起こされます。以下に気をつける点を列挙してみました。
Back to FAQ Index.
デフォルトで、すべての文字列定数宣言は初期化済み変数の宣言と見なされます。これはRAMを占領し(その領域に書き込むコードを書くとコンパイラは警告を発することもあります)、スタートアップコードでRAMに文字列をコピーするために、コピー元として同じ容量のFLASH-ROMも占拠します。コンパイラは最適化で複数の同一の文字列を1つにまとめてはくれますが、それでも1つのコンパイル単位(1つのCソースファイル)に対してしか行いません。※別々のオブジェクトに属する同一の文字列を1つにまとめることはできない。
どのような文字列も、C言語関数に対してはconst char *argumentで期待されるような有効な引数となります。これらはSRAM上の文字列に対するアドレスを渡すだけ。明らかにこの方法は多くのSRAMを浪費してしまいます。Program Space String Utilities において、これらの定数をFLASH-ROMに追い出すための方法が説明されています。しかしながらFLASH-ROMに置かれた文字列定数は、(char *)型を期待する関数にとっては有効な引数とはなり得ません。AVRプロセッサでは、FLASHに置かれた文字列にアクセスするにはLPMという特別な命令が必要になります。これらのことを考慮に入れると、別の関数が必要になります。多くの文字列を扱う標準Cライブラリ関数に対し、文字列引数の一方に対しFLASH-ROM内文字列が指定できる、同等な関数が利用できます。プライベートな関数もまたこれを扱う必要があります。たとえば、以下は簡単なデバッグメッセージをUARTを通して出力するものです。
#include <inttypes.h> #include <avr/io.h> #include <avr/pgmspace.h> int uart_putchar(char c) { if (c == '\n') uart_putchar('\r'); loop_until_bit_is_set(USR, UDRE); UDR = c; return 0; /* so it could be used for fdevopen(), too */ } void debug_P(const char *addr) { char c; while ((c = pgm_read_byte(addr++))) uart_putchar(c); } int main(void) { ioinit(); /* initialize UART, ... */ debug_P(PSTR("foo was here\n")); return 0; }
Back to FAQ Index.
- Note:
- 簡単に言って、関数の末尾につけられた
_P
は、この関数が "Program-space文字列"を受け入れられることを示しています。 PSTR()マクロの使い方も見てください。
C言語でのビット演算は自動的に演算対象をint (avr-gccでは16bit) に変換してしまいます。
これをうまく扱うためには、値が8bitになるように、型キャストしてしまうとよいです。
これはリテラル(定数)についても同じです。
これは特にビットクリアの時大切になります。ビット演算 "not"のための演算子 (~) はmaskの値を int に拡張します。var &= ~mask; /* wrong way! */
これを8bitのままにしたければ、"not"演算子の前に型キャストを掛けます。var &= (unsigned char)~mask;
Back to FAQ Index.
簡単です。出力ファイル (ELF形式) に対し、avr-nm
を使ってください。
-n オプションをつければ、シンボルを数値順に並べ替えてくれます(デフォルトではアルファベット順)。
まず、"symbol _end"
を捜してください。これはRAM領域内で変数に割り当てられていない最初のアドレスです。avr-gcc は内部的には.data/.bssの変数領域アドレスに対して0x800000を加えているので、この分を無視してください。次に、ランタイム初期化コードがスタックポインタを初期化し、これは(内蔵)SRAMの最終利用可能アドレスを指します。"between_end"
領域とSRAM終端の間がスタックで利用できる領域です。(もしアプリケーションが malloc() を使用する場合 ( printf() の内部でも起こる) は、ダイナミックメモリのヒープもここに位置します。malloc() の使い方 もご覧ください。)
アプリケーションで使うスタックの総量は簡単には求め切れません。たとえば、再帰的コールを行う関数を使い、再帰を止めるチェックを怠った場合、スタック要求量の総量は無限大です:-)
(avr-gcc ...... -S で生成できる)各アセンブラファイルには、関数毎のフレームサイズがコメントで記されています。それがその関数で要求されるスタック量です。関数コールの値スティングを追いかけて、スタック使用量の足し合わせていけばスタック要求量総量を計算することができます。
Back to FAQ Index.
いくつかの小さなAVRプロセッサはRAMベースのスタックがない(いくつかはRAMを全く持たない)ので、Cコンパイラで直接的にはサポートされていません。32バイトある汎用レジスタはデータメモリ領域に置かれているので、これをRAMの代わりに用いることは可能ではあります。
Bruce D. Lightner さんがこのやり方についてすばらしい解説をして、彼のweb pageでツールキットとしてまとめてくださいました。
http://lightner.net/avr/ATtinyAvrGcc.html
Back to FAQ Index.
これはMS-DOSのFATファイルシステムでよく知られた問題です。FATファイルシステムはファイルのタイムスタンプを2秒の分解能で管理します。そのためいくつかのMS-DOSから派生したOS(Win9x)は、更新されたファイルのタイムスタンプ計算時、現在の時刻がFATで表現できない(奇数秒)とき、次の秒に繰り上げしてしまうようです。
これにより、makeは、"未来からやってきたファイル"と遭遇してしまいます。
すべてのmakeの決定はファイルタイムスタンプに依存しているので、makeはこのような状況に対し警告を発します。
解決策:そんな下等なファイルシステム/OS なんか使うな。UnixファイルシステムやHPFS(aka NTFS)では、そのような問題を経験しません。
うろうろ歩き回る??(Workaround): ファイルを保存した後、makeを起動する前にちょっと間をおいてください。もしくは単純に警告を無視してください。あなたが神経質な人なら、make clean allを実行しすべてを 再構築してください。
ファイルをファイルサーバーへアクセスするネットワーク環境では、このメッセージはサーバーの時計がクライアントの時計とあまりに異なっていることが原因かもしれません。この場合の解決策は、双方のシステムでNTPなどの適切な時計管理プロトコルを採用することです。しばらくすればクライアントの時計はサーバの時計と歩調を合わせるようになります。
Back to FAQ Index.
通常、各割り込みは固有の割り込み要求フラグビットをコントロールレジスタ上に持っています。コントロールレジスタは各々のビット位置に論理的 1 を立てることで特定の割り込み状況が発生したことを示します。
割り込みハンドラにおいて、通常は、この割り込みフラグビットは割り込み処理途上で自動的にクリアされます。あるものは単に割り込みハンドラをコールするだけでクリアされますし、あるものは通常その割り込み内で必ず行われる特定のハードウェアレジスタの読み書きでクリアされます。(たとえばU[S]ARTのデータ受信完了フラグビットとUDR読み出しの関係)
ハードウェア機構上、個別の割り込み要求bitがセットされていて、グローバル割り込みも許可されている限り、割り込みは要求され続けます。割り込みハンドラを抜ける前に、このビットをクリアすることは必須なことです。さもないと新しい割り込み要求がなくても割り込みが再度掛かってしまいます。
一部のサブシステムは割り込み要求をクリアするために明示的な操作を要求します。有名なのはTWIインタフェースです。これは割り込みのクリアはTWIバスハードウェアハンドシェークの続行を指示します。そういうわけでこれは決して自動的には行われるものではありません。
※これらの場合は、UART-UDR空き割り込みもそうですが、これ以上の処理が要らないと判断した時点で割り込み許可ビットの方をクリアして割り込みを抑止して処理を終了します。
しかしながら、通常の割り込みハンドラが全く使用されない場合や(割り込み要求ビットを監視して処理を行う場合など)、割り込み禁止で保留されている割り込みが割り込み禁止解除前にクリアされていても必ず認識したい場合(たとえばエッジトリガの外部割り込みなど)、ソフトウェアで個々のハードウェア割り込みビットを明示的にクリアしなければなりません。
※たとえば、INT1処理中にもう1つパルスが入ったかどうかチェックしたければ、割り込みルーチンに入るとすぐ割り込みフラグをクリアして、処理が終わった時点で割り込みフラグをクリアせずに終了する(RETFの代わりにRETを使う??)ってことか?
割り込みフラグの解除は、通常は 論理的 1 をそのビットに書き込むことで行われます。これは最初は非論理的なことのように思えます。そのビットは読み込み時は既に論理的 1 であったのに、その割り込みビットをクリアするためになぜ論理的 1 を書き込むのでしょうか?
その謎解きは単純なものです:論理的 1 の書き込みは 1クロックで済む1つのOUT命令で行われ、(1が立っている)1つの割り込みリクエストビットだけがクリアされます。このやり方なら(SBI命令のような)2クロックかかるread-modify-writeサイクルを行う必要がありません。割り込み要求ビットを含むコントロールレジスタのすべてのビットは、割り込み要求ビットです。0の書き込みではビットを変化させない仕様ならば、残りのビットに論理的 0 を書き込む(OUT命令ではこれが行われる)ことで書き換えられることはありません。
※もしも割り込みフラグが単純に1を書き込めばセット、0を書き込めばクリアという仕組みならば、消去するビットには0を、その他のビットについては一度読み込んで、同じものを書き込まなければ成りません(Read-Modify-Write)。CBI命令を使ったとしても内部的には同じことを行っています。この動作には2クロックかかるので、読み込んだ時点と書き込む時点の間に他のフラグビットが変化してしまうことがありえます。
そうすると、他のビットに関して間違ったビットを書き込んでしまうことになります。
割り込みビットのクリアは下記のように書いてはなりません。
TIFR |= _BV(TOV0); /* wrong! */これだけでいいです。
TIFR = _BV(TOV0);
※補足: 1の書き込みがビットセット、0の書き込みがビットクリアを起こす仕様である場合(現実のAVRとは異なる)、
たとえばTIFRのbit0のクリアをするには、以下の動作を行うことになるが、1行目が終わった後にTIFRのbit1がセットされた場合、最後のTIFRへの出力でbit1まで消去されてしまう。
しかし、1の書き込みがクリア、0の書き込みはビットに何の影響もしない仕様なら、以下の操作だけでいい。
in r16 , TIFR ;TIFR=0x01
andi r16 , 0xFE ;bit0 clear
out TIFR , r16 ;TIFR=0x00
他のbitが立っても、bit0クリアの操作は他のビットには影響しない。
- out TIFR , 0x01 ;TIFR=0x01→0x00
プログラムにより明示的にビットを立てることはできないが、割り込み要求ビットの性格上その必要はない。
Back to FAQ Index.
基本的に、ヒューズは特殊なEEPROMエリア内のビットです。技術的問題により、消去されたEEPROMセルはすべてのビットに置いて値1を持ちます。そのためプログラムしていないヒューズは値 1 を持ちます。反対に、プログラムされたヒューズセルはビット値0として読み出されます。
Back to FAQ Index.
疑似命令とオペレータをご覧ください。
Back to FAQ Index.