开头
前阵子帮朋友收拾一套老旧供水泵站的改造项目,感触挺深。现场控制柜里4台水泵、7块仪表,原来全靠人工巡检,值班员每天拿着纸笔挨个抄表,故障晚几小时发现,损失就是上万立方米的水。要上PLC加触摸屏再加网关,一套下来成本顶着预算上限。最后我选了带Lua脚本的大彩DACAI组态屏来做边缘采集节点,Modbus 485把从机仪表数据全部拉上来,屏上直接显示,同时通过4G把数据传到云端,还接了一路北斗定位模块,把泵站巡检终端的位置也一并上报。整个方案,一块屏全干完了。
这个思路现在在很多中小型物联网项目里非常通用,尤其是那种点位不多、现场运维困难、设备分散的场景。它能解决的核心问题有三个:一是省掉专用采集网关,二是现场能直接看状态、能本地调试,三是云端能远程改逻辑、下发脚本,不用总跑现场。对于做组态屏开发、工控系统集成、物联网边缘采集的工程师,这套东西完全可以当模板抄。
下面我把从选型、通信原理、Lua代码、云端下发到北斗定位接入的完整过程,按我实际做的顺序拆开讲清楚,里面的坑和取舍都会提到。
1. 方案架构与整体设计思路
1.1 为什么用带Lua的组态屏做采集网关
做数据采集,常规选项就那么几个:小型PLC、DTU、工业网关、工业平板,再就是这种带Lua脚本能力的组态屏。我拉了一张对比表,各位可以直观感受下差别:
| 方案 | 现场显示 | 二次开发灵活性 | 云端通信 | 综合成本 | 部署难度 |
|---|---|---|---|---|---|
| 小型PLC+触摸屏 | 需要另配屏 | 需梯形图+协议库 | 通常需另加模块 | 高 | 中 |
| DTU | 无显示 | 无,只能透传 | 自带 | 低 | 低 |
| 工业网关 | 通常无 | 中,多数需专业IDE | 自带 | 中高 | 中 |
| 带Lua组态屏 | 自带 | 高,脚本可改协议 | 可选4G/Wi-Fi | 低中 | 中 |
在只有几百个点位以内的中小现场,PLC加屏的方案属于“杀鸡用牛刀”,成本高而且升级逻辑必须到现场或者依赖远程模块。纯DTU虽然便宜,但它只负责数据透传,协议解析、断线缓存、本地显示全都做不了。工业网关功能强但通常价格不低,而且现场没人看得见数据,调试时还得开着电脑跟它连。而带Lua脚本的组态屏,本质上是“屏幕、协议转换器、边缘采集器”三合一,屏上直接显示所有仪表值,设备人员路过扫一眼就知道当前工况,工程师远程下发一个新脚本,连采集逻辑都能改。
另一个被很多人忽略的点是,组态屏本身自带了一套成熟的控件库,跑马灯报警、历史曲线、报表查询这些都是现成的,不用像做纯网关那样从零画界面。项目进度上,光界面这块就能省出一大截工期。
1.2 整体数据链路拓扑
这套系统的完整链路是:现场仪表、电表等从机设备通过RS-485总线接到组态屏的串口1,组态屏作为Modbus主站按轮询表依次读取数据;北斗定位模块通过TTL串口或隔离后的串口2接到屏,持续输出NMEA定位数据;屏内Lua脚本把这两部分数据汇总、清洗,通过4G/Wi-Fi模块上传到云端平台。
云端平台其实承担了两个角色。一是数据接收端,接收屏上传的采集数据和定位数据,供监控页面、报表、告警使用;二是配置下发端,把新版本的Lua脚本、参数表、点表下发到屏,屏收到后校验、存储、热加载生效。画成文字就是:
Modbus从机1 ─┐ Modbus从机2 ─┼─ RS-485总线段 ── 组态屏串口1(Modbus主站) Modbus从机3 ─┘ │ Lua脚本引擎 北斗模块 ── 串口2 ─▶ GGA帧解析 ──▶ 缓存定位数据 │ 4G/Wi-Fi模块 ── 雲端平台 │ 云端下发脚本 → 校验 → 热加载这套拓扑最大的好处是把采集链路和定位链路分开走,不让两类不同波特率、不同协议的数据互相干扰。Modbus轮询本身有严格的时序要求,如果混了高频NMEA数据,串口缓冲区一乱,整个轮询节奏就崩了。所以宁可多占用一路串口,也不要图省事共用。
1.3 硬件选型与端口资源规划
选具体型号时,我最先确认的是三件事:屏的串口数量、是否能扩展4G通信、Lua引擎的可用RAM大小。普通的OLED小屏或低端组态屏,串口资源往往只有一路,这不满足需求。我后来选用的是大彩DACAI主流的几款带网络功能的HMI系列,具体来说它通常具备两路以上串口,能外接或内置4G/Wi-Fi模块,Lua脚本可常驻运行。
端口规划上,我给出的分配方案是:
- 串口1:RS-485,接Modbus总线,波特率9600bps(根据从机设备默认值,后续可调);
- 串口2:接北斗定位模块,波特率9600bps或115200bps,按模块出厂配置;
- 网络通道:4G/Wi-Fi,用于MQTT或HTTP方式连接云端。
这里有个容易被忽略的点:RS-485总线上挂的设备多,但每个设备的默认波特率、校验位可能不同,不能因为主站改了波特率就万事大吉。实际项目中,我优先选择全部从机统一到9600/8/N/1,个别老表必须用4800的,只能拆成两条总线或者加一个协议转换模块。Lua脚本侧要预留一张设备参数表,把站号、波特率、寄存器地址都做成可配置项,方便云端下发修改。
2. Modbus 485从机采集的Lua实现
2.1 Modbus RTU主站通信原理
Modbus RTU是主从问答式协议,一个主站对应多个从站,物理层通常是RS-485半双工。所有通信由主站发起,从站收到合法请求后回复。帧结构非常紧凑:地址码、功能码、数据区、CRC16校验,全部算上也只有几个字节。地址码1到247是有效从站地址,0是广播地址,一般不用于正常采集。
功能码这里只需要关心几类:03读保持寄存器、04读输入寄存器、01读线圈、02读离散输入。实际采集仪表数据,绝大多数用的是03或04。它们的区别在于“保持寄存器”是可读写的,比如设备参数设置,而“输入寄存器”是只读的,比如实时流量、压力、频率。我这里所有仪表采集统一用03,因为很多现场仪表的厂商把实时数据也映射到了保持寄存器区,写法一致,程序处理反而简单。
地址映射容易让人头大。协议报文里的寄存器地址是从0开始的,而很多设备的寄存器手册上写的寄存器号是从1开始的,例如“40001保持寄存器1号”,对应到报文里的地址就是0x0000。有经验的工程师都见过这种坑:照着手册地址原封不动填进报文里,结果读回来的数据和表盘对不上,项目组查了一下午,最后发现就是使用时忘减了1。遇到寄存器号要换算,40001对应报文地址0,40002对应1,依此类推。Lua里我习惯直接在配置表里存协议地址,避免每次换算出错。
2.2 CRC16校验、报文组帧与解析
CRC16是Modbus RTU的保命符。接收端必须校验CRC,否则总线上一旦有干扰,错误的数据可能被当成真值用。Modbus使用的CRC16-Modbus算法,多项式0x8005,初始值0xFFFF,输出时低字节在前。和标准CRC16-CCITT不是一回事,别混。
我在大彩屏Lua里用的CRC计算函数,基本就是从标准查表法改过来的:
-- CRC16-Modbus 计算,返回高字节和低字节 function modbus_crc(frame) local crc = 0xFFFF for i = 1, #frame do crc = crc ~ frame:byte(i) for j = 1, 8 do if (crc & 0x0001) ~= 0 then crc = (crc >> 1) ~ 0xA001 else crc = crc >> 1 end end end local lo = crc & 0xFF local hi = (crc >> 8) & 0xFF return lo, hi end这里特别强调一个发送顺序:计算出来的CRC两个字节,要低字节在前发送。也就是先发lo,再发hi。很多新手在PC上调试时用的模拟器给的是高字节在前,直接照搬到单片机上就死活不通,这是高频翻车点。
组好帧之后,发送、等待、解析是一套完整动作。我习惯的节奏是:发送前清空接收缓冲区,发送后启动200ms超时定时器,在超时前把接收到的字节串拼接起来,按长度判断:请求03功能码返回的帧长度应当是3+n*2字节,n是寄存器个数,比如读2个寄存器,返回帧就是1字节地址+1字节功能码+1字节字节数+4字节数据+2字节CRC,总长9字节。解析时先把站号、功能码校验一遍,再核对CRC,最后按大端序(高字节在前)把寄存器值拼出来。
2.3 轮询调度与多表轮询代码示例
多台从机合理轮询,核心是调度表。我代码里最核心的数据结构是这样的:
| 点号 | 从站地址 | 功能码 | 起始寄存器(协议地址) | 寄存器个数 | 数据类型 | 缩放系数 | 单位 |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 03 | 0 | 2 | INT16 | 0.1 | MPa |
| 2 | 1 | 03 | 2 | 2 | UINT32 | 1 | m³/h |
| 3 | 2 | 04 | 0 | 1 | INT16 | 1 | ℃ |
-- 轮询主逻辑(简化示意) local poll_table = { {addr = 1, fn = 3, reg = 0, count = 2, handle = 1}, {addr = 1, fn = 3, reg = 2, count = 2, handle = 2}, {addr = 2, fn = 4, reg = 0, count = 1, handle = 3}, } local current_index = 1 local req_pending = false function build_request(item) local frame = string.char(item.addr, item.fn, math.floor(item.reg / 256), item.reg % 256, math.floor(item.count / 256), item.count % 256) local lo, hi = modbus_crc(frame) return frame .. string.char(lo, hi) end function on_timer_poll() if req_pending then -- 超时处理:直接超时计次加1,跳下一台 req_pending = false end local item = poll_table[current_index] send_uart1(build_request(item)) req_pending = true current_index = current_index % #poll_table + 1 end实际在组态屏里跑的时候,不建议用while死循环轮询,而是用一个定时器驱动,比如每300ms触发一次“发送下一帧”。好处是主循环不会被卡死,界面还能随时响应触摸操作。请求发出去之后,接收靠串口中断回调把字节存入缓冲区,由另一个逻辑判断帧长度是否完整。
轮询周期有个计算公式可以提前估算:如果总线上有10台设备,每台读1到2个寄存器,单帧请求8字节,返回9字节,按9600bps算,一帧通信耗时大约20ms,再加上响应延时,单台设备大约需要40到60ms,轮询一整圈也就是1秒以内。如果每台要读几十个寄存器,帧长了,这个周期会被拉长,需要结合从站实际响应能力调整定时器间隔。
在多从机系统里,我强烈建议加一个“连续超时N次则标记该从站离线”的状态位。不然某个从站掉线后,主站每次轮询到它都要等满超时时间,整条总线被拖慢,其他正常设备的数据更新周期也跟着受影响。
2.4 现场接线与参数设置要点
Lua写完之后,物理层不过关,一切都是白搭。RS-485接线有几个硬规矩,我在这个项目里踩过不少坑:
- 485的A、B线不能接反。现在大部分仪表有标识,但老设备上可能只标了“+”和“-”,这里的对应关系各厂不统一,有的“+”是A,有的“-”是A,接反的表现是能发不能收或者完全不通,而且多台设备共用一个错误接法时,现象更隐蔽。
- 总线两端必须加120Ω终端电阻。如果只接一台从机,而且距离短,可以不加;但只要距离超过几十米,不加终端电阻就容易出现回波干扰,表现为偶发丢包、CRC错误。
- 屏蔽层单端接地,也就是只在主站侧或只在某一处接地,不要两端都接,否则可能形成地环路。
- 现场经常遇到的共地问题:RS-485理论上没要求共地,但不同设备之间地电位差大了照样打坏收发器。最稳妥的做法是确认所有设备的信号地GND都接到同一参考地。
波特率、校验位、停止位必须和每个从站参数严格对应。仪表出厂很多是9600/8/N/1,但有些老电表默认4800,校验位还要偶校验。如果没法统一,宁可拆总线也不要硬串,否则总线上一个异类设备会拖累全部通信。
3. 云端下发脚本控制机制
3.1 下发脚本为什么必要
现场设备分布广,有些站点在偏远地区,甚至在山里。传统组态屏的逻辑是出厂烧死的,要改采集逻辑、加点位、调报警阈值,必须带笔记本到现场改工程重新下载,几个小时就耗在路上。而我需要的是一个能远程修改程序的方案。
带网络功能的组态屏天然具备一条云通道,但光有通道还不够,必须设计一套“可远程加载脚本”的机制,这样才能在不重启屏、不中断现场显示的情况下动态更新采集逻辑。这是整个系统里最体现工程价值的部分。
下发脚本解决的不只是“改代码”这一件事。点表调整、从机站号变更、报警阈值变化、新增一个北斗围栏区域,这些需求本质上都是“程序参数”或“程序逻辑”的变更,一旦能下发脚本,全部变成云端几分钟的操作。对做运维的人来说,这意味着野外站点再也不用跑断腿了。
3.2 下发链路设计(平台到屏的完整流程)
下发链路的核心是传输、存储、校验、热加载四个阶段。传输阶段,云端平台把脚本文件经过打包后下发到屏,通常走MQTT或HTTP。大彩屏的网络模块收到下载请求后,把内容先存到临时分区,然后校验文件大小、CRC、版本号,全部通过后写入正式脚本区,最后通过Lua引擎的升级接口或重启脚本环境完成热加载。
我给这个项目设计的指令格式是JSON包裹的,大致长这样:
{ "cmd": "update_script", "version": "2", "size": 18624, "crc32": "a1b2c3d4", "data": "base64编码后的脚本内容" }之所以用base64做脚本内容的编码,是因为Lua脚本本身是纯文本,但里面可能包含中文、特殊符号,直接放进JSON容易转义出错,base64编码后传输稳定性高得多。平台先下发一个小体积的“指令头”,屏收到后根据指令头判断要不要继续接收数据体。整体过程类似断点续传的思路,避免大脚本超时失败。
屏端收到数据后,第一步是校验文件尺寸,第二步计算CRC32并与云端值比较,第三步检查版本号是否大于当前运行版本。任何一步失败都直接丢弃,并上报错误状态。全部通过后才允许覆盖旧脚本。我实测下来,这个流程在弱网环境下成功率很高,偶尔一次接收不完整,重试两三次就能成功。
关于Lua引擎的热加载,不同固件版本的机制有差异,有的支持直接在运行中切换脚本,有的需要设置一个“待生效”标记,然后软重启脚本解释器,界面本身不重启。我在项目中用的是后者,因为它更稳定,不会出现脚本切换期间变量状态错乱。热加载后,所有全局变量、定时器状态统统重新初始化,这是设计脚本时需要注意的:不要把关键状态只放在内存里,一定要定期持久化到屏的Flash存储,否则一次下发更新,历史累计数据全没了。
3.3 下发脚本的安全与回滚策略
远程下发是有风险的,脚本一旦语法错误或者逻辑有问题,屏可能白屏或是陷入死循环。我在项目里定了几条规矩:
- 每个站点定义统一的脚本版本号,云端和屏端都记录当前版本;
- 下发前必须先在本地模拟器上跑通,确认无误再推送到云端;
- 屏上保留上一份可用脚本的备份分区,新脚本运行异常时手动或自动回滚;
- 下发动作要有操作权限控制,不能随便一个人从手机端就能改采集逻辑。
自动回滚这块容易被忽略,但极其重要。我见过某同行远程下发脚本后,屏直接失去响应,现场又没人,最后只能让当地电工断电重启,要是断电都解决不了,就得上门重烧。从那以后,我给的屏端脚本都加了一个“看门狗”:脚本启动后必须在5秒钟内完成初始化并置位一个内部心跳标志,否则判定异常,自动恢复上一版本。
给云端平台侧设计回滚也一样重要。平台要记录每次下发的操作日志和版本号,万一现场反馈数据不对,运维人员能在平台上一键把脚本回退到旧版本。这样做还有一个附带好处:不同站点的脚本版本可以差异化,站点A升级了,站点B暂时不动,完全可控。
4. 北斗定位接入与坐标处理
4.1 北斗模块的接入方式与输出协议
方案里的定位需求来自野外巡检车辆和泵站运维终端的轨迹监控。我选用的北斗定位模块通常通过UART TTL电平串口输出数据,接屏的串口2。模块具体型号不展开了,但它的输出协议是通用的NMEA-0183,直接解析即可。
NMEA-0183就是一串按逗号分隔的ASCII语句,以“$”开头,回车换行结尾。常用语句有GGA(定位信息)、RMC(推荐最低定位信息)、GLL(经纬度)、GSV(卫星状态)等。我这里主用GGA,因为它的字段信息最全,包含UTC时间、纬度、经度、定位状态、卫星数、海拔高度。帧长大概70多个字符,每秒输出一次。
接入时有几个细节,新手容易栽。北斗模块通常要求先冷启动、热启动,首次定位时间从几十秒到几分钟不等,如果放在室内或者被金属外壳完全屏蔽,无论等多久都定位不了。项目调试初期,我在控制柜里测试,柜门一关,定位模块直接沉默了,打开门等了一阵子才恢复。所以安装位置务必朝向开阔天空,天线朝上,不要紧贴金属机箱。如果控制柜本身是金属外壳,最好把定位模块的天线引到柜体外侧。
4.2 GGA帧解析与坐标换算
GGA语句格式是:
$GNGGA,062503.00,3114.68236,N,12123.45912,E,1,8,1.02,28.5,M,-5.7,M,,*68字段含义依次是UTC时间、纬度(度分格式)、北纬/南纬、经度(度分格式)、东经/西经、定位状态(0无定位,1有效)、卫星数、水平精度因子、海拔高度等。
这里最关键的坑是坐标格式。NMEA输出的“3114.68236”不是十进制的31.1468236度,而是31度14.68236分。要换算成十进制度数,公式是:
十进制度数 = 度 + 分/60
按上面的例子,纬度就是31 + 14.68236 / 60 = 31.244706度。经度121 + 23.45912/60 = 121.3909853度。我见过有人在平台地图上显示坐标完全对不上,检查半天,发现就是忘了换算,直接把度分当度数传上去了,差了近20公里。
Lua里的解析逻辑大概是这样的:
-- 解析GGA帧,返回经纬度和定位质量 function parse_gga(line) if line:sub(1, 6) ~= "$GNGGA" and line:sub(1, 6) ~= "$GPGGA" then return nil end local parts = split(line, ",") local status = tonumber(parts[6]) local lat = dm_to_degree(parts[2], parts[3]) local lon = dm_to_degree(parts[4], parts[5]) local sats = tonumber(parts[7]) return lon, lat, status, sats end function dm_to_degree(dm_str, dir) local dm = tonumber(dm_str) local deg = math.floor(dm / 100) local minute = dm - deg * 100 local result = deg + minute / 60 if dir == "S" or dir == "W" then result = -result end return result end解析重点有一个:NMEA帧尾的“68”是校验和,做法是“$”和“”之间所有字符的异或和,也是十六进制ASCII。别以为可有可无,总线上的串口数据一旦出现单字节错误,坐标会漂出去几公里。我的习惯是校验和不通过就丢弃整帧,不拿脏数据做后续计算。
4.3 定位数据与采集数据的合并上传
采集数据和定位数据最终要合成一条完整的上传报文。我的策略是:屏内部用一张全局状态表保存采集点值和一个定位缓存块,定位模块每秒刷新一次,Lua脚本在心跳周期内把它们组装成一条JSON,统一走4G上传。
合并时有个时间对齐问题值得提。Modbus轮询一轮大约1秒,北斗定位输出也是每秒一次,但它们两个是独立时钟,直接拼在一起会存在最多一两秒的错位。对实时轨迹显示来说问题不大,但如果是做精准的“位置-设备工况”关联分析,建议在JSON里分别带上各自的采集时间戳,由云端按时间戳对齐,而不是依赖上传时间。
定位数据在使用前还有一道过滤逻辑:只有GGA帧里的定位状态是1(有效)且卫星数不小于4,才更新位置缓存,否则保留上一次有效位置。这样做的好处是,车辆经过隧道、桥下的时候,平台显示的是最后一个有效位置,而不是直接跳到一个随机噪声点。如果项目里需要电子围栏告警,这个过滤更是必须的,否则一天到晚报假警。
在数据量上,其实可以克制一些。定位内容每秒产生一次,如果每帧都上传,流量消耗会非常快。我的方案是:静止状态下每30秒上传一次位置,移动状态下每5秒上传一次,通过前后定位点的位移阈值判断是否在移动。这样一张月流量1G左右的物联网卡完全够用,成本也能压下来。
5. 完整实操流程:从建工程到上线
5.1 工程创建、串口配置与虚拟从机仿真
正式开始搭这个项目时,我习惯先用组态屏配套的开发环境建一个全新工程,把屏型号选定,然后在系统参数里把串口1配置为RS-485主站模式,波特率按从机实际参数设置。串口2配置为调试口或者定位数据接收口,这里我专门强调一下,串口2不要和串口1共用同一个波特率配置,要独立设置。
工程建好后,不要着急写复杂脚本,先做设备仿真。组态屏开发软件通常自带协议仿真功能,可以在PC上模拟一台Modbus从机,按照我定义的从站地址和寄存器表提供数据。我把那7块仪表的点表信息全部录入仿真器,然后写一个最简单的Lua脚本,只读一块表的2个寄存器,先跑通数据通路。
第一次联调时遇到的现象很有意思:屏幕上的数值总是隔几秒跳变一下,一开始我以为是脚本问题,后来发现是仿真器默认应答延迟偏大,主站轮询超时设太短了。调整定时器轮询间隔后数据稳定。这说明一个问题:所有网络和串口通信的调试,一定要先把最简单的链路跑通,不要一上来就堆逻辑,否则问题很难定位。
5.2 脚本挂载与云端绑定调试
基础链路跑通后,我逐步把完整点表写进脚本,并把数据存储到屏内部的虚拟寄存器表里。这一步的关键动作是建立“点号-屏内部寄存器”的映射:Modbus从站读数经过计算缩放后,写入对应的内部地址,界面控件显示直接绑定这个内部地址。这样做的好处是,采集脚本和显示界面解耦,后面改采集逻辑时不用动屏幕控件。
云端绑定阶段,先把屏的4G模块配置好,在屏上设置云端平台的IP或域名、端口、设备ID、设备密钥。然后用虚拟设备在平台侧模拟屏的注册和数据上报,确认平台能正确解析我定义的JSON格式。这里有个经验:JSON里的字段名,尽量提前和平台端的人对齐,如果用“val1、val2”这种无意义命名,后面接大屏、对接第三方系统时会非常痛苦。
我把这个项目里的上报JSON结构固定成下面这种格式,层次清楚,扩展方便:
{ "deviceId": "PZ-001", "ts": 1718265600, "points": { "pressure": 1.26, "flow": 35.7, "pump_status": 1 }, "location": { "lon": 121.390985, "lat": 31.244706, "sats": 11, "ts": 1718265600 } }5.3 脚本热加载验证与上线准备
真正让我紧张的是云端下发脚本测试。我在开发环境模拟器里先写了一个升级脚本,内容是在原有采集基础上新增一个总功率计算逻辑,然后把这个脚本通过平台下发到屏。屏收到后屏幕右下角弹了一个“正在更新脚本”的提示,大概两三秒后消失,新的总功率值出现在界面上,整个串口采集过程没有中断。
为了验证回滚机制,我又故意放了一个带语法错误的脚本上去。屏端检测到脚本异常后,按照预设规则自动恢复了上一版本,并把错误代码通过MQTT回传给平台。这个测试通过后,我才放心把设备发往现场。热加载的逻辑写好了,远程维护的工作量直接减半,以后加一台新表,不用去现场,平台下发一个新点表就完事。
上线前还有几件小事别漏了:一是检查设备时间。定位时间虽然有UTC时间源,但组态屏自身的系统时间如果不对,上传数据的时间戳会错乱,一定要把时间同步机制配好;二是给整机做一个至少两小时的通电持续运行测试,观察内存占用有没有缓慢增长,Lua脚本里如果有全局表不断增长,时间长了会OOM,这种问题只有长时间跑才能暴露;三是把现场每个仪表的量程、缩放系数和单位整理成一份对照表,写在脚本注释里,方便以后接手的人理解。
5.4 现场部署与功能验收
到了现场,第一件事不是急着通电,而是先用万用表量一遍485总线的A、B电压,确保没有异常短路。第二件事是确认从机设备是否已经在线,逐个测一遍通信。我一般会写一个临时的“扫描从站”脚本,自动循环读取1到10号从站的设备标识,哪个通哪个不通一目了然。
全部从机通信正常后,再把标准脚本恢复上去,核对屏幕显示的数值和仪表表盘读数是否一致。这里要特别强调一个动作:至少拨通一个模拟量做满量程测试,比如给压力变送器加压到满量程,确认屏上显示的量程和工程单位是否正确。只对零位,测不出缩放系数错误,这种错误很隐蔽,到真实工况下才暴露。
验收环节还做了两个故障模拟测试。一是拔掉某台从机的485线,确认屏端能在设定时间内报出该点离线,并且轮询其他设备不受影响。二是断掉4G网络再恢复,确认数据自动续传,不丢数据。这两项通过后,项目才算真正达到可交付状态。
6. 常见问题与排查速查
6.1 通信类故障
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 所有从机都不通 | 波特率/校验不一致、A/B接反、终端电阻缺失、总线短路 | 用串口助手先测单台设备的回复帧 |
| 单台设备不通 | 站号错误、地址偏移错误、该设备掉电 | 逐一确认从站参数和报文地址 |
| 偶发CRC错误 | 接线过长、无终端电阻、屏蔽层接地不对 | 检查总线拓扑和线缆质量 |
| 数据能上传但值异常 | 寄存器解析错位、字节序错误、未换算缩放系数 | 对比寄存器手册逐字节分析返回帧 |
这些故障里最常见的是第一行“全部不通”,多数情况根本不是程序问题,而是物理层。我自己的排查顺序一直是:先用PC上的Modbus调试工具直接挂到现场总线测试(把从站临时脱离原系统),能通就是屏侧问题,不能通就是从站或者线缆问题。这种“分段隔离”的排查思路能省掉一大半无效时间。
字节序和缩放系数的问题也很高发。现场有一块压力表,量程0到2.5MPa,返回的寄存器原始值是250,界面显示的却是250MPa。这就是因为缩放系数里写的是1,而实际应该是0.01。这个问题其实防不胜防,唯一有效的手段就是每接入一种新仪表,强制要求根据厂家手册计算一次缩放系数,写进测试用例里固化下来。
6.2 脚本与云端下发故障
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 下发脚本无任何反应 | 网络断、设备未注册、指令头格式不对 | 在平台看设备在线状态和日志 |
| 脚本下载到一半失败 | 弱网传输中断、脚本体积过大 | 分块传输或更换更强的网络环境 |
| 脚本下载成功但功能没变化 | 热加载未触发、版本号没变被判定为重复 | 检查版本号是否递增,确认生效标志 |
| 现场屏死机 | 新脚本语法错误或死循环 | 检查看门狗机制,回滚旧脚本 |
下发脚本这种功能,设计和运维上要当成一个“发布系统”来看,而不是简单的文件拷贝。版本号、发布日志、回滚按钮、操作权限,一个都不能少。我自己吃过亏:在某项目里脚本版本号忘了递增,下发后屏端认为版本号不大于当前版本,直接拒绝更新,而平台日志里看是“下发成功”,两边信息不对称,排查了半天。
关于死循环的问题,有一条老经验:Lua脚本里任何长循环都尽量改写成“分段执行”或基于定时器的协作式任务,比如用一个状态机变量记录当前执行到第几步,每次定时器触发推进一步。这能避免脚本长期占用CPU,保证屏的触摸响应、串口接收和4G通信不被饿死。
6.3 定位与数据质量故障
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 平台无位置数据 | 串口2未正确接收、接线错误、模块未搜星 | 先单独用USB转串口看NMEA输出 |
| 位置“飞点”,轨迹乱跳 | 未过滤GNSS状态、校验和未校验 | 加定位状态和卫星数过滤 |
| 坐标偏差很大 | 度分未换算成十进制度数 | 核对经纬度转换公式 |
| 定位长时间无信号 | 天线位置不合理、被金属遮挡 | 天线引到空旷处并确认馈线连接 |
定位模块有一个容易误判的点:有些模块默认输出的是GGA和GLL等多种帧,如果只写了解析GGA的逻辑,却没有丢弃其他帧,串口缓冲会被无用数据占满,导致GGA帧的接收被拆得七零八落。这个问题的处理办法,是在串口接收回调里按行拼接,以“\n”为边界,每收到完整一行再判断帧类型,而不是逐字节解析。
数据质量方面,我还建议加一个“定位精度阈值”。如果当前水平精度因子(HDOP)大于某个值,比如大于5,就判定当前定位精度太差,不上传位置。这个参数对山区、建筑物密集区域特别有用,能有效抑制“定位无效但仍然上报”的问题。
结尾
整套系统从前到后跑通,我最想强调的还是那个体会:这个方案的灵魂既不是那块屏,也不是Lua脚本本身,而是“把采集、显示、上云、远程维护、定位融合到一个设备里”的工程思维。现场少一个网关少一台设备,就少一类故障点,运维的时候就知道这有多重要了。
最后再分享一个我惯用的小技巧:所有点位配置、从站参数、缩放系数,尽量定义在脚本顶部的一个配置表里,代码里不要出现任何裸写的寄存器地址。这次开会讨论的上位机方案里也沿用了这个表驱动思路,后面不管怎么增删点位,都只需要加一行配置,完全不用动逻辑代码。如果你照着我这个思路做,将来维护这套系统,你一定会感激当初那个把配置和逻辑分开的自己。