尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用LabVIEW和NI-VISA自研串口调试助手:从原理到实战

用LabVIEW和NI-VISA自研串口调试助手:从原理到实战 前阵子调试一块自研的采集板串口报文里既有十六进制指令又有ASCII状态字符串手头几个现成的串口调试助手用下来总觉得差口气没法把收到的报文按协议拆开没法自动换算成工程量更没法把一整晚的连续数据带时间戳存下来。后来索性花了半个下午用LabVIEW加NI-VISA写了一个自己的串口调试助手边用边改前前后后也沉淀了不少经验。这篇就把整套实现思路、代码结构和踩过的坑都摊开说一说。文章比较适合的读者是经常和串口设备打交道的测试工程师、刚接触LabVIEW想找实战练手项目的同学以及打算把串口调试工具集成进自动化测试流程的人。1. 为什么放着现成的串口助手不用非要自己写一个1.1 现成的串口工具在你手里容易出问题的几个场景很多人会觉得SSCOM、Commix这类串口助手已经很成熟了为什么还要重复造轮子我的看法是如果你是偶尔看个串口日志那完全没必要自己写但下面几种场景下现成工具会明显拖后腿。第一个场景是带协议栈的调试。设备返回的是类似AA 55 01 02 CRC_H CRC_L这种帧格式你需要在调试时反复确认CRC校验算得对不对或者要自动填充帧头、帧长、校验字节再发送。现成的串口助手大多只能手动敲帧或者最多给你一个简单的“字符串/HEX”切换框架级的功能就只能靠你自己在脑子里拼。第二个场景是数据要跟其他仪表联动。我给你举个例子调试一个传感器模块时我需要每隔100毫秒下发一次查询指令再把返回温度和湿度实时画成曲线同时判断是否超限。如果串口助手本身没有自动化能力那就得人工盯着屏幕效率非常低。第三个场景是产线或长时间测试。往设备里灌数据灌一整晚早上起来第一件事是看昨晚有没有掉线、有没有超时、有没有非法帧。商用串口助手能保存log但保存的格式、文件切分方式、命名规则不一定合你的意想加个时间戳都要靠手工后期处理。所以结论很明确自己写一个串口调试助手不是为了替代通用工具而是为了把它嵌进你的具体工作流里。LabVIEW恰好是干这件事最顺手的环境之一因为它的图形化界面做起来快串口操作又有VISA统一封装后面接仪器、接DAQ、接数据库都有现成方案。1.2 VISA到底是什么和直接调API有什么不一样第一次看到“LabVIEW实现串口调试助手--VISA”这个标题很多新人会以为VISA是一种串口芯片或者某种协议还有人会联想到银行卡。先把这个概念掰开说清楚。VISA的全称是Virtual Instrument Software Architecture虚拟仪器软件架构是仪器行业里一套统一的I/O接口标准。你在LabVIEW里写的VISA代码不只可以操作串口还可以操作GPIB、PXI、USB、以太网接口的仪器。换句话说VISA把“设备和计算机之间的通信方式”抽象成了一组高度统一的APIVISA Open、VISA Write、VISA Read、VISA Close。如果你用Windows API直接操作COM口写代码时要关心CreateFile、SetCommState、ReadFile这些底层函数还要处理驱动兼容性。VISA则把这一套都封装好了并且提供了跨平台的实现NI-VISA在Windows、Linux、macOS上都有对应版本代码迁移成本极低。用在串口调试助手上VISA带来的最大好处是“会话”概念非常清晰。你把串口看成一个会话打开会话、配置参数、读写数据、关闭会话全部是标准动作出错时还能拿到一个统一的错误码。这套思路一理清串口调试助手的程序骨架就出来了。1.3 我先给这个工具划个功能边界写软件最怕需求膨胀个人工具也一样。我最初定下的功能边界是以下几项支持本机串口自动枚举打开/关闭设备按钮参数配置波特率、数据位、校验位、停止位、流控。支持ASCII和HEX两种收发方式收发计数统计。支持手动发送、定时发送、周期循环发送。接收区支持自动滚动显示同时将数据按时间戳追加写入日志文件。额外预留一个“解析接口”方便针对具体设备的协议格式做二次加工。后面那些复杂功能比如协议CRC自动计算、多设备联动、数据曲线展示我都是在基础框架跑通之后逐步加进去的。建议你也这样做先把“能打开、能收、能发、能存”八字方针做到位再谈其他。2. 串口通信的底层原理VISA是在帮你管理一个会话2.1 从打开设备到收发数据VISA的完整调用链用VISA操作串口你的程序里一定会出现这样一条主链路VISA Find Resource或者直接指定资源名然后VISA Open打开会话之后用VISA Configure Serial Port配置参数循环里用VISA Write和VISA Read做收发最后用VISA Close关闭会话。这里面有几个要点需要展开说说。第一是资源名。串口在VISA体系里的资源名通常是COM3即使你在NI MAX里看设备列表它显示的也是类似COM3到了别的平台可能叫/dev/ttyUSB0。编程时最好别写死资源名而是用VISA Find Resource去系统里查一遍所有可用资源再填进串口下拉框。第二是配置节点。VISA Configure Serial Port这个函数内部其实是在设置一串参数你可以把它看出一个模板化的配置动作。它会把波特率、数据位、停止位、校验位、流控等一并写进会话属性。第三是会话句柄。VISA Open返回的是一个会话句柄后续所有操作都要带着它。就好比你跟设备之间建立了一条电话线通话端不能换人。程序结束前一定要Close否则句柄泄漏下一次打开串口可能报“设备被占用”。有一个很多新手没注意到的动作LabVIEW工程里如果调用了VISA开发机必须安装NI-VISA驱动而且版本最好跟LabVIEW版本匹配。热搜词里有“labview安装错误”和“labview安装路径”很多情况就是先装了LabVIEW再装VISA或者反过来顺序不对导致VISA函数库加载失败。稳妥做法是先装LabVIEW再装NI-VISA运行时然后打开LabVIEW新建VI在函数面板的“仪器I/O”里能看到“VISA会话”相关函数这才算成功。2.2 串口参数里最容易被忽略的三个细节串口参数看起来就是几个下拉框但有几个细节特别影响通信质量。第一个是校验位。很多设备出厂默认None无校验如果你的设备和PC约定的是偶校验或奇校验配置一旦选错收到的数据就是乱码而且很隐蔽。因为你发出去的字符串看着正常读回来的十六进制却完全对不上。第二个是停止位。老一些的设备用1.5或2个停止位现代设备大多默认1个。接老设备时最好查手册确认。数据位同样如此一些工业设备会用7位数据加1位偶校验这种组合在兼容Modbus和老式传感器里比较常见。第三个是流控。很多串口助手默认流控是None也就是不启用硬件握手。但某些设备设计上却要求RTS/CTS流控你不开流控数据照发但设备可能不回包。这类问题排查起来非常绕建议开头就把设备的流控需求确认清楚。我建议在初始化时通过VISA Property Node把这些属性显式设置一轮不要只依赖VISA Configure Serial Port的默认值。代码里体现出来的效果就是打开会话后紧接着设置Baud Rate、Data Bits、Parity、Stop Bits、Flow Control这五个属性再设置读写超时时间和输入输出缓冲区大小。2.3 读操作的“有数据才读”和“定长读取”有什么区别串口通信里读数据是核心也是最容易出bug的地方。VISA Read函数需要告诉它“你想读多少个字节”它才会返回那么多字节。但串口数据什么时候来、来多少完全不可控所以直接用一个固定字节数去读很可能读不够就超时或者读多了把下一个帧的数据也吞进来。常用的做法是配合VISA Property Node里的Bytes at Port属性先问一句“当前缓冲区里有多少字节”再根据这个数字去读。这个属性返回的是当前可读字节数你把它传给VISA Read的byte count输入就能做到“有多少读多少”。另外还要注意Termination Character终止字符。如果设备发送的每一帧数据都以换行符\n0x0A或者回车换行0x0D 0x0A结尾你可以启用终止字符模式设置Termination Character为特定值并将Termination Character Enable设为True。这样VISA读到的数据会一直读到终止字符为止语义上更像“读一行”。但这里有个坑如果设备发送的二进制帧中间恰好有字节等于0x0A启用终止字符会提前截断数据。所以做HEX方式收发时我一般把终止字符禁用。ASCII方式收发时再按设备情况决定是否启用。3. 动手前先把界面和初始化流程定好3.1 前面板控件与图标搭建清单LabVIEW的优点就是前面板拉控件快。我这个串口助手的前面板主要包含串口资源下拉框旁边放一个“刷新”按钮。波特率下拉框数据位、校验位、停止位、流控选项框。“打开串口”和“关闭串口”两个按钮。接收数据显示区用字符串显示控件支持自动滚动。发送数据输入区用字符串输入控件。“发送”按钮、“定时发送”复选框和定时周期输入框。ASCII/HEX模式切换按钮或下拉框。接收字节计数、发送字节计数显示框。“清空接收”“保存日志”按钮。界面布局我习惯把参数配置放在左侧一列接收区占中间大面积发送区放在右下角。这样在调整设备参数时不会挡住数据流显示。控件色调上LabVIEW默认白底黑字其实够用不用特意做花哨美化除非你的工具要给同事用那可以花点时间统一字体和颜色风格。3.2 用资源枚举而不是手动输入COM口号很多示例程序让你直接输入COM口号比如手动填“COM3”。这在临时调试时没问题但如果你经常插拔USB转串口设备COM口号会飘今天插是COM3明天可能是COM5。所以我强烈建议做资源枚举功能。实现思路不复杂用VISA Find Resource配合VISA类的查询字符串轮询出当前系统里所有VISA资源再把资源名下COM端口筛出来填入下拉框。用户点“刷新”按钮时重新执行一次枚举下拉框内容就更新了。这里有个小经验完全关闭串口再刷新枚举出来的资源列表更准确。如果串口被其他软件占用VISA资源列表里可能还会显示但打开时会报错所以打开失败的错误处理也必须做。3.3 程序状态与串口打开失败的兜底处理LabVIEW程序如果只是单个VI运行起来就是在一个大循环里跑事件结构。我建议在顶层VI里定义一个简单的状态枚举未打开、已打开、正在接收、正在发送。打开按钮按下后只有状态是“未打开”才进入打开流程关闭按钮同理只有“已打开”才执行关闭。串口打开失败是最常见的异常比如设备不存在、驱动没装好、串口被别的软件占用、或者权限不足。处理方式是在打开流程末尾接一个Simple Error Handler或者自定义错误弹窗把错误源和错误码显示出来同时把“打开串口”按钮状态复位防止界面看起来是打开状态但实际会话无效。关于错误簇要单独说一句LabVIEW的错误输入/输出线和数据线一样重要绝不能图省事直接悬空。我在调试初期吃过亏VISA Open报错后程序继续走后面VISA Read一直返回超时界面像卡死一样。后来在关键节点全部挂错误处理问题一下子定位到了。串口工具这类长期运行的程序错误处理一定要前置。4. 核心收发逻辑的三种实现方案和我的选型4.1 方案A事件结构加主动查询低速可靠型LabVIEW里最顺手的UI响应机制是事件结构Event Structure按钮按下、值改变都能触发事件。如果你只需要手动点击“发送”才能发数据那核心逻辑非常简单在“发送”按钮值改变事件里将输入字符串转成字节数组调用VISA Write再把发送字节数累加到计数里。接收部分则按时去查一次在每个循环周期里读取Bytes at Port如果大于0就调用VISA Read读取。这种方式叫主动查询适合波特率不高、数据量不大的场景比如115200波特率以内、每秒几百字节的常规设备交互。优点是代码直观、容易调试缺点是在低优先级循环里如果界面被拖动、按钮响应慢接收数据可能出现停顿。如果采用这个方案我建议接收查询放在一个单独的While循环里定时器设50ms到100ms不要和UI事件挤在同一个循环里。接收循环里读到的数据通过队列或通知发给UI线程更新显示避免字符串控件频繁刷新导致界面卡顿。4.2 方案BWhile循环加Bytes at Port轮询通用型方案B跟上一种类似但更强调通用性。核心逻辑是用一个较快的循环10ms到20ms不停读取Bytes at Port有数据就马上读出来然后做显示、计数、写日志发送侧放在事件结构里面点击发送或者定时器到点就执行VISA Write。在LabVIEW里实现轮询并不复杂循环里放VISA Property Node属性选择Serial Settings.Bytes at Port。读取出的字节数i如果大于0将i传给VISA Read。读取返回的字符串通过Format Into String加上时间戳显示在接收区同时写入日志文件。循环里放一个Wait(ms)建议不小于10ms避免循环空转把CPU占用拉满。这个方案是当前我见过的大多数LabVIEW串口示例采用的写法它足够通用适合数据流相对均匀的设备。如果说方案A的弱点是UI和接收可能互相影响方案B把接收循环独立出来稳定性已经好了很多。4.3 方案C生产者消费者加事件回调高速型如果设备以很高的频率连续回传数据比如每毫秒几十个字节或者你需要把串口数据实时画曲线那轮询方案就有点吃力了。这时候推荐生产者消费者模式一个生产者循环专门负责读取串口把读到的数据放进队列一个消费者循环负责解析、显示、存储UI事件结构独立运行通过用户事件通知其他部分做发送或参数变更。我在处理一路921600波特率的传感器数据流时就是用这个思路。生产者循环里读串口放到一个Float数组队列消费者循环做解析和波形显示。界面操作和数据处理彻底解耦前面板再怎么拖窗口数据也不丢。代价是程序结构复杂不少要理解“队列”和“通知”这些多线程概念。对第一次写串口助手的读者来说我建议先把方案B跑通再考虑升级到方案C。4.4 我实际项目中的最终选择我的做法是折中平时低速率调试用方案B代码简单改起来快碰到高速数据流或长时间测试时改造成方案C。有个小技巧是把接收数据解析单独封装成一个子VI输入是读到的原始字符串输出是解析后的通道数值、错误标志等。这样不管底层是轮询还是生产者消费者上层解析逻辑完全不用改后面换协议也只在子VI里改。4.5 定时发送和HEX模式的处理细节定时发送功能本质上是给“发送”按钮加一个周期性触发。最简单的做法是在“定时发送”复选框为真时用定时器事件每隔N毫秒执行一次发送逻辑。N建议设成整数倍比如100毫秒、500毫秒不要小于20ms否则定时器事件可能积压反而拖垮整个程序。HEX模式需要注意用户输入的是55 AA 01 02这种十六进制文本中间有空格发送前需要将字符串拆分成十六进制字节数组然后再传给VISA Write。如果用户在HEX模式输入了非法字符比如GG一定要弹提示或者过滤掉否则转换函数会报错甚至发送出错误字节。接收侧HEX显示正好反过来把VISA Read读到的字符串转成十六进制字符串用空格分隔显示。实现上可以用String To Byte Array获得字节数组再用Format Into String统一按两位HEX格式化输出。5. 数据可视化、日志保存和协议解析的扩展思路5.1 接收区的文本表现与编码问题串口助手显示数据大部分设备回传的是ASCII字符接收区用普通字符串显示控件就能看。但有些设备返回的是GBK编码的中文文本比如GPS模块或带中文状态上报的设备。直接用LabVIEW字符串显示可能乱码原因是LabVIEW内部字符串默认按ASCII或本地编码处理。这时候要在读取后将字节数组按GBK做转换LabVIEW里可以使用Decode String配合编码名称或者用一些外部转换库。如果数据是二进制帧我建议接收区默认按HEX显示同时把原始字节也保留下来。这样对协议调试更直观也方便后面写解析逻辑。5.2 在波形图上实时显示解析出的数值串口调试助手不只是看文本很多时候我们需要把数据“翻译”成曲线。比如传感器每秒钟回传一次温度一次10个字节其中第3到第6字节是一个float。你可以在接收解析子VI里从原始字符串里截取对应字节然后Unflatten From String按单精度浮点数解包得到一个工程量。解包后的数值送到波形图波形图设置为接收“数组”或“单点”模式。每收到一帧数据就往图表里追加一个点。如果时间跨度很长建议在消费者循环里做一个环形缓存只保留最近N个点避免图形控件数据量过大导致刷新卡顿。我遇到过一种情况波形图刷新频率太高每秒钟几十帧前面板画图占用了很高CPU。解决方法是限制视图更新频率比如数值解析每个周期都做但波形图控件每100ms才更新一次。数据一帧不丢画面也流畅很多。5.3 日志保存用CSV还是TDMS怎么带时间戳日志是长时间跑测试时最核心的功能。用CSV的好处是任何文本编辑器都能打开坏处是数据量大时打开慢。用TDMS是NI自家的存储格式写入性能好而且能附带通道名和属性信息配合DIAdem或LabVIEW读取非常方便。我自己的习惯是如果接收的数据多是纯文本报文用CSV如果接收的是结构化多通道数据用TDMS。不管用哪种日志文件命名建议带上日期和时间比如log_20231214_153000.csv这样不会覆盖历史文件。写入时在每一行前面加时间戳字符串格式用%Y-%m-%d %H:%M:%S.%3f这类毫秒级时间对事后分析很有帮助。LabVIEW里获取时间戳用Get Date/Time In Seconds然后Format Date/Time String格式化成你需要的字符串。写入时注意文件句柄的管理日志文件在打开串口时创建关闭串口时关闭不要在每次写一行时都打开关闭文件那样性能很差。5.4 预留协议解析接口把CRC和校验码做成可替换模块协议解析是这个工具进阶的分水岭。以最常见的Modbus RTU为例设备回传的帧是从站地址 功能码 数据区 CRC16调试时需要把帧拆成字段并验证CRC。我建议在接收循环里加一个“帧检测”步骤把读到的数据不断放入一个输入缓冲区然后去匹配帧头、计算长度等长度够了就取出完整帧交到协议解析子VI。协议解析子VI里做CRC计算、字段拆分输出解析结果。这样即使底层的串口缓存和上层解析逻辑不断变化整体模块依然清晰。这样一个协议解析子VI的输入输出接口一定要设计好。输入是原始字节数组和参数输出是解析后的簇比如用Cluster包含地址、功能码、数据数组、CRC是否正确。拿到解析结果后你可以决定显示在表格里、画图、还是写到日志里。6. 实测排坑记录这些坑最影响体验6.1 为什么VISA Open偶尔会报错而且报错的时机很迷我碰到过的VISA Open报错大概有三类。第一类是资源名写错VISA资源名称输入的是COM3但实际系统里串口已经被认成COM5VISA打开一个不存在的资源自然报错。解决办法就是前面说的资源枚举不要手动填。第二类是串口被其他程序占用。出问题时错误信息一般会包括0xBFFF0011这类VISA错误码表示访问被拒绝或者资源未找到。很多USB转串口设备被拔插后系统还残留一个占用的句柄重启开发机或者拔插设备通常能解决。第三类是驱动问题。某些超低成本的USB转串口芯片兼容性不太稳定不是VISA本身的问题。建议调试时先用设备管理器确认驱动正常再用NI MAX的“设备与接口”里测试串口这样能区分是硬件驱动问题还是LabVIEW程序问题。6.2 发出去收不到回包先改这两个属性发数据出去一直没有回包很多人第一反应是协议或设备有问题但更多时候是串口参数和超时设置不对。先查Timeout属性。VISA默认超时时间是2000ms也就是2秒。如果设备响应很慢比如需要5秒才回包你的VISA Read可能已经在2秒时报了超时错误程序返回后你看到的接收区自然为空。把Timeout加大到5000ms甚至10000ms问题消失。再查Termination Character。如果设备回包不带\n结尾但你的程序启用了终止字符读VISA Read会一直等不到终止字符直到超时才把缓冲区数据一次性返回表现也是“好像收不到”。最简单的方式是先禁用终止字符改按字节数读。这两个属性检查完再回头看波特率、校验位、停止位这些参数多数无回包问题都能解决。6.3 高速数传丢数据问题不只在于波特率波特率只是传输速率的上限实际能处理多少数据还取决于你的程序读取效率。如果读取循环里做了很多额外工作比如接收后马上解析显示、写文件、刷新波形图这些都会占用时间。在数据连续到达时读取循环忙不过来接收缓冲区就会溢出导致丢数据。解决思路有两条。一条是加大VISA的I/O Buffer Size属性比如默认输入缓冲是4096字节可以调到65536。另一条是把读取、解析、显示分离成不同任务用队列或数据管道串联起来。部分用户提到“运行labview程序电脑死机”多半就出在这种情况轮询循环过于紧凑加上前面板控件大量刷新CPU被占满。我的做法是接收循环里只做读和入队显示和写文件都放到消费者循环。另一个很低级的坑是Bytes at Port属性读取后实际上会消耗缓冲区的一部分字节吗不会它只是查询。但如果你在读之前调用了它读之后又去读一次两次之间可能会混入新到的数据所以不要在循环里多次查询最好只在接收前查一次。6.4 热插拔之后串口会话失效怎么办USB转串口设备被拔掉再插入原来的COM口可能变了而且旧会话会失效。你的程序里需要对这种情况做重连机制。简单方案是在接收循环里捕获VISA错误如果错误码指示“资源已关闭”或“无效访问”就弹出提示并自动把界面切回未打开状态。进阶做法是后台持续轮询VISA资源列表检测到新的串口插入后自动尝试按设备描述重新打开。6.5 中文乱码和字节序问题一起说中文乱码的本质是编码不一致。设备发送GBK你在LabVIEW里按ASCII显示就会乱。解决方法前面已经提到用Decode String转成统一编码。如果数据在电脑之间转发还要注意串口本身没有“字符”概念只有字节发送端怎么编码接收端就要怎么解码。比如设备按UTF-8发送中文你按GBK解码同样乱码。字节序问题常见于接收浮点数或int16时。同一个0x1234在设备里可能是高字节在前也可能是低字节在前。用Unflatten From String解析时需要检查VI的“字节顺序”设置。调试时如果解析出来的数值明显偏大或偏小先怀疑字节序不要一开始就怀疑协议字段位置。6.6 LabVIEW程序性能的几条硬经验结合热搜词里的“运行labview程序电脑死机”这个现象我多说几句性能优化经验。第一所有耗时操作都不要放在UI事件结构里面。写文件、大量数据转换、发送大段数据这些操作如果和UI按钮事件挤在一起前面板会卡顿甚至看起来像死机。第二控制接收区字符串控件的刷新频率。如果你把每一次read的结果都直接追加到字符串控件5000次刷新后内存占用会很高。用“显示缓冲”思路先拼接到局部字符串变量再每隔一段时间刷一次控件。第三子VI不要频繁动态调用。一个高频率的循环里动态调用子VI带来的额外开销可能吃掉不少CPU。固定调用子VI链接就好能少用就用。收尾前最后说一点这个串口调试助手我后来陆续迭代了很多版本从最初几十行面板到现在已经集成进一套半自动产测框架里。回看整个过程我觉得最有价值的不是代码本身而是把“串口调试”这个看似简单的事情拆成了一个可扩展的程序骨架会话管理、参数配置、收发循环、数据解析、日志存储每一块都能独立替换。遇到新的设备只需要改协议解析子VI其余部分几乎不动。如果你刚接触LabVIEW和VISA建议从最小功能开始先把“打开-发送-接收-关闭”跑通再逐步加功能。每个版本能跑、能验证、能保存心里就不慌。串口调试这件事最终拼的是对设备和协议的熟悉程度工具能帮你自动化的是那些重复劳动而理解数据背后的意义永远是人的活。
返回列表