2024年5月27日 星期一

7.VB.NET 進階篇 筆記 - 串列埠(Serial Port)

VB.NET 串列埠(Serial Port) 筆記(基礎篇)

VB.NET Serial Port 串列埠 筆記(基礎篇)

串口通訊的本質

串口通訊可以比擬為一個郵遞系統,用於在兩個地點之間傳遞信息。在這個系統中,數據就像是一封封的信件,一次只能發送一個字符,但可以連續不斷地傳遞。串口通訊的兩端,一端是發送方,負責將信件投遞到郵箱;另一端是接收方,負責從郵箱中取出信件。為了確保信件能夠正確無誤地到達目的地,雙方需要事先約定好郵遞的規則,如郵遞的速度、信件的格式等參數。

在 VB.NET 中,使用 System.IO.Ports 命名空間提供的 SerialPort 類別,可以方便地實現與串口設備的通訊。可以將 SerialPort 類別看作是郵遞系統的一個端點,通過它來發送和接收信件,實現與另一端設備的信息交流。

串口通訊的特點: 串口通訊是一種異步通訊方式,數據以位元組為單位進行傳輸。串口通訊的特點包括:通訊距離較長、通訊速率相對較低、支持全雙工通訊等。

使用串口通訊可以實現以下功能:

  • 與外部設備進行數據交換,如讀取感測器數據、控制執行器等。
  • 與其他電腦進行通訊,傳送和接收數據。
  • 連接並控制各種串口設備,如條碼掃描器、印表機等。

串口通訊參數

使用串口通訊時,需要設置以下參數:

參數 說明
通訊埠名稱 用於識別要使用的物理通訊埠,例如「COM1」、「COM2」等。
鮑率鮑率表示每秒鐘傳輸的位元數,常用的鮑率有 9600、19200、38400、57600、115200 等。 表示通訊的速度,常用的鮑率如下:
  • 9600
  • 19200
  • 38400
  • 57600
  • 115200
選擇適當的鮑率可以確保數據傳輸的穩定性和效率。
數據位元 表示每個傳輸字符中的位元數,通常為:
  • 7 位元
  • 8 位元
數據位元決定了單次傳輸可以表示的字符範圍。
停止位元 用於標識每個傳輸字符的結束,通常為:
  • 1 位元
  • 2 位元
停止位元為接收方提供了一個同步和處理接收到數據的時間間隔。
同位檢查同位檢查用於檢測傳輸錯誤,常見的同位檢查方式有 None、Odd、Even、Mark、Space 等。 用於檢測傳輸錯誤,常見的同位檢查方式有:
  • None
  • Odd
  • Even
  • Mark
  • Space
同位檢查可以提高數據傳輸的可靠性。
流量控制 用於管理數據傳輸速度,以避免數據溢出或丟失。常見的流量控制方式有:
  • None
  • XOnXOff
  • RequestToSend
流量控制確保了發送方和接收方的數據同步。
讀取超時 設置了在讀取操作期間等待數據的最長時間。如果在指定時間內未收到數據,讀取操作將會超時。讀取超時可以避免程序長時間阻塞等待數據。
寫入超時 設置了在寫入操作期間等待數據傳輸完成的最長時間。如果在指定時間內數據未完全傳輸,寫入操作將會超時。寫入超時確保了程序不會無限期地等待寫入操作完成。

注意: 在使用串口通訊時,通訊的兩端設備需要使用相同的通訊參數,如同在郵遞系統中,發送方和接收方需要遵循相同的郵遞規則和地址格式。只有參數匹配,數據才能在通訊線路上正確地傳輸,確保通訊的可靠性和穩定性。否則,就可能會出現通訊失敗或數據傳輸錯誤的問題。

SerialPort 類別的基本使用

1. 建立 SerialPort 實例

要使用 SerialPort 類別進行串口通訊,首先需要建立一個 SerialPort 實例。

範例要使用的控制項:
Imports System.IO.Ports

Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
End Class

在上述範例中,建立了一個 SerialPort 實例 serialPort,指定通訊埠為「COM1」,鮑率為 9600,無同位檢查,數據位元為 8,停止位元為 1。類似建立一個郵局,並指定了郵遞的速度、信件格式等規則。

2. 設置流量控制

使用 Handshake 屬性設置流量控制方式,以管理數據傳輸的速度和同步。好比在郵遞系統中設置郵件分揀和派送的節奏,控制郵件的流量。

範例要使用的控制項:
serialPort.Handshake = Handshake.XOnXOff

在上述範例中,使用 Handshake 屬性將流量控制設置為 XOnXOff。這意味著通訊雙方將使用特殊字符來控制數據的傳輸和暫停,確保數據不會溢出或丟失。其他常用的流量控制方式還包括 NoneRequestToSend,它們可以類比為不同的郵件分揀和派送策略,適用於不同的通訊場景。

3. 設置讀取和寫入超時

使用 ReadTimeoutWriteTimeout 屬性設置讀取和寫入操作的超時時間(以毫秒為單位)。

範例要使用的控制項:
serialPort.ReadTimeout = 1000 ' 設置讀取超時時間為 1 秒
serialPort.WriteTimeout = 1000 ' 設置寫入超時時間為 1 秒

在上述範例中,使用 ReadTimeoutWriteTimeout 屬性分別設置讀取和寫入超時時間為 1 秒。這意味著,如果在 1 秒內沒有收到或發送完,就會觸發超時事件,避免程序長時間地等待或阻塞。

4. 開啟和關閉串口

使用 SerialPort 進行通訊前需要先開啟串口,就像郵件的處理,首先郵局要先開門。通訊完成後,應該關閉串口以釋放資源。

範例要使用的控制項:
  • Button1:開啟串口按鈕
  • Button2:關閉串口按鈕
  • Label1:顯示串口狀態的標籤
Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例(在此省略相關設置)
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
    
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Try
            If Not serialPort.IsOpen Then
                serialPort.Open() ' 開啟串口
                Label1.Text = "串口已開啟"
            Else
                MessageBox.Show("串口已經開啟")
            End If
        Catch ex As Exception
            MessageBox.Show("開啟串口時出錯:" & ex.Message)
        End Try
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        If serialPort.IsOpen Then
            serialPort.Close() ' 關閉串口
            Label1.Text = "串口已關閉"
        Else
            MessageBox.Show("串口尚未開啟")
        End If
    End Sub
End Class

在上述範例中,使用 Open 方法開啟串口,如同打開郵局的大門,讓郵件可以在郵遞系統中流通。使用 Close 方法關閉串口,則是關閉郵局的大門,停止郵件的收發。在開啟和關閉串口時,通過 Label1 標籤來顯示串口的狀態。

限制說明:

  • 在開啟串口時,需要確保指定的串口名稱可用,且沒有被其他應用程序佔用。如果串口已被佔用,嘗試開啟串口時會引發 UnauthorizedAccessException 異常。
  • 為了避免此問題,可以在開啟串口前先使用 SerialPort.GetPortNames 方法獲取可用的串口列表,並進行相應的處理,避免發生衝突或錯誤。

5. 讀取數據

使用 SerialPortReadReadLineReadExisting 等方法讀取串口中的數據。這三種方法在使用上有一些區別:

方法 說明 範例
Read 從 SerialPort 輸入緩衝區讀取指定數量的位元組到緩衝區中。該方法會阻塞執行,直到讀取到指定數量的位元組或發生超時。
Dim buffer(255) As Byte
Dim bytesRead As Integer = serialPort.Read(buffer, 0, buffer.Length)
ReadLine 從 SerialPort 輸入緩衝區讀取一行字符,該行以換行符結束。該方法會阻塞執行,直到讀取到換行符或發生超時。
Dim line As String = serialPort.ReadLine()
ReadExisting 讀取 SerialPort 對象的輸入緩衝區中所有立即可用的位元組,並將其作為字符串返回。該方法不會阻塞執行。
Dim data As String = serialPort.ReadExisting()
範例要使用的控制項:
  • TextBox1:顯示接收到的數據的文字方塊
Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例(在此省略相關設置)
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
    
    Private Sub SerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
        ' 接收數據的事件處理程序
        Dim receivedData As String = serialPort.ReadExisting() ' 讀取接收到的數據
        Me.Invoke(Sub()
                      TextBox1.AppendText(receivedData) ' 將接收到的數據顯示在文字方塊中
                  End Sub)
    End Sub
End Class

在上述範例中,通過註冊 DataReceived 事件的處理程序 SerialPort_DataReceived,當接收到數據時,會觸發該事件。在事件處理程序中,使用 ReadExisting 方法讀取接收到的數據。通過 TextBox1 文字方塊在界面上顯示接收到的數據,可以看到通訊的結果。

限制說明:

  • 由於 DataReceived 事件是在非 UI 線程上觸發的,因此在事件處理程序中更新 UI 元素時,需要使用 Control.InvokeControl.BeginInvoke 方法將更新操作封送到 UI 線程上執行,以避免跨線程操作異常。

6. 寫入數據

使用 SerialPortWriteWriteLine 等方法將數據寫入串口。這個過程也就是在將郵件投遞到郵箱中,等待郵件被送達目的地。

範例要使用的控制項:
  • TextBox2:用於輸入要發送的數據的文字方塊
  • Button3:發送數據的按鈕
Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例(在此省略相關設置)
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
    
    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        If serialPort.IsOpen Then
            Dim data As String = TextBox2.Text
            serialPort.WriteLine(data) ' 寫入數據到串口
        Else
            MessageBox.Show("請先開啟串口")
        End If
    End Sub
End Class

在上述範例中,在 Button3 的點擊事件處理程序中,使用 WriteLine 方法將 TextBox2 中輸入的字串寫入串口。

SerialPort 的事件處理

SerialPort 類別提供了多個事件,可以用於監視串口的狀態變化和接收到的數據,如同在郵遞系統中設置了郵件追蹤和通知服務,隨時掌握郵件的動態和內容。

1. DataReceived 事件

當串口接收到數據時,會觸發 DataReceived 事件,可以在事件處理程序中讀取接收到的數據。

範例要使用的控制項:
  • TextBox1:顯示接收到的數據的文字方塊
Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例(在此省略相關設置)
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
    
    Private Sub SerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
        ' 接收數據的事件處理程序
        Dim receivedData As String = serialPort.ReadExisting() ' 讀取接收到的數據
        Me.Invoke(Sub()
                      TextBox1.AppendText(receivedData) ' 將接收到的數據顯示在文字方塊中
                  End Sub)
    End Sub
End Class

在上述範例中,通過註冊 DataReceived 事件的處理程序 SerialPort_DataReceived,當接收到數據時,會觸發該事件。在事件處理程序中,使用 ReadExisting 方法讀取接收到的數據。通過 TextBox1 文字方塊在界面上顯示接收到的數據,看到通訊的結果。

限制說明:

  • 由於 DataReceived 事件是在非 UI 線程上觸發的,因此在事件處理程序中更新 UI 元素時,需要使用 Control.InvokeControl.BeginInvoke 方法將更新操作封送到 UI 線程上執行,以避免跨線程操作異常。

2. ErrorReceived 事件

當串口發生錯誤時,會觸發 ErrorReceived 事件,可以在事件處理程序中處理錯誤情況。

範例要使用的控制項:
Public Class Form1
    Private WithEvents serialPort As SerialPort

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例(在此省略相關設置)
        serialPort = New SerialPort("COM1", 9600, Parity.None, 8, StopBits.One)
    End Sub
    
    Private Sub SerialPort_ErrorReceived(sender As Object, e As SerialErrorReceivedEventArgs)
        ' 錯誤處理的事件處理程序
        Me.Invoke(Sub()
                      MessageBox.Show("發生錯誤:" & e.EventType.ToString())
                  End Sub)
    End Sub
End Class

在上述範例中,通過註冊 ErrorReceived 事件的處理程序 SerialPort_ErrorReceived,當發生錯誤時,會觸發該事件。在事件處理程序中,獲取錯誤類型,然後通過 MessageBox 在界面上顯示錯誤信息,知道發生了什麼問題。

常見問題與解決方案

1. 串口被佔用

如果嘗試開啟一個已被其他應用程序佔用的串口,會引發 UnauthorizedAccessException 異常。

解決方案:
  • 確保其他應用程序已關閉,並釋放了對串口的佔用。
  • 使用 SerialPort.GetPortNames 方法獲取可用的串口列表,並選擇一個未被佔用的串口。

2. 接收數據不完整或亂碼

接收到的數據不完整或出現亂碼,可能是由於通訊參數設置不正確或數據傳輸出現問題。

解決方案:
  • 檢查通訊的兩端設備是否使用相同的通訊參數(波特率、數據位元、停止位元、校驗位)。
  • 確保接收端的緩衝區大小足夠,可以通過設置 SerialPort.ReadBufferSize 屬性來調整緩衝區大小。
  • 在接收數據時,可以使用定時器或延遲來等待數據完全到達,然後再進行讀取。

3. 寫入數據失敗

寫入數據失敗,可能是由於串口未開啟、寫入超時或硬件問題導致的。

解決方案:
  • 確保在寫入數據之前已成功開啟串口。
  • 檢查寫入操作是否超時,可以通過設置 SerialPort.WriteTimeout 屬性來調整超時時間。
  • 檢查硬件連接是否正常,確保傳輸線路沒有損壞或鬆動。

完整範例

以下是一個完整的範例,演示了如何使用 SerialPort 進行通訊:

範例要使用的控制項:
  • Button1:開啟串口按鈕
  • Button2:關閉串口按鈕
  • Button3:發送數據按鈕
  • Label1:顯示串口狀態的標籤
  • TextBox1:顯示接收到的數據的文字方塊
  • TextBox2:用於輸入要發送的數據的文字方塊
Imports System.IO.Ports

Public Class Form1
    Private WithEvents serialPort As SerialPort
  
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' 建立 SerialPort 實例
        serialPort = New SerialPort()
        serialPort.PortName = "COM1"
        serialPort.BaudRate = 9600
        serialPort.Parity = Parity.None
        serialPort.DataBits = 8
        serialPort.StopBits = StopBits.One
  
        ' 註冊事件處理程序
        AddHandler serialPort.DataReceived, AddressOf SerialPort_DataReceived
        AddHandler serialPort.ErrorReceived, AddressOf SerialPort_ErrorReceived
    End Sub
  
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Try
            If Not serialPort.IsOpen Then
                ' 開啟串口
                serialPort.Open()
                Label1.Text = "串口已開啟"
            Else
                MessageBox.Show("串口已經開啟")
            End If
        Catch ex As Exception
            MessageBox.Show("開啟串口時出錯:" & ex.Message)
        End Try
    End Sub
  
    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        If serialPort.IsOpen Then
            ' 關閉串口
            serialPort.Close()
            Label1.Text = "串口已關閉"
        Else
            MessageBox.Show("串口尚未開啟")
        End If
    End Sub
  
    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        If serialPort.IsOpen Then
            Dim data As String = TextBox2.Text
            ' 寫入數據到串口
            serialPort.WriteLine(data)
        Else
            MessageBox.Show("請先開啟串口")
        End If
    End Sub
  
    Private Sub SerialPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
        ' 接收數據的事件處理程序
        Dim receivedData As String = serialPort.ReadExisting() ' 讀取接收到的數據
        Me.Invoke(Sub()
                      TextBox1.AppendText(receivedData) ' 將接收到的數據顯示在文字方塊中
                  End Sub)
    End Sub
  
    Private Sub SerialPort_ErrorReceived(sender As Object, e As SerialErrorReceivedEventArgs)
        ' 錯誤處理的事件處理程序
        Me.Invoke(Sub()
                      MessageBox.Show("發生錯誤:" & e.EventType.ToString())
                  End Sub)
    End Sub
  
End Class

上述範例中,建立了一個完整的串口通訊應用程序,它包含了以下功能:

  • 開啟和關閉串口的按鈕事件處理程序。
  • 發送數據的按鈕事件處理程序,將文字方塊中的內容寫入串口。
  • 接收數據的事件處理程序,將接收到的數據顯示在文字方塊中。
  • 錯誤處理的事件處理程序,顯示串口發生的錯誤信息。