組み込みOS自作入門!!「2日目 - メモリマップドI/O〜静的変数の読み書き」 (2nd〜3rdステップ)

さて、二日目です。
12ステップ本は、前半がブートローダの制作ということですが、まだHelloWorldを作っただけで、ブートローダっぽいものは何も実装していません。
今回はメモリマップドI/Oの解説から、シリアルデバイスドライバの解説、数字を出力する関数の作成、静的変数の読み書き対応まで進んだ訳ですが、まだブートローダ本体には辿りつけていません。そのかわりに、マイコンの基礎というか、メインの処理を始めるまでに何をしておかなければいけないのかなどを学べました。あいまいな知識しかない僕にとってはありがたい限りです。
何冊かマイコン関連の本を持っているのですが、それらはすべてスタートアップまでが予め用意されていて、その後の話しか書いていなかったので、これらのことは初めて知りました。

メモリマップドI/O

「メモリマップドI/O」という仕組みで周辺のモジュールやI/Oにアクセスできるということは知っていたのですが、具体的にどのようにして実現しているのか、ということは知りませんでした。僕の勝手な想像で、特定のメモリ番地の値を常に周辺モジュールが監視していて、その値に変化があればそれに応じて周辺モジュールが仕事をするのかなと思っていましたが、そんなややこしいことをする必要はなかったようです。
アドレスバスの性質をうまく利用し、比較器(コンパレータ)と組み合わせれば簡単に実現できます。CPUは信号の入出力しかできないので、アドレスバスにメモリが繋がっていようが、その他周辺機器がつながっていようが同じように操作することができます。


ちなみに、x86系(IntelとかAMD)のCPUは、メモリマップドI/OとI/OマップドI/O(ポートマップドI/O)という仕組みを使用して周辺機器とやりとりをします。メモリマップドI/Oに加えて、ポートマップドI/Oという仕組みがあって、これはデータを出力するOUT命令やOUTS命令と、データを入力するIN命令やINS命令などといった専用命令を用意し、これによって周辺機器と命令や情報のやりとりをします。

数字を出力する関数の作成

前回作った文字列を出力する関数は、文字の入力しか受け付けないため、プログラム中での変数の値などを出力することができません。そこで、数値を文字に変換する関数を作成します。表示する桁数を指定する機能があります。

シリアルデバイスドライバ

H8/3069Fには、シリアル通信モジュールが内蔵されていて、そのドライバの解説です。シリアル通信モジュールはメモリ上にアドレスが割り当てられているのでその割当てられているアドレスを読み書きすれば、シリアルポートを通してデータを送受信したり、シリアルモジュールの状態(送信が完了したかどうか、なにかデータを受け取ったかなど)を知ることができます。

静的変数の読み書き

まったく気付かなかったのですが、前回作ったHelloWorld、静的変数を使用することはできるのですが、その値の書き換えができないんです。
たとえば、

int a;
void main()
{
  a=1;
  putxval(a,0);putc('\n');
  a=2;
  putxval(a,0);putc('\n');
}

(※putxvalは数字を出力する関数)
というプログラムを実行したとして、

1
2

という結果を期待するわけですが、今のままだと、

1
1

という結果になってしまいます。ちなみに、関数内の自動変数に関しては大丈夫です。

void main()
{
  int a;
  a=1;
  putxval(a,0);putc('\n');
  a=2;
  putxval(a,0);putc('\n');
}

というプログラムを実行したとしたら、

1
2

という実行結果になります。静的変数が書き換えられないのは困りますよね…
この違いは何でしょう?
簡単に言うと、変数の実体が存在する場所の違いということになります。関数内で宣言された変数は、その関数が終われば必要なくなる一時的なものなので、スタック領域に配置されます。スタック領域はRAM上にあり、RAMは書き換え可能なので、変数の書き換えができます。一方で、関数の外で宣言された変数やstatic修飾子が付いた変数は静的変数ですから、スタック領域に配置することができません。ですから、これらの変数をどこに配置するのか決めないといけません。現状だと、何も決めなかったので、ROMに配置されていたようです。ROMはCPUの命令では書き換えができませんから、これでは静的変数の書き換えには対応できません。
変数の配置を決めるのは、リンカスクリプトです。これを書き換えれば、どうにかできそうです。変数を読み書きできるようにするためにはRAM上に変数を配置するのですが、書き込み時にRAM上に配置しても、RAMの特性上、一旦電源を切ればその内容は消えてしまうので意味がありません。よって、ROMに変数を書きこんでおいて、起動時にそれらをRAMにコピーするしくみが必要になります。これをローダといいます。
起動時に静的変数をROMからRAMへコピーしておき、その後はプログラムがRAM上の変数を参照するように設定しておけば、すべてうまくいくわけです。
マイコンはスタートするまでにこんなことをやっていたのですね…何も知りませんでした。今までは「スタートアップルーチンで初期化を行っている」程度の認識しかなかったので、勉強になりました。