実際の動作を確認したところで、まずLEDドライバから詳しく見ていきましょう。
LEDドライバの仕様は、次のように決めました。
ここでは“led”と名付けました。
「どのデータ番号でリード/ライト要求が出されると、どういう動作をするか」の仕様を決めます。ここでは「-100番(マイナス100番)に対して1byteのライト要求が出された場合に、ライトされた値をLEDに表示する」とします。リードは今回はサポートしません。
このようにドライバの仕様さえ決めてしまえば、アプリケーションとドライバを並行して個別に開発できます。ドライバ開発を外注することも容易になります。
決定したドライバの仕様に基づいて、これを実装しましょう。
ドライバの枠組みはT-Kernel仕様書で決められています。各ドライバ共通で使えるライブラリもあるので、これらを使えば簡単にドライバを作成できます。ここでは「単純デバイスドライバインターフェイス(SDI:simple device driver interface)」というライブラリを使ってドライバを作成します。今回は次の3つの関数を書けばOKです。
リード関数
デバイスからの読み出し(リード)を担当します。今回のLEDドライバはリードをサポートしないので、単にエラーを返します。
INT read_fn( ID devid, INT start, INT size, VP buf, SDI sdi ) { return E_PAR; /* リードは不可(パラメータエラー) */ }
ライト関数
デバイスへの書き込み(ライト)を担当します。LEDへの表示方法そのものは連載第2回とまったく同じですが、今回はドライバの枠組みでまとめて「アプリケーションからデータ番号-100番にライトすればよい」という汎用的な形にしたところがミソです。
INT write_fn( ID devid, INT start, INT size, VP buf, SDI sdi ) { if (start == -100 && size == 1) { /* 7セグメントLEDに値(0〜99)を表示 */ int p[] = {0x24, 0x3f, 0x62, 0x2a, 0x39, 0xa8, 0xa0, 0x3e, 0x20, 0x28 }; int x = *(B*)buf; if (x < 0 || x > 99) return E_PAR; /* 範囲外の値はパラメータエラー */ out_h( 0x16100002, p[x / 10] ); /* 10の位の表示 */ out_h( 0x16100000, p[x % 10] ); /* 1の位の表示 */ return 1; } else return E_PAR; /* それ以外のデータ番号はパラメータエラー */ }
メイン関数
ライブラリ関数のSDefDevice()を使って、ドライバのロード時にデバイスの登録を行います。ドライバのアンロードは特にサポートしなくてよいでしょう。
ER main( INT ac, UB *av[] ) { static SDI sdi; static SDefDev ddev = { NULL, "led", 0, 0, 0, 1, NULL, NULL, read_fn, write_fn, NULL }; if ( ac >= 0 ) { /* ロード時 */ return SDefDevice( &ddev, NULL, &sdi ); /* デバイス登録 */ } else { /* アンロード時 */ return E_NOSPT; /* サポートしない */ } }
温度センサドライバも、LEDドライバと同様に開発できます。まずは仕様を決めましょう。
ここでは“thermo”と名付けました。
ここでは「-100番(マイナス100番)に対して1byteのリード要求が出された際、現在の温度をセンサから読み出し、その値(単位:℃)を返す」とします。ライトは今回はサポートしません。
次にドライバの実装です。
リード関数
デバイスからのリードを担当します。温度センサからの入力はアナログ値ですが、A/D変換を通してデジタル値に変換され、0x00215000番地から値をリードします。リードした値は℃単位に変換する必要があります。
/* リード関数: 読込処理 */ INT read_fn( ID devid, INT start, INT size, VP buf, SDI sdi ) { if (start == -100 && size == 1) { /* 温度を測定して返す (単位:℃) */ *(B*)buf = in_w( 0x00215000 ) * 300 / 5120 - 300; return 1; } else return E_PAR; /* それ以外のデータ番号はパラメータエラー */ }
ライト関数
今回の温度センサドライバはライトをサポートしないので、単にエラーを返します。
メイン関数
デバイス名が違うだけで後はLEDドライバと同じ形ですが、最初に少しだけA/D変換の初期設定を行っています。
次はアプリケーション側の開発です。どのようなドライバでも、アプリケーションからは「オープン」「リード」「ライト」「クローズ」という統一されたAPIで呼べることになっています。例えば、前記のLEDドライバや温度センサドライバをアプリケーションから呼び出すには、次のようにします。
オープン
まず、温度センサドライバとLEDドライバのデバイス名を指定してオープンします。
dt = tk_opn_dev( "thermo", TD_READ ); dl = tk_opn_dev( "led", TD_WRITE );
返ってきたディスクリプタを変数dt、dlに保存します。
リード
オープン時のディスクリプタ(dt)、読み込みたいデータ番号(-100)、読み込む値の格納場所(t)、サイズ(1byte)を指定してリードのAPIを発行します。
tk_srea_dev( dt, -100, &t, 1, &n );
ライト
オープン時のディスクリプタ(dl)、書き込みたいデータ番号(-100)、書き込みたい内容(t)、サイズ(1byte)を指定してライトのAPIを発行します。
tk_swri_dev( dl, -100, &t, 1, &n );
クローズ
デバイスを使い終わったらクローズのAPIを発行します(ただし今回のアプリケーションは終了せず無限ループするのでクローズは行っていません)。
tk_cls_dev( dt, 0 ); tk_cls_dev( dl, 0 );
以上により、ネットワーク部分を除いたデジタル温度計の本体部分が完成しました。
今回のアプリケーションでは、次の流れで簡単なWebサーバをプログラムしています。
so_socket ソケットの作成 so_bind ソケットへの名前のバインド so_listen ソケット接続のためのリッスン for(;;) { so_accept ブラウザからの接続をアクセプト cre_tsk セッションタスクの生成 }
簡易Webサーバタスク |
so_read ブラウザからの要求を受信 tk_rea_dev 温度ドライバから温度をリード so_write ブラウザへ温度をHTMLで送信 ext_tsk タスク終了
セッションタスク |
Webサーバ全体を1つのタスクとせず、アクセプトしたらその後の処理は別のセッションタスクを生成してそのタスクに任せています。この技法は、組み込み分野に限らずサーバプログラミングでは必ず出てくるもので、PCの世界ではfork(子プロセス生成)などで実現します。今回はT-Kernel ExtensionのAPIのcre_tsk(タスクの生成)とext_tsk(タスクの終了)で簡単に実現できるのがお分かりいただけるかと思います。
一方so_socketなどのネットワークプログラミングのAPIは、PCの世界と基本的に同じです。Teaboardには「TCP/IPマネージャ」が付属しており、これがネットワークプログラミングのAPIを提供しています。
実はこのマネージャはドライバと並んでT-Kernelが提供するもう1つのミドルウェア流通機能である「サブシステム」で実装されています。サブシステムもドライバと同じようにlodspgでロードして、アプリケーションから呼び出して使います。ただしAPIはオープン、リード/ライト系ではなく、任意のAPIが実現できます。しかし、アプリケーション開発者は、通常はミドルウェア内部の実装についてはほとんど意識する必要はありません。単に「PCの世界のネットワークプログラミングAPI名の頭に“so_”を付ければ、ほぼそのまま使える」という理解で十分です。
今回はTeaboardによる「Web配信機能付きデジタル温度計」の例を通じて、T-Engineでの簡単なドライバ開発と、ドライバを利用したプロセスベースでマルチタスクのアプリケーション開発を見てきました。
「組み込み」の世界は、ハンドラやタスクといった「リアルタイム」のミクロ的な視点と、大規模なシステム設計で必要になるプロセスやドライバなど「モジュール化」のマクロ的視点の両方が連携して成り立っている、大変面白く奥の深い世界です。T-KernelとT-Kernel Extensionは、この両方を同時にカバーできるスケーラビリティを備えています。
本連載がT-Engineプログラミングに親しむきっかけとなれば幸いです。
Copyright © ITmedia, Inc. All Rights Reserved.