|
||||||||||||||||||||
|
|
||||||||||||||||||||
| シリアルプログラミングは厄介者 | ||||||||||||||||||||
| OSやアーキティクチャを問わず、RS232C周辺のプログラミングはなかなか手強いものです。 とりわけWin32APIを使ったRS232通信アプリケーションの作成は厄介で、とりあえず動くものを作るのは簡単でも、CPU速度やOSバージョン、チップセットの違い、あるいはオンボードシリアルとUSB-シリアル変換器によるバリエーションが無限にあり、いつでも確実に動くコードを書くのは簡単なことではありません。 RS232Cに限った話ではないのですが、物理的な装置の制御を伴う処理は相手先が安定な装置とは限らず、問題をソフトウェア部分だけに閉じ込めることができないのです。デバッグツールも、オシロや回線モニタなどが必要になる場面が多々あります。 ここでは、数理設計が標準的に使用している社内製RS232用クラスライブラリがうまく動かなかった事例を紹介します。 |
||||||||||||||||||||
|
開発環境 |
||||||||||||||||||||
| ・PC Pentium4 2.4GHz Memory 1G デスクトップタイプ、オンボードシリアル×2 ・OS WindowsXP SP2 ・開発環境 Boland C++Builder5 ・開発リソース RS232C.CPP、RS232C.H |
||||||||||||||||||||
| GID-SSS/IF-232の通信仕様 ・19200bps、Stop1, Parity無し、8data ・0x0dターミネートを含む全18byteのキャラクタ文字列を10ms毎に連続送信 ・電源ONと同時に、PCからの指示無しでデータ送信を開始。電源OFFで停止 ・GID-SSS ファームウェアソースコード ZIP,PIC16F628用 |
||||||||||||||||||||
| 開発するソフトの仕様 | ||||||||||||||||||||
| ・アプリケーション起動時、COMポートをCreateFileして回線を開く ・ユーザからの指示により任意のタイミングで受信開始 ・受信したデータを行単位で演算、表示、記録(たとえば、計測震度の計算や加速度galのグラフ描画など) |
||||||||||||||||||||
|
|
||||||||||||||||||||
| 上記仕様では、受信開始までの時間が長いと受信バッファがオーバーフローします。 バッファサイズを4096byteに設定した場合、4096byte÷18byte/10ms = 約2.3秒でオーバーフローすることになります。 オーバーフロー発生後、下記の状況に陥ることがわかりました。 ・ReadFileを実行に失敗、バッファ内容を読み出せない。GetLastError = 995 ・PurgeCommに失敗、バッファ内容をクリアできない ・デバイスのエラーフラグをクリアするClearCommError関数を実行しても回復しない ターゲットソフト終了後ハイパーターミナルを起動すると受信は成功します。従ってハードウェアの問題ではありません。 |
||||||||||||||||||||
| 問題解決の糸口 | ||||||||||||||||||||
| . | バッファサイズを増やすなりバッファがいっぱいになる前に適当にデータを読み捨てるなりすれば問題は回避できますが、しかしこれでは根本的な解決とは言えません。 ReadFile関数実行後にGetLastError関数でエラーコードを確認しました。 ・エラーコード995 ERROR_OPERATION_ABORTED ・「スレッドの終了またはアプリケーションの要求によって、I/O 処理は中止されました。」 いまひとつ意味不明で的を得ません・・・。 WinAPI32関数を調べるとClearCommErrorという関数があることがわかりました。MSDNを検索すると下記の説明があります。
2回連続でClearCommError関数を呼べばエラーコードのクリアを確認できるはずですが、エラーはクリアされません。 ClearCommErrorでlpErrors を確認すると、常にCE_RXOVERビットが立っています。ときどき、CE_BREAKとCE_FREAMEも立っており、稀に、CE_OVERRUN、CE_RXPARITYも立っています。 回線の初期状態により、RXOVER、BREAK、FRAMEのフラグが立つのは納得できますが、OVERRUN(送信バッファの?)とパリティエラーの検出は納得できません。
|
|||||||||||||||||||
|
|
||||||||||||||||||||
| DCB構造体を変更 | ||||||||||||||||||||
| シリアル通信デバイスの制御設定を定義するDCB構造体に、fAbortOnErrorというパラメータがあります。 念のため、MDSNより日英両方を転載します。
これらを読む限り、fAbortOnErrorビットの意味は下記のように読み取れることができます。このビットがTrueのときに発生したエラーはClearCommError関数によりクリアすることができるはずですが、実際にはそうはなりませんでした。
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| 解決? | ||||||||||||||||||||
| 試しにfAbortOnErrorビットをFalseに設定してコードを走らせて見ると、バッファオーバーランから回復することができました。 ソースコード中、ClearCommErrorを必ず2回連続で記述してあるのは、1回目でエラーコードを読み、2回目でエラーコードがクリアされたことを確認するためです。
上記ソースは受信開始処理部までですので、このあとに通常の受信処理を記述します。 バッファサイズを4096byteにしたときに約2.3秒まではオーバーフローしないので、余裕をみてバッファサイズをこの4倍、16384byteとすると、なんらかの理由で9.2秒もの長時間受信処理を実行できなかったとしてもデータを取りこぼす心配はありません。 100msに設定したシステムタイマのイベントで受信処理を行いながら、同時にIlustlater,AutoCad,Wordなどの重いソフトを起動しても、バッファオーバーランが発生することはありませんでした。 逆に、システムタイマを50msに設定してポーリングさせていると、まれに1バイトも受信できていないことがありました。 データは確実に一定間隔で送信できているので、なんらかの理由でタイマが連続して起動しているものと思われます。 PurgeComm関数を呼んでもバッファがクリアされない問題が解決していませんが、ReadFileで読める限りのデータを読み出すことで回避しています。 fAbortOnErrorフラグをTrueにしたときのClearCommError関数の処理結果がおかしい問題も解決していません。 シリアルのデバイスドライバのバグを疑い、試しに秋月のUSBシリアル変換ケーブル(通販コードM-720)を使用して実験してみたところ、オンボードシリアルと同様の結果が得られました。 PC界の大先輩に聞くところによると、「常時送信しっぱなしの相手なんて想定していないんじゃないの?」とのことです。 なんでも、マイクロソフトはIBMのコンソールとして使用可能かどうかをシリアルポート動作確認の基準にしているらしく、そこから外れたことはあまり考えていないそうです。 真偽の程は判りませんが、ハイパーターミナルが600bpsをサポートしないのは大昔のPCのハード設計の都合(クロック設計がうまくいかなかった)、110bpsをサポートするのは骨董品メインフレーム(VAX11とか?)との通信をサポートするためなんだとか。 巷はUSBや高速LANが花盛りですが、RS232Cはまだまだ健在です。 |
||||||||||||||||||||
| 参考リンク | ||||||||||||||||||||
| MSDNライブラリ 日本語、マイクロソフト。Win32APIの関数や変数を参照することができる MSDN Library 上記の英語版。日本語訳が怪しいとき以外でも参照するのがお勧め MSDN コミュニケーション関連関数 VC++ML VC++プログラミングのメーリングリスト Belution.com プログラマのための掲示板 How to collect data from monitors 大阪大学医学部? リアルタイムデータ処理の詳細解説。マルチメディアタイマの利用など。 Win32エラーコード一覧 シーラ家。GetLastErrorの返り値一覧。winerror.hより抽出し作成 |
||||||||||||||||||||