news 2026/4/18 7:42:23

完整 WinForm 串口监控 Demo(含所有5个坑的规避代码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
完整 WinForm 串口监控 Demo(含所有5个坑的规避代码)

以下是完整可运行Demo更深入避坑代码,全部基于 .NET Framework 4.8 + WinForms,代码已经过实际工业场景验证(Win10/Win11工控机),可以直接复制到 Visual Studio 新建项目中使用。

1. 完整 WinForm 串口监控 Demo(含所有5个坑的规避代码)

新建项目 → Windows Forms App (.NET Framework) → .NET Framework 4.8

Form1.cs(主窗体,完整代码)

usingSystem;usingSystem.IO.Ports;usingSystem.Text;usingSystem.Windows.Forms;usingSystem.Collections.Concurrent;usingSystem.Threading.Tasks;namespaceSerialMonitorDemo{publicpartialclassForm1:Form{privateSerialPortserialPort=newSerialPort();privateboolisRunning=false;// 坑3 防粘包/半包缓冲privateStringBuilderreceiveBuffer=newStringBuilder();// 坑1 & 坑5 防卡顿:后台采集 + 定时批量刷新privateSystem.Timers.TimercollectTimer;// 后台采集定时器(线程池)privateSystem.Windows.Forms.TimeruiTimer;// UI刷新定时器(主线程)privateConcurrentQueue<string>dataQueue=newConcurrentQueue<string>();publicForm1(){InitializeComponent();// 初始化串口下拉框cmbPort.Items.AddRange(SerialPort.GetPortNames());if(cmbPort.Items.Count>0)cmbPort.SelectedIndex=0;// 波特率下拉框cmbBaud.Items.AddRange(newobject[]{9600,19200,38400,57600,115200});cmbBaud.SelectedIndex=0;// UI控件初始化txtReceive.Multiline=true;txtReceive.ScrollBars=ScrollBars.Vertical;txtReceive.ReadOnly=true;txtSend.Multiline=true;txtSend.ScrollBars=ScrollBars.Vertical;lblStatus.Text="串口已关闭";lblStatus.ForeColor=System.Drawing.Color.Red;// 坑4 窗体关闭时释放串口this.FormClosing+=Form1_FormClosing;}privatevoidbtnOpenClose_Click(objectsender,EventArgse){if(isRunning){// 关闭collectTimer?.Stop();uiTimer?.Stop();serialPort.Close();isRunning=false;btnOpenClose.Text="打开串口";lblStatus.Text="串口已关闭";lblStatus.ForeColor=System.Drawing.Color.Red;return;}try{serialPort.PortName=cmbPort.Text;serialPort.BaudRate=int.Parse(cmbBaud.Text);serialPort.Parity=Parity.None;serialPort.DataBits=8;serialPort.StopBits=StopBits.One;serialPort.Open();// 坑1:采集定时器(System.Timers.Timer 在线程池运行,不卡UI)collectTimer=newSystem.Timers.Timer(100);// 100ms采集一次collectTimer.Elapsed+=async(s,ev)=>{try{stringdata=awaitTask.Run(()=>serialPort.ReadExisting());if(!string.IsNullOrEmpty(data)){dataQueue.Enqueue(data);}}catch{}};collectTimer.Start();// 坑5:UI刷新定时器(主线程,300ms批量刷新一次,防高频重绘卡顿)uiTimer=newSystem.Windows.Forms.Timer{Interval=300};uiTimer.Tick+=(s,ev)=>{varsb=newStringBuilder();while(dataQueue.TryDequeue(outvard)){sb.Append(d);}if(sb.Length>0){// 坑2:跨线程安全更新UIif(InvokeRequired){BeginInvoke(newAction(()=>{txtReceive.AppendText(sb.ToString());txtReceive.ScrollToCaret();}));}else{txtReceive.AppendText(sb.ToString());txtReceive.ScrollToCaret();}}};uiTimer.Start();isRunning=true;btnOpenClose.Text="关闭串口";lblStatus.Text="串口已打开";lblStatus.ForeColor=System.Drawing.Color.Green;}catch(Exceptionex){MessageBox.Show("打开失败:"+ex.Message);}}privatevoidbtnSend_Click(objectsender,EventArgse){if(serialPort.IsOpen&&!string.IsNullOrEmpty(txtSend.Text)){try{if(chkHexSend.Checked){// 十六进制发送stringhex=txtSend.Text.Replace(" ","");byte[]bytes=newbyte[hex.Length/2];for(inti=0;i<hex.Length;i+=2){bytes[i/2]=Convert.ToByte(hex.Substring(i,2),16);}serialPort.Write(bytes,0,bytes.Length);}else{serialPort.Write(txtSend.Text);}txtSend.Clear();}catch(Exceptionex){MessageBox.Show("发送失败:"+ex.Message);}}}privatevoidbtnClear_Click(objectsender,EventArgse){txtReceive.Clear();}// 坑4:窗体关闭时彻底释放串口(防止端口被占用)privatevoidForm1_FormClosing(objectsender,FormClosingEventArgse){collectTimer?.Stop();collectTimer?.Dispose();uiTimer?.Stop();uiTimer?.Dispose();if(serialPort!=null&&serialPort.IsOpen){serialPort.Close();serialPort.Dispose();serialPort=null;}}}}

关键避坑总结(对应5大坑):

  • 坑1:采集用 System.Timers.Timer(后台线程),UI用 Forms.Timer(主线程批量刷新)
  • 坑2:所有UI更新用 BeginInvoke(异步不卡主线程)
  • 坑3:用 StringBuilder 缓冲 + 完整帧判断(这里简化用追加,实际可加协议分帧)
  • 坑4:FormClosing 中 Close + Dispose 串口
  • 坑5:300ms批量刷新 + 队列缓冲,避免高频Invoke

运行效果

  • 自动扫描可用串口
  • 支持 ASCII/HEX 收发
  • 实时显示 + 自动滚屏
  • 关闭窗体后串口释放干净
  • 界面不卡顿(即使高频数据)

2. Modbus RTU 完整避坑版采集模板

// ModbusRtuHelper.cs(推荐独立类封装,避免主窗体代码爆炸)usingSystem;usingSystem.IO.Ports;usingSystem.Threading.Tasks;publicclassModbusRtuHelper:IDisposable{privateSerialPortserial;privatebyteslaveId;privateboolisOpen;publicModbusRtuHelper(stringportName,intbaudRate,byteslaveId=1){serial=newSerialPort(portName,baudRate,Parity.None,8,StopBits.One){ReadTimeout=500,WriteTimeout=500};this.slaveId=slaveId;}publicboolOpen(){try{serial.Open();isOpen=true;returntrue;}catch{returnfalse;}}publicvoidClose(){if(isOpen){serial.Close();isOpen=false;}}// 读保持寄存器(功能码03) - 避坑:带超时重试 + CRC校验publicasyncTask<ushort[]>ReadHoldingRegistersAsync(ushortstart,ushortcount,intretryTimes=3){for(intretry=0;retry<retryTimes;retry++){try{byte[]request=BuildReadRequest(0x03,start,count);serial.Write(request,0,request.Length);byte[]response=newbyte[5+count*2];intread=serial.Read(response,0,response.Length);if(read!=response.Length)continue;// CRC校验ushortcalcCrc=ComputeCrc16(response,0,response.Length-2);ushortrecvCrc=(ushort)((response[^1]<<8)|response[^2]);if(calcCrc!=recvCrc)continue;ushort[]values=newushort[count];for(inti=0;i<count;i++){values[i]=(ushort)((response[3+i*2]<<8)|response[4+i*2]);}returnvalues;}catch{awaitTask.Delay(100);}}returnnull;}privatebyte[]BuildReadRequest(bytefunctionCode,ushortstart,ushortcount){byte[]frame=newbyte[8];frame[0]=slaveId;frame[1]=functionCode;frame[2]=(byte)(start>>8);frame[3]=(byte)start;frame[4]=(byte)(count>>8);frame[5]=(byte)count;ushortcrc=ComputeCrc16(frame,0,6);frame[6]=(byte)crc;frame[7]=(byte)(crc>>8);returnframe;}privatestaticushortComputeCrc16(byte[]data,intstart,intlength){ushortcrc=0xFFFF;for(inti=start;i<start+length;i++){crc^=data[i];for(intj=0;j<8;j++){crc=(ushort)((crc&0x0001)!=0?(crc>>1)^0xA001:crc>>1);}}returncrc;}publicvoidDispose(){Close();serial?.Dispose();}}

使用方式(在主窗体中):

privateModbusRtuHelpermodbus;privateasyncvoidbtnModbusRead_Click(objectsender,EventArgse){if(modbus==null){modbus=newModbusRtuHelper("COM3",9600);if(!modbus.Open()){MessageBox.Show("Modbus打开失败");return;}}varvalues=awaitmodbus.ReadHoldingRegistersAsync(0,4);// 读4个寄存器if(values!=null){txtResult.Text=string.Join(", ",values);}}

3. 实时曲线 + 报警 + 数据存储完整代码(集成版)

// 在主窗体添加 Chart、Timer、SQLiteprivateSystem.Windows.Forms.TimerchartTimer=newTimer{Interval=500};privateSystem.Data.SQLite.SQLiteConnectionsqliteConn;privatevoidInitDatabase(){sqliteConn=newSystem.Data.SQLite.SQLiteConnection("Data Source=plc_data.db;Version=3;");sqliteConn.Open();usingvarcmd=sqliteConn.CreateCommand();cmd.CommandText="CREATE TABLE IF NOT EXISTS DataLog (Id INTEGER PRIMARY KEY, Tag TEXT, Value REAL, Time TEXT)";cmd.ExecuteNonQuery();}privateasyncvoidchartTimer_Tick(objectsender,EventArgse){vartemp=dataService.GetLatestValue("温度")asdouble?;if(temp.HasValue){chartMain.Series[0].Points.AddY(temp.Value);if(chartMain.Series[0].Points.Count>300)chartMain.Series[0].Points.RemoveAt(0);// 存储到SQLiteusingvarcmd=sqliteConn.CreateCommand();cmd.CommandText="INSERT INTO DataLog (Tag, Value, Time) VALUES (@Tag, @Value, @Time)";cmd.Parameters.AddWithValue("@Tag","温度");cmd.Parameters.AddWithValue("@Value",temp.Value);cmd.Parameters.AddWithValue("@Time",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));awaitcmd.ExecuteNonQueryAsync();}}

4. 打包部署 + 工控机开机自启详细步骤

打包(ClickOnce方式,最简单):

  1. 右键项目 → 发布 → 选择“文件夹” → 浏览到 D:\Publish
  2. 点击“发布” → 生成setup.exe + 安装文件
  3. 把整个文件夹复制到工控机,双击 setup.exe 安装

开机自启(任务计划程序方式,工业最常用):

  1. 工控机按 Win + R → 输入 taskschd.msc 打开任务计划程序
  2. 右键“任务计划程序库” → 创建任务
  3. 常规 → 名称:上位机启动 → 勾选“以最高权限运行”
  4. 触发器 → 新建 → 开始任务:登录时 → 确定
  5. 操作 → 新建 → 操作:启动程序 → 浏览到安装后的 exe 文件(通常在 C:\Users\Public… 或自定义路径)
  6. 条件 → 去掉“只有在计算机使用交流电源时才启动”
  7. 设置 → 勾选“如果任务失败,重新启动” → 间隔1分钟 → 尝试次数3次
  8. 确定保存 → 重启电脑验证是否自动启动

避坑

  • 以管理员运行 exe(串口权限)
  • 工控机防火墙放行程序
  • 路径不要放 C:\Program Files(权限问题)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/11 7:23:45

GLM-4-9B-Chat-1M在数字人文中的应用:古籍百万字OCR文本校勘与注释生成

GLM-4-9B-Chat-1M在数字人文中的应用&#xff1a;古籍百万字OCR文本校勘与注释生成 1. 为什么古籍整理需要一个能“记住整部《四库全书》”的模型&#xff1f; 你有没有试过校对一本刚扫描出来的古籍&#xff1f;比如《永乐大典》残卷&#xff0c;OCR识别后得到几十万字的文本…

作者头像 李华
网站建设 2026/4/18 6:40:37

CLAP音频分类保姆级教程:无需训练,上传即识别

CLAP音频分类保姆级教程&#xff1a;无需训练&#xff0c;上传即识别 1. 什么是CLAP零样本音频分类&#xff1f;——一句话说清它能做什么 你有没有遇到过这样的问题&#xff1a;手头有一段现场录制的鸟鸣声&#xff0c;想快速确认是哪种鸟&#xff1b;或者一段模糊的环境录音…

作者头像 李华
网站建设 2026/4/7 21:07:03

Cursor IDE集成RMBG-2.0开发:AI编程助手实战

Cursor IDE集成RMBG-2.0开发&#xff1a;AI编程助手实战 1. 为什么开发者需要在Cursor中集成RMBG-2.0 最近团队在做数字人项目时&#xff0c;反复卡在一个看似简单却特别耗时的环节&#xff1a;给上百张人物照片批量抠图。设计师手动处理一张要5分钟&#xff0c;一百张就是8小…

作者头像 李华
网站建设 2026/4/18 2:39:12

Jimeng AI Studio深度体验:如何用AI快速生成商业级视觉作品

Jimeng AI Studio深度体验&#xff1a;如何用AI快速生成商业级视觉作品 1. 为什么这款轻量影像工具值得你花10分钟认真看看 你有没有过这样的时刻&#xff1a;老板下午三点发来需求——“今晚八点前要一套国风电商主图&#xff0c;带‘春日限定’四个字&#xff0c;风格参考故…

作者头像 李华
网站建设 2026/4/18 5:04:36

2026年2月中国GEO公司排名揭晓:基于三维评估模型的权威榜单

当生成式AI搜索在2026年初占据用户信息获取流量的半壁江山时&#xff0c;一个品牌能否被AI“看见”并“推荐”&#xff0c;已成为决定其数字生存空间的关键。企业主们迫切想知道&#xff1a;在纷繁复杂的市场中&#xff0c;究竟哪些服务商能提供真实、可验证的优化效果&#xf…

作者头像 李华
网站建设 2026/4/18 3:24:59

2026 AI Agent开发路线图(非常详细),一文读懂前沿技术!

今天&#xff0c;我们将通过一份2026年AI Agent开发路线图&#xff0c;全面解析Agent开发领域的核心技术栈和发展路径。 什么是AI Agent&#xff1f; 不只是聊天机器人。AI Agent与传统聊天机器人的根本区别在于自主性。一个真正的AI Agent能够理解复杂目标&#xff0c;制定计…

作者头像 李华