質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

50510閲覧

serialportの受信に、ReadLineを使うか、ReadExistingを使うか

ichi_goo

総合スコア18

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

1グッド

0クリップ

投稿2017/02/15 04:47

###前提・実現したいこと
WindowsFormアプリを作成しています。

発信機から送信された文字列を、PCにCOMポートで接続されている受信機で
受け取り、受け取った文字列を処理したいと思っています。

###発生している問題・エラーメッセージ
SerialPort.ReadLineを使うと、文字列の送受信後に切断できなくなり、
SerialPort.ReadExistingを使うと、即時処理ができなくなってしまいます。

○ReadLine使用時は通信後、切断しようとすると以下のメッセージが出ます。

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

○ReadExistingはmsdnの公式ページに「即座に使用できるすべてのバイトを読み取ります。」と説明があり、
実際の動作と受信したデータにズレが生じるようです。

例としては、
「値3」のデータが送信されたが、その段階では受信できず、
次の「値5」を送信したときに「値3」と「値5」の両方が
一緒に受信できる、といった様子です。

「値3」が送信されたらその段階で受信したいと考えています。

###聞きたいこと
通信後切断できるReadExistingを使用したいのですが、
即時処理できないのは困ります。

ReadExistingで即時処理する方法はありますでしょうか?
それともReadlineを使用し、エラーを発生させなくした方が良いでしょうか?
もしくは別のRead系関数を使った方が良いでしょうか?

###該当のソースコード
・ReadLine

C#

1 /// <summary> 2 /// データ受信が発生した時のイベント 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void serialPortMain_DataReceived(object sender, SerialDataReceivedEventArgs e) 7 { 8 if (!serialPortMain.IsOpen) 9 { 10 // 接続していない場合は処理を行わない 11 return; 12 } 13 else 14 { 15 string data = string.Empty; 16 17 try 18 { 19 data = serialPortMain.ReadLine(); 20 data = data.Replace("\r", "").Replace("\n", ""); 21 } 22 catch (Exception ex) 23 { 24 MessageBox.Show(ex.Message); 25 } 26 27 AddRecievedDataDelegate add = new AddRecievedDataDelegate(AddRecievedData); 28 textBoxInfo.Invoke(add, data); 29 } 30 } 31 32 /// <summary> 33 /// 34 /// </summary> 35 /// <param name="data"></param> 36 private delegate void AddRecievedDataDelegate(string data); 37 /// <summary> 38 /// 39 /// </summary> 40 /// <param name="data"></param> 41 private void AddRecievedData(string data) 42 { 43 textBoxInfo.Text += data + "\r\n"; 44 textBoxInfo.SelectionStart = textBoxInfo.Text.Length; 45 textBoxInfo.ScrollToCaret(); 46 47 // 文字列調整 48 StringPartition(data); 49 }


・ReadExisting

C#

1 /// <summary> 2 /// データ受信が発生した時のイベント 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void serialPortMain_DataReceived(object sender, SerialDataReceivedEventArgs e) 7 { 8 if (!serialPortMain.IsOpen) 9 { 10 // 接続していない場合は処理を行わない 11 return; 12 } 13 else 14 { 15 string data = string.Empty; 16 17 try 18 { 19 data = serialPortMain.ReadExisting(); 20 } 21 catch (Exception ex) 22 { 23 MessageBox.Show(ex.Message); 24 } 25 26 AddRecievedDataDelegate add = new AddRecievedDataDelegate(AddRecievedData); 27 textBoxInfo.Invoke(add, data); 28 } 29 } 30 31 // 受信データ 32 private string TestStr = string.Empty; 33 34 /// <summary> 35 /// 36 /// </summary> 37 /// <param name="data"></param> 38 private delegate void AddRecievedDataDelegate(string data); 39 /// <summary> 40 /// 41 /// </summary> 42 /// <param name="data"></param> 43 private void AddRecievedData(string data) 44 { 45 textBoxInfo.Text += data; 46 textBoxInfo.SelectionStart = textBoxInfo.Text.Length; 47 textBoxInfo.ScrollToCaret(); 48 49 TestStr += data; 50 if (TestStr.EndsWith("\r") || TestStr.EndsWith("\n")) 51 { 52 // 改行コードまできたら 53 string[] str = TestStr.Split(new string[] { "\r\n" }, StringSplitOptions.None); 54 55 for(int i = 0; i < str.Length;i++) 56 { 57 if(str[i].Length == 0) { continue; } 58 59 // 改行コードが残っていたら 60 string str1 = str[i].Replace("\r", "").Replace("\n", ""); 61 62 // 文字列調整 63 StringPartition(str1); 64 } 65 66 TestStr = string.Empty; 67 } 68 }

###補足情報(言語/FW/ツール等のバージョンなど)
C#、Visual Studio 2015

Wind👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

シリアルポートによる通信は、メッセージの区切りのもたせ方に大きく2つ方法があります。

  1. メッセージ間で時間を置く
  2. メッセージに構造に区切り情報を持たせる。

後者の場合、(例えば改行コードを区切り文字とするなど)

  • 受信イベントごとにバッファ内の文字列をすべて読み出し、解析用のバッファに投げる
  • メッセージの区切りを検出したら、メッセージ受信イベントを発火する。
  • 検出した分のメッセージを適切に検出用バッファから削除する。

AddRecievedData()がうまく動いているのか確かめたほうがいいと思います。

ただ、いちいち実機でテストするのも面倒なので、
逐次解析をするためのクラスに抜き出して、

C#

1var buffer = new MessageSplitter(); 2buffer.MessageRecieved += (x => Console.WriteLine(x)); 3buffer.Add("値3"); 4buffer.Add("\r\n"); 5buffer.Add("値5\r\n");

みたいにすればデバッグしやすいです。

投稿2017/02/15 05:15

編集2017/02/15 07:02
ozwk

総合スコア13521

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ichi_goo

2017/02/15 05:31 編集

通信方法としては改行コードを区切り文字にできます。 「受信イベントごとにバッファ内の〜」の受信イベントと「メッセージの区切りを〜」のメッセージ受信イベントは別物、ということでしょうか? バッファ内の文字列を全て読み出すのはReadExistingで行うということでしょうか?
ozwk

2017/02/15 05:54 編集

イベントについては新たに発火してもいいし、単にメソッド呼び出しでもいいです。 大筋は質問文のReadExist版でいいです。 AddRecievedData内で多分私の回答のようなことをやろうとしていると思うんですが にしてはなんだか処理が複雑になっている気がします。 AddRecievedData関係だけ抜き出して、適当な文字列を与えていって、 意図通りの動作になるか確認してみてはどうでしょう。
ichi_goo

2017/02/15 06:01 編集

たびたびすみません。 ReadExist版のTestStr += data;でozwkさんのおっしゃる「解析用のバッファに投げる」のと同じになりそうですね。 送られてくる文字列の改行コードが必ずしも"¥r¥n"ではないかもしれないので、複雑な書き方になってしまっています。。 しかしこのdataの中に例として"値:3¥r¥n値:5¥r¥n"みたいに前回のコマンドと今回のコマンドが一度に来てしまって、 「値3」を送信したときと受信したときのタイミングが実際の動作とずれてしまうのですが、そのあたりはいかがでしょうか……。 装置の方から「値:5¥r¥n」を送った時点でやっと受信側で一気に「値:3¥r¥n値:5¥r¥n」が受け取れる感じです。
ozwk

2017/02/15 06:00

"値:3¥r¥n" の後に時間を充分空けて "値:5¥r¥n"を送ったのに、 "値:5¥r¥n"を送るまでDataReceivedが発火しないということですか?
ozwk

2017/02/15 06:07

一回目の送信でDataReceivedがそもそも発火しない場合は、 ReceivedBytesThresholdプロパティが妙な値になっているか、 そもそも送信側が送っていないかです。
ichi_goo

2017/02/15 06:19

来てますね。。。 どうやら、 AddRecievedDataDelegate add = new AddRecievedDataDelegate(AddRecievedData); textBoxInfo.Invoke(add, data); ここが悪さしているっぽいです。見直してみます。 ありがとうございました。 確認できましたら、またご連絡いたします。
ichi_goo

2017/02/15 07:26

できました! ありがとうございます。 AddRecievedDataは正常に動いていたのですが、 判定条件が間違っていました……。 改行コードの判定では不十分だったみたいです。 色々とアドバイスしていただきありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問