連載
» 2006年09月08日 00時00分 公開

ドライバ開発とネットワークプログラミングT-Engineプログラミング入門(4)(3/6 ページ)

[中村 大真 パーソナルメディア株式会社,@IT MONOist]

デジタル温度計プログラムの詳細

LEDドライバ仕様とプログラミング

 実際の動作を確認したところで、まずLEDドライバから詳しく見ていきましょう。

 LEDドライバの仕様は、次のように決めました。

  • デバイス名

 ここでは“led”と名付けました。

  • リード/ライトするデータの意味付け

 「どのデータ番号でリード/ライト要求が出されると、どういう動作をするか」の仕様を決めます。ここでは「-100番(マイナス100番)に対して1byteのライト要求が出された場合に、ライトされた値をLEDに表示する」とします。リードは今回はサポートしません。

※注:
データ番号として-100番を選んだ特段の理由はありませんが、データ番号の割り当てには一定のルールがあります。詳しい説明は割愛します。


 このようにドライバの仕様さえ決めてしまえば、アプリケーションとドライバを並行して個別に開発できます。ドライバ開発を外注することも容易になります。

※コラム:ドライバ流通の意義
PCの世界では、ドライバをバイナリで配布するのが当たり前になっています。ところが組み込みの世界ではこれまでドライバのインターフェイスが標準化されていなかったため、自社開発にしても他社からソースライセンスを受けるにしても、どうしても手間やコストがかさみがちでした。
そこでT-Engineフォーラムでは、ドライバやミドルウェアを流通/利用するうえでのインターフェイスを標準化しました。これにより、アプリケーション開発者はどのドライバでも同じようにバイナリをロードして利用できるようになりました。一方、ドライバ開発者にとっても、汎用的に使えるドライバを開発して、バイナリレベルで流通させることができるようになりました。


 決定したドライバの仕様に基づいて、これを実装しましょう。

 ドライバの枠組みは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.