SerialPort 串口通信基础

发布时间:2026/6/30 23:10:25

SerialPort 串口通信基础 C# 上位机开发 - SerialPort 串口通信基础一篇搞定 建议收藏专栏导航上一篇无本章开篇 | 下一篇DataReceived与线程安全前言工业上位机与 PLC、仪表、扫码枪、称重模块打交道串口RS-232/RS-485仍然是最常见、最可靠的物理层之一。本文从System.IO.Ports.SerialPort入手讲清参数含义、打开/收发流程、可复用的SerialPortManager封装并给出 WinForms 完整示例与 FAQ。读完可直接拷贝到项目里用。串口通信在工业场景中的位置层级典型技术上位机关注点物理层RS-232、RS-485、USB 转串口线序、终端电阻、隔离数据链路异步 UART 帧波特率、数据位、校验应用层Modbus RTU、自定义协议帧格式、超时、重试RS-485 多机总线时同一 COM 口对应一条总线半双工下同一时刻只能一个方向发送协议层要留应答间隔。SerialPort 核心参数说明参数常见值说明PortNameCOM3设备管理器中端口名BaudRate9600、115200必须与从站一致DataBits8几乎总是 8ParityNone、Even、OddModbus RTU 常用 Even 或 None看设备手册StopBitsOne停止位 1ReadTimeout500~3000 ms同步 Read 阻塞上限WriteTimeout1000 ms写超时DtrEnable / RtsEnable视硬件部分 USB 转串口需要 DTR 才能上电易错点改参数前必须先Close()否则部分驱动不生效BytesToRead为 0 不代表没数据可能是事件模式尚未触发。SerialPortManager 封装类将打开、关闭、发送、事件订阅集中管理避免窗体代码里到处new SerialPort。usingSystem;usingSystem.IO.Ports;usingSystem.Text;namespaceUpperComputer.Communication{publicsealedclassSerialPortManager:IDisposable{privatereadonlySerialPort_port;privatebool_disposed;publiceventEventHandlerbyte[]DataReceived;publicboolIsOpen_port.IsOpen;publicstringPortName_port.PortName;publicSerialPortManager(stringportName,intbaudRate9600,ParityparityParity.None,intdataBits8,StopBitsstopBitsStopBits.One){_portnewSerialPort(portName,baudRate,parity,dataBits,stopBits){ReadTimeout2000,WriteTimeout2000,EncodingEncoding.ASCII};_port.DataReceivedOnDataReceived;}publicvoidOpen(){if(!_port.IsOpen)_port.Open();}publicvoidClose(){if(_port.IsOpen)_port.Close();}publicvoidSend(byte[]data){if(datanull||data.Length0)return;if(!_port.IsOpen)thrownewInvalidOperationException(串口未打开);_port.Write(data,0,data.Length);}publicvoidSendHex(stringhex){Send(HexStringToBytes(hex));}privatevoidOnDataReceived(objectsender,SerialDataReceivedEventArgse){try{intcount_port.BytesToRead;if(count0)return;varbuffernewbyte[count];_port.Read(buffer,0,count);DataReceived?.Invoke(this,buffer);}catch(IOException){/* 设备拔线等 */}}publicstaticbyte[]HexStringToBytes(stringhex){hexhex.Replace( ,).Replace(-,);if(hex.Length%2!0)thrownewArgumentException(十六进制长度必须为偶数);varbytesnewbyte[hex.Length/2];for(inti0;ibytes.Length;i)bytes[i]Convert.ToByte(hex.Substring(i*2,2),16);returnbytes;}publicvoidDispose(){if(_disposed)return;_disposedtrue;_port.DataReceived-OnDataReceived;Close();_port.Dispose();}}}WinForms 最小可用示例publicpartialclassFormSerialDemo:Form{privateSerialPortManager_manager;publicFormSerialDemo(){InitializeComponent();comboBoxPorts.Items.AddRange(SerialPort.GetPortNames());if(comboBoxPorts.Items.Count0)comboBoxPorts.SelectedIndex0;}privatevoidbtnOpen_Click(objectsender,EventArgse){if(_manager?.IsOpentrue)return;_managernewSerialPortManager(comboBoxPorts.Text,9600);_manager.DataReceived(_,data){BeginInvoke(newAction((){textBoxLog.AppendText(BitConverter.ToString(data)Environment.NewLine);}));};_manager.Open();lblStatus.Text已打开;}privatevoidbtnSend_Click(objectsender,EventArgse){_manager?.SendHex(textBoxSend.Text);}protectedoverridevoidOnFormClosed(FormClosedEventArgse){_manager?.Dispose();base.OnFormClosed(e);}}界面只需端口下拉、打开/关闭、发送框、日志框。生产环境请加日志落盘与异常弹窗节流避免 PLC 断线时刷屏。同步 Read 与事件模式如何选择方式优点缺点DataReceived 事件不阻塞 UI 线程仍需 Invoke线程安全要注意见下一篇Read(byte[], offset, count)逻辑简单阻塞需后台线程BaseStream ReadAsync现代异步与 SerialPort 组合需自己封装Modbus 主站常用后台线程 读超时或环形缓冲 帧解析第三篇。常见问题 FAQQ1打开 COM 口提示“端口被占用”其他程序串口助手、旧版上位机、虚拟机映射占用了端口。用任务管理器结束进程或拔插 USB 转串口换 COM 号。开发时养成关闭窗体即 Dispose 串口的习惯。Q2能收不能发或能发不能收检查 TX/RX 是否交叉、485 A/B 是否反、是否共地。USB 转 485 需自动方向或手动控制 DE/RERtsEnable在某些芯片上控制发送使能。Q3收到的数据乱码SerialPort.Encoding默认 ASCII二进制协议不要用ReadExisting()当字符串一律byte[]。Q4GetPortNames() 列表为空驱动未装、设备未识别。设备管理器查看“端口(COM和LPT)”。Win10/11 可对未知设备手动指定 CH340/CP2102 驱动。Q5115200 波特率仍丢字节USB 转串口质量、线缆过长、485 反射、UI 线程处理太慢导致驱动缓冲溢出。提高进程优先级不能替代正确的接收缓冲与解析架构。小结掌握PortName、波特率、校验与byte 级收发再用SerialPortManager统一生命周期是上位机通信的第一步。下一篇专门讲DataReceived 线程安全与生产者-消费者模型避免界面卡死与数据竞争。专栏导航上一篇无本章开篇 | 下一篇DataReceived与线程安全

相关新闻