VB.NET Serial Port 串列埠 筆記(基礎篇)
串口通訊的本質
串口通訊可以比擬為一個郵遞系統,用於在兩個地點之間傳遞信息。在這個系統中,數據就像是一封封的信件,一次只能發送一個字符,但可以連續不斷地傳遞。串口通訊的兩端,一端是發送方,負責將信件投遞到郵箱;另一端是接收方,負責從郵箱中取出信件。為了確保信件能夠正確無誤地到達目的地,雙方需要事先約定好郵遞的規則,如郵遞的速度、信件的格式等參數。
在 VB.NET 中,使用 System.IO.Ports 命名空間提供的 SerialPort 類別,可以方便地實現與串口設備的通訊。可以將 SerialPort 類別看作是郵遞系統的一個端點,通過它來發送和接收信件,實現與另一端設備的信息交流。
串口通訊的特點: 串口通訊是一種異步通訊方式,數據以位元組為單位進行傳輸。串口通訊的特點包括:通訊距離較長、通訊速率相對較低、支持全雙工通訊等。
使用串口通訊可以實現以下功能:
- 與外部設備進行數據交換,如讀取感測器數據、控制執行器等。
- 與其他電腦進行通訊,傳送和接收數據。
- 連接並控制各種串口設備,如條碼掃描器、印表機等。
串口通訊參數
使用串口通訊時,需要設置以下參數:
| 參數 | 說明 |
|---|---|
| 通訊埠名稱 | 用於識別要使用的物理通訊埠,例如「COM1」、「COM2」等。 |
| 鮑率鮑率表示每秒鐘傳輸的位元數,常用的鮑率有 9600、19200、38400、57600、115200 等。 | 表示通訊的速度,常用的鮑率如下:
|
| 數據位元 | 表示每個傳輸字符中的位元數,通常為:
|
| 停止位元 | 用於標識每個傳輸字符的結束,通常為:
|
| 同位檢查同位檢查用於檢測傳輸錯誤,常見的同位檢查方式有 None、Odd、Even、Mark、Space 等。 | 用於檢測傳輸錯誤,常見的同位檢查方式有:
|
| 流量控制 | 用於管理數據傳輸速度,以避免數據溢出或丟失。常見的流量控制方式有:
|
| 讀取超時 | 設置了在讀取操作期間等待數據的最長時間。如果在指定時間內未收到數據,讀取操作將會超時。讀取超時可以避免程序長時間阻塞等待數據。 |
| 寫入超時 | 設置了在寫入操作期間等待數據傳輸完成的最長時間。如果在指定時間內數據未完全傳輸,寫入操作將會超時。寫入超時確保了程序不會無限期地等待寫入操作完成。 |
注意: 在使用串口通訊時,通訊的兩端設備需要使用相同的通訊參數,如同在郵遞系統中,發送方和接收方需要遵循相同的郵遞規則和地址格式。只有參數匹配,數據才能在通訊線路上正確地傳輸,確保通訊的可靠性和穩定性。否則,就可能會出現通訊失敗或數據傳輸錯誤的問題。
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。這意味著通訊雙方將使用特殊字符來控制數據的傳輸和暫停,確保數據不會溢出或丟失。其他常用的流量控制方式還包括 None 和 RequestToSend,它們可以類比為不同的郵件分揀和派送策略,適用於不同的通訊場景。
3. 設置讀取和寫入超時
使用 ReadTimeout 和 WriteTimeout 屬性設置讀取和寫入操作的超時時間(以毫秒為單位)。
範例要使用的控制項:
- 無
serialPort.ReadTimeout = 1000 ' 設置讀取超時時間為 1 秒
serialPort.WriteTimeout = 1000 ' 設置寫入超時時間為 1 秒
在上述範例中,使用 ReadTimeout 和 WriteTimeout 屬性分別設置讀取和寫入超時時間為 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. 讀取數據
使用 SerialPort 的 Read、ReadLine、ReadExisting 等方法讀取串口中的數據。這三種方法在使用上有一些區別:
| 方法 | 說明 | 範例 |
|---|---|---|
Read |
從 SerialPort 輸入緩衝區讀取指定數量的位元組到緩衝區中。該方法會阻塞執行,直到讀取到指定數量的位元組或發生超時。 | |
ReadLine |
從 SerialPort 輸入緩衝區讀取一行字符,該行以換行符結束。該方法會阻塞執行,直到讀取到換行符或發生超時。 | |
ReadExisting |
讀取 SerialPort 對象的輸入緩衝區中所有立即可用的位元組,並將其作為字符串返回。該方法不會阻塞執行。 | |
範例要使用的控制項:
- 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.Invoke或Control.BeginInvoke方法將更新操作封送到 UI 線程上執行,以避免跨線程操作異常。
6. 寫入數據
使用 SerialPort 的 Write、WriteLine 等方法將數據寫入串口。這個過程也就是在將郵件投遞到郵箱中,等待郵件被送達目的地。
範例要使用的控制項:
- 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.Invoke或Control.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
上述範例中,建立了一個完整的串口通訊應用程序,它包含了以下功能:
- 開啟和關閉串口的按鈕事件處理程序。
- 發送數據的按鈕事件處理程序,將文字方塊中的內容寫入串口。
- 接收數據的事件處理程序,將接收到的數據顯示在文字方塊中。
- 錯誤處理的事件處理程序,顯示串口發生的錯誤信息。