Palm Programmer's Laboratory
Palm OS Programmer's Companion Volume II/5-4
5-4 シリアルマネージャ
Palm OS シリアルマネージャは、バイトレベルのシリアル I/O や RS-232・赤外線・Bluetooth・USB シグナルの制御を受け持ちます。
- 注
- Palm OS は Bluetooth 接続をサポートしますが、Bluetooth は追加のハードウェアとソフトウェアを必要とし、これを書いている時点ではそれを利用できません。
このセクションでは、シリアルマネージャについて解説し、仮想シリアルドライバの書き方を説明します。以下のトピックが含まれます。
- どのバージョンのシリアルマネージャを使うべきか?
- シリアルマネージャ利用手順?
- ポートを開く?
- ポートを閉じる?
- ポートを設定する?
- データを送信する?
- データを受信する?
- シリアルマネージャのヒントとコツ?
- 仮想デバイスドライバを書く?
- 注
- あらゆるコールを行なう前に、どのシリアルマネージャが存在するか確認するべきです。詳しくは次のセクションを参照して下さい。疑わしいのなら、古いシリアルマネージャ API は常に利用できます。
- 重要
- 仮想シリアルドライバは Palm OS Cobalt ではサポートされていません。サードパーティの開発者だけが、Palm OS 4.x 以前で動作する仮想シリアルドライバを作成できます。
どのバージョンのシリアルマネージャを使うべきか
利用可能なシリアルマネージャには、いくつかのバージョンがあります。Palm OS の最初のいくつかのリリースは、単独のシリアルポートのみをサポートするシリアルマネージャを含んでいました。このシリアルマネージャ用 API は、「Palm OS Programmer's API Reference」の「旧シリアルマネージャ」?章で文書化されています。
もし新シリアルマネージャフィーチャーセット?が存在するなら、シリアルマネージャは異なる API(「Palm OS Programmer's API Reference」の「シリアルマネージャ」?章で説明しています)セットを持ち、複数の物理的なシリアルハードウェアデバイスと仮想シリアルデバイスをサポートできます。物理的なシリアルドライバは、必要なときにハードウェアの通信を管理します。また仮想ドライバは、ある種のブロックベースなシリアルコードに送られるデータのブロックを管理します。ドライバの詳細な操作は、メインのシリアル管理コードから抽象化されています。
Palm OS の最新バージョンでは、新しいバージョンのシリアルマネージャがインストールされるでしょう。バージョン2では USB と Bluetooth の仮想ドライバが提供され、シリアルマネージャと仮想ドライバ API にいくつかの強化が提供されます。
どのAPIを使用するか決めるとき、以下の点に注意して下さい。
- もし新しいアプリケーションコードを書いているところなら、新シリアルマネージャを利用可能であれば、新シリアルマネージャ関数を直接利用することで最良のパフォーマンスを得ることができます。新シリアルマネージャは Palm OS 3.3 で導入されました。もしターゲットとする市場の全デバイスでそれを利用できるなら、新シリアルマネージャを直接利用することを検討して下さい。
- 旧シリアルマネージャ API は全バージョンの Palm OS で利用可能です。しかし、それは RS-232 通信と低レベル赤外線( IrDA )通信しかサポートしていません。
- 新シリアルマネージャ API は IrComm プロトコルをサポートしています。
- 新シリアルマネージャのバージョン 2 は、USBと Bluetooth の通信をサポートしています。
- もし仮想シリアルドライバを書くなら、新シリアルマネージャ API を使う必要があります。しかし、仮想ドライバは Palm OS Garnet ではサポートされないことに注意して下さい。
シリアルマネージャのバージョンの確認
新シリアルマネージャ API を使用できるかどうかを確認するために、FtrGet を以下のようにコールして新シリアルマネージャフィーチャセットの存在を確認します。
err = FtrGet(sysFileCSerialMgr, sysFtrNewSerialPresent, &value);
もし新シリアルマネージャがインストールされているなら、value パラメータはゼロ以外になり、返されるエラーはゼロ(つまりエラー無し)になります。
新シリアルマネージャのバージョン 2 が存在するかどうかを確認するために、シリアルマネージャのバージョンとPalm OSのバージョンを以下のように確認するべきです。
err = FtrGet(sysFileCSerialMgr, sysFtrNewSerialVersion, &value); err = FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
もし value パラメータが 2 だったら、romVersion は 0x04003000になり、FtrGetのコールはゼロを返し(つまりエラー無し)、新シリアルマネージャフィーチャセットのバージョン 2 が存在することになります。
新シリアルマネージャのバージョン 2 は大体 Palm OS 4.0 以降で出荷されています。しかし、Palm OS 3.5 が動くいくつかの Handspring 社デバイスは、バージョン数として 2 を返すシリアルマネージャを持っています。このシリアルマネージャは、Palm OS 4.0 とともに出荷されたシリアルマネージャとはかなり異なるフィーチャセットを持っています。それは仮想ドライバのオペレーションコードや仮想ドライバの USB サポートを含んでいますが、バージョン 2 で追加されたパブリックなシリアルマネージャ関数を含んでいません。また、仮想ドライバは Palm OS Garnet ではサポートされていません。そのために、バージョン 2 のシリアルマネージャ関数を使用する前に、シリアルマネージャのバージョンと Palm OS のバージョンの両方を確認する必要があります。
新シリアルマネージャについて
新シリアルマネージャは、複数のシリアルデバイスを、ハードウェアドライバとデータ構造体の最小限の複製により管理します。古い Palm システムでは、 68328(Dragonball)プロセッサの中でシリアルライブラリが全てのシリアルハードウェアへの接続を管理します。このプロセッサはシステム内の単なるシリアルデバイスです。新しいシステムは、赤外線ポートや USB ポートなどの追加のシリアルデバイスを含んでいます。
以下の図は、シリアルマネージャの通信ソフトウェアとハードウェアドライバの階層について示しています。
図 5.2 シリアルマネージャのシリアル通信アーキテクチャ
シリアルマネージャはインストールされたハードウェアと現時点で開いている接続のデータベースを維持します。アプリケーション、ライブラリ、その他シリアル通信タスクは、物理的ポート番号やタスクが接続を開きたい正確なシリアルハードウェアを識別する4文字コードの指定により、異なるシリアルハードウェアを開きます。シリアルマネージャは、ポートが必要とされたときに動的に開かれるハードウェアドライバを通して、適切なアクションを行ないます。あるハードウェアドライバは、 Palm で利用可能な各シリアル通信ハードウェアデバイスを必要とします。
システムの再起動時、シリアルマネージャは Palm デバイス上の全シリアルドライバを検索します。シリアルドライバは、コードリソースとバージョンリソースを含む独立した .prc ファイルです。このファイルは 'sdrv'(物理シリアルドライバ)形式または'vdrv'(仮想シリアルドライバ)形式です。一度ドライバが見つかったら、関連付けられたハードウェアの位置を尋ね、そのハードウェアの能力に関する情報を提供します。これは見つけられた各ドライバごとに行われ、シリアルマネージャはデバイス上にあるハードウェアのリストを常にメンテナンスします。
一度ポートが開かれたら、シリアルマネージャは現在の情報と特定のポートの設定をメンテナンスするための構造体を割り当てます。ポートを開いたタスクやアプリケーションはポート ID を返し、ほかのシリアルマネージャ関数がコールされたときにこのポートを参照するためのポート ID を提供する必要があります。
ポートを閉じる際、シリアルマネージャは開かれたポートの構造体を解放し、メモリのフラグメンテーションを防ぐためにドライバのコードリソースをアンロックします。
適切なポート名やユーザが異なる接続タイプの接続プロファイルに保持している他のシリアルポートパラメータを入手するために、アプリケーションは接続マネージャを利用できることに注意してください。詳しくは、「接続マネージャ」?セクションを参照してください。
シリアルマネージャ利用手順
利用する API のバージョンにかかわらず、シリアル通信を行う際の主な手順は変わりません。
- シリアルポートを開く。
新シリアルマネージャでポートを開くためには、どのポートを開くかを指定し、接続をユニークに識別するポート ID を入手します。そのポート ID を他の各シリアルマネージャコールに渡します。
旧シリアルマネージャは一つのポートしか持っていないので、接続をユニークに識別するためにシリアルライブラリ参照値を利用します。そのため、旧シリアルマネージャを使う場合は、最初にシリアルライブラリ参照値を入手し、そしてポートを開きます。
「ポートを開く」?を参照してください。
- もし必要なら、接続を設定します。
他のシリアルマネージャコールを使用する前に、ボーレートを変更したり受信キューのサイズを大きくする必要があるかもしれません。「ポートの設定」?を参照してください。 - データの送受信。
「データの受信」?を参照して下さい。 - ポートを閉じる。
「ポートを閉じる」?を参照して下さい。
次のいくつかのセクションでは、これらの手順を詳細に説明します。新旧シリアルマネージャ API で似ている点は、タスクは新シリアルマネージャの用語を使って説明されます。そして旧シリアルマネージャ API は括弧ではさみます。これらの場合、違いは関数の名前と接続を識別するために渡す ID だけです。2 つの API でかなり異なる点は、両方とも説明します。
- ヒント
- デバッグ情報と一般的なエラーの修正方法については「シリアルマネージャのヒントとコツ」?を参照して下さい。
ポートを開く
シリアルマネージャはデバイス起動時にインストールされます。しかし、それが使えるようになる前に、ポートを開いてシリアルハードウェアを使えるようにする必要があります。
新旧シリアルマネージャのために、どの API を使用するかによって異なるポートを開きます。
- 重要
- シリアルポートを開くアプリケーションは、それを閉じる責任があります。シリアルポートを開くことで UART の電源が入り、電池を消耗します。電池の消耗を抑えるために、不要なポートを開いたままにしないようにします。
- もし errNone が戻されたら、ポートは正しく開かれました。アプリケーションはそのタスクを行うことができ、それが完了したらポートを閉じます。
- もし serErrAlreadyOpen が戻されたら、ポートはすでに開かれています。例えば、もし以前のデバッグセッションがポートを閉じていなかったり TCP/IP スタックが開かれたままデバイスに残っていたりするなら、コンソールがポートを開いたときエラーを受け取るかもしれません。
- もしその他のエラーが戻されたなら、ポートは開かれていません。また、アプリケーションはそれを閉じる必要はありません。
新シリアルマネージャでポートを開く
新シリアルマネージャを使ってポートを開くためには、「ポートの指定」?と UART 初期ボーレートをコールします。SrmOpen は、接続をユニークに識別するポート ID を戻します。このポート ID を他のシリアルマネージャコールに渡します。
バージョン2の新シリアルマネージャは、RS-232 や赤外線と同様に USB と Bluetooth 接続をサポートしています。Bluetooth と USB プロトコルでは、なぜアプリケーションがポートを開くのかの理由が重要なことがよくあります。USB や Bluetooth プロトコルでネゴシエートされたときはボーレートは重要ではありません。USB や Bluetooth 接続を開くために、SrmOpenConfigType 構造体を使用します。これはボーレートの代わりに接続の目的を特定できるようにするものです。
一度 SrmOpen または SrmExtOpen のコールが成功したら、それはシリアルマネージャがポートを維持するために内部構造体を正しくアロケートし、シリアルドライバがこのポートに正しくロードされた、ということを示します。
Listing 5.1 ポートを開く (新シリアルマネージャ)
UInt16 portId; Boolean serPortOpened = false; err = SrmOpen(serPortCradlePort /* port */, 57600, /* baud */ &portId); if (err) { // display error message here. } //record our open status in global. serPortOpened = true;
ポートはフォアグラウンド接続( SrmOpen または SrmExtOpen )かバックグラウンド接続( SrmExtOpenBackground )のどちらかにより開かれます。フォアグラウンド接続はアクティブな接続がポートに接続し接続が閉じられるまでポートの利用を制御します。バックグラウンド接続はポートを開きますが、フォアグラウンド接続が要求するその他のタスクの制御を放棄します。バックグラウンド接続は、他のタスクがポートを使用していないときに限りデータを受け取るためにシリアルデバイスを使うサポートタスク(例えばキーボードドライバ)を提供します。
バックグラウンドポートは限定された機能しかもっていないことに注意してください。それらはデータの受信と受信したデータをクライアントに通知することしかできません。
ポートの特定
ポートは以下の方法を使って特定される必要があります。
- 論理ポート( Palm OS Programmer's API Reference の「論理シリアルポート定数」?を参照)
- ポートの特定に推奨される方法は論理ポート名を使用することです。論理ポートはハードウェアに依存します。Palm OS はそれらを適切な物理ポートにマッピングします。物理ポートの代わりに論理ポートを使うほうが良いです。
- 物理ポート( Palm OS Programmer's API Reference の「物理シリアルポート定数」?を参照)
- 物理ポートは、デバイスの物理ハードウェアを参照する 4 文字の定数(uxxx)です。参照しているハードウェアは特定のデバイスに存在しないかもしれないので、これらのポートを使うことは通常は良い考えではありません。
- 仮想ポート( Palm OS Programmer's API Reference の「仮想シリアルポート定数」?を参照)
- 仮想ポートはデバイスにインストールされた仮想ドライバに関連付けられています。例えば、仮想ポート定数 sysFileCVirtIrComm は IrComm プロトコルを実装している仮想ドライバを指定します。
- 接続マネージャ(「接続マネージャ」?を参照)
- もし接続プリファレンスパネルに格納された特定の接続プロファイルを使用したいなら、接続プロファイルからポート名を入手しその名前を使ってポートを開くために、接続マネージャを使用します。
物理および仮想ポートのための他の 4 文字コードが将来追加されるかもしれないことに注意してください。また、ポート ID はクリエイタ ID と同様に 4 文字の定数であり、文字列ではありません。そのため、シングルクォート( ' ' )でくくります。ダブルクォート( " " )ではありません。
旧シリアルマネージャでポートを開く
もし旧シリアルマネージャを使用しているなら、1 つのポートしかないので、ポートを指定するには常にゼロ(もしくは serPortLocalHotSync 定数)を渡します。シリアルライブラリ参照値は接続を識別します。この参照値を入手するには、ライブラリ名に Serial Library を指定して SysLibFind をコールします。
参照値はアプリケーションの1回の呼び出し内であれば同じままです。必要に応じて値を使用して、ライブラリを閉じたり開いたりできます。呼び出しと呼び出しの間で参照値は変わります。そのため、シリアルマネージャを再度開くたびに SysLibFind をコールする必要があります。
SysLibFind のコール後、SerOpen を使用してポートを開きます。SrmOpen のように、参照値と一緒にボーレートを渡します。
Listing 5.2 ポートを開く (旧シリアルマネージャ)
UInt16 refNum = sysInvalidRefNum; Boolean serPortOpened = false; Err err; err = SysLibFind("Serial Library", &refNum); err = SerOpen(refNum, 0 /* port is always 0*/, 57600 /* baud */); if (err == serErrAlreadyOpen) { err = SerClose(refNum); // display error message here. } //record our open status in global. serPortOpened = true;
ポートを閉じる
シリアルポートとアプリケーションが終了したら、旧シリアルマネージャの場合は SerClose 関数を使用してポートを閉じます。もし SrmClose がエラーを戻さなければ、シリアルマネージャが正常にドライバを閉じてポートメンテナンス用のデータ構造体をデアロケートしたことを示します。
電池の消耗を抑えるために、必要なしにシリアルポートを開いたままにしないことが重要です。通常は、必要なしに開いたままにするよりは、閉じて開くを複数回繰り返すほうが良いです。
ポートを設定する
新しく開かれたポートはデフォルト設定のままです。デフォルトのポート設定は以下のとおりです。
- 512 バイトの受信キュー
- デフォルトの CTS タイムアウト(現在は 5 秒)セット
- 1 ストップビット
- 8 データビット
- 入力におけるハードウェアハンドシェイク
- フローコントロールが可能
- RS-232 接続のための、ポートを開くときに指定したボーレート
データ送受信の前に、必要であればこの設定を変更することができます。
受信キューバッファサイズを大きくする
デフォルトの受信キューサイズは 512 バイトです。もしアプリケーション実行中に多くのハードウェアの超過もしくはソフトウェアの超過に気づいたなら、デフォルトの受信キューを大きなものに置き換えることを検討します。
カスタム受信キューを使用するには、アプリケーションは以下のことを行います。
- カスタムキューにメモリチャンクをアロケートします。これは実際のメモリチャンクを必要とします。グローバル変数やチャンクからのオフセットではありません。
- 新しいバッファと新しいバッファのサイズを引数として、旧シリアルマネージャの SerSetReceiveBuffer をコールします。
- ポートを閉じる前にデフォルトキューを復元します。この方法は、送信されたどのビットも行き場があります。
- デフォルトキューの復元後、カスタムキューをデアロケートします。このシステムはデフォルトキューのみをデアロケートします。
以下のコードはデフォルトキューをカスタムキューに置き換える方法の詳細です。
Listing 5.3 受信キューの置き換え
#define myCustomSerQueueSize 1024 void *customSerQP; // Allocate a dynamic memory chunk for our custom receive // queue. customSerQP = MemPtrNew(myCustomSerQueueSize); // Replace the default receive queue. if (customSerQP) { err = SrmSetReceiveBuffer(portId, customSerQP, myCustomSerQueueSize); } // ... do Serial Manager work // Now restore default queue and delete custom queue. // Pass NULL for the buffer and 0 for bufSize to restore the // default queue. err = SrmSetReceiveBuffer(portId, NULL, 0); if(customSerQP) { MemPtrFree(customSerQP); customSerQP = NULL; }
他の設定の変更
他のシリアルポート設定を変更するには、旧シリアルマネージャ API の SerSetSettings を使用します。
Listing 5.4 は、19200 ボー、8 データビット、偶数パリティ、1 ストップビット、0.5 秒の CTS タイムアウト、完全ハードウェアハンドシェイク(入出力)、とシリアルポートを設定します。CTS タイムアウトは、CTS 入力がアサートされないときにシリアルライブラリがバイト送信を待つシステムティックの最大値を指定します。CTS タイムアウトは、srmSettingsFlagCTSAutoM がセットされていなければ無視されます。
Listing 5.4 設定の変更 (新シリアルマネージャ)
Err err; Int32 paramSize; Int32 baudRate = 19200; UInt32 flags = srmSettingsFlagBitsPerChar8 | srmSettingsFlagParityOnM | srmSettingsFlagParityEvenM | srmSettingsFlagStopBits1 | srmSettingsFlagRTSAutoM | srmSettingsFlagCTSAutoM; Int32 ctsTimeout = SysTicksPerSecond() / 2; paramSize = sizeof(baudRate); err = SrmControl(portId, srmCtlSetBaudRate, &baudRate, &paramSize); paramSize = sizeof(flags); err = SrmControl(portId, srmCtlSetFlags, &flags, &paramSize); paramSize = sizeof(ctsTimeout); err = SrmControl(portId, srmCtlSetCtsTimeout, &ctsTimeout, &paramSize);
Listing 5.5 は、旧シリアルマネージャで同様な設定をする方法を示します。
Listing 5.5 設定の変更 (旧シリアルマネージャ)
SerSettingsType serSettings; serSettings.baudRate = 19200; serSettings.flags = serSettingsFlagBitsPerChar8 | serSettingsFlagParityOnM | serSettingsFlagParityEvenM | serSettingsFlagStopBits1 | serSettingsFlagRTSAutoM | serSettingsFlagCTSAutoM; serSettings.ctsTimeout = SysTicksPerSecond() / 2; err = SerSetSettings(refNum, &serSettings);
設定を再び変更するか接続を閉じるかするまでは、設定は有効なままです。シリアルマネージャを設定するには、以下の点に注意してください。
- もし CTS シグナルの欠如が接続の喪失を意味するなら、CTS タイムアウトをセットします。(タイムアウトなしの場合は -1 を指定します)
- もし srmSettingsFlagRTSAutoM がセットされていないなら、RTS 出力は永続的にアサートされます。(デフォルトでこのフラグはセットされています)
- 19200 以上のボーレートでは、完全ハンドシェイク( srmSettingsFlagRTSAutoM | SrmSettingsFlagCTSAutoM )を利用したほうが良いでしょう。
もし現在の設定がどうなっているかを見つけたなら、SrmControl 関数に srmCtlGet op コードのひとつを渡します。例えば、現在のボーレートを探すには、srmCtlGetBaudRate を渡します。旧シリアルマネージャで現在の設定を探すには、SerGetSettings 関数を使用します。
データを送信する
データを送信するには、旧シリアルマネージャで SerSend をコールします。データ送信は同期的に行われます。データを送信するには、アプリケーションは 適切に設定されたポートの接続を開き、送信するバッファを指定するだけです。送信するバッファが大きければ大きいほど、アプリケーションのコールから戻る前の送信関数は長くなります。送信関数は、UART の 先入れ先出し( FIFO )に置かれた実際のバイト数を返します。これにより、何が送信された(エラーのときは何が送信されなかったか)を決めることができるようになります。
Listing 5.6 は SrmSend の利用について解説しています。
Listing 5.6 データ送信
UInt32 toSend, numSent; Err err; Char msg[] = "logon\n"; toSend = StrLen(msg); numSent = SrmSend(portId, msg, toSend, &err); if (err == serErrTimeOut) { //cts timeout detected }
もし SrmSend がエラーを返したなら、または単にデータすべてが送信されたことを保証したいだけなら、以下の関数のうちいずれかを使用することができます。
- もし他のアクションを行う前にすべてのデータがデバイスを去るまで待つ必要があるなら、旧シリアルマネージャの SerSendWait を使用します。FIFO で最後のバイトをロードしたときに、SrmSend 関数は戻ります。SrmSendWait 関数は、FIFO が空であっても戻りません。もし CTS ハンドシェイクがオンで CTS タイムアウト値に達したなら、SrmSend のように SrmSendWait のコールはタイムアウトできます。SerSendWait のコールの旧シリアルマネージャバージョンは、タイムアウトのパラメータをとりますがこのパラメータは無視されることに注意してください。新シリアルマネージャのコールは単にポート ID をとります。
- どれだけの量のバイトが FIFO に残されているのかを決定するために、SrmSendCheck(または SerSendCheck )を使用します。すべてのシリアルデバイスがこのフィーチャーをサポートしているわけではないことに注意してください。
- もしハードウェアが実際の読み取りを提供しないなら、関数はおおよその値を戻します。8 は完全を意味し、4 はおおよそ半分を意味します。もし関数が 0 を戻したら、キューは空です。
- SerSendFlush 関数は、送信されずに FIFO に残っているバイトをフラッシュするのに使用できます。
データを受信する
ポートからのデータを実際に待機する受信側アプリケーションに依存するため、データの受信はより込み入ったプロセスです。
データを受信するために、アプリケーションは以下のようにしなければいけません。
- コードが受信キューからのデータを無期限に待つループを行わないことを保証します。
- これを行う最も一般的な方法は、EvtGetEvent にタイムアウト値を渡すことです。
- 仮想デバイスはしばしばアプリケーションと同じスレッドで実行されます。もしイベントループのタイムアウトを指定しないなら、仮想デバイスや他のシリアル関連コードが適切に受信データを扱うことを防ぐことができます。
- もしあなたのコードがイベントループの外にあるなら、システムが処理すべきイベントがあるかどうかを参照するために EvtEventAvail 関数を使用することができます。また、もしそのようにするなら、SysHandleEvent をコールします。
- データ受信の待機中にシステムがスリープ状態になることを防ぐために、アプリケーションは EvtSetAutoOffTimer をコールするべきです。例えば、シリアルリンクマネージャは新しいパケットを受信するたびに自動的に EvtResetAutoOffTimer をコールします。
- ヒント
- 多くのアプリケーションで、自動オフフィーチャは問題なく存在します。EvtResetAutoOffTimer の使用は慎重に行なって下さい。それを使用するアプリケーションは電池を多く消耗します。
- データを受信するために、SerReceive をコールします。バッファ、受信するバイト数、システムティックにおけるバイト間のタイムアウト、を渡します。このコールは、全ての要求されたデータが受信されるかエラーが起きるかするまでブロックします。この関数は実際に受信したバイト数を戻します。(エラーは関数に渡した最後のパラメータに戻されます。)
- もし受信する前に特定のデータが利用可能になるまで待つなら、SrmReceive をコールする前に SerReceiveWaitをコールします。待っているバイト数(現在の受信バッファサイズより小さい必要がある)と時間(システムティックで)を指定します。もし SrmReceiveWait が errNoneを戻すなら、受信キューは指定されたバイト数を含む、ということです。もし errNone 以外を戻すなら、そのバイト数は利用できないということです。
- 例えば データパケットを受信するときに、SrmReceiveWait は有用です。パケット全体が利用できるまで待ってからパケットを読み取るのに SrmReceiveWait を利用することができます。
- システムがアイドルなときにだけデータを受信したい、というのは一般的なことです。この場合、イベントループで nilEvent に応答するようにします。このイベントは EvtGetEvent がタイムアウトして他のイベントが利用できないときに生成されます。このイベントの応答で、SerReceiveCheck をコールします。SrmReceiveWait とは異なり、SrmReceiveCheck は入力待ちをブロックしません。かわりに、現時点で受信キューにあるバイト数を即座に戻します。もし受信キューにデータがあるなら、それを受信するために SrmReceive をコールします。もしキューにデータがなければ、イベントハンドラは何もせずシステムが他のタスクを実行できるようになります。
- 「エラーの処理」?で説明するように、受信関数のコール時は常にエラー条件を確認し処理します。
- 重要
- 常に行エラーを確認します。予期しない条件のために、成功の保証はありません。行エラーが起きたなら、そのエラーをクリアしない限り全てのシリアルマネージャコールは失敗します。
どのようにデータを受信するかの例として、「データ受信の例」?を参照して下さい。
新シリアルマネージャでは、SrmReceiveWindowClose を使って受信キューに直接アクセスすることができます。これらの関数はバッファのコピーを減らすため、高速なバッファアクセスが可能になります。これらの関数は新シリアルマネージャフィーチャセットが存在しないシステムではサポートされていません。
エラーの処理
もし行でエラーが起きたなら、全ての受信関数はエラー条件 serErrLineErr を戻します。明示的にエラー条件をクリアしてから続行しない限り、このエラーは戻され続けます。行エラーをクリアするには、SerClearErr をコールします。
もしエラーについて更に情報が欲しければ、行をクリアする前に SerGetStatus をコールします。
Listing 5.7 は、フレーミングまたはパリティエラーが戻されたかどうかを確認し、行エラーをクリアしています。
Listing 5.7 行エラーの処理 (新シリアルマネージャ)
void HandleSerReceiveErr(UInt16 portId, Err err) { UInt32 lineStatus; UInt16 lineErrs; if (err == serErrLineErr) { SrmGetStatus(portId, &lineStatus, &lineErrs); // test for framing or parity error. if (lineErrs & serLineErrorFraming | serLineErrorParity) { //framing or parity error occurred. Do something. } SrmClearErr(portId); } }
Listing 5.8 は、同じタスクを旧シリアルマネージャで行なっています。SerGetStatus コールは SrmGetStatus コールと多少異なることに注意して下さい。
Listing 5.8 行エラーの処理 (旧シリアルマネージャ)
void HandleSerReceiveErr(UInt16 refNum, Err err) { UInt16 lineErrs; Boolean ctsOn, dsrOn; if (err == serErrLineErr) { lineErrs = SerGetStatus(refNum, &ctsOn, &dsrOn); // test for framing or parity error. if (lineErrs & serLineErrorFraming | serLineErrorParity) { //framing or parity error occurred. Do something. } SerClearErr(refNum); } }
- ヒント
- 一般的な行エラーの原因とその対処法については、「一般的なエラー」を参照して下さい。
エラー発生時には受信データを全て破棄したい、という場合もあります。例えば、プロトコルがパケットドリブンでデータの破損を検知したなら、続行の前にバッファをフラッシュするべきです。そのためには、SerReceiveFlush をコールします。この関数は受信キュー内のバイトを全てフラッシュし、そして SrmClearErr をコールしてくれます。
SrmReceiveFlush はパラメータとしてタイムアウト値をとります。もしタイムアウトを指定したなら、キューに受信すべきデータをその時間だけ待ち、同様にそれをフラッシュします。もしタイムアウトにゼロを渡したなら、その時点でキューにあるデータがフラッシュされ、行エラーがクリアされ、戻ります。フラッシュのタイムアウトは、ノイズをフラッシュしきるのに充分な大きさでなければいけませんが、次のパケットの一部をフラッシュするほどの大きさでもいけません。
データ受信の例
Listing 5.9 は、シリアルマネージャを使って大きなデータブロックを受信する方法を示しています。
Listing 5.9 シリアルマネージャを使ったデータ受信
#include <PalmOS.h> // all the system toolbox headers #include <SerialMgr.h> #define k2KBytes 2048 /************************************************************ * * FUNCTION: RcvSerialData * * DESCRIPTION: An example of how to receive a large chunk of data * from the Serial Manager. This function is useful if the app * knows it must receive all this data before moving on. The * YourDrainEventQueue() function is a chance for the application * to call EvtGetEvent and handle other application events. * Receiving data whenever it's available during idle events * might be done differently than this sample. * * PARAMETERS: * thePort -> valid portID for an open serial port. * rcvDataP -> pointer to a buffer to put the received data. * bufSize <-> pointer to the size of rcvBuffer and returns * the number of bytes read. * ************************************************************/ Err RcvSerialData(UInt16 thePort, UInt8 *rcvDataP, UInt32 *bufSizeP) { UInt32 bytesLeft, maxRcvBlkSize, bytesRcvd, waitTime, totalRcvBytes = 0; UInt8 *newRcvBuffer; UInt16 dataLen = sizeof(UInt32); Err* error; // The default receive buffer is only 512 bytes; increase it if // necessary. The following lines are just an example of how to // do it, but its necessity depends on the ability of the code // to retrieve data in a timely manner. newRcvBuffer = MemPtrNew(k2KBytes); // Allocate new rcv buffer. if (newRcvBuffer) // Set new rcv buffer. error = SrmSetReceiveBuffer(thePort, newRcvBuffer, k2KBytes); if (error) goto Exit; else return memErrNotEnoughSpace; // Initialize the maximum bytes to receive at one time. maxRcvBlkSize = k2KBytes; // Remember how many bytes are left to receive. bytesLeft = *bufSizeP; // Only wait 1/5 of a second for bytes to arrive. waitTime = SysTicksPerSecond() / 5; // Now loop while getting blocks of data and filling the buffer. do { // Is the max size larger then the number of bytes left? if (bytesLeft < maxRcvBlkSize) // Yes, so change the rcv block amount. maxRcvBlkSize = bytesLeft; // Try to receive as much data as possible, // but wait only 1/5 second for it. bytesRcvd = SrmReceive(thePort, rcvDataP, maxRcvBlkSize, waitTime, &error); // Remember the total number of bytes received. totalRcvBytes += bytesRcvd; // Figure how many bytes are left to receive. bytesLeft -= bytesRcvd; rcvDataP += bytesRcvd; // Advance the rcvDataP. // If there was a timeout and no data came through... if ((error == serErrTimeOut) && (bytesRcvd == 0)) goto ReceiveError; // ...bail out and report the error. // If there's some other error, bail out. if ((error) && (error != serErrTimeOut)) goto ReceiveError; // Call a function to handle any pending events because // someone might press the cancel button. YourDrainEventQueue(); // Continue receiving data until all data has been received. } while (bytesLeft); ReceiveError: // Clearing the receive buffer can also be done right before // the port is to be closed. // Set back the default buffer when we're done. SrmSetReceiveBuffer(thePort, 0L, 0); Exit: MemPtrFree(newRcvBuffer); // Free the space. *bufSizeP = totalRcvBytes; return error; }
シリアルマネージャのヒントとコツ
以下のヒントとコツは、シリアルアプリケーションのデバッグと最初の段階でエラーを避けるのに役立ちます。
デバッグのヒント
以下はデバッグ時にエラーを追うのに役立つヒントです。
- 最初に Palm OS エミュレータを使ってデバッグします。実機でデバッグするのは最後です。
- Palm OS エミュレータは全てのシリアルマネージャ関数をサポートするので、シリアルマネージャを使ったアプリケーションをテストできます。外部デバイスに接続するのにデスクトップコンピュータのシリアルポートを使うことができます。エミュレータをセットアップしてシリアル通信のデバッグに使用する方法について、詳しくはエミュレータのドキュメントを参照して下さい。
- 通信エラーと送受信するデータの量を追います。
- デバッグビルドでは、転送されたデータ量の個々のカウントと、関連する各通信エラーを維持します。これは、依存するプロトコルのタイムアウトとリトライを含みます。
- 早期に認識できるフレームの開始( start-of-frame )署名を使用します。これはパケットベースのプロトコルをデバッグするのに役立ちます。
- デバッグ用の開発者向け裏口を実装します。
- ランタイムにおいて再コンパイル無しに一つ以上のデバッグフィーチャを起動する仕組みを実装します。例えば、一方のデバイスでデバッグしている間にもう一方でタイムアウトしてしまうことを防ぐために、受信タイムアウトを無効にする裏口を作っておきたいかもしれません。また、デバッグ情報を画面に表示する裏口もあるでしょう。例えば、裏口を起動するためにページアップキーを押している間、デジタイザ右上隅のペンダウンイベントをアプリケーションが探すかもしれません。
- デバイスにおけるデバッグ時エラーのロギングに、HotSync(R)を使用します。
- デバイスの HotSync ログにデバッグメッセージを書き込むために、DlkSetLogEntry を使うかもしれません。HotSync ログは 2KB までのテキストを受け入れます。HotSync アプリケーションに切り替え、ログを参照します。
- メモ
- デバッグのために HotSync ログに書き込むのは制限します。ユーザは HotSync ログにデバッグメッセージがあるのを歓迎しないでしょう。
- もしプロトコルアナライザを持っているなら、実際に送受信されるデータを試験するためにそれを使います。
一般的なエラー
どれだけ注意深くしても、エラーは起こります。以下はよく遭遇する問題とその解決策です。
- 何も受信されない
- 破損もしくは不正な接続を確認し、予期されたハンドシェークシグナルが受信されたことを確かめます。
- ゴミを受信する
- ボーレート、ワード長、パリティの一致、を確認します。
- ボーレートの不一致
- もし両側でボーレートが一致しなかった場合、フレーミングエラーを表示するか、送信された文字数と受信した文字数が一致しなくなります。
- パリティエラー
- パリティエラーは、データが破損していることを示します。また、送信側と受信側が同じパリティとワード長を設定されていない、ということを意味します。
- ワード長の不一致
- ワード長の不一致はフレーミングエラーを表示します。
- フレーミングエラー
- フレーミングエラーはビット数の不一致を示し、またストップビットが期待されるときに受信されなかったときに報告されます。これは破損したデータを示します。しかししばしば共通ボーレートやワード長やパリティ設定における不一致をシグナルします。
- ハードウェアのオーバーラン
- シリアルマネージャの受信はサービスルーチンを妨害し、入ってくるデータについていくことができません。完全なハードウェアハンドシェイクを可能にします。(「ポートの設定」?を参照)
- ソフトウェアオーバーラン
- アプリケーションは入ってくるデータを充分な速さで読み取りません。データをより頻繁に読み取ります。もしくはデフォルトの受信キューをより大きな値に置き換えます。(「ポートの設定」?を参照)
仮想デバイスドライバを書く
もし新シリアルマネージャフィーチャセットが存在するなら、また Palm OS のバージョンが 5.0 未満なら、シリアルマネージャは仮想デバイスドライバをシステムに追加する能力をサポートします。仮想シリアルデバイスドライバは、バイトのかわりにブロックで一度にデータを転送し受信します。
仮想ドライバは、独立してコンパイルされ Palm デバイスにインストールされるコードリソース( ID = 0)です。仮想ドライバ .prc ファイルは 'vdrv' 型のファイルであり、そのクリエータタイプは開発者が選択します(また、それは PalmSource, Inc. のクリエータ ID データベースに登録されます)。シリアルマネージャがインストールされたとき、それは 'vdrv' 型のコードリソースをデータベースマネージャから検索します。そして、この仮想デバイスのフィーチャと能力に関する情報を得るために、ドライバのエントリポイント関数をコールします。物理シリアルデバイスドライバとは異なり、仮想デバイスドライバはデータをバイト単位ではなくブロックで送受信します。その目的は、既に通常の RS-232 シリアル通常で使用されているかもしれないシリアルマネージャとは別の API を通して動くことをアプリケーションに強制することなしに、通信プロトコルのレベルをシリアルデバイスから抽象化しておく、ということです。
- 注
- 全て小文字のクリエータタイプは PalmSource, Inc. によって予約されています。クリエータタイプの予約と登録について、詳しくは Palm OS Programmer's Companion I 15ページの「Assigning a Database Type and Creator ID」を参照して下さい。
仮想ドライバ関数
各仮想ドライバがシリアルマネージャと一緒に動くために最低限サポートしなければならない関数が 6 つあります。これらの関数について、このセクションで簡単に説明します。各関数が何を行なうか、詳しくは「Palm OS Programmer's API Reference」の関数の説明を参照して下さい。
- 注
- 仮想シリアルポートは Palm OS Garnet ではサポートされません。
- DrvEntryPointProcPtr は仮想ドライバのコードリソース最初に定義されるべき関数であり、コードリソースの __Startup__ 関数としてマークされるべきものです。コードリソースがロードされたとき、シリアルマネージャはコードリソースの最初に飛び、DrvEntryPoint の実行を開始します。この関数はシステムの再起動時(つまりシリアルマネージャがインストールされたドライバとその能力のデータベースを構築しているとき)と仮想ポートが開かれたときにコールされます。
- VdrvOpenProcV4Ptr 関数は、通信を開始するために仮想デバイスを初期化する責任があります。
- VdrvCloseProcPtr 関数は、仮想デバイスを閉じる必要がある活動すべてをハンドルするべきです。
- VdrvControlProcPtr は、SrmControl 関数を仮想デバイスのレベルに拡張します。
- VdrvStatusProcPtr は、仮想デバイスの現在の状態をあらわすビットフィールドを戻します。
- VdrvWriteProcPtr は仮想デバイスのためにバイトのブロックを書き込みます。
- オプションの VdrvControlCustomProcPtr 関数は、この仮想ドライバ固有に定義されているあらゆるカスタムコントロールコードをハンドルします。
現在の実装では仮想読み取り関数は存在しないことに注意してください。仮想デバイスは、コールバックメカニズムを使用して利用できるデータを通知されたときに、DrvrRcvQType で提供される関数を使用して受信データを保存しなければいけません。
仮想シリアルドライバをどのように実装するかの例は、Palm OS Developer Knowledge Base から CryptoDrvr の例をダウンロードしてください。