C#環(huán)境下串口通信技術(shù)的技術(shù)體系與工程實(shí)踐詳解
串口通信(Serial Communication)是工業(yè)控制、嵌入式開發(fā)、物聯(lián)網(wǎng)領(lǐng)域最基礎(chǔ)也最持久的通信方式。盡管 USB、以太網(wǎng)已普及,RS-232/RS-485 串口因其簡(jiǎn)單可靠、實(shí)時(shí)性強(qiáng)、抗干擾能力好,仍在自動(dòng)化設(shè)備、醫(yī)療儀器、GPS 模塊等場(chǎng)景占據(jù)核心地位。本文系統(tǒng)梳理 C# 環(huán)境下串口通信的技術(shù)體系與工程實(shí)踐。
一、串口通信基礎(chǔ)概念
1. 物理層標(biāo)準(zhǔn)
RS-232:點(diǎn)對(duì)點(diǎn)通信,傳輸距離通常不超過 15 米,電平標(biāo)準(zhǔn) ±3V 至 ±15V,抗共模干擾能力較弱。常見于 PC 與外設(shè)直連、調(diào)制解調(diào)器。
RS-485:差分信號(hào)傳輸,支持總線型組網(wǎng)(一主多從),距離可達(dá) 1200 米,抗干擾能力顯著優(yōu)于 RS-232。工業(yè)現(xiàn)場(chǎng)總線、PLC 通信的首選。
RS-422:差分傳輸?shù)珒H支持一主一從,已逐漸被 RS-485 替代。
TTL 串口:?jiǎn)纹瑱C(jī)直接輸出的 3.3V/5V 電平,需通過 MAX232/MAX485 芯片轉(zhuǎn)換為標(biāo)準(zhǔn) RS 電平才能與 PC 通信。
2. 通信參數(shù)四要素
波特率(Baud Rate):每秒傳輸?shù)拇a元數(shù),常見 9600、115200、921600 等。雙方必須嚴(yán)格一致,否則數(shù)據(jù)完全錯(cuò)亂。
數(shù)據(jù)位:通常為 8 位,兼容早期 7 位 ASCII 的場(chǎng)景已罕見。
停止位:1 位、1.5 位或 2 位,用于幀同步。停止位越多,抗時(shí)鐘漂移能力越強(qiáng),但效率降低。
校驗(yàn)位:無校驗(yàn)、奇校驗(yàn)、偶校驗(yàn)?,F(xiàn)代通信多依賴更高層的 CRC 校驗(yàn),物理層校驗(yàn)位使用減少。
3. 流控制機(jī)制
硬件流控(RTS/CTS、DTR/DSR):通過專用信號(hào)線告知對(duì)方是否準(zhǔn)備好接收數(shù)據(jù),防止緩沖區(qū)溢出。適用于高速、大數(shù)據(jù)量傳輸。
軟件流控(XON/XOFF):通過發(fā)送特殊字符(0x13/0x11)控制數(shù)據(jù)流,節(jié)省硬件連線,但無法傳輸二進(jìn)制數(shù)據(jù)(與控制字符沖突)。
二、.NET 串口架構(gòu)設(shè)計(jì)
System.IO.Ports 命名空間
.NET Framework 時(shí)代內(nèi)置的 SerialPort 類是 C# 串口開發(fā)的標(biāo)準(zhǔn)入口。該類封裝了 Win32 API 的串口操作,提供同步(Read/Write)與異步(BeginRead/BeginWrite)兩種模式。
核心設(shè)計(jì)特點(diǎn):
- 事件驅(qū)動(dòng)模型:DataReceived 事件在接收緩沖區(qū)有數(shù)據(jù)時(shí)觸發(fā)。但需注意,該事件在輔助線程引發(fā),直接操作 UI 需通過 Invoke 機(jī)制跨線程調(diào)度。
- 緩沖區(qū)管理:底層使用操作系統(tǒng)提供的讀寫緩沖區(qū),應(yīng)用層通過 ReadBufferSize/WriteBufferSize 調(diào)節(jié)容量。工業(yè)場(chǎng)景下,過小緩沖區(qū)可能導(dǎo)致高波特率時(shí)數(shù)據(jù)丟失。
- 編碼處理:默認(rèn)使用 ASCII 編碼,處理中文或二進(jìn)制數(shù)據(jù)時(shí)必須顯式指定為 Encoding.UTF8 或直接操作字節(jié)流。
三、數(shù)據(jù)接收的三種模式
1. 模式一:事件驅(qū)動(dòng)(推薦用于不規(guī)則數(shù)據(jù))
設(shè)備主動(dòng)上報(bào)、數(shù)據(jù)包長(zhǎng)度不固定時(shí),依賴 DataReceived 事件最自然。但事件觸發(fā)次數(shù)與數(shù)據(jù)量無固定對(duì)應(yīng)關(guān)系——一次事件可能包含多個(gè)數(shù)據(jù)包,也可能僅收到半個(gè)包。
關(guān)鍵挑戰(zhàn):粘包與分包。串口是字節(jié)流協(xié)議,無消息邊界概念。接收方必須維護(hù)狀態(tài)機(jī),按協(xié)議幀頭、長(zhǎng)度字段、校驗(yàn)和進(jìn)行重組。
典型場(chǎng)景:GPS 模塊以 NMEA 0183 格式輸出,每條語句以 $ 開頭、\r\n 結(jié)尾。接收緩沖區(qū)可能同時(shí)包含完整語句和下一個(gè)語句的前半段。
2. 模式二:輪詢讀?。ㄟm合固定周期)
在定時(shí)器(Timer)中周期性調(diào)用 Read 方法檢查緩沖區(qū)。實(shí)現(xiàn)簡(jiǎn)單、邏輯線性,但 CPU 占用率與輪詢頻率成正比。僅適用于低實(shí)時(shí)性要求的調(diào)試工具。
3. 模式三:異步流式讀?。?NET 6+ 推薦)
利用 SerialPort.BaseStream.ReadAsync 配合 PipeReader,實(shí)現(xiàn)真正的異步零拷貝處理。這是現(xiàn)代 .NET 的高性能方案,避免線程池線程阻塞,特別適合高并發(fā)串口服務(wù)器。
四、協(xié)議設(shè)計(jì)與數(shù)據(jù)完整性
1. 幀結(jié)構(gòu)設(shè)計(jì)原則
工業(yè)通信協(xié)議通常采用以下結(jié)構(gòu):

2. 校驗(yàn)算法選擇
累加和校驗(yàn):計(jì)算簡(jiǎn)單,適合短幀、低誤碼率環(huán)境。但漏檢率較高,兩位錯(cuò)誤可能相互抵消。
CRC-8/16:工業(yè)標(biāo)準(zhǔn),檢錯(cuò)能力強(qiáng)。Modbus RTU 協(xié)議采用 CRC-16,CAN 總線采用 CRC-15。C# 可通過查表法或位運(yùn)算實(shí)現(xiàn),避免每次計(jì)算都遍歷多項(xiàng)式除法。
異或校驗(yàn):計(jì)算最快,但檢錯(cuò)能力最弱,僅適合臨時(shí)調(diào)試。
3. 超時(shí)與重傳機(jī)制
串口通信無內(nèi)置確認(rèn)機(jī)制,應(yīng)用層必須實(shí)現(xiàn)請(qǐng)求-應(yīng)答模式:
- 響應(yīng)超時(shí):發(fā)送指令后啟動(dòng)定時(shí)器,規(guī)定時(shí)間內(nèi)未收到應(yīng)答視為失敗
- 重傳策略:固定重試次數(shù)(通常 3 次),超過則上報(bào)通信故障
- 幀間隔:Modbus 等協(xié)議要求幀間保持 3.5 字符時(shí)間的靜默,避免總線沖突
五、代碼實(shí)現(xiàn)
using System;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
namespace DAL
{
public class AsySerialDal
{
private static readonly object syncRoot = new object();
const int COMDAL_RECVBUF_SIZE = 512;
bool _isReceivingdal = false;
private string m_portdal = "COMX";
bool bComOpenDal = false;//標(biāo)志
SerialPort SerialPort = new SerialPort();
byte[] ComRecvBuf = new byte[COMDAL_RECVBUF_SIZE + 1];
public delegate void ReturnRecStr(byte[] RecveiveInfo, int datalen,string portName);
public event ReturnRecStr ReturnRecStrEvent;
//打開串口的方法
public bool OpenPort(string PortName, int baurate)
{
m_portdal = PortName;
bool ret = false;
try
{
if (bComOpenDal == false)
{
SerialPort.PortName = PortName;//端口號(hào)
SerialPort.BaudRate = baurate;//波特率
SerialPort.Parity = Parity.Odd;//偶校驗(yàn)位
SerialPort.DataBits = 8;//數(shù)據(jù)位為8
SerialPort.StopBits = StopBits.One;//1個(gè)停止位
SerialPort.ReadTimeout = 500;
SerialPort.RtsEnable = true;
SerialPort.Handshake = Handshake.None;//控制協(xié)議
SerialPort.ReceivedBytesThreshold = 1;
SerialPort.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
SerialPort.Open();
bComOpenDal = true;
}
}
catch (Exception ex)
{
}
if (SerialPort.IsOpen)
{
ret = true;
}
return ret;
}
//關(guān)閉串口的方法
public void ClosePort()
{
if (bComOpenDal == true)
{
SerialPort.DataReceived -= SerialPort_DataReceived;
SerialPort.Close();
if (!SerialPort.IsOpen)
{
}
bComOpenDal = false;
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
lock (syncRoot)
{
_isReceivingdal = true;
if (ComRecvBuf != null && bComOpenDal == true)
{
SerialPort sp = (SerialPort)sender;
while (sp.IsOpen && sp.BytesToRead > 0)
{
int len = sp.BytesToRead > COMDAL_RECVBUF_SIZE ? COMDAL_RECVBUF_SIZE : sp.BytesToRead;
sp.Read(ComRecvBuf, 0, len);
if (ReturnRecStrEvent != null)
{
ReturnRecStrEvent(ComRecvBuf, len, m_portdal);//推送到上一層。
}
}
}
_isReceivingdal = false;
}
}
//向串口發(fā)送數(shù)據(jù)
public void SendCommandByte(byte[] WriteBuffer)
{
try
{
if (bComOpenDal == true && SerialPort.IsOpen == true)
{
SerialPort.Write(WriteBuffer, 0, WriteBuffer.Length);
}
}
catch (Exception ex)
{
}
}
}
}
六、實(shí)踐中的典型問題
問題一:打開端口后首包數(shù)據(jù)亂碼
根因:多數(shù) USB 轉(zhuǎn)串口芯片在打開端口瞬間會(huì)輸出緩沖區(qū)殘留數(shù)據(jù)或芯片自檢信息。SerialPort.DiscardInBuffer 可在打開后清空輸入緩沖區(qū),但部分芯片在硬件層面即輸出,軟件無法攔截。
對(duì)策:打開端口后延遲 100-500ms 再發(fā)送首條指令;或在協(xié)議層增加握手階段,待設(shè)備穩(wěn)定后再進(jìn)入業(yè)務(wù)通信。
問題二:高波特率下數(shù)據(jù)丟失
現(xiàn)象:115200 波特率以上時(shí),偶發(fā)字節(jié)丟失或校驗(yàn)錯(cuò)誤。
排查路徑:
- 確認(rèn) USB 轉(zhuǎn)串口芯片規(guī)格:CH340 理論支持 2Mbps,但部分廉價(jià)模塊晶振精度不足,高波特率誤差累積
- 檢查 ReadBufferSize 是否足夠:Windows 默認(rèn) 4096 字節(jié),高速連續(xù)數(shù)據(jù)應(yīng)增大至 64KB
- 驗(yàn)證事件處理耗時(shí):DataReceived 中若執(zhí)行復(fù)雜解析或 UI 更新,可能錯(cuò)過下一次事件
- 關(guān)閉流控制后測(cè)試:硬件流控信號(hào)線接觸不良會(huì)導(dǎo)致發(fā)送方誤判為"接收方未就緒"
問題三:多線程并發(fā)訪問
SerialPort 實(shí)例非線程安全,同時(shí)讀寫會(huì)引發(fā)不可預(yù)期的異?;驍?shù)據(jù)交織。
架構(gòu)方案:
- 生產(chǎn)者-消費(fèi)者隊(duì)列:所有發(fā)送請(qǐng)求入隊(duì),由專用發(fā)送線程順序執(zhí)行
- 讀寫鎖分離:讀線程專注接收與解析,寫線程專注發(fā)送,通過 ConcurrentQueue 交換數(shù)據(jù)
- 避免 UI 線程直接操作串口:所有串口操作委托給后臺(tái)線程,UI 僅通過事件接收解析后的業(yè)務(wù)數(shù)據(jù)
問題四:熱插拔與端口消失
USB 轉(zhuǎn)串口設(shè)備拔出后,已打開的 SerialPort 實(shí)例不會(huì)自動(dòng)感知,后續(xù)讀寫拋出異常。
處理策略:
- 捕獲 IOException 并標(biāo)記端口狀態(tài)為斷開
- 使用 WMI(Windows Management Instrumentation)監(jiān)聽 USB 設(shè)備插拔事件,自動(dòng)重連
- Linux 環(huán)境下監(jiān)控 udev 事件或輪詢 /dev 目錄
問題五:跨平臺(tái)編碼陷阱
處理中文設(shè)備名稱或包含中文的協(xié)議文本時(shí),Windows 默認(rèn) GB2312 與 Linux 默認(rèn) UTF-8 的差異會(huì)導(dǎo)致亂碼。務(wù)必顯式統(tǒng)一編碼,二進(jìn)制協(xié)議則完全規(guī)避此問題。
七、高級(jí)應(yīng)用場(chǎng)景
場(chǎng)景一:Modbus RTU 主站
工業(yè)自動(dòng)化中最常見的串口協(xié)議。實(shí)現(xiàn)要點(diǎn):
- 時(shí)序嚴(yán)格:幀間隔必須大于 3.5 字符時(shí)間,波特率 9600 時(shí)約 3ms
- 廣播模式:地址 0 為廣播,設(shè)備不回應(yīng),需應(yīng)用層延遲等待
- 異常響應(yīng):功能碼最高位置 1 表示錯(cuò)誤,需解析異常碼
場(chǎng)景二:多串口并發(fā)服務(wù)器
工控上位機(jī)需同時(shí)管理數(shù)十個(gè)串口(連接多條產(chǎn)線的 PLC)。架構(gòu)設(shè)計(jì):
- 端口池管理:動(dòng)態(tài)分配/回收串口資源,監(jiān)控各端口健康狀態(tài)
- 統(tǒng)一協(xié)議網(wǎng)關(guān):將不同設(shè)備的私有協(xié)議轉(zhuǎn)換為統(tǒng)一 JSON 或 MQTT 格式,供上層系統(tǒng)消費(fèi)
- 流量整形:限制各端口的輪詢頻率,避免總線擁塞
場(chǎng)景三:串口轉(zhuǎn)網(wǎng)絡(luò)橋接
將傳統(tǒng)串口設(shè)備接入以太網(wǎng)或云平臺(tái)。C# 可實(shí)現(xiàn):
- TCP 透?jìng)鳎捍跀?shù)據(jù) ↔ Socket 字節(jié)流雙向轉(zhuǎn)發(fā)
- MQTT 網(wǎng)關(guān):將串口采集的傳感器數(shù)據(jù)按主題發(fā)布,支持云端訂閱
- WebSocket 代理:瀏覽器通過 WebSocket 直連串口設(shè)備,實(shí)現(xiàn) Web 化監(jiān)控界面
八、調(diào)試與診斷工具鏈
1. 物理層驗(yàn)證
示波器/邏輯分析儀:抓取 TX/RX 波形,驗(yàn)證波特率精度、起始位/停止位格式、電平幅度。RS-485 需差分探頭觀測(cè) A/B 線對(duì)。
USB 協(xié)議分析儀:排查 USB 轉(zhuǎn)串口驅(qū)動(dòng)層面的數(shù)據(jù)丟失或延遲異常。
2. 軟件層監(jiān)控
串口監(jiān)視器:如 Portmon、Serial Port Monitor,攔截系統(tǒng)底層的 IRP(I/O Request Packet),觀察實(shí)際收發(fā)時(shí)序與緩沖區(qū)狀態(tài)。
虛擬串口對(duì):使用 com0com 等工具創(chuàng)建虛擬互聯(lián)串口,在無硬件條件下模擬雙機(jī)通信。
3. 日志策略
生產(chǎn)環(huán)境必須記錄:
- 每次 Open/Close 的時(shí)間與結(jié)果
- 收發(fā)數(shù)據(jù)的原始字節(jié)(十六進(jìn)制)與解析后的業(yè)務(wù)含義
- 超時(shí)、校驗(yàn)失敗、重傳等異常事件的上下文
九、結(jié)語
串口通信是連接數(shù)字世界與物理設(shè)備的最后一英里。C# 憑借 System.IO.Ports 的簡(jiǎn)潔封裝與 .NET 生態(tài)的豐富工具鏈,在工控上位機(jī)、設(shè)備調(diào)試工具、物聯(lián)網(wǎng)網(wǎng)關(guān)等場(chǎng)景中展現(xiàn)出強(qiáng)大生產(chǎn)力。但技術(shù)的簡(jiǎn)潔性背后,是對(duì)物理層時(shí)序、協(xié)議完整性、異常邊界條件的深刻理解。優(yōu)秀的串口程序不是代碼量的堆砌,而是對(duì)"字節(jié)在銅線上如何流動(dòng)"這一本質(zhì)問題的精準(zhǔn)把控。
到此這篇關(guān)于C#環(huán)境下串口通信技術(shù)的技術(shù)體系與工程實(shí)踐詳解的文章就介紹到這了,更多相關(guān)C#串口通信內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于C#的圖表控件庫 ScottPlot編譯visual studio 2022
基于 C# 的 圖表控件庫 ScottPlot,開源免費(fèi),可以用于開發(fā)一些上位機(jī)軟件,如電壓、電流波形的顯示,開發(fā)【示波器】圖形界面,可以顯示一些圖表、波形,總之功能比較的強(qiáng)大,本文介紹了基于C#的圖表控件庫 ScottPlot編譯visual studio 2022,需要的朋友可以參考下2022-06-06
c# 如何實(shí)現(xiàn)不同進(jìn)程之間的通信
這篇文章主要介紹了c# 如何實(shí)現(xiàn)不同進(jìn)程之間的通信,幫助大家更好的理解和學(xué)習(xí)c#,感興趣的朋友可以了解下2020-11-11
C#使用Spire.XLS for .NET對(duì)Excel的行和列進(jìn)行分組和取消分組
在 Excel 工作表中,對(duì)行或列進(jìn)行分組可以將數(shù)據(jù)按項(xiàng)目分類,每一組顯示一個(gè)項(xiàng)目的相關(guān)信息,本文將介紹如何在 C# 和 VB.NET 中,使用 Spire.XLS for .NET對(duì) Excel 的行和列進(jìn)行分組和取消分組,需要的朋友可以參考下2026-01-01
C#利用deskew算法實(shí)現(xiàn)圖像文本傾斜校正
這篇文章主要為大家詳細(xì)介紹了C#如何利用deskew算法實(shí)現(xiàn)圖像文本傾斜校正,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
詳解C#中通過委托來實(shí)現(xiàn)回調(diào)函數(shù)功能的方法
這篇文章主要介紹了C#中通過委托來實(shí)現(xiàn)回調(diào)函數(shù)功能的方法,文中舉了一個(gè)典型的多線程回調(diào)程序?qū)嵗?需要的朋友可以參考下2016-04-04
C#?實(shí)例解釋面向?qū)ο缶幊讨械膯我还δ茉瓌t(示例代碼)
本文我介紹了?SOLID?原則中的單一功能原則(single-responsibility?principle),并通過?C#?代碼示例簡(jiǎn)明地詮釋了它的含意和實(shí)現(xiàn),對(duì)C#?面向?qū)ο缶幊淘瓌t感興趣的朋友跟隨小編一起看看吧2022-02-02
C#動(dòng)態(tài)執(zhí)行批處理命令的方法
這篇文章主要介紹了C#動(dòng)態(tài)執(zhí)行批處理命令的方法,可實(shí)現(xiàn)動(dòng)態(tài)執(zhí)行一系列控制臺(tái)命令,并允許實(shí)時(shí)顯示出來執(zhí)行結(jié)果,需要的朋友可以參考下2014-11-11
C#利用win32 Api 修改本地系統(tǒng)時(shí)間、獲取硬盤序列號(hào)
這篇文章主要介紹了C#利用win32 Api 修改本地系統(tǒng)時(shí)間、獲取硬盤序列號(hào)的方法及代碼分享,需要的朋友可以參考下2015-03-03

