none
SerialPortのDataReceivedイベントについて RRS feed

  • 質問

  • C#Standerd Edition2005 のWindowsFormでSerialPortコンポーネントを使ったアプリを作っています。

    PCのCOMポートには外部機器がつながっており、ヘッダ5バイト+4800バイトのデータが任意のタイミングで送られてきます。5バイトと4800バイトはつながって送られてきます。

    受信バッファは4805バイトよりも非常に大きくとってあります。

    DataReceivedイベントで、COMポートから送られてきたデータを1バイトづつ判定してヘッダ5バイトがマッチしたら、その後の4800バイトを用意した配列に取り込むというアプリを組んでいます。4800バイトを取り込み終わったら念のため受信バッファが0、つまりデータがそれ以上送られていないかを確認してメッセージボックスで表示しています。

    ここで問題がおきているのですが、4805バイトを受信すると、問題なくデータが配列に格納されて受信バッファも0で終わるのですが、その直後に、データを受信していないにもかかわらずにDataReceivedイベントが発生し、タイムアウトが発生してしまいます。エラーメッセージボックスを作成して、このイベントを無視しようとしても、4805バイト受信後に、データのやりとりがまったくない(オシロで確認)にもかかわらずに数回DataReceivedイベントが発生します。データの量を4800バイトからさらに増やすと、データ受信後のイベント発生回数はさらに増えます。

    ここで想像なのですが、データ量が多いと、一回の通信中にDataReceivedイベントが複数回発生し、それが記憶されていて、データ通信が終わったにもかかわらずにイベントが発生してしまう、のではないかと考えています。

    このような場合の回避方法をお気づきの方がおりましたら教えてください。

     

    <エラー時のスタックトレース>

    ・DataReceivedイベント->SerialPort.ReadByte()->SerialStream.ReadByte()->SerialStream.ReadByte(Int32 timeout)

    <フォームのコンストラクタ時のSerialPortの設定>

    serialPort1.ReadTimeout = 1000;
    serialPort1.ReadBufferSize = 230500;
    serialPort1.Open();

    <DataRecievedイベントの冒頭部>

    private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
      if (serialPort1.ReadBufferSize > 0)
      {
        try
        {                                   
          rcvData = new byte[5];
          this.rcvData[0] = (byte)serialPort1.ReadByte();          //1バイト目読み込み
          if (rcvData[0] == 255)
          {
            Invoke(new TextSet(d1.SetText), new object[] { rcvData[0].ToString() });
            rcvData[0] = (byte)serialPort1.ReadByte();             //2バイト目読み込み
            if (rcvData[0] == 98 || rcvData[0] == 99)             
            {
              ...ヘッダ5バイトがマッチしたら4800バイト取り込み...

    エラーの場所は1バイト目読み込みのReadByte()になります。上のrcvDataは別に配列でなくてもかまわないのですが、別な理由で配列にしてます。

    2006年6月12日 2:29

回答

  • Takaです。解決しましたので報告します。

    SerialPortで、DataRecievedイベントを使っている時、大量にデータを受信すると何回もイベントが発生してしまい、それがキャッシュされていて、データを受け取り終わったあとにもイベントが発生してしまい、そのときにはデータは受信されていないのでエラーとなる、というものでした。

    対策としては、データを大量に受信する前で、DataRecievedイベントを外して、終わったあとにイベントを組み込み直す、ということで対応できました。

     

    2006年6月28日 8:53

すべての返信

  • よくわからないのですが、データ量が増えるとイベントの発生回数が増えるということから、フロー制御なんかは関係ないでしょうか?
    DtrEnableや、RtsEnableの辺りなんか・・・。

    2006年6月12日 2:59
    モデレータ
  • Takaです。

    フロー制御はハード、ソフトとも無しという設定です。SerialPortのHandShakeプロパティはデフォルトのままのNoneです。ハードはTXとRXと信号GNDしかつながってません。

    感じとしては、1000バイトぐらい受信するともう一個イベントが立ち上がるような感じです。信号の波形を観測しても、データ受信後はまったく変化なし(待機状態を継続)しているのでやはりソフト的にイベントがスタックされているような雰囲気です。ちなみに、タイムアウトを長くするとその感覚でイベントが立ち上がります。たとえば、タイムアウトを3秒にして、4805バイト送ると、用意した配列に4800バイトのデータが正常に格納された3秒後に一回目のイベントが立ちます。エラー解除するとさらに3秒後にイベントが立ち...を4回繰り返します。この間はまったく信号は出ていないので、やはりC#側でイベントがスタックされている、というような感じです。データ量を増やすと、上の「4回繰り返す」が増えていきます。

    2006年6月12日 5:27
  • その後ですが、ReadTimeoutを-1に設定(タイムアウト無し?)に設定して、DataRecievedイベントの冒頭で

    if (serialPort1.ReceivedBytesThreshold > 0)

    のようにイベント発生時のバッファが空でないかどうかの判定を入れたら「複数回イベントが発生する」というところは見えないようにすることができました。根本的な解決にはなっていませんが。

    それでも2回目の受信をする前にエラーが発生するので中身を見てみるとIOExceptionが発生するようになりました。エラー内容とスタックトレースは以下のとおりです。

    System.IO.IOException:スレッドの終了またはアプリケーションの要求によって、I/O処理は中止されました。

    場所 System.IO.Ports.InternalResources.WinIOError(Int32 errerCode, String str)
    場所 System.IO.Ports.SerialStream.EndRead(IAsysncResult asyncResult)
    場所 System.IO.Ports.SerialStream.ReadByte(Int32 timeout)
    場所 System.IO.Ports.SerialStream.ReadByte()
    場所 System.IO.Ports.SerialPort.ReadByte()
    場所 ....ファイルの位置、行番号

    ということで下のように、IOExcepionをキャッチして無視するととりあえずは「エラーは起きているけどなにもしない」ということで見た目上は回避できました、が、

    catch (System.IO.IOException myIOe)
    {
      //Non operation..
    }

    が、しかし、フォームを閉じようとすると固まります。デバックありで実行してフォームのDisposeでとまるようにして、一行づつトレースするとなぜか最後まで行って正常に終了します。

    という感じで、やはり、複数立ち上がったデータ受信イベントがらみのスレッドが悪さをしているのではないか?という読みです。が、スレッドのあたりをよく理解していないのでそこから先になかなかいけないという状況です。

    2006年6月12日 8:28
  • Takaです。解決しましたので報告します。

    SerialPortで、DataRecievedイベントを使っている時、大量にデータを受信すると何回もイベントが発生してしまい、それがキャッシュされていて、データを受け取り終わったあとにもイベントが発生してしまい、そのときにはデータは受信されていないのでエラーとなる、というものでした。

    対策としては、データを大量に受信する前で、DataRecievedイベントを外して、終わったあとにイベントを組み込み直す、ということで対応できました。

     

    2006年6月28日 8:53
  • 鈴木@KEG と申します。

    だいぶ前の投稿への返信ですけど、他のみなさまのためにも。

    DataRecieved イベント で、受信したバイト数を取得するには、BytesToRead プロパティを参照します。例外の補足を除いたシンプルなコードは、以下のように書けます。

     if (serialPort1.BytesToRead > 0)
     {
         // 受信バッファからデータを取得
        byte[] b = new byte[serialPort1.BytesToRead];
        Read(b, 0, b.Length);

     }

    DataRecieved イベントを受け取った後も、刻々と受信バッファにデータが入るので、BytesToRead を参照するときには注意して下さい( 上の例だと、Read(b, 0, serialPort1.BytesToRead); はNG )。

    ReadBufferSize は、バッファのサイズそのものです。また、ReceivedBytesThreshold は、受信バッファに溜まったデータが何バイトになったら、DataRecived イベントを発生させるか、といったプロパティです。初期値はいずれも 0を越えていますのでサンプルに挙げられたソースでは、常に真となってしまわれると思います。

    DataRecieved イベント内の処理が多かったり、イベント内で複数回のReadを行っていると、DataRecieved イベントは受信バッファにReceivedBytesThreshold の分だけデータが溜まった時に呼ばれるので、イベントがスタックする事もあるかと思います(フロー制御を行っていると、大抵は相手の送信が止まるので確認できませんけれど)。イベントが来ることを防止するのでは無く、BytesToRead で読み取り可能なバイト数を確認する事と、Read の例外をcatch するのが良い方法だと思います。

    2006年10月10日 6:56