2018年12月15日 星期六

VB.NET 之 SerialPort 顯示訊號至 Chart XY 線圖

使用 VB.NET 來使用 SerialPort 類別進行序列埠操作。

由於未來不會持續寫 VB.NET 技術相關文章,因此分類至 C# 中。

VB.NET 使用 SerialPort


在 VB.NET 使用 SerialPort 是使用 Imports System.IO.Ports 這個類別,其實現 SerialPort 去讀取 COM 資料時,有分別為 GUI(Form Application) 及 Console Application 兩種做法,都不同。


Console 版本


Console 中沒有辦法用 Me.Invoke 實踐非同步訊息機制(詳情見 Reference. 1),所以使用 SerialPort 中的一個方法 (ReadExists) ,用此方法來讀取 buffer 中現有的資料,且也不可以使用 ReadLine() 的方法,因為 ReadLine() 會阻塞整個 Console Application 的執行緒,導致 Application 卡住,故只能用 ReadExists() 來做,且 ReadExists() 需要自行判斷資料的 "\r\n" 換行字元,避免資料長短不一。

Example:

Imports System.IO.Ports
Imports System.Net
Imports System.Text

Module Module1

    Sub Main()
        Console.WriteLine("Serial Port List: ")
        GetSerialPortNames()
        Console.WriteLine("Enter the port you want to listen (Ex: COM3):")

        Try
            Dim portname As String = Console.ReadLine()
            Console.WriteLine("Start listen to " + portname)
            ReceiveSerialData(portname)
        Catch ex As Exception
            Console.WriteLine(ex.Message)
            Console.WriteLine("Please Run Program Again.....")
        End Try


        Console.WriteLine("Press any key to exit program.")
        Console.ReadLine()

    End Sub

    Sub GetSerialPortNames()
        ' Show all available COM ports.
        For Each sp As String In My.Computer.Ports.SerialPortNames
            Console.WriteLine(sp.ToString())
        Next
    End Sub

    Function ReceiveSerialData(portname As String)
        ' Receive strings from a serial port.
        Dim returnStr As String = ""
        Dim DataReceived As String

        Dim comport As IO.Ports.SerialPort = Nothing
        comport = New SerialPort()

        comport.PortName = portname
        comport.BaudRate = 9600
        comport.Parity = Parity.None
        comport.DataBits = 8
        comport.StopBits = StopBits.One
        comport.Handshake = Handshake.RequestToSend
        comport.DtrEnable = True
        comport.ReadTimeout = 500
        comport.Open()

        Console.WriteLine("Waiting for Data to be Received...")

        Try
            Do
                DataReceived = comport.ReadExisting()
                If DataReceived Is "" Then
                    Continue Do
                End If
                Console.WriteLine("Data received -> {0}", DataReceived)
            Loop
        Catch
        Finally
            comport.Close() ' Close port
        End Try

    End Function


End Module

範例中沒有自動判斷 ReadExists() 是否遇到 \r\n 去換行。


GUI 版本


GUI 版本中,可以使用事件系統來傳遞值,因此有一個方法叫做 port_DataReceived,用來接收非同步情形下傳入的值事件,接著要在此控制 GUI 的主執行緒,一定要透過 Delegate 的委派機制去呼叫控制,所以使用 updateCharts() 方法來控制 GUI 的主執行緒 XY 圖之值。




XY 線圖是透過 ToolBox 中的 Charts 去新增,然後在屬性控制中,有一個 Series 欄位(型別為一集合類) 是透過 Series 的類別去控制它的線圖,可以從程式控制 (於範例中 34 行) 看到這樣的方式,或是直接在屬性欄位做修正也可以。



Example:

Imports System.IO.Ports
Imports System.Text
Imports System.Windows.Forms.DataVisualization.Charting

Public Class Form1

    Private port As New SerialPort("COM4", 9600, Parity.None, 8, StopBits.One)
    Private ReadOnly _startOfText As String = System.Text.Encoding.ASCII.GetChars(New Byte() {2})
    Private ReadOnly _endOfText As String = System.Text.Encoding.ASCII.GetChars(New Byte() {4})

    Public Event MessageReceived(ByVal message As String)
    Public Event DataIgnored(ByVal text As String)

    Private message As String

    Private s As New Series

    Private _buffer As StringBuilder = New StringBuilder

    Private logFile As String

    Dim count As Int32 = 1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        s.ChartType = SeriesChartType.Line
        s.Name = "Series Line 1"
        Chart1.Series.Add(s)
    End Sub

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        My.Computer.FileSystem.WriteAllText("C:\logFile.csv", logFile, False)
        MessageBox.Show("Saved")
    End Sub

    Private Sub updateText()
        ListBox1.Items.Add(Me.message)
        ListBox1.TopIndex = ListBox1.Items.Count - 1

        'add in xy
        Try
            s.Points.AddXY(count, Int32.Parse(Me.message))
            count += 1
            logFile += "," + Me.message
        Catch
        End Try

    End Sub

    Private Sub port_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
        Try
            Dim msg As String = port.ReadExisting()
            message = msg
            Me.Invoke(New EventHandler(AddressOf updateText))
        Catch ex As Exception
            MessageBox.Show(ex.Message())
        End Try
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        AddHandler port.DataReceived, New SerialDataReceivedEventHandler(AddressOf port_DataReceived)
        Try
            port.Open()
        Catch ex As Exception
            MessageBox.Show(ex.Message())
        End Try
    End Sub

    Public Sub AppendText(ByVal text As String)
        _buffer.Append(text)
        While processBuffer(_buffer)
        End While
    End Sub

    Private Function processBuffer(ByVal buffer As StringBuilder) As Boolean
        Dim foundSomethingToProcess As Boolean = False
        Dim current As String = buffer.ToString()
        Dim stxPosition As Integer = current.IndexOf(_startOfText)
        Dim etxPosition As Integer = current.IndexOf(_endOfText)
        If (stxPosition >= 0) And (etxPosition >= 0) And (etxPosition > stxPosition) Then
            Dim messageText As String = current.Substring(0, etxPosition + 1)
            buffer.Remove(0, messageText.Length)
            If stxPosition > 0 Then
                RaiseEvent DataIgnored(messageText.Substring(0, stxPosition))
                messageText = messageText.Substring(stxPosition)
            End If
            RaiseEvent MessageReceived(messageText)
            foundSomethingToProcess = True
        ElseIf (stxPosition = -1) And (current.Length <> 0) Then
            buffer.Remove(0, current.Length)
            RaiseEvent DataIgnored(current)
            foundSomethingToProcess = True
        End If
        Return foundSomethingToProcess
    End Function


    Public Sub Flush()
        If _buffer.Length <> 0 Then
            RaiseEvent DataIgnored(_buffer.ToString())
        End If
    End Sub



    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        port.Close()
    End Sub
End Class


Reference:
1. https://stackoverflow.com/questions/26689762/how-to-use-invoke-required-and-invoke-in-console-application

https://stackoverflow.com/questions/11458850/how-to-read-serial-port-communication-into-buffer-and-parse-out-complete-message
https://dotblogs.com.tw/billchung/2012/01/20/66860
https://dotblogs.com.tw/chou/2009/03/20/7614
https://social.msdn.microsoft.com/Forums/vstudio/en-US/6e5ee56f-a201-4a27-91f9-1bfbf7ab1f89/problem-in-receiving-data-through-serial-port-when-using-readline?forum=csharpgeneral


沒有留言:

張貼留言

© ERIC RILEY , 自由無須告知轉貼
Background Japanese Sayagata by Olga Libby