ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用VB.NET从零开发串口调试助手:串口通信参数、SerialPort实战与踩坑全记录

用VB.NET从零开发串口调试助手:串口通信参数、SerialPort实战与踩坑全记录 简介这是一份基于VB.NET原生SerialPort组件实现串口助手的完整源码示例面向需要绕开AXCom控件、亲自掌握串口通信与委托线程机制的VB.NET开发者。资源共37个文件压缩包仅181KB包含6个vb源码、已编译exe、调试pdb以及config、xml、resx等配置与界面资源文件覆盖窗体设计、串口参数配置、数据收发和线程安全更新UI的完整流程。目前已有519人参与学习。作者放弃网上常见的AXCom控件改用最新SerialPort类重点展示波特率、数据位、停止位、奇偶校验等核心参数的设置方式并理清异步收发时委托与线程的关系亲测可运行。整个工程结构清晰压缩包轻量既可当作串口调试工具的开发模板也能为VB.NET通信编程提供参考直接编译即可体验。 搞嵌入式这些年谁电脑里没装过两三个串口调试工具。从最早的串口调试助手、SSCOM到后来的XCOM、友善串口助手装了删、删了装。说实话这些工具都能用但用久了总觉得差点意思——界面布局不合自己习惯发送区不支持我想要的快捷方式想按项目定制个协议格式还得去求人改软件。后来我干脆花了一个周末用VB.NET给自己写了个串口助手这一用就是好几年。这篇文章我就把这个过程完整摊开讲。从串口通信那几个必须搞明白的参数到VB.NET里SerialPort组件的实际用法再到接收显示、发送控制、波形绘制这些核心功能怎么一步步写出来最后是实战中一定会踩的坑。不管你是刚接触上位机的嵌入式新人还是想把手头工具换得更顺手的工程师这篇文章应该都能给你点实在的参考。1. 为什么自己动手写串口助手1.1 现成工具能用但总有让你不舒服的地方市面上的串口调试助手确实不少功能也大同小异。但实际用起来每个人遇到的需求都不一样。比如我经常要调试Modbus RTU协议一条报文七八个字节每次都要手打那段十六进制字符串或者从文本文件里复制过来非常累。还有些工具不支持定时发送我调试传感器轮询的时候就得手动一条条发。更麻烦的是有些项目需要把串口收到的数据按特定格式解析出来甚至直接绘图显示。通用工具大部分要么不做这功能要么做得很简陋。毕竟人家做的是通用产品不可能为了你一个项目去定制功能。这其实就是自己写工具最大的价值——你可以完全按自己的工作方式来设计它。1.2 技术选型为什么是VB.NET可能有人会说写串口助手用C#不行吗用Python不也行吗当然都可以。我选VB.NET不是因为它比别的语言强而是因为它在Windows平台上做这种小工具确实方便。.NET框架里自带了System.IO.Ports命名空间里面封装好了SerialPort类打开串口、读写数据都是现成的方法不需要你去调底层API。Visual Studio社区版免费拖拽控件做界面半小时就能把界面骨架搭起来。这对于工控、测试领域的工程师来说学习成本很低很多老工程师多多少少都会点VB的语法迁移到VB.NET很自然。你要问C#和VB.NET哪个好我个人觉得在串口助手这种场景下没本质区别因为核心调用的都是同一个SerialPort类。选VB.NET纯粹是熟悉和顺手的问题。2. 串口通信的那些参数你得真搞懂2.1 波特率、数据位、停止位、校验位到底在说什么串口通信的原理其实很朴素一根发送线TX一根接收线RX发送方按照约定的节奏一位一位地把电平信号送出去接收方用同样的节奏去采样。关键在于约定的节奏和约定的格式这就是我们常说的波特率、数据位、停止位、校验位。波特率是每秒传输多少个bit单位bps。常见的有9600、115200工业设备里4800、19200也有不少。波特率不是越高越好线缆长了、环境干扰大了高速率下就容易出错。而且通信双方必须严格一致差一点都不行。数据位通常是8位老一点设备有7位的情况。停止位有1位、1.5位、2位三种正常情况下选1位。校验位有五种None无校验、Odd奇校验、Even偶校验、Mark恒为1、Space恒为0绝大多数场景用None或Even。这几个参数是串联成一帧的完整格式实际调试时两边设不一致表现就是要么收一堆乱码要么干脆没数据。比如你设了偶校验对方没校验那接收端就会不停地报错数据根本进不到你的接收缓冲区。2.2 硬件层面TTL、RS232、RS485的区别做上位机经常要跟硬件连绕不开这三种电平标准。TTL电平是单片机直接输出的0V表示低电平3.3V或5V表示高电平。这种电平只能板内短距离通信最多几十厘米。RS232是负逻辑-3V到-15V表示13V到15V表示0传输距离能到15米左右。电脑老的DB9串口就是RS232电平。现在笔记本没有串口了需要用USB转串口线转出来。RS485是差分信号用A、B两根线之间的电压差来传数据抗干扰能力强最远能到1200米工业现场用得最多。但它有个特点——半双工同一时刻只能发或者只能收。对你写的串口助手来说这些电平标准其实不影响代码——不管外面是TTL还是RS232还是RS485转换完之后到USB串口芯片那里你接收到的都是一样的字节流。但如果你在调试时发现收不到数据得能想到是不是电平不匹配、接线反了的问题。2.3 USB转串口芯片那点事现在调试都是USB转串口常见芯片就那几款CH340、FTDI FT232、CP2102、PL2303。CH340是国产的性价比高模块几块钱一个缺点是驱动偶尔有兼容性问题。FTDI稳定性好但价格贵市面上假芯片多。CP2102中规中矩。Win10以上系统一般能自动装驱动但CH340偶尔会掉链子识别成了未知设备。我的经验是直接去芯片官网下驱动手动装稳定得多。那些驱动精灵、驱动大师什么的装完经常给你来个全家桶没必要。在写串口助手的时候建议加一个图标或提示显示当前用的什么芯片方便排查问题。3. 核心功能逐行实现3.1 创建项目与界面布局设计用Visual Studio新建一个Windows Forms应用.NET Framework这里要注意如果你装的是新版VS创建项目时选Windows窗体应用(.NET Framework)跑起来最省心。项目名我就叫它MySerialTool。界面布局按自己习惯来参考我自己的工具大概是下面这个结构顶部区域串口选择、波特率选择、数据位、校验位、停止位下拉框以及打开串口按钮接收区一个多行文本框勾选HEX显示可以切换十六进制勾选时间戳可以给每条数据加时间发送区一个多行文本框支持按HEX或ASCII发送旁边是发送按钮附加功能定时发送勾选框加间隔输入框清空接收区按钮、保存日志按钮界面代码我就不贴了拖控件谁都会。重点说逻辑代码。3.2 枚举串口与打开串口打开界面时需要先把系统里现存的串口号枚举出来填到下拉框里。SerialPort类提供了现成的方法Imports System.IO.Ports Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load GetPorts() End Sub Private Sub GetPorts() Dim ports As String() SerialPort.GetPortNames() cmbPort.Items.Clear() For Each p As String In ports cmbPort.Items.Add(p) Next If cmbPort.Items.Count 0 Then cmbPort.SelectedIndex 0 End If End Sub打开串口的代码是这样的Private sp As New SerialPort() Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click If Not sp.IsOpen Then Try sp.PortName cmbPort.SelectedItem.ToString() sp.BaudRate Convert.ToInt32(cmbBaud.SelectedItem) sp.DataBits 8 sp.Parity Parity.None sp.StopBits StopBits.One sp.Handshake Handshake.None sp.DtrEnable False sp.RtsEnable False sp.Open() btnOpen.Text 关闭串口 Catch ex As Exception MessageBox.Show(打开串口失败 ex.Message) End Try Else sp.Close() btnOpen.Text 打开串口 End If End Sub这里有两个容易踩的坑。第一个串口在打开状态下去修改PortName、BaudRate这些属性会直接抛异常。所以如果你要做参数实时修改得先关串口再改参数再重新打开。第二个很多USB转串口板子在打开串口的瞬间会把DTR或RTS引脚拉低如果你的单片机电路正好用这两个信号控制复位和BOOT会导致设备一打开串口就重启。所以我这里直接设成了False后面的排查部分还会讲。3.3 数据接收与跨线程更新UI接收数据有两种常见方式。一是开个线程循环读二是用SerialPort自带的DataReceived事件。后者更省事。事件触发时操作系统在后台线程调用所以直接在事件里操作界面控件会抛跨线程异常。我的写法是事件里面把数据读进缓冲区再用Me.Invoke回到UI线程更新界面Private Sub sp_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles sp.DataReceived Dim bytesToRead As Integer sp.BytesToRead Dim buffer(bytesToRead - 1) As Byte sp.Read(buffer, 0, bytesToRead) Me.Invoke(Sub() AppendReceiveData(buffer) End Sub) End Sub Private Sub AppendReceiveData(data() As Byte) Dim text As String If chkHexDisplay.Checked Then text BytesToHex(data) Else text Encoding.Default.GetString(data) End If If chkTimestamp.Checked Then txtReceive.AppendText([ DateTime.Now.ToString(HH:mm:ss.fff) ] text vbCrLf) Else txtReceive.AppendText(text) End If End Sub这里有几个细节值得说。ReceivedBytesThreshold属性默认是1也就是说缓冲区每进来1个字节就会触发一次DataReceived。如果数据量很大这个事件会被频繁触发性能损失明显。所以我一般把它改成大一点的数比如1024攒够一批再通知。还有一个就是要一次把缓冲区的数据全部读走不要只读一部分。用BytesToRead拿到当前有多少字节然后一次性Read出来。要是每次只读固定长度数据稍多就会留在缓冲区造成延迟甚至丢失。3.4 数据发送与HEX处理发送数据就简单很多Private Sub btnSend_Click(sender As Object, e As EventArgs) Handles btnSend.Click If Not sp.IsOpen Then MessageBox.Show(串口未打开) Return End If Try If chkHexSend.Checked Then Dim data() As Byte HexToBytes(txtSend.Text) sp.Write(data, 0, data.Length) Else Dim data() As Byte Encoding.Default.GetBytes(txtSend.Text) sp.Write(data, 0, data.Length) End If Catch ex As Exception MessageBox.Show(发送失败 ex.Message) End Try End SubHEX字符串转字节数组的函数注意考虑用户可能输入带空格或者不带空格的情况Private Function HexToBytes(hexStr As String) As Byte() hexStr hexStr.Replace( , ).Replace(-, ).Trim() If hexStr.Length Mod 2 0 Then Throw New Exception(HEX字符串长度不是偶数) End If Dim bytes((hexStr.Length \ 2) - 1) As Byte For i As Integer 0 To hexStr.Length - 1 Step 2 bytes(i \ 2) Convert.ToByte(hexStr.Substring(i, 2), 16) Next Return bytes End Function发送时有一个很隐蔽的坑编码问题。如果你用ASCII方式发送中文字符串有些设备用的是GB2312或者GBK编码而你的程序在中文Windows系统上用Encoding.Default通常能对上。但换了一台英文系统的电脑Encoding.Default就变成ASCII了发出去的中文就乱了。最严谨的做法是显式指定编码Dim data() As Byte System.Text.Encoding.GetEncoding(GB2312).GetBytes(txtSend.Text)同理接收的时候如果知道对方的中文编码是GB2312也用同样的方式解码。3.5 定时发送实现定时发送用WinForms的Timer控件最方便。界面放一个TimerTick事件里调用发送逻辑间隔通过Interval属性设置单位是毫秒。Private Sub chkAutoSend_CheckedChanged(sender As Object, e As EventArgs) Handles chkAutoSend.CheckedChanged If chkAutoSend.Checked Then TimerSend.Interval Convert.ToInt32(txtInterval.Text) TimerSend.Start() Else TimerSend.Stop() End If End Sub Private Sub TimerSend_Tick(sender As Object, e As EventArgs) Handles TimerSend.Tick btnSend.PerformClick() End Sub定时发送调试Modbus轮询特别有用。我之前调试一个温湿度传感器上位机要每隔100ms读一次寄存器手动发送根本来不及用定时发送就完全自动化了。4. 进阶功能让工具更顺手4.1 接收区波形绘制做PID调参的时候光看ASCII数字还是不够直观把收到的数据画成波形图就方便多了。.NET自带Chart控件不用装第三方库。简单做法是设定一个协议规则比如设备每帧发两个字节代表一个数值或者发一行val123这样的文本。上位机收到后解析出数值加到Chart里Imports System.Windows.Forms.DataVisualization.Charting Private Sub AddWavePoint(value As Double) If Chart1.Series(Wave).Points.Count 500 Then Chart1.Series(Wave).Points.RemoveAt(0) End If Chart1.Series(Wave).Points.AddY(value) End Sub只保留最近500个点防止数据越来越多导致绘图变卡。这个功能虽然代码量不大但在调PID的时候简直就是神器关键参数的变化趋势一眼就能看出来。4.2 日志保存与加载调试中经常要把串口数据存下来方便事后分析。在接收数据的时候顺手把原始数据写到日志文件里就行。文件名可以带上日期时间Private Sub AppendReceiveData(data() As Byte) ...显示逻辑省略... If chkLog.Checked Then Dim line As String DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss.fff) text File.AppendAllText(log_ DateTime.Now.ToString(yyyyMMdd) .txt, line vbCrLf) End If End Sub这里要注意日志文件的打开顺序和异常处理最好在程序启动时就检查目录可写。另外如果是长时间连续接收建议每次写入时先打个日志大小避免单个文件无限膨胀。4.3 常用命令快捷面板把调试时反复发送的固定报文做成按钮点击就发送。这个是纯体力活但确实能大幅提升效率。比如Modbus报文、查询命令、设置命令这些放一行按钮有时候比敲键盘快得多。5. 实战问题与排查速查5.1 乱码问题排查乱码在实践中遇到最多原因也最多。最常见的是波特率不一致两边差一位小数都不行。其次是校验位和数据位设置不一致。再就是编码问题设备按UTF-8发字符串你这边按GB2312解码中文就会变锟斤拷。排查方法是先用最简单的模式验证9600波特率、8数据位、无校验、1停止位发一串英文和数字看看是否正常。如果正常再逐步调整参数对比。5.2 数据丢失数据丢失有两个常见原因。第一接收事件触发了但你没把缓冲区数据全部读完新的数据把旧数据覆盖了。第二在高波特率下比如921600接收线程处理太慢数据积压导致缓冲区溢出。解决办法是把SerialPort.BufferSize调大比如设置成8192或者更大在DataReceived里一次性把BytesToRead读干净不要在该事件里做耗时的UI操作分析、存文件放到工作线程去做5.3 串口被占用导致打不开USB转串口如果拔插频繁经常会出现COM口被占用的情况。一种可能是其他调试工具还开着占用了串口另一种可能是拔插太快系统没来得及释放端口。遇到打不开的情况去设备管理器看看端口是哪个COM号看看是不是有黄色感叹号。如果是卸载设备重新扫描或者换个USB口插。代码层面打开串口一定要try catch给出明确的错误提示不然用户根本不知道出了什么事。5.4 单片机一打开串口就复位这个问题我做STM32调试时遇到过当时差点以为是硬件问题。后来发现是CH340模块的DTR和RTS信号接到了MCU的复位电路上。很多开发板的自动下载电路就是靠这两个信号来切换BOOT状态和复位但平时调试时如果上位机打开串口时把DTR/RTS拉低了就会导致开发板复位。解决办法很简单打开串口时把DtrEnable和RtsEnable都设为False。如果你需要控制这两个信号也可以在界面上做成复选框手动控制电平状态。5.5 接收区UI卡顿数据量大的时候TextBox内容越来越多UI就会越来越卡。这跟WinForms控件本身的性能有关。我的做法是限制接收区最大长度超过一定数量就截断前面的内容If txtReceive.TextLength 100000 Then txtReceive.Text txtReceive.Text.Substring(txtReceive.TextLength - 50000) End If另外接收数据时尽量用AppendText而不是直接对Text做拼接。AppendText内部做了优化效率比txtReceive.Text xxx高不少同时光标还能自动滚动到末尾。写到这里这套串口助手的基本功能就成型了。从最基础的串口打开关闭到接收区显示、HEX转换、定时发送再到波形绘制、日志保存每一步都是我在实际项目中验证过、踩过坑才完善的。说实话自己写串口助手这个过程比单纯下载一个工具来用有价值得多。它不仅让我对串口通信协议有了更深入的理解也让我在遇到各种通信问题时能更快定位到是硬件问题、协议问题还是上位机代码问题。后面如果你也想动手写一个建议从最简版本开始先把打开串口和数据收发跑通再按自己的实际需求一步一步加功能用着不顺手的就改最后你会收获一个完全符合自己习惯的工具。本文还有配套的精品资源点击获取
返回列表