非同期シリアル通信について


Kanagawa  2007-09-06 20:06:21  No: 66234

非同期シリアル通信プログラムについて質問です。シリアルにて1秒間隔で送られてくる可変長のメッセージ(ASCIIデータ)を、受信して表示するプログラムを作っています。

通常は正しく受信して表示出来るのですが、1時間ほど連続試験した際に、たまに受信データが壊れている事があります。メモリを破壊しているのか、正しく非同期で受信出来ていないのかよくわかりません。どなたかわかる方がいましたら、教えてください。

開発環境:VC++.NET2003
OS:WindowsXP
※Core2のPCを使用した場合、現象が起こりやすくなります。

受信データが壊れると言うのも、

[送信側]
AAAA
BBBB
CCCC
DDDD

[受信時]
AAAA
BBBB
AAAA  ※受信した過去のデータが再び受信される
CBCC  ※正しいデータの中に、過去のデータの一部が入る

一度受信データが壊れると、後はずっと壊れたデータを受信します。
(ReadFileExにて読み取ったバッファ自体に壊れたデータが入っていました。)


Kanagawa  2007-09-06 20:08:19  No: 66235

参考として、ソースを載せておきます

//*** 受信監視処理
//***  COMポートからのメッセージの受信を監視する
VOID CSerial::WatchComProc()
{
  COMSTAT    ltComState ;
  DWORD  ldwWaitTime = 1 ;
  DWORD  ldwEvtMask = 0 ;
  BOOL  lblResult = FALSE ;

  if( SetCommMask( m_tComCtrlInf.hComDev, m_tComCtrlInf.dwEventMode | EV_CTS | EV_DSR | EV_RLSD ) == FALSE )
  {
    TRACE0( "[WatchComProc]SetCommMask Failure!!\n" ) ;
    return ;
  }

  while( m_tComCtrlInf.blConnect == TRUE )
  {
    ldwEvtMask = 0;

    if( WaitForSingleObject( m_pExitEventRxWatch -> m_hObject, ldwWaitTime ) == WAIT_OBJECT_0 )
    {
      break ;
    }

    // ETXを受信したらイベントを発行する
    WaitCommEvent( m_tComCtrlInf.hComDev, &ldwEvtMask, &m_tComCtrlInf.tOverLappedRead ) ;
    ClearCommError( m_tComCtrlInf.hComDev, &m_tComCtrlInf.dwError, &ltComState );

    if( ltComState.cbInQue )
    {
      if( ( ldwEvtMask & m_tComCtrlInf.dwEventMode ) == 0 )
      {
        // イベントが発生していない場合
        continue ;
      }

      // バッファに溜まっているだけ取得する
      do
      {
        lblResult = ReadData() ;
      }while( lblResult == TRUE ) ;
    }
  }
}

//*** COMポートより受信メッセージの取得
BOOL CSerial::ReadData()
{
  COMSTAT    ltComState ;
  CWnd*    lpMainWnd  = NULL;
  DWORD    ldwSize = 0;
  TCHAR    sError[100];
  BOOL    lblReadState = FALSE ;
  BOOL    lblRetyrFlg = FALSE;

  lpMainWnd  = AfxGetApp()->m_pMainWnd;

  do
  {
    // COMポートの状態をチェックする
    ClearCommError( m_tComCtrlInf.hComDev, &m_tComCtrlInf.dwError, &ltComState );
    
    if( m_tComCtrlInf.dwError > 0 )
    {
      // エラーが発生した場合
      wsprintf( &sError[0], "ClearCommError1:%d", m_tComCtrlInf.dwError ); 
      lpMainWnd->MessageBox( sError, "Comm Error" );
      break ;
    }

    ldwSize = ltComState.cbInQue ;
    if( ldwSize <= 0 )
    {
      // バッファに溜まっていない場合
      break ;
    }
    // 溜まっているデータサイズをチェックする
    if( ldwSize > ( DWORD )m_iRxBuffSize )
    {
      //  確保してあるバッファサイズより多い場合
      ldwSize    = m_iRxBuffSize ;

      lblRetyrFlg = TRUE;
    }

    // 非同期読み込み
    lblReadState = ReadFileEx( m_tComCtrlInf.hComDev, ( LPVOID )m_bpRxBuff, ldwSize, &m_tComCtrlInf.tOverLappedRead, FileIOCompletionRoutine ) ;

    // 失敗した場合
    if( ( lblReadState != TRUE ) || 
    // 戻り値自体は成功だがエラーがある場合
    // ※MSDN参照
      ( ( lblReadState == TRUE ) && ( GetLastError() != ERROR_SUCCESS ) ) )
    {
      // その他のエラーの場合
      ClearCommError( m_tComCtrlInf.hComDev, &m_tComCtrlInf.dwError, &ltComState ) ;
      wsprintf( &sError[0], "ReadFileEx ClearCommError:%d", m_tComCtrlInf.dwError ); 
      lpMainWnd->MessageBox( sError, "Comm Error" );
      lblRetyrFlg = FALSE;
      break;
    }

    // I/O 完了コールバック関数が呼び出されるまで停止する
    SleepEx( INFINITE, TRUE );

  }while( 0 ) ;

  return( lblRetyrFlg ) ;

//*** 読み込み込み用CALLBACK関数
VOID CALLBACK FileIOCompletionRoutine(
  DWORD dwErrorCode,                // 完了コード
  DWORD dwNumberOfBytesTransfered,  // 転送バイト数
  LPOVERLAPPED lpOverlapped         // I/O 情報がある
                                    // 構造体へのポインタ
                  )
{
  CSerial*  lpSerial = NULL;

  TRACE0( "FileIOCompletionRoutine\n" );

  do
  {
    // ReadFileEx 関数がファイルの終端よりも後ろを読み取ろうとした場合
    if( dwErrorCode == ERROR_HANDLE_EOF )
    {
      break;
    }

    // エラーが発生した場合
    if( dwNumberOfBytesTransfered <= 0 )
    {
      break;
    }

    if( ( lpOverlapped == NULL ) || (lpOverlapped -> Pointer == NULL ) )
    {
      break;
    }

    lpSerial = ( CSerial* )lpOverlapped -> Pointer;

    if( lpSerial -> GetReceiveMode() == TRUE )
    {
      // 受信したメッセージをとりあえず、キューへセットする
      // 受信処理
//      lpSerial -> SetData( dwNumberOfBytesTransfered ) ;
    }
  }while( 0 );
  TRACE0( "FileIOCompletionRoutine Complete\n" );
}


tetrapod  2007-09-07 02:26:05  No: 66236

とりあえず字下げも消えちゃってるし読む気力がわかないので一般論だけ

まずは通信線上の信号レベルでデータが化けているかどうかを信頼の置ける測定器で観測
信号は正しくてソフトの読み取り結果が間違っているということならデバッグするべし

ぱっと見てヘンと思ったところ (思っただけなので責任もたん)
==TRUE とかは限りなく禁じ手だし
lpOverlapped -> Pointer って何?
<winbase.h> の OVERLAPPED に Pointer なんてメンバーがあるとは聞いてない
ReadFileEx+SleepEx するくらいならふつーに ReadFile すりゃいい


kanagawa  2007-12-22 19:31:47  No: 66237

まず返事がかなり遅くなってすいません。
他におかしな所がないか自分でチェックしてたり、他の仕事に追われていました。字下げも消えちゃってすいません。後でもう一度ソースをアップします。==TRUEは確かにそうですね。

さて・・・
通信線上の信号レベルでデータが化けているかどうかですが、調べてみた所データは化けていませんでした。

lpOverlapped -> PointerはOVERLAPPED構造体に変数があります。ReadEX等を使う場合は、Pointerのエリアを使えると記述がありました。

そしてふつーにReadFileすればいいのですが、最初はReadFile(同期、非同期両用)をしていました。ReadFileした直後にデバッグ用でファイル保存してみたりもしたのですが、ここでもデータ化けが起こっていました。シリアルの受信方法や関数の使い方違うのではと予想し、それが非同期専用の関数ReadFileExを使った理由です。


仲澤@失業者  2007-12-26 01:48:50  No: 66238

受信のみで問題が発生するのであれば
まずは送信系のマスクとイベントをはずしてデバッグしたらどうでしょう。

過去のデータをもう一度受信するなどは、誤って関係ないイベント
タイミングで受信データを読みにいっているぐらいしか思いつきません。
また、一般に送信イベントを関する必要は無いはずです。

できるのであれば同期に戻し、別スレッドにした方が良いかもしれません。
基本的なDCBのチェックもやり直したほうがいいかもしれません。
それでもだめなら、外乱も疑ってみましょう。


kanagawa  2008-02-01 08:20:39  No: 66239

お返事ありがとうございます。
またまた返事が遅くすいません。

>受信のみで問題が発生するのであれば
>まずは送信系のマスクとイベントをはずしてデバッグしたらどうでしょう。

送信系のマスクをはずしてデバッグしてみましたが、やはり変わりません。

>過去のデータをもう一度受信するなどは、誤って関係ないイベント
>タイミングで受信データを読みにいっているぐらいしか思いつきません。
>また、一般に送信イベントを関する必要は無いはずです。

Portモニターを使用して見てみると、正しいデータを受信しているみたいです。1バイトずつ正しいデータを受信していました。

ここで他のシリアル通信TOOLを試してみたのですが、やはり途中からデータが壊れます。ちなみにハイパーターミナルはデータは壊れなかったのですが、途中でPCが落ちてしまいました。

>できるのであれば同期に戻し、別スレッドにした方が良いかもしれません。
>基本的なDCBのチェックもやり直したほうがいいかもしれません。
>それでもだめなら、外乱も疑ってみましょう。

色々とチェックしてみたのですが、やはりおかしな所は見えず・・・
オーバーフローしているのかなと思って、バッファサイズを変更しても
変わらずです。

何が考えられるでしょうか?ドライバが悪い???


kanagawa  2008-02-01 08:32:21  No: 66240

ちなみにケーブルはUSB-232C変換ケーブルを使っています。その辺も関係あるのでしょうか?


ななしさん  2008-02-01 08:41:01  No: 66241

>1時間ほど連続試験した際に、たまに受信データが壊れている事があります。
>ドライバが悪い???
>ちなみにケーブルはUSB-232C変換ケーブルを使っています
ていうか、そのレベルまで行くと実際に実行環境でデバッグできる本人にしか分からないので、質問しても無駄だと思う。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加