2.1 核心通信机制


2.1 核心通信机制

本节摘要:一次 Modbus 通信就是一次严格受控的事务——主站发起、从站应答、超时兜底、异常码纠偏。本节拆解事务的完整生命周期,把"轮询为什么不能太快""从站不回话怎么办""异常响应算不算故障"这些现场高频疑问一次讲透。

凌晨一点的轮询卡顿

场景先摆出来:凌晨一点,你在中控室盯着一条供水管线的历史曲线,发现压力点的采集间隔本该是一秒,实际却隔十几秒才跳一次。值班师傅说"网络有点卡,老毛病了"。网络卡不背这个锅——Modbus RTU 走的是 RS-485 总线,根本不经过 IP 网络。真正的原因往往藏在事务层面:某个从站的响应慢了,主站超时重试,一轮轮询的时间被拉长,整条总线的采集周期跟着遭殃。要定位这类问题,你必须先搞清楚一次问答事务内部到底发生了什么。这就是本节的任务。

学习目标

阅读完本节,你应当能够:

  1. 完整描述一次主从事务的发起、应答、超时、重试流程;
  2. 说出常用功能码的分工与请求、响应帧的字段构成;
  3. 解释从站异常响应的语义,区分协议异常与链路故障;
  4. 估算给定波特率下的轮询周期上限,判断采集周期设置是否合理。

一、事务模型:一问一答的纪律

Modbus 的通信纪律可以归纳为三句话:**只有主站能开口,从站永远被动;一次只谈一件事,谈完才能谈下一件;问出去的问题必须有回音,没有回音就当失败处理。**这三条纪律共同构成事务模型。

主站发起一次事务的流程是固定的:组帧(填从站地址、功能码、数据)→ 发送 → 启动超时计时 → 等待响应。从站收到帧后依次做四件事:校验完整性(CRC 对不对)、比对地址(是不是叫我的)、解析功能码(要我干什么)、执行并组响应帧。任何一步失败,从站的反应都不同——CRC 错了就沉默(丢弃,不回话),地址不是自己也沉默,只有"是我、听懂了、但办不到"时才回异常响应帧。

这个设计里最容易被误解的是"沉默"的语义。总线上多个从站共享一条线缆,如果每个从站都在 CRC 错误时回一句"你发错了",总线上会同时出现多个应答互相打架。所以协议规定:**除了被点到名的从站,其他人一律闭嘴。**这也解释了现场排查的第一原则——主站收不到响应时,问题可能出在链路任何一环,不能直接断定从站死了。

主站事务状态推演(文字伪码,便于对照抓包) send_request(slave=3, func=0x03, start=100, count=2) t0 = now() loop: frame = listen(timeout=200ms) if frame is None: # 超时仍无响应 retry += 1 if retry < 3: resend; continue else: mark slave3 offline; break if frame.crc_bad: continue # 校验失败的帧直接丢弃等下一帧 if frame.addr != 3: continue # 别人的应答不归我管 if frame.func == 0x83: # 高位翻转为异常标志 log(exception_code=frame.data[0]); break else: deliver(frame.data); break

超时值怎么定是现场最常见的争执点。定短了,慢从站被误判离线;定长了,真故障迟迟不重试。经验法则:超时应大于"从站最大处理延时加一帧传输时间"的两倍余量,再结合轮询周期反推。比如 9600 波特率下一次读写往返的传输时间约十几毫秒,从站处理留二十毫秒余量,超时设在两百毫秒量级是安全的;若把超时压到五十毫秒,遇到响应慢的老仪表就开始误报离线。

二、功能码:问题类型的最小集合

主站的问题类型由功能码表达,常用集合其实很小:读线圈 01、读离散输入 02、读保持寄存器 03、读输入寄存器 04、写单个线圈 05、写单个寄存器 06、写多个线圈 15、写多个寄存器 16。八条指令覆盖了绝大多数现场需求,这是协议"极简哲学"的又一体现。

四类数据对象与读写权限的对应关系值得单独记:线圈与保持寄存器是读写的(对应主站可操控的输出与参数),离散输入与输入寄存器是只读的(对应设备自报的状态与测量值)。现场大量"写不进去"的故障,根因是把值写到了只读区——比如试图用 06 号功能码改写输入寄存器,设备回异常码 02(非法地址),不是设备坏了,是位置不对。

正常响应与异常响应的区分规则很简洁:**功能码最高位被置 1 就是异常应答。**主站发 03,从站办不成时回 83 加一个字节的异常码。常用异常码含义:01 功能码非法(设备不支持)、02 地址非法(越界或只读)、03 数据非法(数量超限)、04 从站内部故障、06 从站忙。看到 04 与 06,重试是合理的;看到 01 与 02,重试一百次也没用,要改请求本身。

图5 一次完整事务的时序与异常分支

图5 一次完整事务的时序与异常分支

三、轮询调度:一条总线的时间账

RS-485 是半双工共享总线,同一时刻只能有一个人说话。主站的调度方式因此非常朴素:按点表顺序逐个从站逐组寄存器地问,问完一轮再来一轮。整条总线的采集周期,等于所有事务耗时之和,这就是开头案例里"一处慢、全线慢"的数学原因。

把时间账算细一点:一次事务耗时 = 请求帧传输时间 + 从站处理时间 + 响应帧传输时间 + 帧间静默间隔。以 9600 波特率、每字节十位计,传输速率约每毫秒一个字节;请求八字节加响应十一字节约二十毫秒,帧间隔按规范要留三点五个字符时间,从站处理留一二十毫秒——单次事务大约五十毫秒。总线上若有十台从站、每台问两组寄存器,一轮轮询就要一秒。这个账算完你就明白:采集周期不达标时,第一件事是查最慢的那台从站,第二件事是合并寄存器读取(把相邻地址一次读完,减少事务个数),最后才是提波特率。

⚠️ 常见坑:广播地址 0 可以让所有从站同时执行写命令(比如全厂同时复位),但广播永远没有应答,主站无法确认执行结果。对可靠性有要求的写操作,老老实实逐站单播。

四、两个进阶话题:读完这节再去看规范文档

**话题一:读读写写合并在一次事务里。**常用八条功能码之外,还有一个组合功能码(十六进制的 17):一次事务里先读一组寄存器、再写一组寄存器,响应里带回读结果。它解决的问题很具体——某些设备的参数修改要求"写后立即回读确认",分两次事务会有间隙,组合功能码把读写原子化。日常项目用它的机会不多,但读设备文档遇到时不要陌生:它就是 2.1 事务纪律的一次精致应用,一次问答办两件事,事务性反而更强。

**话题二:串行与 TCP 在"静默"上的分野。**本章的帧间静默讨论基于 RTU,值得把 TCP 侧的对应物也点破:以太网没有静默定界这回事,MBAP 头的长度字段显式划界,因此 TCP 上不存在"字节卡顿导致帧错位"的故障模式。但 TCP 引来了新的时序话题——串行侧"从站处理延时"在 TCP 侧变成"网络转发加服务器处理"的混合延时,抖动来源更多。把 RTU 超时参数照搬到 TCP 链路是常见失误,尤其跨交换机、跨路由的场景,超时要按最坏路径重新核算。一句话总结:静默定界的时代怕卡顿,长度定界的时代怕抖动。

这两个话题放在本节末尾而非正文,是因为它们属于"读过实务再看才会心一笑"的内容——如果你此刻已经能会心一笑,说明前面三节真的学进去了。

本节要点回顾

  • 事务三纪律:主站唯一发起、事务原子串行、超时兜底重试,三者共同保证共享总线的秩序;
  • 沉默的多义性:CRC 错、地址不符、设备断电都表现为"没有响应",不能直接等同从站故障;
  • 异常码分流:01、02 类异常改请求,04、06 类异常可重试,先分类再行动;
  • 轮询时间账:总线周期是全部事务耗时之和,优化顺序是先摘慢从站、再合并读、最后提速率。

下一节进入三个传输变种,看同一套事务模型如何适配串行链路与以太网。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U