「main()」関数が起動したら、後はシリアルに「Hello World」という文字列を出力するだけです。しかし、フルスクラッチの「Hello World」では、単に「printf()」を呼べばいいというわけにはいきません。なぜならば、その「printf()」自体が用意されていないからです。何しろ“完全自作”なので、“自分が作っていないものは当然ながら使えない”ということになります。
さらに、シリアル出力を行うならば、デバイスドライバも用意する必要があります。そこで、シリアル出力用の簡単なライブラリ(シリアルドライバ)を自作しています。
表1の「serial.c」というファイルが、簡便なシリアルドライバです。内容はリスト5のようになっています。
…(中略)… /* 送信可能か? */ int serial_is_send_enable(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; return (sci->ssr & H8_3069F_SCI_SSR_TDRE); ★送信が完了すると、SSRレジスタのTDREフラグが立つ。フラグが落ちている場合はまだ送信処理中 } /* 1文字送信 */ int serial_send_byte(int index, unsigned char c) { volatile struct h8_3069f_sci *sci = regs[index].sci; /* 送信可能になるまで待つ */ while (!serial_is_send_enable(index)) ★ビジーループで待ち合わせる ; sci->tdr = c; sci->ssr &= ~H8_3069F_SCI_SSR_TDRE; /* 送信開始 */ ★SSRレジスタのTDREフラグを落とすことで送信を開始する return 0; }
リスト5の「serial_send_byte()」は、シリアルへの1文字送信用のライブラリ関数です。シリアルが送信可能になるまでビジーループで待ち合わせ、1文字送信を行います。
送信時には、「sci->tdr」や「sci->ssr」という構造体メンバを操作しています。これらは「serial.c」の先頭付近でリスト6のように定義されています。
…(中略)… #define H8_3069F_SCI0 ((volatile struct h8_3069f_sci *)0xffffb0) #define H8_3069F_SCI1 ((volatile struct h8_3069f_sci *)0xffffb8) #define H8_3069F_SCI2 ((volatile struct h8_3069f_sci *)0xffffc0) struct h8_3069f_sci { ★シリアルコントローラーのレジスタ群の定義 volatile uint8 smr; volatile uint8 brr; volatile uint8 scr; volatile uint8 tdr; volatile uint8 ssr; volatile uint8 rdr; volatile uint8 scmr; }; …(中略)… static struct { ★シリアルコントローラーのレジスタ群のマッピング先アドレス volatile struct h8_3069f_sci *sci; } regs[SERIAL_SCI_NUM] = { { H8_3069F_SCI0 }, { H8_3069F_SCI1 }, { H8_3069F_SCI2 }, }; …(後略)…
リスト5の変数「sci」には「H8_3069F_SCI0」〜「H8_3069F_SCI2」といった定数値が代入されます。これらは「0xffffb0」〜「0xffffc0」として、「#define」により定義されていますから、結果として「sci->tdr」や「sci->ssr」へのアクセスは、「0xffffb0」〜「0xffffc0」近辺のアドレスへのアクセスになります。
H8マイコンは、周辺I/Oを「メモリマップドI/O」という仕組みで操作できるようになっています。これはどのような仕組みかというと、周辺I/Oのレジスタがまるでメモリ上にあるかのように、特定のアドレスにマッピングされているというものです。
よって、C言語からはそのアドレスをポインタにより操作することで、周辺I/Oを操作することができます。プログラム上はポインタ経由でのメモリアクセスのように見えますが、実質はシリアルコントローラーのレジスタ操作になっているというわけです。
シリアルドライバが作成できれば、後はそれを利用して1文字出力や1行出力を行うようなライブラリ関数を作成できます。そこで、標準Cライブラリの「putc()」「puts()」に相当する関数を自作しています。
リスト7は「lib.c」の抜粋です。「serial.c」で用意されているシリアルへの1文字送信用関数(serial_send_byte())を利用して、文字列などを出力します。
…(中略)… /* 1文字送信 */ int putc(unsigned char c) { if (c == '\n') serial_send_byte(SERIAL_DEFAULT_DEVICE, '\r'); ★改行コードの変換(\n→\r\n) return serial_send_byte(SERIAL_DEFAULT_DEVICE, c); } /* 文字列送信 */ int puts(unsigned char *str) { while (*str) putc(*(str++)); return 0; }
ここまで作成して、ようやく「Hello World」という文字列が送信できることになります。
いかがでしたでしょうか? C言語の入門書で最初に書かれる「Hello World」ですが、その中身は単純なようでいて、突き詰めれば実はとても奥の深いプログラムだと思われたのではないでしょうか。「“Hello World”についてレポートを書け」という課題が出たとしたら、それこそA4用紙10枚くらいは軽く書けてもおかしくないほど、さまざまな技術的要素がそこには凝縮されているように筆者は思います。
今回は各種ソースコードを説明し、「Hello World」の出力に必要な作業は何かを解説しました。
次回は「Hello World」を発展させ、ブートローダーを作成し動作させてみます。ご期待ください! (次回に続く)
坂井弘亮(さかいひろあき)
某企業でネットワーク系の開発を行いながら、個人で組み込みOS「KOZOS」の開発や書籍/雑誌記事執筆、各種セミナーでの発表やイベント出展、勉強会の開催など多方面で活動中。IPAセキュリティ&プログラミングキャンプ 2010講師。著書は「12ステップで作る組込みOS自作入門」など多数。
Copyright © ITmedia, Inc. All Rights Reserved.