H/W のレジスタなど、ソースコード上ではいじらなくても値が変化する可能性のある変数があります。そのような変数は、volatile 指定をしておく必要があります。
volatile 指定をしないと、コンパイラの最適化により、最初の値評価以外のコードが消されてしまう可能性があります。その結果、絶対に抜けることのない無限ループとなってしまいます。
例として、以下のようなコードは、ソースコード上 while ループ内で timer_reg と expired の値が変化することはないので、コンパイラは無駄な処理と判断して削除します。
しかし、実際には H/W が timer_reg の値を更新していくので、while ループを回すことに意味があります。そこで、volatile 指定を行い、コンパイラにそのことを教えてあげます。
long* timer_reg; long expired = 1000; while (*timer_reg != expired) {}
組込みソフトウェアのコーディングで、レジスタ変数と while ループを使う場合ば、頭の片隅に入れておくと良いです。
メモリアクセスの高速化のために、CPU などはキャッシュを備えています。全てのアクセスがキャッシュ経由で行なわれるのなら、あまり意識はしなくて良いのですが、キャッシュを経由せずに直接メモリ本体にアクセスする H/W もあります。その際に、キャッシュ上のデータと、メモリ本体にあるデータが食い違う可能性があります。
キャッシュ上のデータをメモリ本体に反映しているか。CPU で操作したあと、そのデータを別の領域へ DMA コントローラでコピーする場合、事前にキャッシュ上のデータをメモリ本体へ Write Back しておく必要があります。
キャッシュ上のデータがメモリ本体の内容を破壊しないか。DMA コントローラでコピーした先の領域に対応するキャッシュデータがある場合、せっかくコピーしたデータが、古いキャッシュデータで上書きされてしまいます。それを回避するために、事前にキャッシュ上のデータを無効化しておく必要があります。
各 H/W によって、メモリアクセスで利用できるアラインメントが異なります。その制約に沿わないアドレスを渡すと、動作は不正となり、デバッグに苦労します。
また、構造体を定義する時も、CPU のアラインメントを意識して、パディングを考慮しておきます。
4byte, 32byte, 4KB など、アラインメントは、H/W によって様々です。
リンカスクリプトには、シンボルを定義して、Cコード側から参照させることが出来ます。この際、リンカスクリプトに書けるのは、シンボル名とシンボルのアドレスになります。
例えば以下のようなシンボルがあった場合、
SAMPLE_AREA=0x02000000 SAMPLE_AREA_SIZE=0x1000
Cコードでは、次のように値を取得できます。
extern unsigned long SAMPLE_AREA; extern unsigned long SAMPLE_AREA_SIZE; printf("addr = %x, size= %x\n", &SAMPLE_AREA, (unsigned long)&SAMPLE_AREA_SIZE);
注意する点として、リンカスクリプトに定義できるのは、あくまでシンボルのアドレスのみです。シンボルが示すメモリ空間に格納される実値を定義できるわけでは、ありません。故に、以下のような場合は、何が返るかわかりません。
printf("value = %x\n", *(unsigned long*)&SAMPLE_AREA_SIZE);