Modbus 读取数据总是卡顿不连续:轮询节奏与超时怎么配
数据能读到、就是时快时慢、曲线上还带豁口——这类问题可能不在协议层,而在你给总线排的时间表上。这篇只讲一种成因:轮询周期与超时重试配不上总线的实际服务时间。如果你现在是完全读不到数据,那是另一回事,先去看 Modbus 读不到数据?8 步排查。
先把「卡顿」和「不连续」拆成两件事
它们在界面上看起来一样,成因却相反。
- 卡顿:所有点位最后都读到了,只是整体刷新变慢、周期忽长忽短。数据是全的,晚到而已。
- 不连续:某些采样点根本没落库,曲线断开或时间戳出现跳变。这是读失败,不是慢。
分辨方法不用工具:看你落库的时间戳序列。相邻时间戳之间的间隔如果整体变大但分布均匀,是卡顿;如果大部分间隔正常、偶尔冒出一个数倍的空档,是丢采样。两者的处方完全不同——前者要削请求量,后者要查这一次为什么失败。同一套系统上两种情况经常并存,先分开统计,再分别下手。
给一轮轮询记一笔时间账
Modbus 是主从轮询,主站问一句、从站答一句,同一条串行总线上这些事务只能排队进行。所以一轮的时长不是「你设的轮询间隔」,而是所有事务实际花费之和。你设的间隔如果比这个和还短,请求就会堆积,表现就是周期越拖越长。
一次 RTU 事务的时间由这几段构成:
- 请求帧发上线的时间。RTU 帧的组成是规范定死的:从站地址一字节、功能码一字节、随后是该功能码的数据字段、末尾两字节 CRC。
- 从站处理时间。这一段取决于设备实现,协议不管。
- 响应帧发上线的时间。读寄存器类响应的长度同样可算:从站地址、功能码、字节计数各一字节,加上 N 个寄存器的 2N 字节数据,再加两字节 CRC。
- 帧之间的静默间隔。RTU 没有包边界,靠总线空闲来分帧,这段静默是协议要求的开销,不能省。
前三段里凡是「上线时间」都能自己算:串口上每个字符占的位数 = 起始位 + 数据位 + 校验位(无校验时为 0)+ 停止位,字符时间 = 位数 ÷ 波特率,帧时间 = 字节数 × 字符时间。把你轮询表里每条请求按上式算一遍求和,就得到这条总线一轮的下限。低波特率下这笔账相当可观,而且它和你读多少个寄存器直接挂钩。
这笔账算出来之后,很多「设备卡」的结论会自己推翻——不是设备慢,是你排的活儿本来就装不进你定的周期。帧结构与功能码的细节可参照 Modbus 寄存器映射实战,本篇不重复。
一个掉线的从站,拖慢的是别人
超时这个参数有个反直觉的性质:通信正常时它一分钟也不占,一旦某个从站不应答,它就变成全额支出。
于是每一轮里,异常从站带来的固定开销 = 超时时长 × (1 + 重试次数) × 异常从站数量。这三项是相乘的,所以「一台设备偶尔掉线」在轮询表里放大成整轮的塌陷。更容易误诊的是:受害者不是那台掉线的设备——它本来就没数据——而是排在它后面的所有从站,它们的采样被推迟甚至被挤掉。现场表现就是「明明是 A 展项的屏没反应,怎么查出来是 B 的传感器有问题」。
判断办法很直接:把可疑从站临时从轮询表里摘出去(不是断电,是不再向它发请求),如果整体刷新立刻恢复,成因就锁定了。摘出去之后再单独用低频任务去探它,别把它留在主轮询里原地重试。
超时设长了和设短了,现象不一样
超时该取多少没有通用答案,取决于设备处理速度、波特率、帧长和链路层级。但长短失当的现象是可分辨的,这比抄一个推荐值有用:
- 设得偏短:健康的事务被误判为失败。表现是数据有洞、失败偶发且随机,重试往往能成,总时长反而不一定变长。
- 设得偏长:数据不缺,但整体拖慢、值滞后,且异常时抖动特别大(因为每次异常都要全额等满)。
所以先看你的症状属于哪一类,再往对应方向调。定下限的依据是你自己链路上正常响应耗时的上包络——把连续多轮的响应耗时记下来,取观察到的最大值再留余量,而不是取平均值,因为平均值必然会让一部分正常事务被砍掉。定上限的依据是你能容忍的一轮总时长:超时乘上重试次数再乘上可能异常的从站数,不能超过这个总时长。
别忘了从站有权说「我忙」
Modbus 规范里有一个专门的异常码表示从站正忙、稍后重试。它和读不到数据不是一回事——能收到这个异常响应,说明链路、地址、参数全是通的,是你问得太密,或者从站正在执行一个耗时较长的内部操作。
看到它时正确的动作是降低对这台设备的轮询频率,而不是加大超时或加重试。加重试只会让总线更挤,把偶发变成持续。这也是把轮询表分级的理由:变化快的量(状态、开关反馈)高频问,变化慢的量(配置、累计值、铭牌信息)低频问,别让一张大表用同一个节奏去扫。此外,把地址相邻的寄存器合并进一次请求,能显著减少往返次数——但一次请求可读的寄存器数量有协议上限,超了从站会回非法数据值的异常码,不会给你静默截断。
间歇失败:中间那一层可能重新切了你的帧
如果直连转换器时正常、经串口服务器或某些 USB 转串口芯片就间歇失败,问题在承载层的分帧上。
RTU 的分帧靠总线静默,帧内字节之间的间隔有上限,超了从站就认为这是碎帧、直接丢弃。而串口服务器要把串口字节流打包成网络报文,打包策略(按字符间隔触发、按长度触发、按帧转发等)决定了它会不会把一帧拆开、或者把两帧粘起来发。这一层的抖动在总线空闲时看不出来,一忙就暴露——所以它的典型面孔正是「平时好好的,一到开馆全量轮询就丢点」。
定位方法是做替换对照:同一套串口参数、同一条请求,分别经串口服务器和直连转换器各跑一轮,比较失败率。若只有前者失败,就去调它的打包策略。选型与参数含义见 串口服务器(串口转网口)选型科普。
还有一层是 RS-485 半双工的收发换向:总线上同一时刻只允许一个设备驱动,主站发完必须交还总线,转换器才控制方向切换。换向时机不当会截断响应帧的尾部,而尾部正好是 CRC,于是主站判定校验失败——现象又是偶发失败,不是稳定失败。总线本身的电气纪律见 RS485 总线展厅布线与地址规划。
有一种「不连续」根本不在通信层
两个容易被算到通信头上的现象:
读到重复值。从站内部的采样刷新速率如果低于你的轮询速率,你会连续读到同一个数。这不是丢点,曲线上表现为平台阶梯而不是豁口。区分标准就是这个:有豁口是丢采样,有阶梯是过采样。遇到阶梯,降低轮询频率就行,调超时没有任何用。
时间戳本身带抖动。轮询系统里,你落库的时间戳通常是主站收到响应的时刻,而不是从站采样的时刻,两者之间隔着排队、传输和处理。同一条总线上排在后面的从站,其时间戳与真实采样时刻的偏差天然更大,而且偏差随总线负载变化。要做趋势分析或多设备对齐时,这一点必须先说清楚,否则你会去追一个并不存在的「数据抖动」故障。
最容易栽的坑
三条:
- 只调超时不调轮询周期。时间账不平,怎么调超时都是在腾挪,症状会从丢点变成拖慢,然后再变回来。
- 把偶发失败当设备坏。偶发失败的成因可能在分帧、换向、打包策略这些「时机」问题上——这类成因换一台同型号设备并不会消失。
- 把掉线的从站留在主轮询里重试。它一台的超时开销由全体分摊,正确做法是摘出去降频探测,恢复后再放回。
下一步的顺序建议是:先算时间账确认周期是否装得下,再按丢点/拖慢分诊调超时,最后才去查中间层的分帧。顺序反了,你会花很长时间调一个本来就配不平的表。
留言讨论
评论发布后会被人工复核,违规内容将被删除。
如果发表没有反应,可以前往联系我们告诉我们。