プロが教えるわが家の防犯対策術!

こんにちわ。よろしくお願いします。

どの言語として扱うか迷ったのですが、恐らくはC/C++が一番だろうと思ってここで質問させていただきます。

質問の内容ですが、現在、シリアル通信のアプリケーションを作っていまして、CreateFileの引数にFILE_FLAG_OVERLAPPEDを与えて非同期で読み書きを行うようにしています。
非同期ですので、OVERLAPPED構造体をstaticで宣言し、
WriteFile時に引数で与えています。
---
m_hComm = CreateFile( (const char*)pCom, GENERIC_READ|GENERIC_WRITE,0, NULL,OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ) ;
WriteFile(m_hComm, pwData,dwLen, &dwWrite, &osWriter);
---

問題はここからで、OVERLAPPED構造体のイベントが、シグナル状態になり、GetOverlappedResult(m_hComm, &osWriter, &dwWrite, TRUE)を抜けるのはいいのですが、実際には送信は完了しておらず、GetCommStateで通信速度を変更すると送信途中のデータが化けて通信エラーになってしまいます。

どうやら、今使用しているPCのUARTの出力バッファが大きいのか、Windows側のバッファからUARTバッファに書き込みが完了した時点でWindowsはOVERLAPPED構造体のイベントをシグナルにしているようです。
(もちろんFIFOは切ってあります)

他のPCで確認したところ、問題ありませんでした。
もしどなたか、シリアルの出力バッファを監視するいい方法があったら教えていただけないでしょうか。

A 回答 (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の変更だったのですが、そのテストをする前になんだかいい感じの結果になってしまったので(^^;
    • good
    • 0
この回答へのお礼

早速いろいろ試して見ました。こちらでも上記アルゴリズムで問題なく動作しました。どうやらハンドルをCloseするときにKILLメッセージがドライバに送られているみたいですねぇ。

しかし、「動いている」とはいえ「速度を変更するたびにポートの開け閉め」はプロトコルとしてなんだか恥ずかしい(笑)。

因みに、フラッシュという事でFlushFileBuffers(hCom)を試したりしたんですが、予想通り意味を成してません。
せめてドライバがLSRレジスタステータスを見せてくれれば……。

いやぁ、本当にありがとうございます。
とりあえずは動く方法も解りましたし、COMに対する造詣も深まりました。
とりあえずこれで締めさせていただきます。
では。

お礼日時:2005/08/11 16:25

もう1つ試したいことがあるので、締め切りはもう少々お待ちを・・・

    • good
    • 0

実験してみました。

FIFOをOFFにしても「1バイト分待つ」だけじゃダメですね・・・
    • good
    • 0

DDKにある16550用シリアルドライバのソースを読んでみましたが、ステータスレジスタでFIFOとシフトレジスタのデータを全部送信し終えたかどうかをチェックしている場所自体がありませんでした。

また、ステータスレジスタの内容を直接返すIOCTLもないようです。

ですので、シグナル後に一定時間(FIFOがオフであれば、1バイトの送信時間分)だけ待つ、という泥臭い方法を取るしかないのかなと思います。
    • good
    • 0
この回答へのお礼

ありがとうございます。

ううむ、やはりその手になってきますか。
私の手元にもDDKがありますし、いっそのこと自分でドライバ作ろうかと考えたりもしたのですが、プロトコルとドライバを同時に配布して強制的にドライバを書き換えるのはいかにも"ヤクザ"ですし……と、悩んでいたんですよ。

あ、16650UARTドライバを読んで頂いてありがとうございます。
どうやらうちのPCは16750っぽいです(dell inspiron8500)。60バイト前後が閾値になっているのでそう予測しているだけですが。

>実験してみました。FIFOをOFFにしても「1バイト分待つ」だけじゃダメですね・・・

そうなんです。FIFO切っても、ドライバのバッファに書き込んだ時点でwindowsは監視を止めてしまうんです。そして、UARTの型番の違い等は吸収してくれないし…。
非同期は諦めて同期処理で実装したら上手く行くだろうかと現在検討中です。(どうも結果は同じ気がしてならないのでげんなりしていますが)

ありがとうございました。貴重な情報をいただけて助かりました。

お礼日時:2005/08/11 00:24

SetCommMask(),WaitCommEvent()を使う。



EV_TXEMPTY;/* 送信バッファから最後のデータを送信*/
イベントがあるので、これで処理すれば大丈夫じゃないかと。

この回答への補足

回答ありがとうございます。

Windows(KERNEL)から上がってくるイベント(シグナル?)は「Windows(KERNEL)がUART(UART Driver)の出力バッファにデータを出力し終わった」タイミングで、「送信が完了した」タイミングではないんですよ。

簡単に言ってしまえば、WindowsのWaitCommEvent()から制御が戻るタイミングは信頼性が無いということでして。

WaitCommEventで上手くいけば良かったんですが・・・。
ありがとうございます。

補足日時:2005/08/10 13:12
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています