コマンド応答をマルチタスクで実装してみよう:H8マイコンボードで動作する組み込みOSを自作してみよう!(5)(2/3 ページ)
連載「H8マイコンボードで動作する組み込みOSを自作してみよう!」の第5回。今回は“コマンド応答プログラムをマルチタスク構成で実装”し、動作させます。マルチタスクOSを利用するメリットをしっかりと理解しましょう。
4.コマンド応答を試してみよう
それでは、実際の動作を確認してみましょう。まずは、KOZOSのWebサイトから「osbook_03.zip」をダウンロードし解凍してください。
今回は、「12」というフォルダのプログラムを動作させてみます。フォルダ「12」以下には「bootload」「os」という2種類のソースコードがあります。「bootload」がブートローダー、「os」がブートローダーから起動して動作させるプログラムになります。
ビルドと起動の手順を以下にまとめます。なお、ビルドと起動の詳しい手順については、前回までの内容を参照してください。
- 「bootload」のフォルダに入って「make」コマンドを実行することで、ブートローダーの実行モジュールを作成する
- さらに「make image」コマンドにより、マイコンボードへの書き込み用ファイル「kzload.mot」を作成する
- 「os」のフォルダに入って「make」コマンドを実行することで、OSの実行モジュールである「kozos」というファイルを作成する
- マイコンボードとPCをシリアルケーブルで接続する
- 「h8write」により、マイコンボードのフラッシュROMにブートローダーを書き込む(注1)
- マイコンボードでブートローダーを起動する
- PC側でTera Termなどの端末エミュレータを起動し、マイコンボードと接続する
- ブートローダーで「load」コマンドを実行し、シリアル経由(XMODEM)でOSの実行モジュールをマイコンボードに転送する
- 「run」コマンドでOSを起動する
http://sourceforge.jp/projects/kz-h8write/
リスト3は、実際にOSを起動し、「echo」コマンドを実行してみたところです。「echo aaa」と入力し、マイコンボードが「aaa」と応答していることが分かります。
kzload> run starting from entry point: ffc020 kozos boot succeed! command> echo aaa aaa command>
5.動作の詳細
では、プログラムの動きを解説していきます。今回のプログラムは、図2に示したシーケンス図のような動作をしています(以下、図2を再掲)。
リスト4のソースコードは、シリアル受信割り込みのハンドラでの処理です。シリアル受信を検知したら改行コードが入力されるまでバッファリングし、改行が入力されたらバッファの内容をコマンド処理タスクに転送します。KOZOSでは前述した「メッセージ」というタスク間通信の方法が提供されており、「kx_send()」というサービスコールで受信文字列をコマンド処理タスクに送信します。これは、図2の左上の動作に相当します。
static int consdrv_intrproc(struct consreg *cons) { …… if (serial_is_recv_enable(cons->index)) { /* 受信割り込み */ c = serial_recv_byte(cons->index); …… if (c != '\n') { /* 改行でないなら、受信バッファにバッファリングする */ cons->recv_buf[cons->recv_len++] = c; } else { …… memcpy(p, cons->recv_buf, cons->recv_len); kx_send(MSGBOX_ID_CONSINPUT, cons->recv_len, p); ……(後略)……
リスト5は、コマンド処理タスク(command.c)です。KOZOSでは「kz_recv()」というシステムコールにより、メッセージを受信します(“MSGBOX_ID_CONSINPUT”というIDが、リスト4での送信時のものと一致していることに注目してください)。
受信した文字列を見て「echo」だったら後続の文字列を応答として「send_write()」という関数に渡します。「send_write()」では「kz_send()」というシステムコールにより、コンソールドライバタスクに対して送信要求(CONSDRV_CMD_WRITE)を送信します。これは図2の右側の動作に相当します。
/* コンソールへの文字列出力をコンソールドライバに依頼する */ static void send_write(char *str) { …… p[1] = CONSDRV_CMD_WRITE; memcpy(&p[2], str, len); kz_send(MSGBOX_ID_CONSOUTPUT, len + 2, p); } int command_main(int argc, char *argv[]) { …… while (1) { …… /* コンソールからの受信文字列を受け取る */ kz_recv(MSGBOX_ID_CONSINPUT, &size, &p); …… if (!strncmp(p, "echo", 4)) { /* echoコマンド */ send_write(p + 4); /* echoに続く文字列を出力する */ ……(後略)……
コンソールドライバタスクのメインループでは、「kz_recv()」システムコールにより“メッセージ受信待ち”をしています(リスト6)。コマンド処理タスクから渡された送信要求はここで受信され(“MSGBOX_ID_CONSOUTPUT”というIDが、リスト5での送信時のものと一致しています)、「consdrv_command()」が呼ばれ、送信要求(CONSDRV_CMD_WRITE)だと判断されて文字列送信のための「send_string()」が呼ばれます。
/* スレッドからの要求を処理する */ static int consdrv_command(struct consreg *cons, kz_thread_id_t id, int index, int size, char *command) { switch (command[0]) { …… case CONSDRV_CMD_WRITE: /* コンソールへの文字列出力 */ …… send_string(cons, command + 1, size - 1); /* 文字列の送信 */ …… } int consdrv_main(int argc, char *argv[]) { …… while (1) { id = kz_recv(MSGBOX_ID_CONSOUTPUT, &size, &p); …… consdrv_command(&consreg[index], id, index, size - 1, p + 1); ……(後略)……
リスト7は「send_string()」によるシリアル送信です。まずは「send_string()」で送信完了割り込みを有効にして、「send_char()」により先頭の1文字を送信します。
先頭1文字の送信が完了すると、送信完了割り込みが発生し、割り込みハンドラの「consdrv_intrproc()」が呼ばれます。ここで送信完了割り込みの場合には、再度「send_char()」が呼ばれ、後続の文字が送信されます。このように割り込みが繰り返され、文字列が1文字ずつ送信されていきます。これは図2の左下の動作に相当します。
/* 送信バッファの先頭1文字を送信する */ static void send_char(struct consreg *cons) { …… serial_send_byte(cons->index, cons->send_buf[0]); …… } /* 文字列を送信バッファに書き込み送信開始する */ static void send_string(struct consreg *cons, char *str, int len) { …… if (cons->send_len && !serial_intr_is_send_enable(cons->index)) { serial_intr_send_enable(cons->index); /* 送信割込み有効化 */ send_char(cons); /* 送信開始 */ } } static int consdrv_intrproc(struct consreg *cons) { …… if (serial_is_send_enable(cons->index)) { /* 送信割り込み */ …… send_char(cons); ……(後略)……
Copyright © ITmedia, Inc. All Rights Reserved.