シリアルポート通信

タグの編集
投稿者 hori  (社会人) 投稿日時 2015/4/13 12:56:29
VB2010Exにて、自分の店で使うアプリを趣味半分で作っているのですが
先日シリアルポートで電話番号を通知してくれる機械を買いました。
わからないのでメーカーに問い合わせたら
個別の案件には答えられないと、けんもほろろでしたのでここにおずがり致します。

その機械にはテスト送信の機能があって

通信仕様は

規格:仮想COMポート (僕の環境では "COM3" で間違いないはずです)
同期方式:非同期
通信速度:9600bps
ビット長:7
パリティ:偶数
ストップビット:1

で、テスト送信されるデータは、STX(02H)+21キャラクタ+ETX(03H)、とのことです。
                             ↓
(これはアスキー文字列であり、VBの ReadLine() で読み込む時に
TextBox に表示可能な文字に自動変換されるものと認識していますが
それで間違いないでしょうか?)

そこで、Form に Button を貼り付け以下のようなコードを書きました。
(書いたと言ってもMSフォーラムのコピペですが・・・)

=====================================

 Private Sub Button3_Click(sender As System.Object, e As System.EventArgs) Handles Button3.Click

        Dim returnStr As String = ""

        Dim port As SerialPort = New SerialPort("COM3", 9600, Parity.Even, 7, StopBits.One)

        port.Open()

        port.ReadTimeout = 10000

        Try

            Do
                Dim Incoming As String = port.ReadLine()
                If Incoming Is Nothing Then
                    Exit Do
                Else
                    returnStr &= Incoming & vbCrLf
                End If
            Loop

        Catch ex As TimeoutException
            returnStr = "Error: Serial Port read timed out."
        Finally
            If port IsNot Nothing Then port.Close()
        End Try

        MsgBox(returnStr)

 End Sub

==============================================

これを実行し、ボタンを押してタイムアウトまでの間に
件の機械をテスト送信させるのですが 
"Error: Serial Port read timed out."が表示されます。

コードがおかしいとすればどこがおかしいのでしょうか?

コードに問題がなければ機械側の問題ということでしょうか?

ご教示よろしくお願いいたします。


投稿者 shu  (社会人) 投稿日時 2015/4/13 14:04:52
STXとETXは開始と終了を表すマーカーなので
これらも含めて読込ます。

ReadLineはテキストとして改行が見つかるまで読込を
行いタイムアウトまで待ちます。今回の場合改行はないので
必ずタイムアウトしてしまいます。

ReadByteを使用して1byteづつチェックしてSTXの次から
読み始めETXで終了を判断します。間のByte列を
System.Text.Encoding.Ascii.GetStringにて文字列化します。

このような待ち受けの場合、
OpenのみをClickイベントで行い
SerialPortのDataReceivedイベントでデータを取得した方がよいです。
Closeはまた別のイベントで行うようにするとよいかと思います。
投稿者 hori  (社会人) 投稿日時 2015/4/14 00:09:33
shu さま。ありがとうございます。

本日は所用で外出し、今、帰宅しましたので
明日、仰せの線で試してみます。

光明が見えました。結果はまたご報告いたします。

ありがとうございます。
投稿者 hori  (社会人) 投稿日時 2015/4/14 14:11:22
昨日の、shu さまの助言を得て試してみました。

Byte 型の変数を配列にして

dt(n) = port.ReadByte

とやるとエラーになってしまい、その解決方法が分からなかったので
どうせ使う文字は数字とSTXとETXだけなので
アスキーの文字コード表から拾って下記のようにやってみました。

=============

        Dim returnStr As String = ""

        Dim port As SerialPort = New SerialPort("COM3", 9600, Parity.Even, 7, StopBits.One)

        port.Open()

        port.DtrEnable = True
        port.RtsEnable = True

        port.ReadTimeout = 10000

        Dim STX As Integer = 2
        Dim ETX As Integer = 3

        Dim dt0 As Integer = 48
        Dim dt1 As Integer = 49
        Dim dt2 As Integer = 50
        Dim dt3 As Integer = 51
        Dim dt4 As Integer = 52
        Dim dt5 As Integer = 53
        Dim dt6 As Integer = 54
        Dim dt7 As Integer = 55
        Dim dt8 As Integer = 56
        Dim dt9 As Integer = 57

        Try

            Do

                If port.ReadByte = STX Then

                    'MsgBox("STX")

                ElseIf port.ReadByte = ETX Then

                    'MsgBox("ETX")

                    Exit Do
                Else

                    'MsgBox("dt")

                    Select Case port.ReadByte

                        Case dt0
                            returnStr &= "0"
                        Case dt1
                            returnStr &= "1"
                        Case dt2
                            returnStr &= "2"
                        Case dt3
                            returnStr &= "3"
                        Case dt4
                            returnStr &= "4"
                        Case dt5
                            returnStr &= "5"
                        Case dt6
                            returnStr &= "6"
                        Case dt7
                            returnStr &= "7"
                        Case dt8
                            returnStr &= "8"
                        Case dt9
                            returnStr &= "9"
                        Case Else
                            returnStr &= "?"
                    End Select

                    'MsgBox(returnStr)

                End If
            Loop

        Catch ex As TimeoutException

            returnStr &= "Error"

        End Try

        MsgBox(returnStr)

        port.Close()

        port.Dispose()

================

[ ' ]のついている行は実験および動作確認のためです。

これでやると、ちゃんと [STX] は確認できますが
[ETX]に至らず途中でタイムアウトになり
本来21ケタの数列であるはずのデータが[ ? ]の混じった10ケタの文字列になります。

Do ループの中の Select Case を止めてやると [ETX] まで至ります。

それで、[STX] を確認した時点で、ReadExisting を入れてみました。

=================

        Dim returnStr As String = ""
        Dim port As SerialPort = New SerialPort("COM3", 9600, Parity.Even, 7, StopBits.One)
        port.Open()
        port.DtrEnable = True
        port.RtsEnable = True
        port.ReadTimeout = 10000

        Dim STX As Integer = 2
        Dim ETX As Integer = 3

        Try

            Do

                If port.ReadByte = STX Then

                    'MsgBox("STX")

                    System.Threading.Thread.Sleep(1000)

                    returnStr = port.ReadExisting

                    MsgBox(returnStr)

                ElseIf port.ReadByte = ETX Then

                    MsgBox("ETX")

                    Exit Do
                Else

                End If
            Loop

        Catch ex As TimeoutException

            returnStr &= "Error"

        End Try

        MsgBox(returnStr)

        port.Close()

        port.Dispose()

================

この実験時に、[STX] 確認のために Msgbox を表示させたのですが
その [OK] ボタンを押すタイミングで表示されるデータの量が変わることに気付きました。

そこで、Msgbox の代わりに 

System.Threading.Thread.Sleep(1000)

を入れてみると、とりあえず期待するデータを得られるようになりました。

とはいえ、[ETX] に至らずタイムアウトして終わりますが・・・・・

なんとなく、首のところがイガイガした感じですが「まぁ、いいか」って感じです。
投稿者 shu  (社会人) 投稿日時 2015/4/14 15:22:22
> 本来21ケタの数列であるはずのデータが[ ? ]の混じった10ケタの文字列になります。
ここが違うのではないでしょうか?
?になるということは数字でないものが届いているということになります。
10桁というと通常の固定電話の番号と桁数が一致するので11桁目以降は必要ないと
考えられます。数字として取得できる部分だけ文字列連結されればそれでよいかと思います。
投稿者 hori  (社会人) 投稿日時 2015/4/14 17:44:04
shu さま。ありがとうございます。

その後いろいろ実験してみましたが

ReadByte を使う方は、僕の知識の範囲では何をやっても [ ? ] が出るので
ReadExisting の方でやってみようと思います。

毎回同じ結果が出るわけではありませんが、だいたい
スリープタイムを 10ms にすると14ケタ、15ms で20ケタ
16ms で規定通りの21ケタの数列になり、これらの場合は MsgBoxに [EXT] も表示されます。
17ms以上で21ケタの数列と間隔をおいて [ ・ ] らしきものが表示され [EXT] には至りません。
というより、 [ ・ ] らしきものが [EXT] なのかなぁと思います。

僕の環境ではデータの読み込みにそれだけの時間がかかるということなのでしょう。
ReadByte の方は、読む位置が微妙にずれているのではないかなと思っています。

まぁ、このやり方で本当にいいのか不安は不安ですが
何にせよ、String のデータにできれば数字だけ抜き出すくらいはできるので何とかなりそうです。
ありがとうございました。
投稿者 shu  (社会人) 投稿日時 2015/4/15 00:59:26
詳しくみていない箇所がありました。

If port.ReadByte = STX Then
・・・
ElseIf port.ReadByte = ETX Then
・・・
Else
    Select Case port.ReadByte
・・・
End If


これだと
> If port.ReadByte = STX Then 
ここで1回目
> ElseIf port.ReadByte = ETX Then
ここで2回目
>  Select Case port.ReadByte
ここで3回目
と1回のループで3Byte分読み込んでしまいます。
1回目のループではSTXの条件に合うので1回で済みますが2回目の
ループ以降3Byte分読んでしまいます。

Dim bytData = port.ReadByte()   '--- 実際には 
Select Case bytData
    Case STX
・・・
    Case ETX
・・・
    Case Else
・・・
End Select

のようにSelect Caseの前で1回だけReadしてその取得した値で判定を
するようにした方がよいです。
ReadExistingだとETXの判断をすることが出来ず送信された内容を含めて
読み込んでしまいます。
投稿者 hori  (社会人) 投稿日時 2015/4/16 22:30:12
Shu さま。昨日は一日外仕事でしたので失礼しました。

おっしゃる通り、Select を使えばバッチリでした。
これで、首のイガイガもすっきりしました。 ありがとうございました。
投稿者 Sizu  (社会人) 投稿日時 2015/7/3 02:57:14
horiさま、皆さま、
 私も通信プログラムの簡素化を期待してvb2010exのSerialPortを利用したアプリを作成中ですが、
 STXを送る方法が解りません。他のブログでも探し試験しましたが決定的な解決方法が解りません。
 現状では、最も直接的なコーディング方法として、以下の様にしましたが、受信側のマイコンは
 制御コード(02H)ではなく、32H(数字の2)で受信されます。別の制御コード(03H)は、33H(数字の3)
 で受信されます。
      SerialPort.Write(&H2)

 済みませんが、STXやETXをどの様なコーディング方法で送るのか、ご教授ください。
投稿者 まりもん  (社会人) 投稿日時 2015/7/3 16:29:07
SerialPort.Write(&H2)
の場合、数字の「2」がString型にキャストされて送信されています。
ですので、Writeを使うのであれば、
SerialPort.Write(Chr(&H2))
とすればいいと思います。
投稿者 Sizu  (社会人) 投稿日時 2015/7/5 16:11:39
まりもんさま、皆さま

 STXの送信に苦慮していましたSizuです。
 まりもんさんから頂いたアドバイス通りで送信出来ました。

  ☆ SerialPort.Write(Chr(&H2)) 'STX送信

 的確なアドバイスをありがとうございました。

 Chr(integer)関数は、入力の数値に対して文字コードを返して
 くれる関数なのですね。私はてっき逆にり文字の"2"(=H32)に変換
 されると思い込んでいました。(正直以外に思いました)

 ストリンクと文字、Val()とAsc()、.ToStringとChr()の使い分け
 が未だに良く分かっておりませんね。これか勉強します。
投稿者 Sizu  (社会人) 投稿日時 2015/7/9 22:52:00
horiさま、まりもんさま、皆さま、

 Sizuです。horiさま、済みません、も少し居候させてください。

 PC(vb2010)とマイコンと間のシリアル通信ですが、PCで送受信
 (往復)が出来るようになりました。受信にはSerialPort.ReadByte()
 を使用しています。

 しかしながら、(horiさんと同じ様に?)正常受信の成功率が8%
 に留まっている他、マイコンの即時応答はオシロで確認済みですが、
 30秒、ひどい時には1分以上してから来ます。これではまだまだ
 です。

 Serial受信はセカンドスレッドが使用されますが、Formスレッドとの
 間はPublic変数にしFormスレッドで参照のみします。
 初めは受信にdelegateで送達通知のみしていましたが、これも処理
 負荷になると思い止めました。

 受信信号の遅延、不達の要因を調べたいのですが、サービスキュー、
 スレッドの使用状況を確認する方法をどなたかご存知でしょうか?

 ご教授いただけますと幸甚です。
投稿者 (削除されました)  () 投稿日時 2015/7/10 02:45:10
(削除されました)
投稿者 まりもん  (社会人) 投稿日時 2015/7/10 13:34:15
どのような処理をしているのか分かりませんが、正常なデータを受信できないから
遅延、不達とお考えなのでしょうか?
また、オシロで確認されたとありますが、PCのコネクタ側での計測なのでしょうか?
接続形態が分かりませんが、ケーブル長やケーブルの質による信号劣化等は
考えられないでしょうか?
PCからのデータ送信後、1バイトも受信しないのでしょうか?

プログラムに問題があるのであれば、出来る限りソースを提示したほうがアドバイスを
得られると思います。
投稿者 shu  (社会人) 投稿日時 2015/7/10 13:48:02
スレタイがよくないのでしょうが

複数の異なる問題が1スレに含まれるのは後々分かりにくい事になります
別スレを立てた方がよいです >Sizu さん
投稿者 Sizu  (社会人) 投稿日時 2015/7/10 18:56:11
horiさま、皆さま、

 こんにちはSizuです。

 お邪魔して申し訳ありませんでした。
 私のシリアル通信の問題も簡単には行けそうになく、別スレッドを立てる様に致します。

 horiさま、うまく行くをお祈りしていします。