シリアル通信の出力バッファと送信完了イベントについて

このQ&Aのポイント
  • シリアル通信における出力バッファと送信完了イベントについて説明します。
  • シリアル通信アプリケーションでOVERLAPPED構造体のイベントが発生し、通信速度変更時にデータが化ける問題が発生。
  • PCのUART出力バッファのサイズやWindows側のバッファとの関係について調査し、解決方法を求めています。
回答を見る
  • ベストアンサー

シリアル通信の出力バッファと送信完了イベントについて

こんにちわ。よろしくお願いします。 どの言語として扱うか迷ったのですが、恐らくは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で確認したところ、問題ありませんでした。 もしどなたか、シリアルの出力バッファを監視するいい方法があったら教えていただけないでしょうか。

質問者が選んだベストアンサー

  • ベストアンサー
  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.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の変更だったのですが、そのテストをする前になんだかいい感じの結果になってしまったので(^^;

tamanegi_majin
質問者

お礼

早速いろいろ試して見ました。こちらでも上記アルゴリズムで問題なく動作しました。どうやらハンドルをCloseするときにKILLメッセージがドライバに送られているみたいですねぇ。 しかし、「動いている」とはいえ「速度を変更するたびにポートの開け閉め」はプロトコルとしてなんだか恥ずかしい(笑)。 因みに、フラッシュという事でFlushFileBuffers(hCom)を試したりしたんですが、予想通り意味を成してません。 せめてドライバがLSRレジスタステータスを見せてくれれば……。 いやぁ、本当にありがとうございます。 とりあえずは動く方法も解りましたし、COMに対する造詣も深まりました。 とりあえずこれで締めさせていただきます。 では。

その他の回答 (4)

  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.4

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

  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.3

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

  • xcrOSgS2wY
  • ベストアンサー率50% (1006/1985)
回答No.2

DDKにある16550用シリアルドライバのソースを読んでみましたが、ステータスレジスタでFIFOとシフトレジスタのデータを全部送信し終えたかどうかをチェックしている場所自体がありませんでした。また、ステータスレジスタの内容を直接返すIOCTLもないようです。 ですので、シグナル後に一定時間(FIFOがオフであれば、1バイトの送信時間分)だけ待つ、という泥臭い方法を取るしかないのかなと思います。

tamanegi_majin
質問者

お礼

ありがとうございます。 ううむ、やはりその手になってきますか。 私の手元にもDDKがありますし、いっそのこと自分でドライバ作ろうかと考えたりもしたのですが、プロトコルとドライバを同時に配布して強制的にドライバを書き換えるのはいかにも"ヤクザ"ですし……と、悩んでいたんですよ。 あ、16650UARTドライバを読んで頂いてありがとうございます。 どうやらうちのPCは16750っぽいです(dell inspiron8500)。60バイト前後が閾値になっているのでそう予測しているだけですが。 >実験してみました。FIFOをOFFにしても「1バイト分待つ」だけじゃダメですね・・・ そうなんです。FIFO切っても、ドライバのバッファに書き込んだ時点でwindowsは監視を止めてしまうんです。そして、UARTの型番の違い等は吸収してくれないし…。 非同期は諦めて同期処理で実装したら上手く行くだろうかと現在検討中です。(どうも結果は同じ気がしてならないのでげんなりしていますが) ありがとうございました。貴重な情報をいただけて助かりました。

  • terra5
  • ベストアンサー率34% (574/1662)
回答No.1

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

tamanegi_majin
質問者

補足

回答ありがとうございます。 Windows(KERNEL)から上がってくるイベント(シグナル?)は「Windows(KERNEL)がUART(UART Driver)の出力バッファにデータを出力し終わった」タイミングで、「送信が完了した」タイミングではないんですよ。 簡単に言ってしまえば、WindowsのWaitCommEvent()から制御が戻るタイミングは信頼性が無いということでして。 WaitCommEventで上手くいけば良かったんですが・・・。 ありがとうございます。

関連するQ&A

  • VC++でシリアル通信

    質問 VC++をはじめて2ヶ月程度のものです。 現在、計測装置からのデータをシリアル通信でパソコンに取り込もうとしています。 ホームページなどを参考にコードを書きましたが、 以下のようではだめでしょうか? 環境はWinXP、VC++6.0でダイアログベース。 実行結果はボタンを押すとエディットボックスに以下のように表示がでてきます。 フフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフフ・ よろしくお願いいたします。 ボタンに割り当てたコード void CTest_commDlg::OnButton1() {     HANDLE hComm; //通信ハンドル DCB myDCB; //----------ポートオープン hComm = CreateFile("COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); //----------DCB構造体を使って通信設定をする BuildCommDCB("9600,n,8,1",&myDCB); //----------DCB構造体セット SetCommState(hComm, &myDCB); //----------タイムアウト設定 COMMTIMEOUTS CommTimeout; GetCommTimeouts(hComm,&CommTimeout); CommTimeout.ReadIntervalTimeout = 500; CommTimeout.ReadTotalTimeoutMultiplier = 0; CommTimeout.ReadTotalTimeoutConstant = 500; CommTimeout.WriteTotalTimeoutMultiplier = 0; CommTimeout.WriteTotalTimeoutConstant = 500; SetCommTimeouts(hComm,&CommTimeout); //----------受信 char RecBuff[64]; DWORD ReadLen; ReadFile(hComm,RecBuff,64,&ReadLen,NULL); //----------エディットボックスに表示 m_ed1.SetWindowText(RecBuff); }

  • シリアル通信の受信待ちについて

    シリアル通信で10Byte受信するまで待ち続けたいのですが、0ByteでReadFileが抜けてしまいます。 ReadFileの最後のパラメタが、NULLならば第3パラメタの値まで待ち続けると思っていたのですが、なにか設定が足りないのでしょうか? タイムアウト値は0にしています。(色々変えて見ましたが同じでした) HANDLE hComm; /* シリアルポートのハンドル */ DCB dcb; char* pszBuf="1234567890"; /* 書込・読込領域 */ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); GetCommState(hComm, &dcb); /* DCB を取得 */ dcb.BaudRate = 9600; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; dcb.ReadIntervalTimeout = 100; dcb.ReadTotalTimeoutMultiplier = 0; dcb.ReadTotalTimeoutConstant = 0; dcb.WriteTotalTimeoutMultiplier = 10; dcb.WriteTotalTimeoutConstant = 500; SetCommState(hComm, &dcb); /* DCB を設定 */ DWORD dwRead; /* ポートから読み出したバイト数 */ ReadFile(hComm, pszBuf, 10, &dwRead, NULL);

  • Win32でシリアル通信

    現在.NET2003のWin32を使ってシリアル通信を行うプログラムを作成中なんですが、CreateFile()、WriteFile()といった関数を使っているのですがどうもうまくいきません。 どなたかWin32について詳しい方、ご教授頂けないでしょうか?また、こういった事が書いているHP等をご存知の方教えて頂けないでしょうか? ちなみに、ポートを開く部分は下記のようになっているのですが、何か間違いがあれば教えて下さい。 お願い致します。 /* シリアルポートを開く */ BOOL SerialOpenPort(int nPortNumber) { char szSerial[256]; if(g_ahSerial[nPortNumber - 1] != NULL) { return FALSE; } sprintf(szSerial, "COM%d", nPortNumber); g_ahSerial[nPortNumber - 1] = CreateFile( szSerial, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );   if(g_ahSerial[nPortNumber - 1] == INVALID_HANDLE_VALUE) { g_ahSerial[nPortNumber - 1] = NULL; return FALSE; } if(!InitDCB(nPortNumber)) { return FALSE; }  if(!InitCommTimeOuts(nPortNumber)) { return FALSE; }  return TRUE; }

  • シリアル通信のポートオープン

    シリアル通信のテストプログラムを作成中でして、一通り動作するようになったのでエラー処理を確認しようとしたところ、存在しないCOMポート?なのにオープンが成功したと処理してしまいます。 環境  P社のノートパソコン  Windows XP Pro SP3  Visual C++6.0 SP6  USB接続のRS-232C変換アダプタを使用  デバイスマネージャでは、拡張ポートはCOM4のみとなっている  ハイパーターミナルの「接続の設定」では、接続方法の選択肢にCOM3とCOM4の2つがある ・COM4を使うと、オープン後、正常に通信できます。 ・COM3を使うと、オープンは出来るが、通信は出来ません。 ソースを記載します。 if(HANDLE_CHECK(m_hComm) == FALSE){ ss.Format("COM%d", m_nRsPort); m_hComm = ::CreateFile(ss, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if(HANDLE_CHECK(m_hComm) == TRUE){ dcb.DCBlength = sizeof(DCB); if(::GetCommState(m_hComm, &dcb) != FALSE){ dcb.BaudRate = m_nRsBaudrate; dcb.Parity = m_nRsParity; ・ ・ ・ ・ ・ ・ if(::SetCommState(m_hComm, &dcb) != FALSE){ COM3の場合もCOM4の場合もここを通っている。 } else{ } } } } COM3のオープン時、エラーの検知は出来ないのでしょうか? よろしくお願いします。

  • 大きなファイルの読み書き

    WindowsXP + SDKにてC++の開発をしています。 ディスクから50MB程度のファイルを読んで処理を施しまた書き戻すという事をやっていますが HANDLE handle; char fileName[256]; strcpy(fileName , "ファイル名"); handle = CreateFile(fileName , GENERIC_READ , NULL , NULL , OPEN_EXISTING , FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL ,NULL); としてファイルのオープンには成功するのですが OVERLAPPED ol; unsigned long size; unsigned long readSize; unsigned char *mem; ReadFile(handle , mem , readSize , &size , &ol) としていざ読もうとすると "ERROR_NO_SYSTEM_RESOURCES"が戻ってきて処理がとまってしまいます。 今までまったく同じコードで1~12MB程度のファイルは問題なく読み書き出来ていたのですが50Mのファイルを扱おうとしたところ問題が発生してしまいました。 FILE_FLAG_NO_BUFFERING と FILE_FLAG_OVERLAPPEDをはずすと読めるのですが余りにもアクセスが遅いので使い物になりません。 何か回避方法がありましたら御教授願います。

  • 非同期のプロセス間通信(パイプ)で全データ受信する

    こんにちは。 非同期のプロセス間通信(パイプ)で詰まっています。 環境はWindowsで処理系はC++(Win32)です。 スレッドを用意して、 hPipe = ::CreateFile( L"\\\\.\\pipe\\pipename", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ) ; で作成し、OVERLAPPED構造体を // ZeroMemory( &so, sizeof( OVERLAPPED )) ; sOverlapped.hEvent = ::CreateEvent( NULL, FALSE, FALSE, NULL ) ; として、 const size_t bufsize = 16 ; BYTE     buffer[ uBufSize ] ; DWORD    dwNumberOfBytesRead ; bRet = ::ReadFile( hPipe, buffer, bufsize, &dwNumberOfBytesRead, &so ) ; で読み込みをしています。ReadFile関数はすぐ戻りbRetはFALSEで返ってくるため、GetLastErrorを調べて、 switch( ::GetLastError()) {   case ERROR_IO_PENDING :     bRet = ::GetOverlappedResult( hPipe, &so, &dwNumberOfBytesRead, FALSE ) ;     break ; } としてWaitForMultipleObjectsでsOverlapped.hEventがシグナル状態になったら取得したデータの処理をしています。 シグナル状態になるならないに関わらず、上記のReadFileとGetOverlappedResultはループでぐるぐる回しています。 上記の状態で短いデータならよかったのですが、上記のReadFileで読み込み最大サイズの16バイトを超えてしまとうと、 残りの部分のみしか取得できませんでした。 0123456789ABCDEFabcdefg というデータを受信しようとしたとき、後半のabcdefgだけしか取得できませんでした。 すべてのデータを正しく取得するにはどのようにしたらよいのでしょうか?

  • CreateFileしてもうまくいきません・・。

    シリアルポートで通信するためのプログラミングを始めたばかりなのですが、COMポートのオープンが成功しません。 以下を実行しているのですが、何が間違いでしょうか? ちなみに、PCMCIAに挿入したPCカードから通信したいと考えています。単純に"COM1"というのであっているのかも分かりません・・。 よろしくお願いします。 void CMyDlg::OnReset() { HANDLE hCom; hCom = CreateFile( "COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED NULL ); if(hCom == INVALID_HANDLE_VALUE){ MessageBox("ポートオープン失敗"); } else{ MessageBox("ポートオープン成功"); } }

  • PICとPCでのシリアル通信

    PICとPC間でのシリアル通信を行ってるんですけどうまくいきません。ハイパーターミナル使えばうまくいくんでPIC側のソース(C)はうまくいってると思います。ハイパーターミナルを使わずにシリアルの送受信のプログラム(C++)を組んでるんですけどうまくいかなくて。。。 アドバイスなどお願いします!!どこが違うんでしょうか。。。 ●PC側のソース(C++) #include "stdafx.h" #include <stdlib.h> #include <windows.h> #include<iostream> using namespace std; #define COM_PORT_NAME "COM1" #define BAUD_RATE 9600 #define BYTE_SIZE 8 #define PARITY EVENPARITY #define STOP_BIT TRUE #define F_PARITY ONESTOPBIT HANDLE hComm; // シリアルポートとの通信ハンドル bool ComInit() { // シリアルポートを開ける hComm = CreateFile( COM_PORT_NAME, /* シリアルポートの文字列 */ GENERIC_READ | GENERIC_WRITE, /* アクセスモード:読み書き */ 0, /* 共有モード:他からはアクセス不可 */ NULL, /* セキュリティ属性:ハンドル継承せず */ OPEN_EXISTING, /* 作成フラグ: */ FILE_ATTRIBUTE_NORMAL, /* 属性: */ NULL /* テンプレートのハンドル: */ ); if (hComm == INVALID_HANDLE_VALUE) { printf("シリアルポートを開くことが出来ませんでした。\n"); return false; } // 通信属性を設定する DCB dcb; GetCommState(hComm, &dcb); /* DCB を取得 */ dcb.BaudRate = BAUD_RATE; dcb.ByteSize = BYTE_SIZE; dcb.Parity = PARITY; dcb.fParity = STOP_BIT; dcb.StopBits = F_PARITY; SetCommState(hComm, &dcb); /* DCB を設定 */ return true; } void ComEnd() { // ハンドルを閉じる CloseHandle(hComm); } bool WriteData(char *buff, unsigned int data_size) { DWORD dwWritten; /* ポートへ書き込んだバイト数 */ WriteFile(hComm, buff, data_size, &dwWritten, NULL); if (dwWritten!=data_size) { printf("データの送信に失敗しました。\n"); return false; } return true; } bool ReadData(char *buff, unsigned int max_size) { DWORD dwErrors; /* エラー情報 */ COMSTAT ComStat; /* デバイスの状態 */ DWORD dwCount; /* 受信データのバイト数 */ DWORD dwRead; /* ポートから読み出したバイト数 */ ClearCommError(hComm, &dwErrors, &ComStat); dwCount = ComStat.cbInQue; if (dwCount > max_size) { printf("バッファサイズが足りません。\n"); return false; } ReadFile(hComm, buff, dwCount, &dwRead, NULL); if (dwCount != dwRead) { printf("データの受け取りに失敗しました。\n"); return false; } return true; } int main(int argc, char* argv[]) { char ch; while(1){ cin >> ch; printf("入力 %c\n", ch); ComInit(); WriteData(&ch, strlen(&ch)); ReadData(&ch, strlen(&ch)); ComEnd(); } return 0; }

  • Visual C++で

    VisualC++を使って、ファイルにint型の二次元配列を書き込みたいのですが、WriteFile関数を使えばよいのかな?というところまではわかったのですが、二番目の引数を"int"型から"LPCVOID"型に変形できませんと言われてしまいます。 雰囲気的には下のような感じになるのかなぁー、と思いましたが、わかりません。教えてください。お願いします。 fp = CreateFile("test.dat", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); for(i = 0; i < 100; i++){ for(j = 0; j < 100; j++){ WriteFile(fp, array[i][j], sizeof(array), &dwRote, NULL); } }

  • VB2005でWin32APIを用いてRS-232CのRTS信号を、デ

    VB2005でWin32APIを用いてRS-232CのRTS信号を、データ送信中だけHIGHにさせるプログラムを作成中です。 色々調べて、Win32APIを使うことで、それらしい動作をさせるプログラムを作れることが分かり、色々調べてコーディングしたのですが、上手く動きません…。処理の流れは以下のようになっています。 (1) CreateFile()関数でCOMを開く。   (CreateFile()の「フラグ」には"FILE_FLAG_OVERLAPPED"を指定) (2) CreateEvent()関数にてイベントオブジェクトを作成。 (3) EscapeCommFunction()でRTS信号をHIGHに設定。 (4) WriteFile()関数でRS-232Cへデータを送信する。 ※ここまでは、オシロスコープにて正しく動作していることを確認しています。 (5) データ送信完了を待つために、SetCommEvent()関数で"EV_TXEMPTY"イベントマスクをセット。 (6) WaitCommEvent()で送信完了を待つ。   ⇒WaitCommEvent()関数が、データ送信完了を待たずに抜けてきてしまう(Falseがリターンされる)…。 (7) そこで、WaitForSingleObject()を使ってイベントハンドラが"シグナル状態"になるのを待つ。   ⇒タイムアウトに"INFINITE"を指定すると、無限待ち状態に陥ってしまう。   ⇒また、タイムアウトに"1000"を指定すると、約1秒後に"WAIT_TIMEOUT"が返ってきてしまう。 WriteFile()関数、そしてWaitCommEvent()関数の引数には、OVERLAPPED構造体へのポインタを渡しているのですが、思うように動いてくれません。 ※Win32APIの関数宣言の記述方法(データ型など)が誤って、このようになっていたりするのでしょうか… どなたかヒントでもお教えいただけると、大変助かります。 以上、よろしくお願いいたします。

専門家に質問してみよう