1. 为什么现场还是离不开Modbus RTU:1200做485主站的几种套路
做现场项目久了你会发现一个现象:车间里跑得最稳的通讯,往往不是你花了大价钱买的工业以太网,而是那两根拧在一起的485线。尤其是一些中小型现场,十几台仪表、变频器、温控器散落在几十米甚至上百米的范围内,拉网线不现实,上Profinet IO又得全换支持PN的从站设备。这个时候,Modbus RTU加485总线几乎是唯一的选择——成本低、从站设备便宜、编程也相对直观。
西门子S7-1200系列做主站去轮询从站,市面上常见的有三条路线。第一条是用通讯板CB1241,插在1200本体左侧,只支持RS485,性价比高,但占用一个扩展槽位。第二条是用通讯模块CM1241,同样是RS485接口,处理能力比通讯板强一点,支持Modbus RTU主站和从站模式,适合挂在CRT后面扩展。第三条是走第三方网关,把Modbus RTU转成Profinet,用1200的PN口去读写网关,这种适合从站数量特别多、或者现场已经有成熟网关方案的场景。
表格对比一下:
| 方案 | 硬件接口 | 支持模式 | 占用资源 | 适用场景 |
|---|---|---|---|---|
| CB1241通讯板 | RS485 | 主站/从站 | 1200本体左侧槽位 | 从站数量少、空间紧凑 |
| CM1241通讯模块 | RS485 | 主站/从站 | CRU右侧扩展 | 从站数量多、复杂报文 |
| 网关(如PN转485) | 以太网+RS485 | 取决于网关 | 1200 PN口 | 从站分布集中、需远程监控 |
我自己做过的某水处理项目中,用的是CB1241方案,带了8台电磁流量计和6台浊度仪。选择这个方案的原因很简单:项目预算控制严,1200本体已经选到1214C,左侧空了一个槽位,正好插CB1241,不需要额外挂CM模块,也从站数量没有超过15台,485总线长度控制在五十米以内。如果当时从站数量超过二十台,或者现场干扰特别大,我可能会直接上CM1241甚至网关方案——因为通讯板的主站处理能力有限,轮询周期拉长以后,实时性会变得很难看。
这里有个很重要的认知:Modbus RTU是半双工通讯,主站发请求,从站回响应,一问一答,绝不允许两个设备同时往总线上发数据。所以主站的核心工作不是“同时读一堆表”,而是“排队问话”——先问1号从站,等它答完,再问2号从站,再问3号……这个“排队问话”就是轮询。轮询写得好不好,直接决定了整个485网络的稳定性和实时性。很多刚接触485通讯的工程师,程序里直接写了连续好几条MB_COMM_LOAD和MB_MASTER指令调用,没有状态控制,结果两条指令交替使能,总线冲突,从站乱回,甚至烧通讯口——这些坑,下面一节先讲清楚。
2. 接线和硬件组态里那些容易埋雷的细节
很多人程序写的没问题,但通讯就是不稳定,一顿排查下来发现是接线和组态埋了雷。先说说485接线的基本规则:A线接A线,B线接B线,千万别交叉;线缆用双绞屏蔽线,屏蔽层单端接地;总线两端各接一个120欧终端电阻。这个“单端接地”很多初学者不理解——为什么要单端?因为两端都接地会形成地环路,现场电机启动时地电位一波动,通讯直接乱掉。我见过一个现场,屏蔽层在两端都接了配电柜的地排,结果一到变频器启动,PLC报从站超时,后来把一端剪断,问题立刻消失。
还有一个高频问题:485的A/B线到底哪根接哪根?这得看设备标牌。大部分国产仪表用A+、B-标注,但也有不少设备用D+、D-,甚至有的老设备标注是“TX/RX”,此时拿万用表量一下通讯端口在空闲状态下的电压,A线对B线大约2-5V,A线相对GND为正,B线相对GND为负,就能判断出来。另外有些设备把A和B标反了,你按标牌接上去通讯不了,反过来接反而通了——这种情况遇到过不止一次,所以现场调试时手里备一对杜邦线,反接测试是最快的验证手段。
从站侧还有一个容易被忽视的配置项:从站地址和通讯参数必须在设备端先设置好。比如你要读一台温控器的当前温度,它的Modbus地址可能是1号,波特率9600,数据位8,无校验,1停止位。如果PLC这边组态设成了19200,从站那边还是9600,那通讯完全建立不起来。更坑的是有的仪表默认从站地址是1,你挂了五台仪表全设成1,PLC轮询时候只会有第一台应答,后面的全显示超时——这属于“地址冲突”问题,排查起来很像通讯故障,但本质上是设备配置错误。
硬件组态这块,以TIA Portal V17为例,添加CB1241通讯板后,需要给通讯板分配一个IP地址——注意,CB1241本身是485接口,但它也占用一个IP地址,这个IP用于和CPU本体之间的内部数据交换,不能和PLC本体IP冲突。然后在“设备组态”里勾选“启用该模块”,配置好波特率、数据位、校验位和停止位。这里有个经验:波特率优先选9600,只有从站设备明确支持19200且通讯距离短才用高波特率。485长距离传输时,波特率越高越容易受干扰,9600是绝大多数现场仪表的“公约数”。
接线长度方面,9600波特率下,485理论传输距离可以到1200米,但现场实际能到300米就谢天谢地了。如果总线距离过长,或者中间经过强电桥架,建议加485中继器。中继器要选带光电隔离的,供电单独拉,不要把中继器的电源和PLC的24V电源并在一起——隔离的意义就是把两段总线在电气上彻底断开,防止干扰顺着总线串过去。以下是我整理的一个极简接线检查清单,每次做485项目前我都会过一遍:
- A/B线有没有反接(对照设备标牌,必要时万用表量空闲电压)
- 屏蔽层是否单端接地(另一端悬空包好绝缘)
- 总线两端是否已并120Ω终端电阻(短距离2-3台设备可以不加,但超过10米建议加)
- 所有从站地址是否唯一(逐一检查设备面板/软件配置)
- 波特率、校验位、停止位PLC与从站是否完全一致
- 是否共地(多台设备供电的负极最好连接在一起,防止电位差过大)
最后一点“共地”多说一句:现在很多仪表的485口是隔离的,A/B对GND悬浮,这种情况下如果不把各个设备的电源负极连起来,A/B线上的电压参考点不一致,轻则通讯误码,重则偶尔烧口。最稳妥的做法是:主站这一侧的485口参考地和从站侧的参考地拉一根线连通,如果从站侧已经是隔离设计,且现场干扰不大,可以让它悬浮,但总线上所有隔离设备必须“统一悬浮”,不能一部分悬浮一部分接地。
3. 轮询程序的完整套路:状态机思想,而不是傻循环
写轮询程序,最忌讳的是把一组MB_MASTER指令一股脑全使能。Modbus RTU是半双工,一次只能有一个请求在总线上,所以程序的骨架必须是“状态机”,而不是“循环扫描触发”。具体思路是:
第一步,用户中断或循环中判断当前状态。我习惯用一个整型变量来表示当前正在轮询的从站号,辅以另一个整型变量表示当前要执行的功能码动作。比如从站1要读保持寄存器地址40001到40010,这是一条读请求;从站1还要写一个寄存器到保持寄存器地址40020,这又是另一条写请求。读和写不能在同一条MODBUS_MASTER指令里完成,所以要给每个从站分配一个“动作列表”,轮询的时候先执行动作1,收到正常响应后切换动作2,全部执行完才跳到下一个从站。
这样做的好处是:你永远不会在同一时刻发两条请求。因为状态机是串行流转的:当前动作完——无论是成功还是超时——才轮到下一个动作。哪怕PLC扫描周期只有几毫秒,也不可能在Modbus指令还没执行完时就再次触发它。这个逻辑用STL写是:
L #CurStep L 0 ==I JCN read1 ... read1: CALL "MB_MASTER" ...但用SCL写会更清晰,比如用CASE语句:
CASE #CurState OF 0: // 轮询从站1,读取保持寄存器 #ReqType := 1; #MB_ADDR := 1; #REQ := TRUE; ... 1: // 等待从站1响应 IF #MB_DONE THEN #CurState := 2; END_IF; ... END_CASE;别小看这个“状态切换”,很多现场程序不稳定,就是因为写完这条指令后立即触发下一条指令,没有等“DONE”或“ERROR”信号。MB_MASTER指令本身有REQ上升沿触发机制,但如果你上一个请求还没结束(BUSY状态),下一个REQ上升沿来了,指令会报错甚至排队混乱。所以写轮询代码的第一原则是:一次只有一条MB_MASTER指令在总线上跑,其余指令的REQ必须为FALSE。
关于轮询周期的计算,这里给一个直观的估算方法:Modbus RTU一帧报文一般8-12个字节(读保持寄存器请求大概8字节,响应根据寄存器数量变化),9600波特率下,每个字节大约1毫秒(1个起始位+8个数据位+1个停止位=10位,10/9600≈1.04ms)。所以一条读请求从发出到收到完整响应,理想情况下需要:发出8字节约8ms+从站处理时间约5-20ms+响应8-10字节约10ms,单个来回大约20-40ms。如果轮询16台从站,每台做一条读请求,一轮下来大约是0.5-1秒。如果你要求每500毫秒刷新一次数据,那这个轮询方案就做不到了——必须考虑用更快的波特率,或者减少每台的寄存器读取数量,或者干脆拆分总线、用多个通讯口并行轮询。
这里就需要谈到“寄存器数量”对轮询周期的影响。很多人图省事,一条MODBUS_MASTER指令把从站所有需要的寄存器一次性全读了,比如从40001读到40050,共50个寄存器。这在Modbus协议上没问题,响应帧会很长,但要注意:从站的响应帧最大长度是254字节。如果一次读太多寄存器导致响应超过254字节,部分仪表会返回异常码(Illegal data address),或者直接不回。而且长响应帧在弱电磁环境下更容易出错,CRC校验失败的概率会上升。我一般控制在一次读32个寄存器以内,再长了宁可分两条指令。
轮询状态机的“从站健康状态”字段也很重要。每个从站我可以分配两个判断结果:CRC校验错误和超时。CRC校验错误说明总线有干扰或从站响应帧损坏,此时可以让该从站“跳过”,但不要立马判定从站离线。超时(比如设置300ms未收到响应)说明从站可能断电、地址错误或通讯断开,此时记录连续超时次数,连续超时超过3次才置位“从站离线”标志。为什么要这样做?因为现场偶尔会有一次两次的瞬态干扰,如果每次都把从站标记成离线,操作画面上的报警会频闪,操作员会麻木,反而掩盖了真正的问题。
下面把这个轮询控制逻辑的SCL框架完整写出来,方便你直接套用:
// 全局变量区 // #busy: BOOL 当前总线是否忙 // #reqType: INT 0=读保持寄存器 1=写保持寄存器 // #slaveAddr: INT 当前从站号 // #mbError: BOOL 通讯错误标志 // #retryCnt: INT 当前从站连续失败次数 // 主轮询循环 CASE #step OF 0: // 初始化,从第1个从站开始 #slaveAddr := 1; #step := 1; 1: // 对当前从站发起读请求,注意REQ需要上升沿 #reqType := 0; #mbAddr := #slaveAddr; #req := TRUE; #step := 2; 2: // 等待MB_MASTER执行完成 #req := FALSE; IF #mbDone THEN #step := 3; // 成功,处理数据 ELSIF #mbError THEN #step := 4; // 失败,记录错误 END_IF; 3: // 处理读到的数据,存入对应的DB变量 // #rcvDBNum := 数据目标DB; // #rcvOffset := 偏移; #step := 5; // 跳到写请求或下一个从站 4: // 失败处理,连续失败3次则标记离线 #retryCnt := #retryCnt + 1; IF #retryCnt >= 3 THEN #slaveOffline[#slaveAddr] := TRUE; #retryCnt := 0; END_IF; #step := 5; 5: // 如果当前从站还有写请求,则发写请求;否则指向下一个从站 IF #needWrite[#slaveAddr] THEN #reqType := 1; #mbAddr := #slaveAddr; #writeDone := FALSE; #step := 6; ELSE #slaveAddr := #slaveAddr + 1; IF #slaveAddr > #maxSlave THEN #slaveAddr := 1; END_IF; #step := 1; END_IF; 6: // 等待写请求完成 #req := FALSE; IF #writeDone THEN #needWrite[#slaveAddr] := FALSE; #step := 5; ELSIF #mbError THEN #step := 5; END_IF; END_CASE;这个框架最关键的两点:一是所有指令的REQ信号只保持一个扫描周期,执行前先置TRUE,执行中马上置FALSE;二是每个从站的处理流程是串行的——读成功或失败都进入下一个状态,不会在总线上同时发出多条请求。
4. 通讯不稳定时的排查链路:踩过的坑和最终解法
程序跑通了,上电测试一切正常,但现场运行几天后开始出现偶发超时——这是485通讯项目里最常见的问题,也是最难的问题。因为有时候是几小时出一次,你守在旁边它偏偏正常,你一走它就掉线。这种问题没法靠“盯”解决,必须按链路逐层排查。
先说一个我亲身经历的案例。某车间的温控系统用了12台温控器做485通讯,用的就是CB1241 + 9600波特率,轮询周期大约1秒。项目刚上电时测试全部正常,但运行了三天后,操作员反映某台温控器的温度偶尔刷新不了,报表里出现缺失值。我过去看了半天,发现出现问题的并不是固定的某一台,而是位置在总线末端的几台轮流“掉线”。这就很有意思了,不是从站本身故障,而是总线层面的问题。
我先用万用表量了A/B线之间的静态电压——1.2V左右,偏低。空闲状态的485差分电压应该至少有2V,这说明末端电阻可能有没接好,或者某台设备的485驱动器带载能力弱。跑到总线末端一看,果然,那个120欧终端电阻只是拧在接线端子上,端子锈蚀导致接触电阻变大,终端电阻等于没接。处理完这个之后,电压恢复到2.6V,问题消失了大半。
但还有一个隐患:末端设备是其中一个温控器,它的485芯片是5V供电的,它的驱动器在总线上压降比较大,在波波特率94波特率下本来没事,但现场有大功率变频器启动时,共模干扰窜到485线上,低压状态的差分信号被干扰淹没,就出现偶发超时。这正好解释了为什么“掉线”的都是末端设备——它们的信号余量最小。
这个案例给了我很深的教训:485通讯看似简单,但信号波动、终端电阻、共地问题、设备芯片驱动能力,任何一个环节都能让通讯变得“时好时坏”。排查这类问题,我的建议是分为四层,逐层排查,不要上来就怀疑PLC程序:
第一层:物理层检查。用万用表量A/B电压、检查终端电阻是否完好、检查屏蔽层是否单端接地、看有没有和动力线走在同一个桥架。这一层解决80%的偶发性问题。
第二层:通讯参数一致性。确认PLC和所有从站的波特率、校验位、停止位完全一致,特别注意有些仪表内部还有一个“数据位”选项——固定为8,但有的老仪表支持7位数据位,虽然现在的Modbus RTU基本都统一到8位,但如果有老设备混在总线里,务必逐台确认。
第三层:协议层。看从站的异常码。比如有的从站返回02(Illegal data address),说明你的起始地址或数量不合法;返回03(Illegal data value),说明数据内容不合法;返回01(Illegal function),说明功能码不被支持。从站返回这些异常码时,PLC的MB_MASTER会报错误,但很多工程师不读错误码,只看到“通讯失败”就一头扎进硬件排查,白费功夫。先读错误码,能省很多时间。
第四层:应用层。确认程序里的轮询逻辑是否正确——是不是有两条MB_MASTER指令在同一周期同时使能,或者有没有其他通讯指令(如TSEND_C、TRCV_C)和Modbus指令争抢资源。1200为了保证数据一致性,同一个通讯模块的指令是串行执行的,但如果你在OB1里同时调用了MB_MASTER和别的通讯指令,可能造成握手延迟,轮询节奏被打乱。经验是:通讯指令全部集中到一个组织块里调用,不要分散在OB1、OB10和FB里各自触发。
排查链路写下来看着长,但实际做熟了,一般半天内就能定位问题。我个人的习惯是:每次排查串口通讯问题,先拿笔记本的USB转485口接上总线,运行一个Modbus调试软件,轮询一遍从站。如果笔记本软件能正常读到所有从站,说明物理层和从站都正常,问题一定出在PLC侧的程序或组态。如果能读通大部分但从站偶尔超时,再回头查物理层。这个方法比反复修改PLC程序快得多。
5. 数据解析中的字节序陷阱:一个字一个字的血泪史
Modbus RTU的寄存器都是16位的,一个寄存器存两个字节。但这里有个大坑:不同厂家的设备,寄存器内部字节的顺序可能不一样。西门子的MB_MASTER指令读取到的数据是按“高字节在前”还是“低字节在前”存放,取决于你在指令引脚上选择的“数据字节顺序”参数。用过1200的人都知道,MB_MASTER有四个引脚控制字节序:BYTE_ORDER、WORD_ORDER、LONG_ORDER、FLOAT_ORDER。
先说说最简单也最常见的16位寄存器。一个寄存器存一个16位数,比如温度值423,十六进制是0x01A7。有的设备发送时先发0x01再发0xA7,有的先发0xA7再发0x01。MB_MASTER的BYTE_ORDER引脚就是让你选择这个顺序的。很多仪表手册会写“寄存器数据格式:高字节在前”或“低字节在前”,对不上就出现数值完全不对的情况——比如读到423变成了57927,或者负数变成正数。
32位的浮点数和32位整数就更麻烦了,涉及到WORD_ORDER(字序)和LONG_ORDER(长字序)。举个典型例子:某流量计把瞬时流量存成32位浮点数,占了两个寄存器。这两个寄存器在内存里的顺序是:寄存器1在前、寄存器2在后,还是反过来?每个寄存器内部的高低位顺序又是怎样?如果四个组合全试一遍,你会发现有且只有一种组合能读出正确值。我见过有人四个组合挨个试,试对了就写死——这样做没问题,但一定要把选对的那个值记录下来,否则几个月后另一台设备出现同样问题,又从头试起。
这里给一个实用建议:从站设备的Modbus寄存器表上,如果标明的是“IEEE 754标准浮点数”,那么正常来说FLOAT_ORDER选“Big-Endian”就能读对。但实务中很多国产仪表是不按标准来的,甚至有的仪表浮点数在寄存器里存储顺序是反的。遇到这种情况我一般先用Modbus调试软件读一段原始字节,然后用十六进制转浮点的工具比对,确定实际字节顺序再设置PLC侧的引脚参数。
顺带提一个容易被忽略的点:保持寄存器(40001区)和输入寄存器(30001区)的地址编号在PLC侧怎么写。之前有朋友过来问我:设备手册写读40021寄存器,为什么程序里填地址21读出来是0?后来发现40021对应的Modbus协议地址是20(寄存器编号减去40001),但有些设备手册里的40021指的是协议地址21。这个偏移量的差异很容易让人懵,我自己的处理办法是:别管手册怎么编号,以实际通讯测试为准——从地址0开始读几个寄存器,看看哪个地址的数据和面板值对得上。这也是调试软件比PLC程序方便的地方,地址以调试软件读出来的为准,然后在PLC程序里保持一致。
再补充一个关于“数据累加”的细节:如果一次读32个寄存器,这些数据显示在数据接收区是“从高到低”连续排列的,第一个寄存器对应的是你指定的起始地址。如果你要读10台设备的累计流量,这10个累计流量的寄存器可能分布在200个寄存器范围内,每台都读200个寄存器显然太浪费总线的带宽。更合理的做法是:把每台从站要读的寄存器按功能分组,比如“瞬时流量组”(读第0-19号寄存器)、“累计流量组”(读第20-39号寄存器),每天定时轮询一次累计流量组,瞬时流量组保持每几百毫秒刷一次。这样既不浪费总线周期,也能保证关键数据刷新率。
6. 从站数量多、数据量大时的优化策略与下一步扩展
现场最常见的情况是:从站数量5-15台,每台需要读的数据也就几个寄存器,用上面第三节的状态机框架就完全够用了。但如果你的项目里从站数量超过20台、每台又要读几十个数据点,轮询周期就会变得不可接受。这时候有几种优化手段,按成本从低到高排列。
第一种是拆分组轮询。不是每台从站都每秒轮询一次,而是区分“关键数据”和“非关键数据”。比如变频器的运行频率和电流是关键数据,每隔500ms刷一次;耗电量这种累计数据,10秒刷一次甚至1分钟刷一次都无所谓。那你的状态机里就可以维护两个轮询列表:高优先级列表和低优先级列表。高优先级列表走一轮,穿插一条低优先级请求,以此保证关键数据的刷新间隔。这种方式不增加任何硬件成本,只是程序逻辑稍复杂一点,但很多现场完全够用。
第二种是提高波特率。从9600提到19200或38400,理论上轮询周期可以缩短一半以上。但前提是:总线上所有从站都支持这个波特率,且总线长度不长、干扰不大。根据我之前做的现场测试,38400波特率下,20米的短线、无变频干扰的环境完全没问题;但如果总线超过100米、旁边有变频器柜,还是乖乖用9600。信号完整性的优先级永远高于刷新率,这也是一个现场经验:通讯偶尔卡一下的故障所造成的影响,远比慢几百毫秒刷新一次更大。
第三种是换用RS485转Profinet网关,把轮询任务下放到网关去执行。网关自己作为Modbus主站按自己的节奏轮询所有从站,然后把数据映射成Profinet IO数据,1200只需要通过PN口读这些映射好的数据。这样做的好处很明显:Modbus轮询的实时性由网关保证,PLC程序只需要处理映射区的数据,通讯可靠性大幅度提升;坏处是增加硬件成本。我在一个项目里最多的时候挂了32台电表,PLC直接轮询的周期实在撑不住,最后上了两台网关,每台网关带16台电表,PLC这边用Profinet从网关读数据,刷新时间直接稳定在200ms以内。如果你以后遇到类似的场景,这是个值得考虑的方案。
再往下走,还可以考虑在1200侧改用S7-1200+CM1241模块做多主站方案,或者直接上1500系列配合Modbus TCP转以太网。但这些都是“大改”性质的方案,不到万不得已不建议动。
写到这里,我回想了一下这几年经手的485通讯项目,真正让项目稳定的往往不是某一项高深的技巧,而是那些最基础的东西:正确接线、地址唯一、波特率统一、轮询逻辑串行、错误处理有超时机制、字节序选对。把这些基本功做到位,Modbus RTU在S7-1200上跑起来非常可靠。说句实在话,很多说485通讯“不稳定”的项目,仔细排查下来,80%以上的问题都出在物理层和组态细节上,真正常规程序逻辑问题反而很少。
最后分享一个小习惯:每次做完一个485项目,我会把现场所有从站设备的寄存器表整理成一份Excel文档,标明设备型号、站号、波特率、关键寄存器地址、字节序选择、数据类型,放进项目归档。这东西平时不起眼,但半年后设备厂家来改造、或者从站设备损坏需要更换时,拿着这份表调通讯参数能省下一整天的排查时间。做现场的人都知道,项目做完只是开始,后期维护才是真正考验设备基本功的地方。