- ベストアンサー
シリアル通信の出力バッファと送信完了イベントについて
- シリアル通信における出力バッファと送信完了イベントについて説明します。
- シリアル通信アプリケーションでOVERLAPPED構造体のイベントが発生し、通信速度変更時にデータが化ける問題が発生。
- PCのUART出力バッファのサイズやWindows側のバッファとの関係について調査し、解決方法を求めています。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
2400bpsで9文字の文字列を送出してからビットレートを57600bpsに変更する、という処理を5回繰り返すテストプログラムです。 ちょっと乱雑なソースですがご勘弁を。(インデントは全角スペースです。ご注意ください。) int main(int argc, char** argv) { for (int i = 0; i < 5; ++i) { HANDLE hFile; DWORD dw; hFile = CreateFile( "com1", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); DCB dcb = { sizeof(DCB) }; GetCommState(hFile, &dcb); dcb.BaudRate = CBR_2400; SetCommState(hFile, &dcb); char buf[100]; sprintf(buf, "%d ABCDE\r\n", i); WriteFile(hFile, buf, lstrlen(buf), &dw, NULL); // "# ABCDE\r\n" totals 9 bytes, or 90 bits. // At 2400bps, 90 bits need 37.5ms to be transmitted. #if 0 CloseHandle(hFile); hFile = CreateFile( "com1", GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); #endif Sleep(15); // 37 makes mess, 38 ok. dcb.BaudRate = CBR_57600; SetCommState(hFile, &dcb); Sleep(100); CloseHandle(hFile); } return 0; } 同期処理にしていますが、同じ現象が発生しています。 9文字(90ビット)を2400bpsで送出すると37.5msかかる計算になるのですが、このプログラム中にあるSleep(15)の15を37にすると文字化けが発生し、38にすると文字化けが発生しないので、おおよそ計算どおりと言えるかと思います。 ここで#if 0の行を#if 1に変更すると、Sleep(15)がSleep(0)でも文字化けは発生しなくなります。つまり、デバイスのハンドルをcloseするときにはバッファがflushされるということです。 close時は必ずflushする、という記述に行き当たったわけではないので、closeさえすれば万事OKという保証はできませんが、ドライバの動作としては妥当だと思います。 #実は、「試したいこと」とはこれではなくてDevice Power Stateの変更だったのですが、そのテストをする前になんだかいい感じの結果になってしまったので(^^;
その他の回答 (4)
- xcrOSgS2wY
- ベストアンサー率50% (1006/1985)
もう1つ試したいことがあるので、締め切りはもう少々お待ちを・・・
- xcrOSgS2wY
- ベストアンサー率50% (1006/1985)
実験してみました。FIFOをOFFにしても「1バイト分待つ」だけじゃダメですね・・・
- xcrOSgS2wY
- ベストアンサー率50% (1006/1985)
DDKにある16550用シリアルドライバのソースを読んでみましたが、ステータスレジスタでFIFOとシフトレジスタのデータを全部送信し終えたかどうかをチェックしている場所自体がありませんでした。また、ステータスレジスタの内容を直接返すIOCTLもないようです。 ですので、シグナル後に一定時間(FIFOがオフであれば、1バイトの送信時間分)だけ待つ、という泥臭い方法を取るしかないのかなと思います。
お礼
ありがとうございます。 ううむ、やはりその手になってきますか。 私の手元にもDDKがありますし、いっそのこと自分でドライバ作ろうかと考えたりもしたのですが、プロトコルとドライバを同時に配布して強制的にドライバを書き換えるのはいかにも"ヤクザ"ですし……と、悩んでいたんですよ。 あ、16650UARTドライバを読んで頂いてありがとうございます。 どうやらうちのPCは16750っぽいです(dell inspiron8500)。60バイト前後が閾値になっているのでそう予測しているだけですが。 >実験してみました。FIFOをOFFにしても「1バイト分待つ」だけじゃダメですね・・・ そうなんです。FIFO切っても、ドライバのバッファに書き込んだ時点でwindowsは監視を止めてしまうんです。そして、UARTの型番の違い等は吸収してくれないし…。 非同期は諦めて同期処理で実装したら上手く行くだろうかと現在検討中です。(どうも結果は同じ気がしてならないのでげんなりしていますが) ありがとうございました。貴重な情報をいただけて助かりました。
- terra5
- ベストアンサー率34% (574/1662)
SetCommMask(),WaitCommEvent()を使う。 EV_TXEMPTY;/* 送信バッファから最後のデータを送信 */ イベントがあるので、これで処理すれば大丈夫じゃないかと。
補足
回答ありがとうございます。 Windows(KERNEL)から上がってくるイベント(シグナル?)は「Windows(KERNEL)がUART(UART Driver)の出力バッファにデータを出力し終わった」タイミングで、「送信が完了した」タイミングではないんですよ。 簡単に言ってしまえば、WindowsのWaitCommEvent()から制御が戻るタイミングは信頼性が無いということでして。 WaitCommEventで上手くいけば良かったんですが・・・。 ありがとうございます。
お礼
早速いろいろ試して見ました。こちらでも上記アルゴリズムで問題なく動作しました。どうやらハンドルをCloseするときにKILLメッセージがドライバに送られているみたいですねぇ。 しかし、「動いている」とはいえ「速度を変更するたびにポートの開け閉め」はプロトコルとしてなんだか恥ずかしい(笑)。 因みに、フラッシュという事でFlushFileBuffers(hCom)を試したりしたんですが、予想通り意味を成してません。 せめてドライバがLSRレジスタステータスを見せてくれれば……。 いやぁ、本当にありがとうございます。 とりあえずは動く方法も解りましたし、COMに対する造詣も深まりました。 とりあえずこれで締めさせていただきます。 では。