シリアル通信 初心者 への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 YUU  (社会人) 投稿日時 2015/8/26 20:02:39
返信遅れてすみませんでした。

結論としては解決いたしました。ごり押し気味ですが1byteずつ取得し仕様の形に近づけたと考えております。

シリアル通信を少しも知らない状況でのスタートとはいえグダグダ感はぬぐえませんが許容の範囲には収まったかと思います。

長々お付き合いありがとうございました。至らぬ点ばかりで失礼しました。このスレッドは一応解決とさせていただきます。

機会がありましたらお力を貸していただけると幸いです。
投稿者 daive  (社会人) 投稿日時 2015/8/20 10:19:25
<機器>                                  <アプリ>

 <STX>テキスト<ETX><BCC> → アプリケーション受信
       <ACK>または<NAK>  ←
        <EOT>             →

この流れの場合では、
問答無用で、
 <STX>テキスト<ETX><BCC>
まで、受信します。⇒固定長データですよね?

受信したデータから、
<BCC>は、チェックデータなので、当然除外して、
仕様書にて「テキストの先頭文字からETXまでの文字単位での
排他論理和で1バイトのバイナリで構成」
にしたがって、残った部分から、BCCを計算します。

<STX>テキスト<ETX>

この時に、STXが入るか、入らないか、BCCの初期値が与えられているかいないかで
計算結果が変わるのは、理解できる筈です。
⇒受信したデータを、演算するのは、表計算ソフトを使うと、
  シュミレーションが出来ると、以前に書きました。

受信した、<BCC>と、計算した BCCが
同じであれば、
ACK送信
同じでなければ、
NAK送信
ということです。
投稿者 YUU  (社会人) 投稿日時 2015/8/19 22:24:32
今回の投稿で長々と質問、ご迷惑をおかけしてすみませんでした。

結論としてデータ受信に成功しました。原因は機器側の設定ミスになります。

設定については私が行っているわけではなく、オンラインで確認することのできない環境の為リアルタイムでの確認ができず現状に至ったわけです。(データ送信がOFFに・・・)

ハンドシェイクだのPG側の制御に関してまったく関係のない箇所が問題でした。

>フローコントロールは(ハンドシェイク)、本当に必要なことですか?
今回、初のシリアル通信制御とのことでハンドシェイクでの制御を行う事を前提に考えてきました。
データがデータなだけに正確性に欠けるわけにはいかない為としていたのですが正直データ量もそれほどでもないので必要なかったかもしれません。

データを受信できたことで新たな問題点に行き当たりました。

流れとしては↓です。

<機器>                                  <アプリ>

<STX>テキスト<ETX><BCC> → アプリケーション受信
       <ACK>または<NAK>  ←
       <EOT>             →

文末である<ETX>を取得できたのですが<BCC>の処理に手を焼いております。

チェックサム 計算が必要なのは分かったのですが伝文にて付与される<BCC>を取得する方法に悩まされております。


            'シリアルポートのバッファーの読み込み
            Dim strReceived As String = Me.SerialPort.ReadExisting
            Dim Inp As String = String.Empty
            
            'ここで何らしかの方法でチェックサム計算?
            Dim BCC ・・・

            If String.IsNullOrEmpty(strReceived) = False Then

                For i As Integer = 1 To Len(strReceived)

                    Inp = Mid(strReceived, i, 1)

                    Select Case Inp
                        Case Chr(&H2)  '電文始まり<STX>  
                        
                        Case BCC
                                
                                 'ここでの正否で処理を行う?

    End Select

↑が現在のコードです。省略はしていますが大まかにはこのような感じです。
<BCC>については仕様書にて「テキストの先頭文字からETXまでの文字単位での排他論理和で1バイトのバイナリで構成」とされているので最初のデータ取得時に計算させ定義させるのが定石でしょうか?。

そうであればCaseにて処理が可能で正否の判定もできるかと考えております。

チェックサム計算の際にはどのように処理を運ぶものなのでしょうか?

投稿者 daive  (社会人) 投稿日時 2015/8/17 21:29:43
仕様書も、メーカー、機器型番も、出てこないのでは、お力になれそうも有りません。

RS-232Cとは?
 http://www.fukufukudenshi.jp/v2/RS-232C/
 興味があれば、この辺も参考になるかも。

①、②
⇒フローコントロールは(ハンドシェイク)、本当に必要なことですか?
  8ビットPCの時代では無いのですから、私は、通常は使用しません。
  PC側(親側)が、遅くて、通信データの取りこぼし(バッファオーバーフロー)
  する状況など、極限られた状況で、使用していましたが、
  16ビットPCになって以後、必要だったことはありません。
  8ビット時代でも、C/アセンブラで通信ルーチンを、書いて、済んでいました。
  どうしても、使用しなければならない場面では、通信速度を下げて、対応するのが簡単です。
  通信手順(今回は、ACK/NACKシーケンスの様子ですが)と、
  フローコントロールをごっちゃにして考えていませんか?
  また、無手順とは、何を指して、どういう動きを想定していますか?
  例えば、計測機器側から、一方的にデータを送って来るだけで、
  ACK/NAK/EOT が不要な、モードですか?
  

⇒ という事を簡単に、目視する装置が、ラインモニタだったり、横取りケーブルだったりします。
投稿者 YUU  (社会人) 投稿日時 2015/8/17 18:59:55
返信遅れてすみません。いくつか助言をいただきフローまで書いていただきありがとうございました。

現状、理解の追いついていない部分での問題なのか、設定の問題なのか、仕様書の問題なのか不明ですがデータを受信できないのはかなり不安で焦りますね。

時間的に急ぐものでもないので構わないのですが実際問題テストに支障が出ているのも事実です。

そこで一つ一つ解消するために現在の疑問点をいくつかあげさせていただきます。

①無手順からハンドシェイクの設定以降後、受信できなくなった。仕様書曰く、無手順にてXON,XOFF制御(ソフト制御)のオプション設定ができるとの記載がありました。ということはハンドシェイク設定はハードでの制御ということになるような気がします。

ハード制御ではピンが重要とのことで、ハンドシェイクのプロパティをRequestToSendにし、 SerialPort.RtsEnable = Trueでよかったのでしょうか。

②改めてハンドシェイクについて調べ気になった個所が。とあるサイトにて「送信側から受信側に「データ送信中」という信号を送り、受信側はその信号を受け、信号線からデータを読み込みます」という文面が。これらから解釈するに機械から送信される際に何かしら準備OKを返す必要があるのでしょうか。
コントロール側が自動制御してくれるものと思っていたのですが誤った考えなのか。

③BCCについて。データに付加されるBCC。仕様書には「テキストの先頭文字からETXまでの文字単位での排他論理和で1バイトのバイナリで構成」となっております。機械側から受信時に文末に<BCC>が付加されているということはSerialPort.NewLine = "\r"←は誤りということにはならないでしょうか。
受信時の文末は無手順時は確かに\rで正しかったですがハンドシェイクでは<BCC>となっており文末判定に誤りがあるのではないでしょうか?

長々と書いてしまいすみません。勉強不足は痛感しておりますが頼れる者もおらず困っております。
投稿者 daive  (社会人) 投稿日時 2015/8/14 13:49:17
簡単に書くと、こんな感じですが(ポーリング型)
各処理を、どの様に&どの様な、イベント処理とするか、ポーリング処理とするかで、
考え方が、変わる部分が発生します。
(vb.net timer 高精度)
VB.NETは、イベント駆動型ですので、ポーリング処理は出来るだけ避けて、
イベント、インターバルタイマーでの処理などを、考えます。

COM受信待ち(ポーリング的処理の場合)
① ↓
データ受信待ち(受信イベント待ち)
  ↓
1Byte目なら
 タイムアウト値セット
  ↓
タイムアウトチェック(何処で、どの様にタイムアウトチェックするか)
必要に応じて規定bytes目チェック
必要に応じてSTX / ETX チェック、データ受信チェックで行っても良い。
必要に応じてEOT チェック、データ受信チェックで行っても良い。
  ↓
 ①へ
各チェック項目を、内部ステータスや、
デバッグ情報出力、画面表示としておくと、
解りやすい。

電文受信チェックメイン(ポーリング的処理の場合)
   ↓
データ受信チェック(受信完了チェック、電文チェック、BCCチェック、タイムアウトチェック等)
  ↓
OKなら、ACK送信
NGなら、NAK送信
   ↓
ACKであればの、処理、EOT受信処理
NAKであればの、処理、EOT受信処理
   ↓
受信処理終了
(イベント型に書き直すのは、どの様に、どうするか、⇒状態遷移、ステートマシーン)

タイムアウトであれば、
タイムアウト処理
  NAKか、受捨てにするかどうかは、要件次第

パリティーエラー処理
  NAKか、受捨てにするかどうかは、要件次第

オーバーフロー処理
  NAKか、受捨てにするかどうかは、要件次第
 端末機器側が、電源ON/OFF時に不正データを送信する機器かどうかの
 確認が必要です。


検索ワード
通信 bcc 計算
通信 チェックサム 計算
bccの計算方法は、ネット検索した結果と異なる場合もあります。
基本的考え方は同じでも、実際の計算値は、現物合わせだったりします。
⇒EXCELなどで、検証ルーチンを作るのが簡単だったりします。

 >伝送方式:調歩同期式
 >伝送速度:9600bps
>伝送コード:ASCII
>データビット:8bit
>パリティチェック:なし
>ストップビット:1bit
との事ですから、1ビット当たり=1/9600秒でデータの送受信ができる事になります。
スタートビット:1ビット
 データビット :8ビット
 パリティビット:なし
 ストップビット:1ビット
 より、1キャラクタ当り、10ビット:10/9600秒で、1キャラクタの送受信が
可能という事になります。
 1キャラクタ当り、1ms強 
タイムアウト時間等は、この値を元に、最短通信間隔を加味して、決定します。
つまり、理屈上は、
 100キャラクタ:100ms強で受信できるはずだが、
 相手の都合もあるので、必ずしも、100ms強ではない。
という事です。
タイムアウト値は、可変設定にしておくか、設定ファイルとするのが普通。 
投稿者 (削除されました)  () 投稿日時 2015/8/14 12:53:20
(削除されました)
投稿者 YUU  (社会人) 投稿日時 2015/8/14 12:13:13
>PC側アプリで、電文の内容を確認後、

やはり何らかの形で返答する必要があったのですね・・・。ハンドシェイクについての理解が足りていないようです。

通信内容はハンドシェイク設定の場合

<機器>                                  <アプリ>

<STX>テキスト<ETX><BCC> → アプリケーション受信
       <ACK>または<NAK>  ←
       <EOT>             →

1データのみの場合はこのようなやり取りのようです。

無手順設定の場合は垂れ流しの状態で受信ができたので注視していませんでした。

ハンドシェイク設定でも一文は取得できると思い受信ができてから次の処理と考えていたのです。

テキストの長さは100文字。項目ごとに区切りがあり決まりもあるのですがそれらは無手順と変わりないので

判定の方はできております。


            Dim strDataReceived As String
            Dim intSTR As Integer
            Dim chrDATA As String

            strDataReceived = SerialPort.ReadExisting

            intSTR = Len(strDataReceived)

            For i As Integer = 1 To intSTR
                chrDATA = Mid(strDataReceived, i, 1)
                Select Case chrDATA
                    Case Chr(STX)

                    Case Chr(ACK)

                End Select

            Next i

↑の様に制御コードを取得し取得出来次第処理をかけるという感じなのでしょうか?
<BCC>というものがよくわからずどのタイミングで正否の応答を返すものなのでしょうか?

投稿者 (削除されました)  () 投稿日時 2015/8/14 12:11:13
(削除されました)
投稿者 daive  (社会人) 投稿日時 2015/8/14 11:26:54
SerialPort.Handshake プロパティ 
 https://msdn.microsoft.com/ja-jp/library/system.io.ports.serialport.handshake%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
ここは、読みましたか?

規定数の電文を受信した後、
PC側アプリで、電文の内容を確認後、
電文が正であれば、ACK を送信
電文が不正であれば、NAK を送信
の部分が無いようですが?
何が正で、何が不正かは、掲示が無いので不明です。
正/不正のチェックを、BCC、CRCで行うのか、
内容を走査して、行うのかすら不明です。

プロトコル不明です。
 垂れ流しの固定長データを受信して
 それに対して、ACK/NAKを返すだけであれば、
 そう難しく無いのですが?、その辺も不明
投稿者 YUU  (社会人) 投稿日時 2015/8/14 10:26:44
daive様、いつも返信ありがとうございます。

>今回の話の基盤を作るためには、

1、機器に関しては私は把握しておりません。(薄い仕様書(説明書程度)を頂いたのみ)
  何かしらの検査機器のようです。(URL,機器名の記載なし)

2、通信仕様は上記にも書いておりますが、
伝送方式:調歩同期式
伝送速度:9600bps
伝送コード:ASCII
データビット:8bit
パリティチェック:なし
ストップビット:1bit
フロー制御なし(X-ON/X-OFF)
ハンドシェイク

電文は固定長で長さが決められております。

状況としては上記では電文をイベントで受診できたと記載しているのですがそれは「無手順」の設定で取得できていたというものでした。

要望としては「ACK」,「NAK」のやり取りをしエラーを最小限にするというものらしく「ハンドシェイク」の設定が必要となってきます。

しかし、ハンドシェイクの設定後、受信イベントが走らない模様です。何か原因があるのでしょうか?

シリアル通信初心者の為、至らなぬ点ばかりで申し訳ありません。

下記がコードです。だいぶ省略していますが要点のみ。

'ボタンイベント
        If SerialPort.IsOpen = True Then
            SerialPort.DiscardInBuffer()       '受信バッファ破棄
            Try
                SerialPort.Close()             'シリアルポートをクローズ
                cmbComPort.Enabled = True
                cmbBaudRate.Enabled = True
                cmbHandShake.Enabled = True
                btnConnect.Text = "接続"
            Catch ex As Exception
                MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.[Error])
            End Try
        Else
            Dim baud As BuadRateItem = CType(cmbBaudRate.SelectedItem, BuadRateItem)
            Dim ctrl As HandShakeItem = CType(cmbHandShake.SelectedItem, HandShakeItem)

            SerialPort.PortName = cmbComPort.SelectedItem.ToString()
            SerialPort.BaudRate = baud.Baudrate
            SerialPort.Handshake = ctrl.Handshake
            SerialPort.Parity = Parity.None
            SerialPort.DataBits = 8
            SerialPort.StopBits = StopBits.One
            SerialPort.Encoding = Encoding.ASCII
            SerialPort.NewLine = "\r"
            SerialPort.DtrEnable = True
            SerialPort.RtsEnable = True

            'SerialPort.ReadTimeout = 1000        '受信タイムアウト時間

            Try
                SerialPort.Open()

                cmbComPort.Enabled = False
                cmbBaudRate.Enabled = False
                cmbHandShake.Enabled = False

                btnConnect.Text = "切断"
            Catch ex As Exception    '開けなかった場合の例外処理
                MessageBox.Show(ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.[Error])
            End Try
        End If

   ''' <summary>
    ''' データ受信が発生したときのイベント処理
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub SerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort.DataReceived

        'シリアルポートをオープンしていない場合、処理を行わない
        If SerialPort.IsOpen = False Then
            Return
        End If

        Dim Len As Integer = SerialPort.BytesToRead  '受信バイト数取得
        Dim InData(Len - 1) As Byte                     '受信値読み込み配列

        SerialPort.Read(InData, 0, Len)                  '読み込み

        ''1バイトずつメインスレッドにてテキストボックス書込み
        For i As Integer = 0 To InData.Length - 1
          Me.Invoke(New dlgReceive(AddressOf RcvDataToTextBox), InData(i))
        Next

End Sub

投稿者 daive  (社会人) 投稿日時 2015/8/9 13:48:33
今回の話の基盤を作るためには、
1.使用する(接続する)機器のメーカー、型番、サイトURL
2.通信仕様、プロトコル仕様などが、解る内容か、URL
3.PC側の232ポートは、本体内蔵COMポート、USB-COMポートの別やら
の掲示が必要ではないかと、思います。
⇒USB-COMや、後付のPCI用COMボードでは、
  XP 系 OSと、Win7 以後では、ドライバーの挙動が異なる場合もありますので、
  OS毎、USB-COMの種類毎に検証が必要です。
  FT232/PL2303/CH340/各メーカー毎のCOM・仮想COM

デバッグ用に、可能であれば、232用のラインモニタを入手してください。
  低価格品は、10万程度~、横取りケーブルであれば、数万~
  通信アナライザー/計測器 例
  http://www.lineeye.co.jp/html/product.html

>一般的な考え?処理が分からないため質問させていただきます。
仕様が不明な為、適切な回答は得られないと思います。
電文の、仕様はどうなっているのか?
具体的な電文の仕様を、掲示してください。
1.ASCIIの文字コード部分なのか、00h~FFhまで使うのか
2.フロー制御があるのか、無いのか。(含むX-ON/X-OFF)
  コマンドレスポンス型なのか、垂れ流し型なのかとか
3.固定長なのか、可変長なのか、
  可変長の場合は、電文長がデータに含まれるのか、
  もしくは、ターミネーションで識別するのか、など
4.含まれるデータを可視化する場合に、何を想定しているのか?
  16進ダンプなのか、文字でのダンプなのか?
  文字であれば文字コードには、日本語が含まれるか?
 ⇒DeleGate/ BackGroundworker の知識が必要になるはずです。

>制御コードを取得する必要があり
制御コードとは、何を意味していますか?
具体的には、プロトコルなどの仕様書に書かれている筈ですが?
 SOH:01h、STX:02h、ETX:03h、EOT:04h、ENQ:05h、ACK:06h
 ASCII文字列を使用した場合でも、$、!、#など記号+数字、文字を、
 制御コードに使ったりします。
歴史的経緯から、232、422、485などのシリアル通信は、
メーカー毎、機器毎の独自方式が殆どです。
一般的、汎用化は、やって出来なくは無いですが、結構複雑になります。
 通信ツール/通信サンプルソース 
 http://homepage2.nifty.com/nonnon/top5.html
FAXや、古のJUST-PCなど、具体的な通信プロトコルの仕様が定められている物もあります。
投稿者 ななしん  (社会人) 投稿日時 2015/8/7 14:50:52
何が聞きたいのか、漠然としすぎててよくわかりませんでした。
質問内容を整理し、ポイントを絞ったほうが回答が得られると思います。


>を行う前に制御コードを取得し処理を考えております。電文の始まり、終わり、異常等様々な
>制御コードがあるかと思うのですがどのように処理を行うのが良いのでしょうか。

プロトコルにより違いますので、そのプロトコルに合わせてください。
通常、どうにかして1つの電文のまとまり(Byte配列なり文字列なり)を作成し、プロトコルに
合わせて解析するという流れになると思います。

投稿者 YUU  (社会人) 投稿日時 2015/8/6 20:34:24
ななしん様。返信ありがとうございます。

Chr()でうまくいきました。当方が勘違いをしていたみたいです。

悩みの一つが解消され次に、③変換されたテキストを項目別(空白毎に区切る)にリストビュー表示

を行う前に制御コードを取得し処理を考えております。電文の始まり、終わり、異常等様々な制御コードがあるかと思うのですがどのように処理を行うのが良いのでしょうか。

シリアル通信のサンプルは接続、切断、受信、送信の簡易的なのが多く細かな制御部分の処理が見受けられません。(探し方が悪いのかも)

今回、初めてのシリアル通信制御もあっていろいろ調べながら作成している途中です。

現在は1byteずつ受信なのでそれらをCase文にて処理を行うのか一度に取得しバイト配列に突っ込み変換後、区切りごとにサーチをかけるのか。

今後も考えクラス化し汎用的な処理は一元化しようかとも考えているのですがどのようなものなのでしょうか。一般的な考え?処理が分からないため質問させていただきます。
投稿者 ななしん  (社会人) 投稿日時 2015/8/6 14:18:52
ASCIIだけなら、
Chr(Data)
ではだめなんですか?
投稿者 YUU  (社会人) 投稿日時 2015/8/6 14:10:02
仕様は
伝送方式:調歩同期式
伝送速度:9600bps
伝送コード:ASCII
データ長:8bit
パリティ:なし
ストップビット:1bit
です。
投稿者 kiku  (社会人) 投稿日時 2015/8/6 10:17:56

一般的に文字に変換できないデータを受信する場合もあり、
文字に変換するためには、受信したデータのどこから文字変換を
開始するのかを明確にする必要があります。

どういう仕様(フォーマット)でデータが受信されますか?

また、文字コードとして何を使われているのかを明確にする必要があります。
文字でも文字コードが変われば受信されるバイナリデータは変わるということです。

文字コードは何を使われていますか?

具体的な変換例は下記を参照。
http://dobon.net/vb/dotnet/string/getencoding.html
投稿者 YUU  (社会人) 投稿日時 2015/8/5 21:08:41
↑マルチスレッドを利用し、UI処理を行っております。

受信時に1byteずつ別スレッドに渡しておりうまく変換できません。

変換方法を調べているとByte配列をEncodingしているみたいで現状ではできません。

始めは受信時に全て取得し(SerialPort.ReadExisting)にてこれらを別スレッドに投げ処理を行っていたのですが、受信されるデータを1byteずつ取得する必要がありこの処理は中止。

制御コードを取得する必要があり上記は無理と判断。

タブコントロールの別タブにテキストコントロールをそれぞれ貼り付け、タブ選択イベントにて変換をかけるのも考えましたが・・・

お力を貸していただけると幸いです。

投稿者 YUU  (社会人) 投稿日時 2015/8/5 20:37:16
現在、シリアル通信処理を制御するプログラムを作成しております。

行いたい処理は下記の通りです。

①SerialPortコントロールを利用し受信イベントにてデータをキャッチ。

②データをリッチテキストボックスにて出力。(バイナリデータとテキスト文字列)

③変換されたテキストを項目別(空白毎に区切る)にリストビュー表示

↑を繰り返すアプリケーションを作成しております。

現在②の途中まで作成できたのですが行き詰ってしまい困っております。

リッチテキストボックスを2箇所設置。片方をバイナリデータ、もう片方を変換されたテキストデータ。

バイナリデータの出力は容易に行えたのですがテキストにて変換された文字列をうまく出力できません。

    Delegate Sub dlgReceive(ByVal Data As Byte)

    Private Sub SerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort.DataReceived

        'シリアルポートをオープンしていない場合、処理を行わない
        If SerialPort.IsOpen = False Then
            Return
        End If

        Try
            Dim RxLen As Integer = SerialPort.BytesToRead  '受信バイト数取得
            Dim InData(RxLen - 1) As Byte                  '受信値読み込み配列

            SerialPort.Read(InData, 0, RxLen)              '読み込み

            '1バイトずつメインスレッドにてテキストボックス書込み
            For i As Integer = 0 To InData.Length - 1
                Me.Invoke(New dlgReceive(AddressOf RcvDataToTextBox), InData(i))
            Next

        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

    End Sub

    Private Sub RcvDataToTextBox(ByVal Data As Byte)

        If IsNothing(Data) = True Then
            Return
        End If

        'バイナリ表記
        Dim strBinary As String
        strBinary = Convert.ToString(Data, 16).ToUpper()  'HEX変換・大文字
        strBinary = strBinary.PadLeft(2, "0"c)            '1文字の場合0を付加

        Try
            Me.txtbox1.AppendText(strBinary & " ")
            Me.txtbox1.SelectionStart = Me.txtbox1.Text.Length
            Me.txtbox1.ScrollToCaret()
        Catch ex As Exception
            MsgBox(ex.Message)
        End Try

    End Sub