C# SerialPort類實現(xiàn)串口通信的實戰(zhàn)指南
前言
在工業(yè)自動化、物聯(lián)網(wǎng)和嵌入式系統(tǒng)中,串口通信仍然扮演著不可替代的角色。盡管網(wǎng)絡(luò)通信技術(shù)發(fā)展迅速,但在一些對穩(wěn)定性、實時性要求較高的場景中,串口通信依然具有廣泛的應(yīng)用基礎(chǔ)。
C# 語言通過 System.IO.Ports 命名空間中的 SerialPort 類,為開發(fā)者提供了便捷的串口編程接口。本文將從基礎(chǔ)使用出發(fā),深入講解 SerialPort 類的屬性、事件處理、異常捕獲及 WinForm 中的實際應(yīng)用,幫助開發(fā)開發(fā)穩(wěn)定、安全的串口通信程序。
一、SerialPort 類
1、SerialPort 類的基本屬性與構(gòu)造函數(shù)
C# 提供了 SerialPort 類用于串口通信,它支持多種構(gòu)造函數(shù)。一個完整的構(gòu)造函數(shù)如下:
public SerialPort(
string portName,
int baudRate,
Parity parity,
int dataBits,
StopBits stopBits
)
例如,設(shè)置串口為 COM1、波特率 9600、無奇偶校驗、數(shù)據(jù)位 8 和停止位 1 的代碼如下:
SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
2、屬性說明
| 屬性名稱 | 描述 | 常用取值 |
|---|---|---|
| PortName | 串口號,如 "COM1"、"COM2" | 具體的系統(tǒng)端口號 |
| BaudRate | 數(shù)據(jù)傳輸速率 | 9600、115200 等 |
| Parity | 奇偶校驗 | None、Even、Odd |
| DataBits | 每個數(shù)據(jù)幀的位數(shù) | 8 |
| StopBits | 數(shù)據(jù)幀結(jié)束標志 | One、Two |
| Handshake | 數(shù)據(jù)傳輸時的流控制措施 | None、XOnXOff、RequestToSend |
| ReadTimeout | 讀取數(shù)據(jù)的超時時間 | 毫秒數(shù),如 500 |
| WriteTimeout | 寫入數(shù)據(jù)的超時時間 | 毫秒數(shù),如 500 |
| CtsHolding | true 表示對方設(shè)備已準備好接收數(shù)據(jù) | true、false,需硬件 CTS 支持 |
| CDHolding | true 表示檢測到載波信號 | true、false,需硬件 CD 支持 |
二、串口事件:數(shù)據(jù)接收與 UI 更新
在串口通信中,數(shù)據(jù)接收是核心環(huán)節(jié)。SerialPort 類通過 DataReceived 事件實現(xiàn)異步接收。在 WinForm 應(yīng)用中,事件處理函數(shù)運行在后臺線程,不能直接更新 UI 控件。為此,應(yīng)使用 BeginInvoke 方法進行異步更新,避免阻塞主線程。
public delegate void UpdateUIDelegate(byte[] data);
private void Comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
byte[] receivedData = new byte[8];
try
{
serialPort.Read(receivedData, 0, 6);
this.BeginInvoke(new UpdateUIDelegate(UpdateUI), receivedData);
}
catch (TimeoutException ex)
{
MessageBox.Show("超時:" + ex.Message);
}
}
private void UpdateUI(byte[] data)
{
string receivedStr = System.Text.Encoding.Default.GetString(data);
this.textBoxData.Text = receivedStr;
}
三、串口的打開、關(guān)閉與參數(shù)配置
1、打開串口
打開串口前應(yīng)確保參數(shù)配置完成,并進行異常捕獲:
try
{
serialPort.Open();
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show("權(quán)限不足或串口正在使用:" + ex.Message);
}
catch (IOException ex)
{
MessageBox.Show("I/O錯誤:" + ex.Message);
}
2、關(guān)閉串口與安全退出
關(guān)閉串口時,若存在未完成的線程操作,可能導(dǎo)致死鎖。
建議使用標志變量控制流程:
private bool isReceiving = false;
private bool isTryingToClose = false;
public void SafeCloseSerialPort()
{
isTryingToClose = true;
while (isReceiving)
{
System.Windows.Forms.Application.DoEvents();
}
serialPort.Close();
}
3、參數(shù)配置示例
SerialPort serialPort = new SerialPort(); serialPort.PortName = "COM5"; serialPort.BaudRate = 115200; serialPort.Parity = Parity.None; serialPort.DataBits = 8; serialPort.StopBits = StopBits.One; serialPort.Handshake = Handshake.None; serialPort.ReadTimeout = 500; serialPort.WriteTimeout = 500;
四、常見異常處理策略
1、端口占用與權(quán)限問題
try
{
serialPort.Open();
}
catch (UnauthorizedAccessException ex)
{
MessageBox.Show("串口訪問權(quán)限不足:" + ex.Message);
}
catch (IOException ex)
{
MessageBox.Show("串口可能不存在或被占用:" + ex.Message);
}
2、超時異常處理
try
{
byte[] buffer = new byte[serialPort.BytesToRead];
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
}
catch (TimeoutException ex)
{
MessageBox.Show("數(shù)據(jù)讀取超時:" + ex.Message);
}
3、未打開串口的操作
if (!serialPort.IsOpen)
{
MessageBox.Show("串口尚未打開,請先調(diào)用 Open() 方法。");
return;
}
4、CtsHolding 與 CDHolding 支持
public static bool IsHardwareFlowControlSupported(SerialPort port)
{
try
{
bool originalRts = port.RtsEnable;
bool originalDtr = port.DtrEnable;
port.RtsEnable = false;
Thread.Sleep(10);
bool ctsLow = port.CtsHolding;
port.RtsEnable = true;
Thread.Sleep(10);
bool ctsHigh = port.CtsHolding;
port.RtsEnable = originalRts;
port.DtrEnable = originalDtr;
return ctsLow != ctsHigh;
}
catch
{
return false;
}
}
五、WinForm 環(huán)境下串口通信的實現(xiàn)示例
以下是一個完整的 WinForm 示例界面和核心代碼結(jié)構(gòu):
using System.IO.Ports;
using Timer = System.Windows.Forms.Timer;
namespace AppSerialPortExplained
{
public partial class Form1 : Form
{
private SerialPort serialPort = new SerialPort();
private Timer statusTimer = new Timer();
public Form1()
{
InitializeComponent();
RefreshPortList();
comboBoxBaud.Items.AddRange(new object[] { "1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200" });
comboBoxBaud.SelectedIndex = 3;
comboBoxParity.Items.AddRange(new object[] { "None", "Even", "Odd", "Mark", "Space" });
comboBoxParity.SelectedIndex = 0;
comboBoxDataBits.Items.AddRange(new object[] { "5", "6", "7", "8" });
comboBoxDataBits.SelectedIndex = 3;
comboBoxStopBits.Items.AddRange(new object[] { "One", "Two", "OnePointFive" });
comboBoxStopBits.SelectedIndex = 0;
comboBoxHandshake.Items.AddRange(new object[] { "None", "XOnXOff", "RequestToSend", "RequestToSendXOnXOff" });
comboBoxHandshake.SelectedIndex = 0;
numericUpDownReadTimeout.Value = 500;
numericUpDownWriteTimeout.Value = 500;
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.ErrorReceived += SerialPort_ErrorReceived;
serialPort.PinChanged += SerialPort_PinChanged;
InitializeTimer();
}
private void InitializeTimer()
{
statusTimer.Interval = 100;
statusTimer.Tick += StatusTimer_Tick;
}
private void RefreshPortList()
{
string selectedPort = comboBoxPort.Text;
comboBoxPort.Items.Clear();
comboBoxPort.Items.AddRange(SerialPort.GetPortNames());
if (comboBoxPort.Items.Count > 0)
{
if (comboBoxPort.Items.Contains(selectedPort))
comboBoxPort.Text = selectedPort;
else
comboBoxPort.SelectedIndex = 0;
}
}
private void buttonRefresh_Click(object sender, EventArgs e)
{
RefreshPortList();
}
private void buttonOpen_Click(object sender, EventArgs e)
{
if (!serialPort.IsOpen)
{
try
{
serialPort.PortName = comboBoxPort.Text;
serialPort.BaudRate = int.Parse(comboBoxBaud.Text);
switch (comboBoxParity.Text)
{
case "None": serialPort.Parity = Parity.None; break;
case "Even": serialPort.Parity = Parity.Even; break;
case "Odd": serialPort.Parity = Parity.Odd; break;
case "Mark": serialPort.Parity = Parity.Mark; break;
case "Space": serialPort.Parity = Parity.Space; break;
}
serialPort.DataBits = int.Parse(comboBoxDataBits.Text);
switch (comboBoxStopBits.Text)
{
case "One": serialPort.StopBits = StopBits.One; break;
case "Two": serialPort.StopBits = StopBits.Two; break;
case "OnePointFive": serialPort.StopBits = StopBits.OnePointFive; break;
}
serialPort.Handshake = (Handshake)Enum.Parse(typeof(Handshake), comboBoxHandshake.Text.Replace("XOnXOff", "XonXOff"));
serialPort.ReadTimeout = (int)numericUpDownReadTimeout.Value;
serialPort.WriteTimeout = (int)numericUpDownWriteTimeout.Value;
serialPort.Open();
}
catch (Exception ex)
{
MessageBox.Show("打開串口失?。? + ex.Message);
}
}
}
}
}
效果預(yù)覽

六、常見問題與解決方案
在實際開發(fā)過程中,使用 SerialPort 類時會遇到許多常見問題,下面列舉并詳細介紹解決方案:
死鎖問題與 UI 更新阻塞
在調(diào)用 serialPort.Close() 時,如果數(shù)據(jù)接收線程仍在運行,采用 Invoke 調(diào)用 UI 更新方法會導(dǎo)致同步等待,最終引起死鎖問題。解決這一問題的方法是改為使用 BeginInvoke 進行異步調(diào)用,以避免線程阻塞。
串口線程安全性問題
在多線程環(huán)境下,數(shù)據(jù)接收線程與 UI 主線程可能同時訪問共享資源,若不加保護,容易引起數(shù)據(jù)競爭問題。通常的解決辦法是采用標志控制(如 isReceiving 和 isTryingToClose)以及使用 Application.DoEvents() 循環(huán)確保所有后臺線程結(jié)束后再關(guān)閉串口。
異常捕獲不足
許多開發(fā)在編寫串口通信代碼時,往往忽略了對各種異常(超時、I/O 錯誤、未打開串口等)的充分捕獲。應(yīng)在關(guān)鍵操作(如 Open、Read、Write)處使用 try-catch 結(jié)構(gòu),將異常信息反饋給用戶,并記錄日志以便后續(xù)分析。
串口數(shù)據(jù)粘包或格式不正確
在數(shù)據(jù)連續(xù)傳輸?shù)膱鼍爸?,串口可能會因為?shù)據(jù)粘包的問題導(dǎo)致解析錯誤。為解決這一問題,建議在數(shù)據(jù)傳輸協(xié)議中明確數(shù)據(jù)邊界,如采用特定的分隔符,或者在數(shù)據(jù)頭部增加包長度信息,然后在接收時進行數(shù)據(jù)拆包解析。
總結(jié)
通過對 SerialPort 類的詳細解析,本文展示了如何在 WinForm 環(huán)境下正確設(shè)置串口參數(shù)、打開關(guān)閉串口以及處理常見的異常情況。合理的異常捕獲、線程安全機制以及 UI 數(shù)據(jù)更新策略,不僅提高了應(yīng)用的穩(wěn)定性,也為編寫高質(zhì)量串口通信程序提供了有效的技術(shù)支持。
在工業(yè)自動化、嵌入式設(shè)備通信等領(lǐng)域,串口通信依然是不可替代的技術(shù)手段。隨著國產(chǎn)軟硬件生態(tài)的不斷完善,開發(fā)在串口通信方面的實踐經(jīng)驗也日益豐富。面對未來不斷變化的硬件通信需求,開發(fā)應(yīng)繼續(xù)關(guān)注異常自愈和智能數(shù)據(jù)解析技術(shù)的進步,為行業(yè)應(yīng)用提供更全面、可靠的解決方案。
以上就是C# SerialPort類實現(xiàn)串口通信的實戰(zhàn)指南的詳細內(nèi)容,更多關(guān)于C# SerialPort串口通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#如何判斷.Net?Framework版本是否滿足軟件運行需要的版本
這篇文章主要介紹了C#如何判斷.Net?Framework版本是否滿足軟件運行需要的版本問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
C#中多態(tài)現(xiàn)象和多態(tài)的實現(xiàn)方法
這篇文章主要介紹了C#中多態(tài)現(xiàn)象和多態(tài)的實現(xiàn)方法,較為詳細的分析了多態(tài)的原理與C#實現(xiàn)多態(tài)的方法,以及相關(guān)的注意事項,需要的朋友可以參考下2015-05-05
C#?DLL跨語言調(diào)用的兩種實現(xiàn)方法
C#使用foreach遍歷哈希表(hashtable)的方法

