ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大彩串口屏Modbus工程示例:主从站脚本与CRC校验实战

大彩串口屏Modbus工程示例:主从站脚本与CRC校验实战

简介:广州大彩串口屏Modbus工程示例是一份工业自动化场景下的HMI通信实操资料,主要面向需要将串口屏作为Modbus从设备接入PLC或上位机、完成数据交互与触控显示的初、中级工程师,也适合正在评估人机界面方案的选型人员。压缩包为RAR格式,共129个文件、22.63MB,其中既有bmp/jpg/png界面素材与tft工程文件,也有bin、hex固件及c源码、xml配置,并附有doc说明文档,可分别用于界面设计、字库烧写、协议开发和通信调试。预览中的GB2312/GBK多字号字库覆盖了12×12到64×64多种规格中文显示,Modbus Demo则完整演示了从从站地址设置、寄存器读写到异常处理的实现流程。目前已有1820人学习,对正在集成广州大彩串口屏或计划以Modbus协议对接控制系统的开发者,这套示例能快速引导上手MiniC Demo,帮助理解主从通信机制、缩短联调周期并规避常见配置失误。

1. 大彩串口屏跑Modbus:一个工程示例能帮你解决什么

干过工业设备的人,多半遇到过这个场景:现场有一块支持 Modbus RTU 的仪表或 PLC,老板让你用触摸屏把数据显示出来、再能改几个设定值,交货期压得很紧。手头正好有一块广州大彩串口屏,于是问题变成了同一个——这个“广州大彩串口屏 Modbus 工程示例”到底怎么搭,能不能直接抄?答案是能,但前提是你得搞清楚大彩屏跑 Modbus 的三种姿势、地址映射规则、以及脚本里必须自己写的 CRC 校验,否则收到的数据永远是乱的。

这篇笔记就按我实际做过的方案讲:从选型、工程配置、变量映射,到最小可跑的 Modbus RTU 轮询脚本,再落到四个高频坑。适合手里有大彩屏、想自己搭通信、不想再塞一块单片机做协议转换的工程师。照着做,一个能读能写、能上电自恢复的 Modbus 人机界面,一个周末能跑通。

2. 先把通信模式选对:大彩屏做主站、做从站还是做透传

2.1 大彩屏跑 Modbus 的三种工作模式

大彩串口屏本身是一块“带着液晶的嵌入式板子”,Modbus 协议并不是硬件自带的,而是靠组态软件里的脚本或将变量区映射成寄存器来实现的。所以第一步不是打开软件,而是想清楚你的屏在总线上扮演什么角色。

第一种是屏做 Modbus 主站。屏主动发请求,去读总线上的仪表、PLC、温控器,拿到数据后显示在画面上。这种模式最常用,也是这个标题里“工程示例”最常指的场景。好处是系统里省掉了一片单片机或者一块带协议的网关,屏直接挂在 RS485 总线上干活。缺点是需要屏支持脚本功能,因为主动轮询、CRC 计算、接收解析这套逻辑,必须用脚本写成状态机,光靠组态软件的固定配置做不灵活。

第二种是屏做 Modbus 从站。PLC 或者上位机是主站,大彩屏被当成一个带有若干寄存器的从设备挂在总线上。组态软件里定义变量,变量映射成寄存器地址,主站通过 Modbus 读写这些寄存器,相当于读写屏上的显示控件和控制位。这种模式的优势是屏不需要写任何通信脚本,纯配置,适合替换传统文本屏或数码管显示面板,稳定性最高。

第三种是串口透传模式。屏的串口数据直接透传给另一端,相当于屏只是一个显示终端,协议解析全部交给外部的 MCU 或上位机。说句实在话:如果项目里原本就有一片 MCU 在跑逻辑,我通常建议用透传,把协议解析留在 MCU 侧,出问题好排查,屏端零风险。透传模式不挑屏的型号,几乎所有大彩屏都能用。

这三种模式的选取,取决于你系统里谁主谁从。项目里已有 PLC 做主站、大彩屏又被 PC 或触控面板控制,选“屏做从站”;现场只有一堆表计和传感器,屏是唯一的人机交互入口,选“屏做主站”;而主控逻辑在一颗单片机上、屏只是画面,那就别把 Modbus 复杂化,直接透传,把 Modbus 帧交给单片机处理即可。

2.2 从工程选型看:哪类大彩屏能跑主站脚本

我遇到不少问“为什么我的屏下不了脚本”的开发者,最后都是一句话:型号选错了。

大彩的屏大致分几类:基础画图型,只支持把组态软件做好的画面下载进去,按画面 ID 切换,不支持脚本;增强型/脚本型,支持类 C 的脚本,可以处理串口收发、定时器、控件读写,跑 Modbus 主站只能选这类;物联/高级型,除了脚本还有网口、Wi-Fi 等,如果项目未来要走 MQTT 或 HTTP,一步到位选这种。选型前,先问清楚供应商“这款屏支持不支持脚本下载和串口事件回调”,比看宣传页写什么“智能串口屏”靠谱得多。

接口电平也是翻车高发区。大彩屏的原生串口多半是 TTL 电平,如果你要挂 RS485 总线,需要选带 RS485 接口的型号,或者在屏的主板外边加一块 TTL 转 RS485 模块,板上自己出 485 芯片和接插件。RS232 同理。这里有个非常容易踩的坑:屏接口写着“485 A/B”,你以为插上两芯线就能通,结果屏端电平是 5V TTL,而你接的仪表是标准 RS485,虽然 A/B 两根线也能收到几个字节,但误码率极高,尤其在波特率高于 9600 时。凡是看到“屏是 TTL 串口”,也别管是不是叫 485,先把电平转换这层想清楚。

2.3 串口参数与 Modbus 帧格式:先建立一张通信参数表

不管哪种模式,Modbus RTU 的串口参数总逃不过这几项:波特率、数据位、停止位、校验位。工业现场最常见的是 9600 8 N 1 或 115200 8 N 1。这里有个行业习惯要注意:很多国产仪表出厂默认 9600 8 N 1,从站地址是 1;而 PLC 扩展模块或温控器可能默认 19200 甚至奇偶校验。开工前,把每台从机的实际参数整理成一张表,包括从机地址、波特率、校验位、寄存器起始地址、寄存器数量、数据类型,这一张表后面写脚本、做画面、联机调试都要反复用。

Modbus RTU 的帧格式也要心里有数。一帧报文由从机地址(1 字节)、功能码(1 字节)、数据区(N 字节)、CRC16(2 字节,低字节在前)组成,帧与帧之间要有至少 3.5 个字符时间的静默间隔。读保持寄存器用功能码 03,写单个寄存器用 06,写多个寄存器用 16(0x10),读线圈用 01。大彩脚本里没有现成的 Modbus 协议栈,需要自己拼帧、自己算 CRC,这部分我会在第 4 章给出完整可跑的实现。

3. 建工程与配参数:变量、寄存器和画面控件怎么对齐

3.1 在组态软件里新建工程与设置串口

工程从打开组态软件开始。新建工程时,先选对屏的具体型号,这个型号直接决定脚本功能是否可用;然后选择横屏还是竖屏,分辨率按实际屏体选。这一步有个细节:软件里如果选了不支持脚本的型号,后面脚本编辑器是被禁用的,你连主站轮询都写不了,只能做从站或透传。

工程属性里通常有“设备通信”或“串口设置”这一类面板,把波特率、数据位、停止位、校验位填进去。注意:这个参数是屏发送和接收用的默认串口参数,如果脚本里自己控制了串口波特率,那这里的参数会被脚本里设置的覆盖。我一般会把工程属性里的参数和脚本里的初始话参数保持一致,省得联机时两个人调了半天发现是两套参数在打架。

工程里还需要设置“通信协议类型”,有的型号固件支持在组态软件里把它指定为“Modbus 从站”,这时候屏会自动把定义好的变量映射成寄存器;如果选“无协议”,那串口数据全部交给脚本处理,主站模式通常用这一项。这步很容易弄反:明明想写主站脚本,却把协议类型设成了“Modbus 从站”,结果脚本发出来的报文根本没从串口出去,因为总线控制权已经被内部协议栈接管了。

3.2 画面控件变量与寄存器地址的映射关系

在大彩的组态软件里,画面上的文本、按钮、仪表等控件,都可以绑定一个变量名。这个变量名是屏内部用的,跟 Modbus 寄存器地址没有天然关系。主站模式下,你在脚本里把读回来的寄存器值赋给这个变量,画面就刷出来了;从站模式下,组态软件或固件按你配置的寄存器映射表,把外部主站的读写请求直接映射到这些变量上。

所以规划变量时要先建一张“变量注册表”,比如:

变量名含义数据类型对应寄存器(协议地址)功能码
temp_buf温度16位无符号0x000003
setpoint设定值16位有符号0x000106
alarm_flag报警位线圈0要求000001
pressure_32压力32位浮点0x0002-0x000303

这张表里有几个值得注意的地方。寄存器地址通常填协议地址,也就是从 0 开始,而不是手册里那些 40001 这种寄存器编号。设备手册写“寄存器地址 40001”,对应的协议地址是 0x0000;手册写“40002”,协议地址是 0x0001。如果你照着 40001 写进脚本,读回来的数据大概率是空的或者串位了。这块的坑在第 5 章会详细盘点。

数据类型同样要提前定死。16 位寄存器可能是无符号,也可能是有符号(负温度、负压力就用有符号);32 位数据会占两个连续寄存器,是高低字反过来的还是正序的,不同厂商器件差异很大。脚本里解析时把两个寄存器拼一个 32 位值,如果拼反了,读出来的浮点数能差出几个数量级。最好的做法是在变量注册表里就记清楚字节序。

工程里还有一层“工程量转换”。Modbus 读出来的是原始值——比如温控器的寄存器值是 3750,实际温度是 37.50℃,中间有个 100 倍的缩放。不要指望画面控件自己去缩放,我一般放在主站脚本里做:读回 raw_value 后,先判断是不是一个合法值(有些仪表用 0x8000 表示传感器故障),再做标度换算,最后才写入画面变量。把缩放逻辑集中在脚本里,后续换设备型号时只改一处。

3.3 屏做从站:组态软件里的寄存器映射配置

如果你的项目里 PLC 是主站,大彩屏做从站,整个配置会轻松很多。先在组态软件里勾选“Modbus 从站协议”,然后按 PLC 程序员的视角去分配寄存器地址。这里我建议遵循一个约定:把输入显示类变量集中放在一块地址区,把按钮和状态位放另一块区域,读和写分开。比如 0x0000~0x000F 放只读显示量,0x0100~0x010F 放可写设定值。这样做的好处是,PLC 侧程序的地址表跟屏侧画面变量表一一对应,两边谁接线谁调试都不会搞混乱。

从站模式不需要写脚本解析报文,但要注意“同步刷新”问题:外部主站写入寄存器的频率如果很高,而画面控件刷新率跟不上,值的更新看起来会跳跃或卡顿。解决办法是,把寄存器里读到的值先放到一组全局变量,画面控件绑定这组变量,用定时器定时刷新控件,而不是寄存器一变立刻刷。这个经验也适用于主站模式,凡是画面更新不顺,先怀疑刷新频率,而不是通信参数。

4. 用脚本写一个可复现的 Modbus RTU 轮询:CRC、收发与解析

4.1 脚本工程的框架:定时器与串口收发的事件模型

大彩的脚本是类 C 语法,运行在屏自己的嵌入式系统上,常见的模型是“定时器 + 串口收发事件”。你通过脚本在串口上发一帧报文,然后等待串口接收中断或回调给你数据;Modbus 是半双工协议,同一时刻只能有一个设备在总线上发数据,所以轮询逻辑必须做成“发一帧、等一帧、解析一帧、再发下一帧”,绝对不能发完就不管了。

脚本工程里我最先写的是三个基础部分。一是定时器初始化,用来做超时看门和周期轮询;二是串口发送接口,脚本里直接调用的发送函数;三是串口接收处理函数,每收到一个字节就会触发,需要自己拼帧、判断帧完整、然后解析。不同型号的脚本 API 名称有差异,但事件模型的骨架一致。下面这段是类 C 伪代码,重点看逻辑,实际照抄时按自己型号的脚本手册把发送函数名替换掉就行。

// 全局缓冲区与指针定义 byte recv_buf[256]; // 接收缓冲区 word recv_cnt = 0; // 已接收字节数 byte wait_from = 0x01; // 当前等待响应的从机地址 byte wait_func = 0x03; // 当前等待的功能码 bool comm_busy = false; // 正在等待响应 // 定时器回调,周期比如 200ms,用来驱动轮询 void timer1_handler() { if (comm_busy == false) { send_read_request(wait_from, 0x0000, 2); comm_busy = true; start_timeout_timer(300); // 300ms 超时 } else { if (is_timeout()) { recv_cnt = 0; comm_busy = false; } } }

这段逻辑就是整个 Modbus 主站的核心状态机。comm_busy是“正在等一帧响应”的标志,发送之后置位,收到有效响应或超时之后复位,然后再去发下一帧。定时器回调里同时承担发帧和超时检测两件事:如果总线不忙就发请求;如果忙就看看是不是超时了,超时就必须清空缓冲区并把comm_busy复位,否则一个掉线设备会卡死整条总线的轮询。超时时间我习惯设 300ms,这个值比多数仪表响应时间(通常 50~100ms)宽裕,又不会让轮询周期长得离谱。

4.2 读保持寄存器:功能码 03 的最小请求与响应解析

Modbus RTU 读保持寄存器的请求帧是固定长度 8 字节:从机地址、功能码 03、起始寄存器高字节、起始寄存器低字节、寄存器数量高字节、寄存器数量低字节、CRC 低字节、CRC 高字节。CRC 计算是整个脚本里最容易被忽略的部分,我把计算函数单独写出来,它是 Modbus RTU 的标准 CRC16,多项式 0xA001,初始值 0xFFFF,结果低字节在前发送。

// CRC16 计算,Modbus RTU 标准 word calc_crc16(byte *data, word len) { word crc = 0xFFFF; word i, j; for (i = 0; i < len; i++) { crc ^= data[i]; for (j = 0; j < 8; j++) { if (crc & 0x0001) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; } // 组装读保持寄存器请求帧 void send_read_request(byte addr, word reg_start, word reg_cnt) { byte buf[8]; buf[0] = addr; buf[1] = 0x03; buf[2] = (byte)(reg_start >> 8); buf[3] = (byte)(reg_start & 0xFF); buf[4] = (byte)(reg_cnt >> 8); buf[5] = (byte)(reg_cnt & 0xFF); word crc = calc_crc16(buf, 6); buf[6] = (byte)(crc & 0xFF); buf[7] = (byte)(crc >> 8); uart_send(buf, 8); }

这里有两个参数要仔细核对。reg_start是协议地址,从 0 开始,不是设备手册里的寄存器编号。reg_cnt是你要读的连续寄存器个数,一次最多读多少取决于从机上限,多数表计支持一次读 125 个保持寄存器,但轮询周期会随之变长,我一般一次只读自己需要的连续地址段。buf[2]和buf[3]拼的是起始寄存器的高字节和低字节,也就是大端序,Modbus RTU 的数据区默认大端,这一点好多人第一次写反过。

再看看响应解析。收到一帧响应后,先做三个校验:地址是否跟自己发出的请求一致、功能码是否一致(注意功能码最高位如果被置 1,表示从机返回异常,比如 0x83 就代表读保持寄存器失败)、CRC 对不对。校验通过再取数据区。响应帧结构是:地址、功能码、字节数 N(等于 2 倍寄存器数量)、数据区 N 字节、CRC 两字节。数据区按大端拼回 16 位值即可。

// 串口接收回调,每收到一个字节触发一次 void uart_recv_handler(byte data) { recv_buf[recv_cnt] = data; recv_cnt++; // 简单判断:地址 + 功能码 + 字节数 + 数据 + CRC (2字节) if (recv_cnt >= 5) { byte expected_len = recv_buf[2] + 5; // 数据字节数 + 地址/功能码/长度/CRC if (recv_cnt >= expected_len) { parse_response(); recv_cnt = 0; comm_busy = false; } } } // 解析响应并更新画面变量 void parse_response() { if (recv_buf[0] != wait_from) return; if (recv_buf[1] != wait_func) return; word crc = calc_crc16(recv_buf, recv_cnt - 2); if ((byte)(crc & 0xFF) != recv_buf[recv_cnt - 2]) return; if ((byte)(crc >> 8) != recv_buf[recv_cnt - 1]) return; // 示例:读两个寄存器,拼成两个 16 位无符号值 word val0 = ((word)recv_buf[3] << 8) | recv_buf[4]; word val1 = ((word)recv_buf[5] << 8) | recv_buf[6]; // 工程量转换示例:寄存器原始值 3750 实际为 37.50℃ if (val0 != 0x8000) // 某些仪表固定 0x8000 表示传感器故障 { set_text_value("temp_disp", val0 / 100.0); } set_text_value("pressure_disp", val1); }

expected_len的计算逻辑是响应帧结构决定的:第一个字节地址、第二个字节功能码、第三个字节数据字节数 N,之后是 N 个数据字节,最后两个字节 CRC,所以总长度等于N + 5。串口接收是按字节逐个触发的,每次收到字节都检查一下总数够不够,直到攒够整帧才做解析,这比先定长后解析的逻辑更稳妥。还有一种是固定帧尾用 3.5 字符时间静默来分割帧,但在脚本里实现定时器到毫秒级比较麻烦,我用“按长度攒帧”更靠谱。

收到响应后要把comm_busy复位,否则定时器下一次触发时永远不会发新请求。这一点是我实际调试时最常检查的:画面卡住不动,先看脚本里是不是所有 return 路径都把comm_busy复位了。比如上面的响应解析里,地址不符、功能码不符、CRC 错误三条 return 路径都没有复位总线状态,必须靠超时定时器去恢复,如果超时逻辑没写好,总线就死在那里了。

4.3 写寄存器:功能码 06 与按钮事件的联动

读是轮询,写是事件驱动。画面上放一个“设定值确认”按钮,用户输入目标值后点击按钮,脚本里把触点事件抓出来,组装写单个寄存器帧发出去。写单个寄存器功能码 06 的请求帧也是 8 字节:地址、06、寄存器地址高字节、低字节、写入值高字节、低字节、CRC 低、CRC 高。

// 功能码 06 写单个寄存器 void send_write_request(byte addr, word reg_addr, word value) { byte buf[8]; buf[0] = addr; buf[1] = 0x06; buf[2] = (byte)(reg_addr >> 8); buf[3] = (byte)(reg_addr & 0xFF); buf[4] = (byte)(value >> 8); buf[5] = (byte)(value & 0xFF); word crc = calc_crc16(buf, 6); buf[6] = (byte)(crc & 0xFF); buf[7] = (byte)(crc >> 8); uart_send(buf, 8); comm_busy = true; } // 按钮按下事件:将输入框内容写入从机 void button_write_click() { int set_val = get_int_value("set_input"); // 从画面输入框读取 send_write_request(0x01, 0x0001, (word)set_val); }

写操作之后同样要置comm_busy,因为从机会回复一帧“写成功”的响应。千万不要发完写请求就马上发读请求,半双工总线上自己发出去的数据还没落稳,再发一帧就会把总线打乱。收到写响应后,解析流程和读响应一致,地址、功能码、CRC 校验通过后复位comm_busy即可。写多个寄存器(功能码 16)的组帧大同小异,数据区多了一个“写入字节数”字段,再跟上寄存器数据,CRC 范围从地址一直算到最后一个数据字节,代码结构是一样的。

这里有个画面交互上的细节:写操作应该在按钮事件里做“本地回显确认”。比如用户点按钮后,先把输入值显示在“当前设定值”控件上,然后再发 Modbus 写请求,等收到成功的响应之后,再把状态栏文字切换为“写入成功”。如果等到响应回来再回显,操作手感会很差,因为从机响应偶尔会因为总线干扰而丢失,画面就一直没反应。把本地回显和总线状态分开处理,用户界面友好很多。

5. 大彩屏 Modbus 联机避坑:地址偏移、大小端、时序这几个坑排队等着你

5.1 寄存器地址偏移:手册写着 40001,脚本里却写 0x0001

现象:联机调试时,读回来的数据全是 0,或者读回的数据是另一个寄存器里的值,跟手册对不上;用串口调试助手抓屏发出的请求帧,发现帧里的寄存器地址和预期差了一个数。

原因:Modbus 协议规约地址从 0 开始,但很多设备手册按“数据区地址”给寄存器编号,比如保持寄存器编号 40001 对应协议地址 0x0000,40002 对应 0x0001。如果你把 40001 当作协议地址直接填进脚本(即 0x0001),就在本来 1 的偏移量上又加了一个 1,读到的当然不是目标寄存器。

解决:把所有手册里的寄存器编号先做一个换算:协议地址 = 寄存器编号 − 40001(保持寄存器)、− 30001(输入寄存器)、− 00001(线圈)。换算结果写进第 3 章那张变量注册表,之后写脚本和从站配置都只用协议地址。我习惯在脚本里给每个地址写注释标明手册编号,比如0x0000 /* 手册编号40001,温度实际值 */,防止换人维护时又一次踩偏。

5.2 大小端与符号位:温度读出来是 65535 或者负值乱跳

现象:寄存器原始值是 0x0EA6(十进制 3750),画面却显示了一个好像把高低字节调换过的数值——比如 42496,或者在 0 和 65535 之间来回跳。还有一类是负温度显示成 60000 多。

原因:Modbus 数据区大端序,但设备厂家在“寄存器内部字节序”上做手脚的大有人在。有些仪表把两个寄存器组成 32 位数据时,高字和低字的顺序跟标准相反;16 位数据若是负值,按无符号解析就会得到 65535 这类值。屏端的脚本默认按无符号大端拼值,跟从机的数据格式对不上,显示自然乱。

解决:先在串口调试助手上直接给从机发一条功能码 03 的请求,把原始响应字节抓出来看。比如 32 位浮点压力值,响应数据区 4 个字节可能是41 0A 00 00(标准 IEEE754 大端),也可能是00 00 0A 41(字节逆序)。脚本里按实际抓到的顺序解析,拼成 32 位后再用内存拷贝方式转成 float,或者用“高位在前还是低位在前”两个分支分别处理。16 位有符号数也要在脚本里做类型转换,不能直接按无符号显示。总结一句话:以抓包为准,不要以手册为准。

5.3 485 总线上屏和数据没共地,或者 A/B 接反导致偶发丢帧

现象:屏和从机波特率完全一致,短距离测试正常,拉到现场线一长就开始偶发性丢数据;或者更诡异的是,正常能通信,但每隔几分钟就有一帧响应收不到。

原因:RS485 是差分信号,但系统必须共地。现场如果屏的 485 接口和从机之间没有拉一根公共地线,共模电压一旦超出 485 芯片的承受范围,接收端就会收出乱码或拒绝接收。另外 A/B 反接会导致信号极性反相,有的设备能正常通信,有的则完全不通,这是个容易被忽略的物理层问题。

解决:485 总线不能用普通的双绞电源线随便绞在一起,我用的是屏蔽双绞线,屏蔽层单端接地;屏端和从机端之间一定要确认参考地连接,如果两个设备都隔离供电,就要专门拉一根地线。接线时先在屏端确认哪根是 A(对应差分正极)、哪根是 B(对应差分负极),然后沿线到每个节点都保持 A 接 A、B 接 B。如果现场条件差,建议把波特率降到 9600,抗干扰能力会明显提升。

5.4 串口是 TTL 电平,直接焊在 485 总线上收不到数据

现象:屏是 TTL 串口,直接从屏上引出了 TX、RX、GND 三根线,接了一个“TTL 转 RS485 模块”后挂到总线上,但模块发热严重或者通信完全不通。

原因:TTL 转 485 模块看起来是“解决电平问题”的,但很多模块是 3.3V 供电的,而屏的 TTL 逻辑电平可能是 5V,或者模块的 TXD/RXD 方向跟屏的引脚定义没对齐。更大的坑是,有些屏的串口 TX 和 RX 是复用关系,收发共用一个引脚,直接接上模块后,模块在发送数据时电平冲突。

解决:先查清屏的串口引脚定义,确认它是“独立 TX/RX”还是“单线半双工”;如果模块需要额外控制方向(DE/RE),必须由脚本里的 GPIO 或者模块硬件自动切换。比较省事的做法是直接选用带 RS485 接口的大彩型号,内部已经把电平转换和方向切换做了,接线只有 A/B 两根线,出问题的概率低很多。如果必须用现成的 TTL 屏转 485,我会选带自动方向切换功能的模块,并在脚本里把串口配置为“半双工模式”。

5.5 多从机轮询时一个掉线,整条总线卡死

现象:单从机时一切正常,挂两台以上从机之后,只要其中一台掉线或响应慢,后面所有的从机数据全都刷新不了;总线上还能看到屏在发请求,但始终没有收到任何响应。

原因:脚本里comm_busy没有得到正确复位。最常见的是:发送了请求后置comm_busy = true,等待响应;但掉线的从机根本没响应,接收回调永远不会触发,comm_busy一直是 true,定时器就再也不发下一帧请求,总线被一个坏节点“堵死”。超时看门如果没有做,这个问题就会无限期卡住。

解决:超时看门必须独立于接收事件。我在定时器回调里做两个事:如果comm_busy == true且距发帧时间超过 300ms,就清空接收缓冲、comm_busy = false;下一次定时器触发时,自然就会去发下一台从机的请求。注意超时时间不能设得太短,否则响应慢的从机会被误判掉线;也不能太长,否则总线利用率低。工业仪表多数 100ms 内会响应,300ms 是一个稳妥的中间值。想更健壮的话,还可以加一个“连续失败计数”,同一从机连续掉线 3 次后在画面上弹一个通信告警,但不中断轮询其他从机。

6. 收尾验证:用模拟从机把工程跑稳,再固化一份通信参数表

工程写完之后,先别急着接现场设备。我一般会在 PC 上用串口 Modbus 从机模拟器,把大彩屏的串口通过 USB 转 485 接到电脑上,模拟器里配置从机地址、寄存器初始值、响应延迟,然后观察屏上画面数据是否和模拟器里的寄存器值一致。这个过程能排除掉“屏脚本问题”和“现场设备问题”两个未知变量,联机时一旦出问题,至少能确定问题出在现场那一半。验证时特意把模拟器响应延迟设成 150ms,确认脚本的超时机制不会误判,再把模拟器从机地址改错一位,观察屏的通信告警是否正常出现。

调通后,我会在屏上加一个“通信自检”页面:显示已收帧计数、CRC 错误计数、超时次数。这个页面是给现场维护的人看的,也是下次排查时的第一手证据。脚本里每次解析响应成功就rx_ok_count++,每次 CRC 校验失败就rx_err_count++,超时一次就timeout_count++,然后在画面上用低频定时器刷新这三个变量。哪台从机出问题,数据是直接可见的,不用再去猜。

最后一步是固化通信参数表。我会把它作为注释块写在脚本文件头部,并在工程的交付说明里放一份:从机地址、波特率、数据位、停止位、校验位、寄存器地址映射表、字节序、工程量缩放系数、轮询周期、超时时间。有人会说这张表有什么好写的?但恰恰是这张表,能省掉之后每次维护时拿串口助手抓包核对半天的时间。我吃过一次亏:某次交付后半年,现场反馈温度显示不对,远程排查发现是另一家供应商把从机波特率从 9600 改成了 19200,而屏端脚本注释里根本没写原始参数,双方互相踢皮球踢了两天。从那以后,我交付的每个 Modbus 工程都带这张表,并且表里一定附上“最后修改时间”。

主站脚本、从站映射、透传三种模式你都跑过一遍之后,会发现大彩屏做 Modbus 真正难的不是协议本身,而是把串口时序、地址映射、数据格式这三件事在工程里理清楚。把这个工程示例当起点,把参数表当交付标准,少踩一个坑就是赚一天时间。希望这篇能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进