AVR Libc Home Page | ![]() |
AVR Libc Development Pages | |||
Main Page | User Manual | Library Reference | FAQ | 索引 | Example Projects |
uint8_t flag; ... ISR(SOME_vect) { flag = 1; } ... while (flag == 0) { ... }
最適化オプションを使っている場合、上記のようなループにおいて、コンパイラがループ内部で flag を書き換えていないと判断してしまい、(flagは0のままでこのwhile節の中身は無条件に実行されると判断して) 最適化を行い flag の参照を完全に除去してしまいます。(残念ながら割り込みルーチンの中身まではチェックしてくれない)
コンパイラに、値が(割り込みなどにより)文法解析のスコープ外※から変更されうると知らせるためには、以下のような宣言が必要です。
※ISRルーチン内は、C言語の解析では呼び出されない、実行されないコード扱いなので、スコープ外と同じ扱いになってしまう・・・
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〜r7 レジスタを利用すべきです。
レジスタr8〜r15は、あまりに多く長い引数がある場合に、コンパイラによって渡される引数用途に使われます。
アプリケーション全体を見渡してこのような部分が見あたらないとしても、これらのレジスタは(※ライブラリやモジュールなどで?)レジスタ変数として使われることもあります。??
ライブラリ関数なども含め、確かにレジスタ割り当て変数がきちんと整合しているかについて厳重な注意が必要です。詳しくは、インラインアセンブラ−アセンブラコードで使われるCの名前も参照ください。
Back to FAQ Index.
できるだけ早期に初期化を行う(MCUCR、WDTCRなどスタートアップルーチンが実行される前に設定する)方法は、ちょっと変わっていて、しかし柔軟な方法があります。
基本的には以下のような小さなアセンブラファイルを書くことになります。
これをアセンブルし、できたxram.o を他のファイルと共にリンクしてください。このコードは初期化コードの中でもリセット直後に実行されます。リンカスクリプトの新しい.initN セクションについてのコメント(どれを使うべきかなど)をご覧ください。??
※.initN セクション関連も参照。
このやり方の利点は、どんな初期化コードでも挿入できることです。
これは超早期スタートアップなので、スタックも、__zero_reg__も用意されていないことにご注意ください。
使用しない場合はプログラムメモリが消費されることはありません。??and no program memory space is wasted if this feature is not used.
いくつかのとても特殊なケースを除き、リンカスクリプトを変更する必要はありません。
__stack(内蔵RAMの終端、スタック開始端) はデフォルト値のままにしておく方がよいです。その方がスタックに高速な内蔵メモリを使えるため、より高速です。
またATmega161など一部のデバイスではバグのためスタックを動かせません。
データセクションをスタックより上位アドレスに置くには、-Wl,-Tdata,0x801100 のようにアドレス指定を加えてください。
Cからメモリセクションを利用するためのより詳しい情報(Cコードからの利用法など)は、 メモリセクションもご覧ください。There is also an example for Cでセクションを扱うには、用例も紹介しています。 Cコードでは、どの関数もセクション.init3
に置かれるべきです。というのは、セクション.init2
内のコードで内部レジスタ__zero_reg__
をクリアしているからです(※__zero_reg__
の存在を前提とするコードや関数が多いと言うことか?)
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)のようにして使ってください。
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
※訳者はgdbを使っていませんので、よくわかりません。原文も読んでください・・・・
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だけビットが立っているか、アドレスが0x3f以下であるかどうかなんてわからないので、sbi命令を使うわけにはいきません。
また、portがメモリアドレス0x5f以下である保証もないので、out命令も使えません。
引数 port はコンパイラ警告を回避するために volatile していしなければなりません。
- Note:
- IOアドレスを渡す今回のような用途ではIN/OUT命令はその性格上利用できません。。興味を持たれた方は詳細をInstruction Set data sheetで確認して下さい。
※in/outは命令語に埋めこまれた定数値でしかポートアドレスを指定できない。また、0x20-0x3F(IOアドレス0x00-0x1F)の範囲しか対象にできない。
最後に、この操作をマクロで実現してみましょう。この工夫されたマクロは実行速度とコードサイズ、両方の要求を満たす最も効率的な方法です。
※マクロ引数を定数で渡せば、コンパイラはld/stより短く速いin/out命令を使えるアドレスであるかどうかわかるので、適切な最適化ができる。
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.
スタック上にローカル変数を確保しようとする時に、コンパイラは以下のようなコードを生成します。
/* prologue: frame size=20 */ push r28 push r29 in r28,__SP_L__ in r29,__SP_H__ sbiw r28,20 in __tmp_reg__,__SREG__ cli out __SP_H__,r29 out __SREG__,__tmp_reg__ out __SP_L__,r28 /* prologue end (size=10) */
1.現在のスタックポインタ値を読み込み、(in命令)
2.変数確保量だけその値をずらし、(sbiw)
3.割込を無効化し(cli)
4.スタックポインタ上位を書き戻し
5.SREGを書き戻し
6.最後にスタックポインタ下位を書き戻し
ぱっと見には、SREGの書き戻しとスタックポインタ下位の書き戻しの間には競合があるように見えます。
※SREGの書き戻しで無効化していた割込が有効化される→スタックポインタ書き戻し最中に割込がかかる懸念
しかし、割込許可操作(SREGのIビット書き込み又はIビットが立った値をSREGに書き戻し)の後、AVRハードウェアは最低でも次の命令を実行するまでは割込は許可されません。
そう言うわけで、このコードは、操作の統合性を壊されることなく最低限の割込禁止時間で処理を行うことを保証しているわけです。
Back to FAQ Index.
ソースコードに以下のようなコメントがあります:
5つのうちのどのリンカスクリプトも、実際にldに与えられたコマンドラインに従い使用されます。
.x スクリプトはデフォルトのスクリプトです。
.xr スクリプトはリロケートしないでリンクするためのもの(-r
フラグ対応)
.xu スクリプトは .xr と似ていますが、コンストラクタを生成する点が違います(-Ur
フラグ)。
.xn スクリプトは -n フラグ(同一ページにtextとdata双方を混在させる)でのリンク用です。
.xbn スクリプトは -N フラグ(.xnと同じ??)でのリンク用です。
Back to FAQ Index.
GNUリンカ avr-ld
はバイナリデータを直接取り扱うことはできません。しかし、avr-objcopyという協調ツールがあります。これは出力時に、リンクして生成されたELFフォーマットファイルからバイナリを
Intel Hex ロードファイルとして取り出すものとしてよく知られたプログラムです。
avr-objcopy
はリロケート可能なオブジェクトファイルをバイナリ入力から生成することもできます。たとえばこんな感じで。
avr-objcopy -I binary -O elf32-avr foo.bin foo.o
これは、foo.bin
というバイナリファイルから、 foo.o
という名前のオブジェクトファイルを生成します。
このファイルはデフォルトでは .data
セクション扱いとなり、_binary_foo_bin_start_
と_binary_foo_bin_end_
、2つのシンボルを持ちます。これら2つのシンボルはC言語ソースから、データのアクセスのために参照可能です。
FLASH ROM にデータを置きたい場合は(C言語で
PROGMEM アトリビュートをつけるように)、 セクションはコピー時に改名されなければなりません。
ここで section フラグが使えます。
avr-objcopy --rename-section .data=.progmem.data,contents,alloc,load,readonly,data -I binary -O elf32-avr foo.bin foo.o
これらはMakefikeに入れて使うと便利です。foo.binファイルが変更されれば、foo.oが、そして最終ELFファイルも再生成されます。
Back to FAQ Index.
AVRソフトウェアリセットの正統な方法はウォッチドッグタイマを使うことです。
ウォッチドッグタイマをタイムアウト時間を最短で有効にし、無限ループに突っ込みます。これによりウォッチドッグタイマがプロセッサをリセットしてくれます。
リセットベクタへのジャンプを使わない理由は、ウォッチドッグタイマリセットはAVRのリセットを行い、レジスタは規定値にリセットされるのですが、リセットベクタへのジャンプではそれが行われないからです。これはあまりいいやり方ではありません。
警告! 古いAVRデバイスは、リセット時ウォッチドッグタイマを無効にしてしまいます。これらの古いAVRデバイスでは、ウォッチドッグ有効によるソフトリセットはやりやすいです。ウォッチドッグはリセット後無効になってくれるからです。新しいAVRデバイスでは、一度ウォッチドッグタイマが有効とされれば、リセット後も有効です!これら新しいAVRデバイスでは、.init3
(main()に入る前のスタートアップコード)内にウォッチドッグタイマを無効化するコードを入れる必要があります。さもないと(※ウォッチドッグ定時解除を含むユーザーコードを実行する前に)ウォッチドッグタイマがかかってしまいリセットがかかり続けることになる場合があります。
ソフトウェアリセットのコードを生成するマクロを照会します。
#include <avr/wdt.h> ... #define soft_reset() \ do \ { \ wdt_enable(WDTO_15MS); \ for(;;) \ { \ } \ } while(0)
新しいAVRデバイスでは (ATmega1281等) リセット後早期にウォッチドッグタイマを無効とするために以下の関数を入れてください。
#include <avr/wdt.h> ... // Function Pototype void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3"))); ... // Function Implementation void wdt_init(void) { MCUSR = 0; wdt_disable(); return; }
Back to FAQ Index.
AVR-LibCから math ライブラリをリンクしていないのではないでしょうか?
GCCは浮動小数点演算用のライブラリを持っていますが、AVRに最適化されたものではありません。そのためかなり大きいコードを吐き、正しくもないかもしれません。
このようなことは浮動小数点演算関数を標準Cライブラリから使用していなくても、浮動小数点演算を行うだけで発生し得ます。
AVR-LibCから math ライブラリをリンクすれば、手作業で最適化されたAVRアセンブリルーチンに置き換えられ、より小さいコードになります。
mathライブラリのリンクについては、sin()などの関数で、undefined reference to ・・・・ といったエラーメッセージが出ます の項も参照下さい。
Back to FAQ Index.
リエントラント可能コードとは、2,3回多重に呼ばれうるコードのことを指します。マルチタスクオペレーションや、割込コードが走っている時の割込許可などで注意が必要です。
gccネイティブで生成されるコードはリエントラント可能です。
しかし、avr-libcではいくつかのコードは明示的にリエントラント可能とされていますが、リエントラント不能とされるコードもいくつかあります。
一般的に、グローバル変数(IOレジスタ含む)を読み書きするライブラリコールは全てリエントラント不能です。
あるスレッドがグローバル変数を読み書きしているときに、他のスレッドが同時に気づかずに同じ事をして、矛盾やエラーを引き起こすことがあり得るからです。
再帰不能として知られるライブラリコールは一スレッドでしか使用できません。
他のスレッドは同じ保存領域を使用するライブラリコールを使用できません。
下記はライブラリコールについて既知の事柄を挙げたものです。
Library call | 再帰可能 | Workaround/Alternative |
rand(), random() | 状態保存にグローバル変数使用 | リエントラント可能バージョン: rand_r(), random_r(). |
strtod(), strtol(), strtoul() | 成功・失敗の結果保存にグローバル変数 errno 使用 | errno 結果を無視するか、 コール時 cli()/sei()や ATOMIC_BLOCK() で保護するか sccanf() or sccanf_P() を使用するかしてください。 |
malloc(), realloc(), calloc(), free() | スタックポインタとグローバル変数をメモリ割り当て/解放に使用 | コール時 cli()/sei()や ATOMIC_BLOCK() で保護する。 OSを使用しているなら、OSが提供するメモリアロケータを使用してください。OSはおそらくスタックポインタを調整しているでしょうから |
fdevopen(), fclose() | calloc() とfree() を内部で使用 | コール時 cli()/sei()や ATOMIC_BLOCK() で保護する。 fdev_setup_stream() やFDEV_SETUP_STREAM().を使用する Note: fclose() は free() を呼ぶ( fdevopen().で開いた場合) |
eeprom_*(), boot_*() | I/O レジスタを操作 | コール時 cli()/sei()や ATOMIC_BLOCK() で保護する。 |
pgm_*_far() | I/O レジスタ RAMPZ を操作 . | GCC 4.3では、 RAMPZ は自動的にISR用に保存されます。普通に割込を使う場合は何もする必要はありません。いくつかのOSはタスクスイッチ時自動的にRAMPZを保存するかも知れません。OSのドキュメントを読んで確認してください もしくは、コール時 cli()/sei()や ATOMIC_BLOCK() 、又はOSの機構で保護してください。 |
printf(), printf_P(), vprintf(), vprintf_P(), puts(), puts_P() | フラグとグローバル変数FILE内の文字数カウンタを変えてしまう | 1つのスレッドでしか使用できません。返される文字数カウントが重要でないなら、_Pバージョンを使わないことで解決可能です。 Note: 書式化して文字列に出力するタイプの関数、 sprintf(), sprintf_P(), snprintf(), snprintf_P(), vsprintf(), vsprintf_P(), vsnprintf(), vsnprintf_P() などはスレッドセーフです。書式化された文字列は fwrite() で出力するようにすれば、これは文字列を送る下位の機構のコールであるので、安全です。 |
fprintf(), fprintf_P(), vfprintf(), vfprintf_P(), fputs(), fputs_P() | フラグとグローバル変数FILE内の文字数カウンタを変えてしまう. グローバル変数FILEを複数スレッドで使ってしまう危険有り |
各スレッドをそれぞれ独自のFILEに割り当ててください。 返される文字数カウントが重要でないなら、_Pバージョンを使わないことで解決可能です。 |
assert() | 埋め込みでfprintf() を含む。 fprintf() 参照 | See above for fprintf(). |
clearerr() | フラグとグローバル変数FILE内の文字数カウンタを変えてしまう | 各スレッドをそれぞれ独自のFILEに割り当ててください。 |
getchar(), gets() | フラグとグローバル変数stdin 内の文字数カウンタを変えてしまう . |
1つのスレッドでしか使用できません。 *** |
fgetc(), ungetc(), fgets(), scanf(), scanf_P(), fscanf(), fscanf_P(), vscanf(), vfscanf(), vfscanf_P(), fread() | フラグとグローバル変数FILE 内の文字数カウンタを変えてしまう . |
各スレッドをそれぞれ独自のFILEに割り当ててください。
*** Note: 文字列からのスキャンを行う sscanf() and sscanf_P()等はスレッドセーフです。 |
*** それでも、多重に文字入力を行う場合の動作ははっきりしませんが、そのエントリ先はきちんと(各々のFILEに)含まれています???
It's not clear one would ever want to do
character input simultaneously from more
than one thread anyway, but these entries
are included for completeness.
新しいことが見つかる、もしくは紹介されましたらこのテーブルは今後随時更新していきます。
Back to FAQ Index.
EEPROM関連でよくある2つのトラブルとして、データシートで示された耐久性を越えて使用した場合、EEPROM書き込み中にAVRをリセットしてしまった場合が挙げられます。
EEPROM 書き込みは、完了するまで10msecほどかかります。CPUはそんな長時間待てないため(1MHzクロックでも10000クロック)、内部ステートマシンがEEPROM書き込みリクエストを扱います。EEPROMステートマシーンはEEPROM書き込みリクエストが始まったとき全てのEEPROMレジスタセットアップを受け取ります一度EEPROMステートマシーンが動き始めれば、EEPROM関連レジスタの変更は確実に書き込み動作を破壊してしまいます。
データシートには、書き込みが進行中かどうか調べ、その間ユーザープログラムにより関連レジスタを操作し内容にする方法が記されています。
デバイスに電源が入っている限り、EEPROMステートマシーンは常に書き込み進行中の状態です。
The datasheet always shows the proper way
to tell when a write is in progress, so that
the registers are not changed by the user's
program. The EEPROM state-machine will always complete the write in progress unless power
is removed from the device.
どのEEPROMテクノロジーにも言えることですが、EEPROM書き込み状態の間に電源が落ちれば、書き込み中のバイトの状態は不定になります。
旧世代のAVRでは、EEPROMアドレスレジスタ (EEAR)
はリセットやブラウンアウト検出やウォッチドッグタイマ作動やリセットピン動作でゼロに初期化されます。
EEPROM書き込み開始の瞬間にリセットがかかってしまった場合、書き込み自体はうまくいきますが、書き込みアドレスは予定のアドレスがゼロにすり替えられてしまいます。
書き込みプロセスの後半でリセットがかかってしまった場合は、書き込みアドレスと0番地双方のデータが壊れてしまいます。
AVRが書き込み中リセットで0番地破壊を引き起こしたかどうか判別するには、EEPROMアドレスレジスタの初期値を参照してください。
EERAが初期値としての0x00または0x0000になっている場合は、0番地及び書き込み番地が壊れているかも知れません。
新世代AVRの場合はリセット後のEEAR初期値は「未定義」であり、少なくとも0番地の破壊は引き起こしません(0番地に書き込みしている最中を除く)
EEPROMは耐久性に制限があります。データシート代表特性には、全温度での保証されたEEPROM書き込み回数が記されています。
常に、書き込み前には必ず読み込みを行い、本当に書き込みが必要がどうか(今の値と同じでないかどうか)チェックし、不要な書き込みによる消耗を抑えるべきです。
AVRはEEPROM書き込みにページング機構を用いています。これは透過的に使用でき使用にあたってほとんど気にすることはありませんが、ひとつだけ注意が必要です。
EEPROMに1バイトを書き込んだとき、EEPROMページ全体が透過的に消去と書き換えが行われます。これにより、書き込みバイト以外のバイトにも書き込み動作がなされ消耗を招きます。
s use a paging mechanism for doing EEPROM
writes. This is almost entirely transparent
to the user with one exception: When a byte
is written to the EEPROM, the entire EEPROM
page is also transparently erased and (re)written,
which will cause wear to bytes that the programmer
did not explicitly write.
EEEPROM寿命使用を越えない様にしEPROM寿命を延ばしたければ、一度にEEPROMページサイズ分だけ多バイト同時に書き込むべきであり、1バイトずつ書き込むべきではありません。
EEPROM書き込みページサイズはデバイスにより異なります。それはデータシートのメモリプログラミングの項(通常はデータシートの末尾、電気的特性のちょっと前)に書かれています。
書き込み寿命に達したときのバイト・ビット不良のメカニズムは、通常
"stuck" ビットによるもので、値がゼロか1に固定されてしまうものです。
そのため、一見読み込み→書き込み動作で正しい値が書き込まれたように見えても、過剰な書き込みによる消耗で電荷保持能力が落ちていると、時間経過と共に値が変化することがあります。
Back to FAQ Index.
いくつかのAVRデータシートでは、(UBRRに設定する)ボーレート設定のための値の計算式として、以下のように書かれています。
(F_CPU/(UART_BAUD_RATE*16L)-1)
残念なことに、この計算式は全てのクロック速度・ボーレートの組み合わせで正しく動くとは限りません。
割り算部での端数切り捨てのためです。
切り捨てで求めるより、四捨五入でより近い整数値を求めるようにする方がいいです。こんな感じで。
((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1)
この方法は <util/setbaud.h>: ボーレート計算のための支援マクロ. で使われています。
Back to FAQ Index.