「組み込み」ならではの基礎知識 ――スタートアップ・ルーチンからハードウェアまで
さて次に,「見かけ上,冗長な」を説明します.例えば,シリアル・インターフェースを使ったデータの受信を考えてみます.メモリ・マップ方式のシリアル・ポートを持つマイコン注4-1の場合,受信状態を表すレジスタ(ステータス・レジスタと呼ばれる)を読み出せば,そのシリアル・インターフェース経由でデータを受け取ったかどうかがわかります注4-2.では,そのレジスタを読み出すにはどうすればよいのでしょうか?
メモリ・マップ方式の場合は,一つのレジスタに対してあるアドレスが割り付けられます.つまり,割り当てられたアドレスの内容を読み出せばよいわけです.受信状態を表すレジスタのアドレスを0x00efd00c番地,レジスタの長さを4バイトと仮定すると,受信状態を表すレジスタの読み出しは,
*(unsigned long int *)(0x00efd00c)
となります.レジスタのアドレスが0x00efd00c,レジスタ長が4バイトなので,0x00efd00cを(unsigned long int *)でポインタ型にキャストして読み出すわけです(前項の「3.array[-1]はなぜ動くのか」を参照).さらに,受信が完了したかどうかはレジスタの特定のビットが立っているかどうかによって判定すると仮定すれば,その判定とそれに続く受信処理は,例えば次のように書けます.
if (((*(unsigned long int *) (0x00efd00c))&4) != 0) {/* 受信完了か */ 受信処理 }
しかし,いつ受信が完了するかわからないので,while(1)でループを回して(ポーリング方式注4-3)レジスタをチェックしに行くことにします.これでプログラミングは完了です.
while (1) { if ((*(unsigned long int *) (0x00efd00c))&4) != 0) {/* 受信完了か */ 受信処理 } }
何か気づきませんか? このプログラムは先ほど説明した最適化のパターンにはまってしまいます.つまり,Cコンパイラが,
if ((*(unsigned long int *) (0x00efd00c))&4) != 0) {/* 受信完了か */ while (1) { 受信処理 } }
と最適化してしまうかもしれません.これでは,レジスタを読みに行く最初の1回で受信完了になっていないと,永遠にデータを受信できないかもしれません.
注4-1;メモリ・マップ方式のI/Oの詳細については,「8.メモリとポート」を参照していただきたい.
注4-2;受信状態を表すレジスタはいくつかのフィールドに分けて,それぞれのフィールドに意味を持たせているのが普通である.ここでは受信が完了したか未完了かがわかればよいので,レジスタ内の完了/未完了を表すフィールド(1ビットが割り当てられている)のビットが立っているかどうかを確認すればよい.
注4-3;ポーリング方式の詳細については,「6.ポーリングと割り込み」を参照していただきたい.