連載「H8マイコンボードで動作する組み込みOSを自作してみよう!」の第5回。今回は“コマンド応答プログラムをマルチタスク構成で実装”し、動作させます。マルチタスクOSを利用するメリットをしっかりと理解しましょう。
本連載では、学習用・ホビー用の組み込みOS「KOZOS」を使って、マイコンボード上でいろいろと実験をしつつ、フルスクラッチで組み込みOSを自作していく過程を体験していきます。最終的に、ソフトウェア完全自作のWebサーバを動かすことにチャレンジします!
前回までの内容で、「タスク」の管理を実装し、プログラムをマルチタスクで動作させることができるようになりました。
今回は“コマンド応答プログラムをマルチタスク構成で実装”し、動作させてみます。
本連載では、秋月電子通商の「H8/3069Fネット対応マイコンLANボード(完成品)」(図1)を利用します。マイコンボードの詳細については連載第1回を参照してください。ちなみにこのボードは、3750円(税込)と非常に安価で入手性も良く、個人のホビー用途にオススメです。
本連載で紹介するソースコードは、KOZOSのWebサイトからダウンロードできます。また、開発環境の構築方法は連載第1回で説明してありますので、興味のある方はぜひソースコードをビルドし、実機での動作を試してみてください(幾つか対応は必要となりますが、シミュレータ上でも動作させることが可能です)。
なお、上記ソースコードは、筆者の書籍「12ステップで作る組込みOS自作入門」の各章(ステップ)に応じたものになっています。書籍の方にも詳しい説明がありますので、そちらも参考にしてみてください。
今回は「コマンド応答」というプログラムを実装します。PCとマイコンボードをシリアルケーブルで接続し、PC側から「Tera Term」などの端末エミュレータで「echo hello」などと送信してやると、マイコンボードが「echo」というコマンドを解釈して、後続の「hello」を応答するというものです。
これは単にシリアル入力を見張って、行単位で解釈し、応答するという単純なプログラムでも実現することができます。しかし、今回はこれを“タスク”の概念を利用して、マルチタスク構成で実装してみます。ところで、なぜわざわざ“タスク”によって実現するのでしょうか? これは、単にシリアル入力を見張るような単純な実装では、幾つかの問題点があるためです。
まず、マイコンボード上でのシリアル受信ですが、これは連載第2回で説明した「シリアルコントローラ」のレジスタをビジーループで見張ることで、受信を検知することができます。以下のリスト1は、KOZOSのシリアル受信の例ですが、シリアルからの文字受信のベタな実装となっています。ここでは、関数「serial_recv_byte()」が「serial_is_recv_enable()」経由でシリアルコントローラ(SCI)のステータスレジスタ(SSR)をビジーループで見張り、シリアル受信を検知しています。
/* 受信可能か? */ int serial_is_recv_enable(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; return (sci->ssr & H8_3069F_SCI_SSR_RDRF); } /* 1文字受信 */ unsigned char serial_recv_byte(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; unsigned char c; /* 受信文字が来るまで待つ */ while (!serial_is_recv_enable(index)) ; c = sci->rdr; sci->ssr &= ~H8_3069F_SCI_SSR_RDRF; /* 受信完了 */ return c; }
また、シリアル送信についても同様です。シリアル送信もやはりシリアルコントローラに依頼することになりますが、1行を一気に送信依頼するようなことはできません。“1文字単位での依頼”になります。
さらに、1文字単位でも一気に送信するようなことはできません。前の文字の送信が完了しないと、次の文字の送信依頼が行えないのです。このため、ビジーループで送信完了を見張り、完了後に次の文字の送信依頼を行うような動作が必要になります。リスト2は、KOZOSでのシリアル送信関数の実装の例ですが、「serial_is_send_enable()」をビジーループで呼び出すことで、SCIのSSRレジスタを見張っています。
/* 送信可能か? */ int serial_is_send_enable(int index) { volatile struct h8_3069f_sci *sci = regs[index].sci; return (sci->ssr & H8_3069F_SCI_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; /* 送信開始 */ return 0; }
ここで、前回説明したような問題が発生します。受信も送信も“ビジーループ”によってシリアルコントローラのレジスタを見張ることになるため、その間に他の処理ができなくなってしまうのです。また、CPUも空動作を続けているため、電力を無駄に消費することにもなります。
1つの解決策としては、シリアルの「受信割り込み」と「送信完了割り込み」を利用して、割り込みベースの動作にすることです。例えば、これらの割り込みに「割り込みハンドラ」を用意して、受信割り込みが入ったら受信処理を、送信完了割り込みが入ったら次の文字の送信処理を行うようにします。このようにすれば解決できますが、プログラム全体を割り込みベースで書く必要があり、時間のかかる処理などは書き方に工夫が必要になります。
そして、もう1つの解決策は、OSを利用してタスクベースの動作にすることです。以降で詳しく見ていきましょう。
図2は、OSを利用してコマンド応答処理をマルチタスクで実装した場合のシーケンス図の例です。
まず、「コマンド処理」と「コンソールドライバ」の2種類のタスクを作成します。
シリアルからの文字受信は受信割り込みハンドラで受け付け、改行コードが入力されるまでバッファリングします。改行を受信したら、1行分のデータをコマンド処理タスクに送信します。このデータ送信には、OSが提供する「メッセージ通信」のサービスを利用します。
コマンド処理タスクでは、送られてきた1行の文字列データを解析します。例えば、ここで「echo hello」といった文字列が送られてきたとしたら、「echo」コマンドと解釈して、続く「hello」の文字列を応答しようとします。
「hello」の文字列の応答は、コンソールドライバタスクに依頼します。つまり、コマンド処理タスクは、メッセージ通信によって、送信文字列である「hello」をコンソールドライバタスクに送信します。コンソールドライバタスクは、コマンド処理タスクから送信要求として送られてきた「hello」の文字列の、最初の1文字である「h」を送信します。
シリアルコントローラは「h」を送信し、完了すると送信完了割り込みを発行します。送信完了割り込みの割り込みハンドラでは、続く「e」の文字を送信します。これを繰り返すことで、「l」「l」「o」と引き続き送信されることになります。
このシーケンス図で注目すべき点は、文字送信をシリアルコントローラに依頼してから送信完了割り込みが入ってくるまでの“すき間”です。ここでCPUはシリアル送信に対して処理すべきものがなく、暇になります。OSを利用していれば、ここでOSが他のタスクを実行してくれます。
例えば、前回説明した“LEDの点滅”などの処理を別タスクとして起動しておけば、たとえシリアルへの文字列送信中でもOSが適切にタスクの切り替えをしてくれるため、送信完了待ちのビジーループのためにLEDの点滅が止まってしまうようなことを避けることができます。
また、“省電力モードに入る”というだけのタスクを別途起動しておけば、実行すべきタスクが何もないときには、自動的に省電力モードに移行できるようにもなります。
このような処理は、OSを使わなくとも割り込みをベースにして状態遷移するような書き方にすることで実現できます。しかし、OSを利用することで、処理を“タスク”という単位に分割することができ、並列で動作させたい処理をタスク単位で独立して記述できるようになるため、管理がしやすくなります。
Copyright © ITmedia, Inc. All Rights Reserved.