忍者ブログ

VB.NET-TIPS などプログラミングについて

VB.NETのTIPS(小技集)を中心に、Javascript その他のプログラミングについて少し役に立つ情報を発信します。いわゆる個人的な忘備録ですが、みなさんのお役に立てれば幸いです。

SerialPortコントロールの使い方その6(ハンドシェィクによるデータ送受信)

シリアル通信を利用して外部装置とデータの送受信を行う場合、パソコン側が主で、外部装置が従となる関係が多いと思います。 外部装置は、パソコン側から送信されてくるコマンドに対応して処理等を行い、処理結果を返してくるやり方です。

私の経験上、この外部装置との通信で多いのはシーケンサ(PLC)等とのデータの送受信がありました。 シーケンサはメーカ毎にデータの送受信を行うコマンドが全く異なります。 その為、各メーカ毎のコマンドの規約に合わせて処理を行う様にプログラムするわけです。

今回の例は、下図の様なPLCからのデータ読み出しと書込みの簡単なコマンドを想定しています。

読み出しコマンドも書き込みコマンドも、最初にPLC側にコマンドを送信し、結果を受信することを想定しています。
さて、このコマンドを処理するクラスを作成するのですが、今までのクラスを大部分生かすわけですが、 以下のメソッドを作成します。

  • Open シリアルポートのオープン処理を行う。(今までと同様)
  • Close シリアルポートのクローズ処理を行う。(今までと同様)
  • ReadData PLCのアドレスを与えて、PLCデータの読込処理を行う。
  • WriteData PLCのアドレス・データを与えて、PLCデータへの書込処理を行う。

今回のクラスにおいてデータ受信ハンドラの処理内では、専ら応答データを受信することとしています。 受信データの最終文字としての CR(0DH) を検知した時点で応答受信が在ったものとしています。

ReadData WriteData 共に最初にPLC側にコマンドを送信しますので、 この部分を別関数 SendData として宣言しています。 また、応答受信用には別関数 ReceiveData として宣言しています。

ReceiveData では応答受信を待つために、100msecの待ちをシステムの Sleep 関数で50回ループしています。 そのループの中に応答受信フラグがONするのをみて、受信が在ったことを検知しています。 (この待ち時間を短くすればプログラムの応答速度はよくなります。)

SerialPortコントロールの使い方その6(シリアル通信クラス・ハンドシェィク受信クラス)

Imports System.IO.Ports

''' -----------------------------------------------------------------------
''' 
''' シリアル通信クラス・ハンドシェィク受信
''' 
''' -----------------------------------------------------------------------
Public Class ClsSerialRcvHandshake
    ''' 
    ''' シリアルポートクラス
    ''' 
    ''' 
    Private SerialPort As SerialPort

    ''' 
    ''' コマンド応答受信フラグ
    ''' 
    ''' 
    ''' コマンドを送信した後での応答を受信したフラグ。送受信文字列のターミネータ[ETX](0x03)を発見した時にオンする
    ''' 
    Private fRxResponse As Boolean = False

    ''' 
    ''' 読込データ格納先
    ''' 
    ''' データ受信イベントで蓄えられる
    Private RxBuf() As Byte

    ''' 
    ''' エラーメッセージ
    ''' 
    Private strLastError As String = ""

    ''' 
    ''' 応答メッセージ
    ''' 
    Private strLastResponse As String = ""

    ''' 
    ''' 送受信文字列のターミネータ(0x0d)
    ''' 
    Private Const CMD_TERM As Byte = &HD

    ''' -----------------------------------------------------------------------
    ''' 
    ''' コンストラクタ
    ''' 
    ''' <param name="SerialPort">シリアルポートコントロール</param>
    ''' -----------------------------------------------------------------------
    Sub New(ByVal SerialPort As SerialPort)
        'シリアルポートの退避
        Me.SerialPort = SerialPort
        '受信イベントのハンドラ設定
        AddHandler Me.SerialPort.DataReceived, AddressOf DataReceivedHandler
    End Sub

    ''' -----------------------------------------------------------------------
    ''' 
    ''' エラーメッセージプロパティ
    ''' 
    ''' -----------------------------------------------------------------------
    ReadOnly Property LastError As String
        Get
            Return Me.strLastError
        End Get
    End Property

    ''' -----------------------------------------------------------------------
    ''' 
    ''' 応答メッセージプロパティ
    ''' 
    ''' -----------------------------------------------------------------------
    ReadOnly Property LastResponse As String
        Get
            Return Me.strLastResponse
        End Get
    End Property

    ''' -----------------------------------------------------------------------
    ''' 
    ''' ポートオープン
    ''' 
    ''' True:正常終了, False:エラー
    ''' -----------------------------------------------------------------------
    Function Open() As Boolean
        '戻り値初期化
        Open = False
        Try
            'ポートチェック
            If Me.SerialPort.IsOpen = False Then
                '未オープンならば、オープンする

                'ポート設定(ここは通信相手に合わせる)
                Me.SerialPort.PortName = "COM7"
                Me.SerialPort.BaudRate = 9600
                Me.SerialPort.DataBits = 8
                Me.SerialPort.Parity = Parity.None
                Me.SerialPort.StopBits = StopBits.One
                'オープン
                Me.SerialPort.Open()
            End If
            '結果を返す
            Open = Me.SerialPort.IsOpen

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

    ''' -----------------------------------------------------------------------
    ''' 
    ''' ポートクローズ
    ''' 
    ''' True:正常終了, False:エラー
    ''' -----------------------------------------------------------------------
    Function Close() As Boolean
        '戻り値初期化
        Close = False
        Try
            'ポートチェック
            If Me.SerialPort.IsOpen = True Then
                'オープン済みならば、クローズする
                Me.SerialPort.Close()
            End If
            '結果を返す
            Close = Not (Me.SerialPort.IsOpen)

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

    ''' -----------------------------------------------------------------------
    ''' 
    ''' データ受信ハンドラ
    ''' 
    ''' 
    ''' [STX]から始まるデータをコマンドバッファに格納し[ETX]受信時に文字列に変換
    ''' 
    ''' -----------------------------------------------------------------------
    Private Sub DataReceivedHandler(sender As Object, e As SerialDataReceivedEventArgs)
        Try
            Dim SP As SerialPort = CType(sender, SerialPort)

            'バッファのバイト数チェック
            Dim DataLen As Integer = SP.BytesToRead
            If DataLen = 0 Then
                Exit Sub
            End If

            'データ長チェック
            If DataLen > 4096 Then
                DataLen = 4096
            End If

            '読込バッファの確保
            Dim Buf(DataLen) As Byte
            '読込
            SP.Read(Buf, 0, DataLen)
            '
            If RxBuf Is Nothing Then
                '格納バッファの領域確保
                RxBuf = Array.CreateInstance(GetType(Byte), DataLen)
                'コピー
                Array.Copy(Buf, RxBuf, DataLen)
            Else
                '後ろに追加する
                Dim Length As Integer = RxBuf.Length
                ReDim Preserve RxBuf(Length + DataLen - 1)
                Buf.CopyTo(RxBuf, Length)
            End If

            '格納先の最後の文字が「CR」(0x0d)か確認する
            If RxBuf(UBound(RxBuf)) = CMD_TERM Then
                'コマンド応答受信フラグON
                Me.fRxResponse = True
            End If

        Catch ex As Exception
            'エラー処理
        End Try
    End Sub

    ''' -----------------------------------------------------------------------
    ''' 
    ''' 接点エリアリード
    ''' 
    ''' <param name="Addr">接点アドレス(4文字:[10^3][10^2][10^1][16^0]</param>
    ''' <param name="Data">接点データ</param>
    ''' 応答受信結果(True:OK, False:NG)
    ''' 
    ''' 送信コマンド:"RD" + NNNN + CR
    ''' 受信コマンド:"OK" + MMMM + CR ⇒ データが正常の場合(MMMM:データ値)
    '''       :"NG" + 0000 + CR ⇒ データがエラーの場合(エラーでは0000:ゼロ)
    ''' 
    ''' -----------------------------------------------------------------------
    Public Function ReadData(ByVal Addr As String, ByRef Data As Integer) As Boolean
        '戻り値初期化
        ReadData = False
        Try
            '最終応答,エラーをクリア
            Me.strLastResponse = ""
            Me.strLastError = ""

            '接点NOの4文字チェック
            If Addr.Length <> 4 Then
                Exit Function
            End If
            '送信コマンド生成
            Dim strCMD As String = "RD" & Addr

            'コマンド送信
            If Me.SendData(strCMD) = False Then
                Return False
            End If

            'コマンド受信
            Dim strRX As String = ""
            If Me.ReceiveData(strRX) = False Then
                Return False
            End If
            '最終応答を退避
            Me.strLastResponse = strRX

            '接点結果を返す
            If strRX.Substring(0, 2) = "OK" Then
                'データ値をInteger変換
                Data = CInt(strRX.Substring(2, 4))
                '正常を返す
                ReadData = True
            Else
                'エラー受信
                Me.strLastError = "NG応答"
            End If

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

    ''' -----------------------------------------------------------------------
    ''' 
    ''' 接点への書込み
    ''' 
    ''' <param name="Addr">接点アドレス(4文字:[10^3][10^2][10^1][16^0]</param>
    ''' <param name="Data">接点データ</param>
    ''' 応答受信結果(True:OK, False:NG)
    ''' 
    ''' 送信コマンド:"WR" + NNNN + ":" + MMMM + CR
    ''' 受信コマンド:"OK" + CR ⇒ 書込み正常の場合
    '''       :"NG" + CR ⇒ 書込みエラーの場合
    ''' 
    ''' -----------------------------------------------------------------------
    Public Function WriteData(ByVal Addr As String, ByVal Data As Integer) As Boolean
        '戻り値初期化
        WriteData = False
        Try
            '最終応答,エラーをクリア
            Me.strLastResponse = ""
            Me.strLastError = ""

            'アドレス4文字チェック
            If Addr.Length <> 4 Then
                Exit Function
            End If
            '送信コマンド生成
            Dim strCMD As String = "WR" & Addr & ":" + Data.ToString("0000")

            'コマンド送信
            If Me.SendData(strCMD) = False Then
                Return False
            End If

            'コマンド受信
            Dim strRX As String = ""
            If Me.ReceiveData(strRX) = False Then
                Return False
            End If
            '最終応答を退避
            Me.strLastResponse = strRX

            '接点結果を返す
            If strRX.Substring(0, 2) = "NG" Then
                'エラー受信
                Me.strLastError = "NG応答"
                'エラーを返す
                Return False
            End If

            '正常を返す
            Return True

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

    ''' -----------------------------------------------------------------------
    ''' 
    ''' コマンド送信
    ''' 
    ''' <param name="strSend">送信文字列</param>
    ''' 送信結果(True:OK, False:NG)
    ''' 
    ''' -----------------------------------------------------------------------
    Private Function SendData(ByVal strSend As String) As Boolean
        '戻り値初期化
        SendData = False
        Try
            'ポートオープンチェック
            If Me.SerialPort.IsOpen = False Then
                Return False
            End If

            'Shift JISとしてバイト型配列に変換
            Dim bytesData As Byte()
            bytesData = System.Text.Encoding.GetEncoding(932).GetBytes(strSend)

            'CRの1バイト分領域拡張
            Dim nIdx As Integer = UBound(bytesData)
            ReDim Preserve bytesData(nIdx + 1)
            bytesData(nIdx + 1) = CMD_TERM

            'コマンド応答受信フラグOFF
            Me.fRxResponse = False

            'コマンド送信
            Me.RxBuf = Nothing  '受信バッファのクリア
            Me.SerialPort.Write(bytesData, 0, bytesData.Length)
            '正常
            Return True

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

    ''' -----------------------------------------------------------------------
    ''' 
    ''' レスポンス受信処理
    ''' 
    ''' <param name="RcvRes">受信レスポンス</param>
    ''' 受信結果(True:OK, False:NG)
    ''' 
    ''' -----------------------------------------------------------------------
    Private Function ReceiveData(ByRef RcvRes As String) As Boolean
        '戻り値初期化
        ReceiveData = False
        Try
            '-----
            '受信フラグがONするまで待つ
            '-----
            'リトライ件数
            Dim nRetry As Integer = 50  '50×100msec=5sec

            While nRetry > 0
                System.Threading.Thread.Sleep(100)
                Application.DoEvents()
                If Me.fRxResponse = True Then
                    '受信フラグONの場合、ループを抜ける
                    Exit While
                End If
                nRetry -= 1
            End While

            '-----
            'タイムアウトチェック
            '-----
            If nRetry = 0 Then
                'タイムアウト表示
                Me.strLastError = "Receive Time Out Error!"
                Return False
            End If

            '受信バイトを文字列変換
            Dim strRx As String = System.Text.Encoding.GetEncoding(932).GetString(Me.RxBuf)
            strRx = strRx.Substring(0, strRx.Length - 1)    '最後の「CR」は省く

            '-----
            '受信データ処理
            '-----
            ''BCCの計算

            '受信レスポンスを返す
            RcvRes = strRx

            '結果正常
            Return True

        Catch ex As Exception
            'エラー処理
        End Try
    End Function

End Class



このクラスを使用した例を以下のソースに示します。

SerialPortコントロールの使い方その6(シリアル通信クラス・ハンドシェィク受信クラス)使用例

Public Class frmSerialRcvHandshake

    'シリアル通信クラス
    Private mclsSerial As ClsSerialRcvHandshake

    ''' 
    ''' フォームクローズイベント
    ''' 
    Private Sub frmSerialRcv_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
        'シリアル通信のクローズ
        mclsSerial.Close()
    End Sub

    ''' 
    ''' フォームロードイベント
    ''' 
    Private Sub frmSerialRcv_Load(sender As Object, e As EventArgs) Handles Me.Load
        'シリアル通信クラスの生成
        mclsSerial = New ClsSerialRcvHandshake(Me.SerialPort1)
        'シリアル通信のオープン
        If mclsSerial.Open() = False Then
            'オープンエラー(必要があれば処理する)
        End If
    End Sub

    ''' 
    ''' 受信処理
    ''' 
    Private Sub btnRX_Click(sender As Object, e As EventArgs) Handles btnRX.Click
        Try
            Dim nData As Integer
            Dim strAddr As String = CInt(txtRxAddr.Text).ToString("0000")
            Me.txtRxData.Text = ""
            Me.txtRxRes.Text = ""
            Me.txtRxErr.Text = ""
            '受信
            If Me.mclsSerial.ReadData(strAddr, nData) = True Then
                Me.txtRxData.Text = nData.ToString("0000")
                Me.txtRxRes.Text = Me.mclsSerial.LastResponse
                Me.txtRxErr.Text = ""
            Else
                Me.txtRxData.Text = ""
                Me.txtRxRes.Text = Me.mclsSerial.LastResponse
                Me.txtRxErr.Text = Me.mclsSerial.LastError
            End If

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

    ''' 
    ''' 送信処理
    ''' 
    Private Sub btnTX_Click(sender As Object, e As EventArgs) Handles btnTX.Click
        Try
            Dim nData As Integer = CInt(Me.txtTxData.Text)
            Dim strAddr As String = CInt(txtTxAddr.Text).ToString("0000")
            Me.txtTxRes.Text = ""
            Me.txtTxErr.Text = ""
            '送信 
            If Me.mclsSerial.WriteData(strAddr, nData) = True Then
                Me.txtTxRes.Text = Me.mclsSerial.LastResponse
                Me.txtTxErr.Text = ""
            Else
                Me.txtTxRes.Text = Me.mclsSerial.LastResponse
                Me.txtTxErr.Text = Me.mclsSerial.LastError
            End If

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

End Class


このフォームは、以下の図の様に、シリアルコントロール及び、 受信処理用のアドレス、受信結果表示データ、応答結果文字列表示用、エラー表示用の各テキストボックスと、 送信処理用にアドレス、送信データ、応答結果文字列表示用、エラー表示用の各テキストボックスを 画面に張り付けてあります。

シリアル通信クラスをフォームの静的変数として宣言します。 受信ボタンをクリックした時には、アドレス値を取得し、シリアル通信クラスの ReadData メソッドにそのアドレスを渡して、 受信を行います。メソッドが正常に終了した時には、データ値と受信文字列を表示し、エラーが在った場合にはエラー内容を表示しています。
また、送信ボタンをクリックした時には、アドレス値及びデータ値を取得し、シリアル通信クラスの WriteData メソッドにアドレス、データ値を渡して、 送信を行います。メソッドが正常に終了した時には、受信文字列を表示し、エラーが在った場合にはエラー内容を表示しています。

以下の図は、受信処理での正常時・エラー発生時・タイムアウトエラー時の表示を示します。
(尚、外部装置の外部装置の替わりとして、前回まで使用してきたRS232Cのテストツールを同様に使用しています。)

また、以下の図は、送信処理での正常時・エラー発生時・タイムアウトエラー時の表示を示します。


最後に、このシリアル通信クラスの問題となるかもしれない点として、 受信処理及び送信処理で装置側から応答が返るまでその処理からは抜け出してこないことがあります。 応答が返るまで待つことが特に問題無い場合はこのままでも利用できると思います。

もし速度的に問題があり、受信・送信コマンドを送ってすぐにそのメソッドから戻りたい場合には、 応答を受信したタイミングでイベントを発生させればできるとは思います。
ただ、パソコン側が主導的に外部装置を制御する場合でしたら、そこまでは必要ないとも思います。

USB ⇒ RS232C 変換ケーブル

シリアル通信に関連してですが、 最近のPC、特にノートPCの場合ではRS232Cのコネクタがついていることは殆んどありません。
そのため、以下の様なUSBを使ったRS232Cの変換ケーブルが使えそうです。
iBUFFALO USBシリアルケーブル(USBtypeA to D-sub9ピン)1.0m ブラックスケルトン BSUSRC0610BS
サンワサプライ USB-RS232Cコンバータ USB-CVRS9
Plugable USB‐9ピンRS232シリアルアダプター (Prolific社製 PL2303HX Rev Dチップセット採用)

関連する記事

SerialPortコントロールの使用方法:[SerialPort,Invoke]
SerialPortコントロールの使い方その2
SerialPortコントロールの使い方その3(外部装置からの垂れ流しデータ受信)
SerialPortコントロールの使い方その4(データ受信時にイベントを発生させる)
SerialPortコントロールの使い方その5(データ受信時及び、信号変化時にイベントを発生させる)
PR

コメント

コメントを書く