FB_ModbusRtuSlave

用官方 Modbus_Comm_Load + Modbus_Slave 指令封装的 Modbus RTU 从站:1000 个保持寄存器直接放在背景 DB 里,26 个诊断引脚把状态码翻译成「该去查什么」。
S7-1200 / S7-1500 · TIA Portal SCL · 需 Modbus RTU 指令库
✅ 现场实测:本块已在真实工程的 RS485 串口上跑通,上位机用 FC03 读、FC06/FC16 写均正常。 站上另有一版自由口从站(不依赖库,自己组帧), 未在本工程实测通过的不放上来——这里的每个块都是实际跑过的。
下载 SCL 源码(zip,含主版本 + 导入版 + 纯 ASCII 版)
TIA Portal SCL · 免费,可直接用于工程,出问题欢迎反馈

一句话:让本 PLC 当 Modbus RTU 从站,上位机(DCS / 触摸屏 / 另一台 PLC / 上位软件) 用 FC03 读、FC06/FC16 写本站数据。所有脏活(端口初始化、重试、状态分类、诊断计数) 都封在块里,外面只要给一个硬件标识符。

硬件要求


一、文件清单

文件用途
FB_ModbusRtuSlave.scl主版本(UTF-8 + BOM)。阅读、留档、后续修改以它为准
FB_ModbusRtuSlave_GB2312.scl导入博图用这个(GBK / ANSI 编码,中文注释不乱码)
FB_ModbusRtuSlave_EN.scl纯 ASCII 版,怕编码出问题就用它(注释为英文)
本块不依赖 CRC16 辅助 FC——CRC 校验由库指令内部完成,所以下载包里只有 1 个 FB,导一次就完事。 (对比:自由口版需要自己算 CRC,所以要额外导一个共享 FC。)

二、上手三步

第 1 步:导入

项目树 → 外部源文件 → 添加新的外部源文件 → 选中 .scl → 从源生成块
导入后 30 秒自检(重要):双击生成的 FB,看接口表 —— Input / Output / InOut / Static 四栏都必须有变量。 如果全是空的、代码区一堆 #xxx 划红线,说明声明段没被解析, 生成的块是残废的,删掉重新来。

(踩过一次:源码里写了老 S7-SCL 的 VAR_CONSTANT ... END_CONST, TIA 只认 ... END_VAR 结尾,结果接口整个没建起来。现在这份源码已改用 VAR 段放常量,不会再踩。)

第 2 步:确认「非优化访问」(最容易漏的一步)

源码里已写死属性 { S7_Optimized_Access := 'FALSE' },导入后还是再看一眼: 右键 FB → 属性 → 取消勾选「优化的块访问」。

这一步不做,MB_HOLD_REG 会直接报 16#818C,一条都不通。 手册原文:保持寄存器数据块必须支持绝对地址访问,优化访问没有绝对地址。

第 3 步:在 OB1(或循环中断 OB)里无条件调用一次

"FB_ModbusRtuSlave_DB"(
    bEnable   := TRUE,
    uPortHwId := 269,          // ← 改成你的模块硬件标识符
    uBaud     := 9600,
    uParity   := 2,            // Modbus RTU 常用偶校验
    uSlaveAddr:= 1             // ← 本站站号
);

调用频率有硬要求:手册规定主站的响应超时内,Modbus_Slave 至少要被执行 2 次。 上位机 RESP_TO 设 1000 ms,本块的调用周期就不能大于 500 ms。 放 OB1 里一般没问题;OB1 周期长(> 200 ms)就丢进循环中断 OB(OB30 之类)。

不能塞进条件分支。必须每周期无条件调用一次,缺调用就等于从站不在线。

三、数据在哪:背景 DB 里的 aHoldReg

导入后打开自动生成的背景 DB(例如 FB_ModbusRtuSlave_DB),里面有个 aHoldReg:

Modbus 地址背景 DB 里的变量
40001aHoldReg[0]
40002aHoldReg[1]
…………
41000aHoldReg[999]

PLC 自己读写数据就是直接操作这个数组:

// 主站块采集的第 3、4 个字 → 交给上位机的 40001、40002
"FB_ModbusRtuSlave_DB".aHoldReg[0] := "FB_ModbusRtuCycle_DB".aData[2];
"FB_ModbusRtuSlave_DB".aHoldReg[1] := "FB_ModbusRtuCycle_DB".aData[3];

// 上位机写下来的设定值 → 取出来用
#rSetValue := WORD_TO_INT("FB_ModbusRtuSlave_DB".aHoldReg[10]);   // 40011
主站侧一律按 40001 起算,对应下标 0,不是 1。差一位是这个坑的高发区。
FC03 一次最多读 125 个,FC16 一次最多写 123 个;超了从站回异常码。 FC01/05/15 线圈类不进这个数组——那是直接映射到 CPU 的过程映像区(Q0.0 起),与本块无关。

四、引脚速查

输入

引脚类型默认说明
bEnableBoolTRUE总使能,FALSE 停止响应(端口配置保持)
uPortHwIdUInt269硬件标识符,设备组态里查,不是槽位号
uBaudUDInt9600波特率,只认手册列的那几种
uParityUInt00 无 / 1 奇 / 2 偶
uMode485USInt4端口工作模式,485 二线半双工就是 4,见下节
uSlaveAddrUInt1本站站号 1..247(0 是广播地址,不能用)
uFlowCtrlUInt0流控制,RS485 半双工用 0
uRtsOnDly / uRtsOffDlyUInt0RTS 延时,一般不管
uRespToUInt1000响应超时,对从站影响不大
tHostTimeoutTimeT#10s这么久没来帧就算上位机掉线
tRetryTimeT#5s端口初始化失败后的重试间隔
bTrackChangeBoolTRUE追踪哪个寄存器被改写
bErrClrBool—上升沿:清故障锁存与错误计数
bReloadBool—上升沿:重新初始化端口(改波特率后用它)

端口 MODE:485 半双工必须写 4(不改就是一帧不通)

MODE 不是 Modbus_Comm_Load 的引脚,是它背景数据块里的 Static 变量。 西门子官方文档(Entry ID 47756141)原文:MODE 默认 0,需「通过起始值」改成 4。

MODE含义
0全双工 RS232 ← 出厂默认
1全双工 RS422 四线(点对点)
2全双工 RS422 四线(多点主站)
3全双工 RS422 四线(多点从站)
4半双工 RS485 二线 ← 用 485 就填这个
不改的后果:波特率、校验、站号全都对,bReady 也是 TRUE,但一帧都不通, STATUS 常挂 818B / 80C8。这是 485 调试最隐蔽的一个坑 —— 因为它不会报「参数错」,只是静默不通。

本块的处理:在 FB 内部每拍把 uModeEff 写进 #instLoad.MODE, 且在 REQ 上升沿之前写 —— 指令只在 REQ 触发时才把配置刷进模块,写晚了就白写。
配套诊断说明
uModeAct回读端口实际生效的 MODE。HMI 上放一个显示,看到 4 就安心
bModeFaultuMode485 填了 0..4 以外的值,已钳回 4 并持续报警

输出 · 健康度

引脚说明
bReady端口就绪,可以通信
bNdr上位机刚写入新数据(脉冲一拍)
bDr上位机刚读取数据(脉冲一拍)
bCommBeat通信心跳,每成功一帧翻转一次(接 HMI 闪灯)
bHostLost上位机掉线(超过 tHostTimeout 没来过一帧)
tIdleTime距上次成功通信多久了

输出 · 故障码与分类(本块的重点)

引脚说明
bError / wStatus当前拍有错 / 实时状态字
bErrLatch / wLastErr故障锁存 / 锁存的最后错误码(排查看这个)
uErrSrc错误来源:1 端口初始化,2 从站指令
bErrCfg端口组态类:硬件标识符、波特率、校验
bErrHold保持区类:指针无效、越界、DB 被优化访问
bErrFrame帧格式类:CRC 错、字节数不对、功能码不支持
bErrPara地址 / 长度参数非法
bErrBusy端口忙(上位机催太急,或两个主站抢线)
bErrOther上面没覆盖的码
bLineNoise线路干扰:CRC 坏帧占比 > 5%
bParaFault本站站号不在 1..247

输出 · 计数器与追踪

引脚说明
dwReqCnt / dwReadCnt / dwWriteCnt成功处理 / 读 / 写 次数
dwErrCnt故障次数(按错误沿计,持续报错不刷屏)
wReqTotal从站收到的请求总数(含坏帧、广播、别的站号)
wCrcErrCRC 错误帧计数(Word,65535 回绕)
wExcCnt回给主站的异常响应数(越界、不支持的功能码)
wSuccCnt无协议错误的请求数
bDataChanged / wLastChgIdx有值被改写 / 最近被改写的下标(0 = 40001)
wReqTotal / wCrcErr / wExcCnt / wSuccCnt 是西门子系统手册(Table 13-85)明列的公开静态变量, 本块把它们引出来,不用自己解析报文就能看总线质量。

五、排错表:看一眼就知道查什么

现象看哪个引脚原因 / 怎么办
参数全对,但静默不通(bReady 是 TRUE,就是没数据)uModeAct端口 MODE 不是 4。485 二线半双工必须 4,uModeAct 回读应为 4
一条都不通,bReady 一直 FALSEbErrCfg + wLastErr=0001uPortHwId 填错。去设备组态 → 模块属性 → 硬件标识符抄,常见 269 / 271
同上,wLastErr=0002bErrCfg波特率不在手册列表里(比如填了 14400)
同上,wLastErr=0003bErrCfg校验参数只能是 0 / 1 / 2
同上,wLastErr=8180bErrCfgMB_DB 没连上 → 见第六节退路方案
通了但数据全是 0 / 读到乱数bErrHold + wLastErr=818C背景 DB 被设成优化访问了,取消勾选
上位机报「非法地址」bErrHold + wLastErr=8383主站读的地址超出 40001..41000;或 FC01/05/15 线圈类不走这个数组
时通时断,偶尔丢帧wCrcErr 增长、bLineNoise终端电阻没接 / 屏蔽层两端都接地 / 波特率校验与上位机不一致
上位机轮询快了就出错bErrBusy + wLastErr=8200上位机发太快,或者总线上有两个主站
上位机读写的地址很怪(差 1)—主站侧 40001 对应下标 0,不是 1
上位机显示掉线bHostLost先看 wCrcErr 是不是在涨:涨 = 线路问题;不涨 = 上位机真停了或站号填错
dwErrCnt 一秒涨几千—正常的,指针类错误是每拍都报。看 wLastErr 是多少,bErrClr 清掉再观察

状态码分类速查(西门子官方码)

码点亮哪个分类位含义
0000 / 7000 / 7001 / 7002—正常或执行中,不报警
8188bErrPara写只读区或 MODE 非法
8189bErrPara数据地址非法
818AbErrPara数据长度非法
818BbErrHold数据区指针无效(大小不对)
818CbErrHold指针指向了优化访问的 DB
8383bErrHold访问越出 MB_HOLD_REG 范围
8200bErrBusy端口正忙
8380 / 8381 / 8382 / 8384 / 8385 / 80C8bErrFrame帧坏了:CRC / 功能码 / 长度 / 超时
0001 / 0002 / 0003 / 0004 / 8180bErrCfg端口初始化阶段(uErrSrc = 1)的组态类错误

六、退路方案(编译报错时看这里)

源码里 MB_DB 写成 #instSlave.MB_DB(多重实例静态量), 诊断计数器写成 #instSlave.Bad_CRC_Count 等。 个别 TIA 版本不允许从多重实例访问静态变量,编译会报「无法访问该元素」。遇到就把从站指令改成独立实例:

  1. 在 FB 外面(比如 OB1 里)拖一个 Modbus_Slave,让它生成自己的背景 DB,例如 DB_MbSlave。
  2. 在 DB_MbSlave 里加一个数组 aHoldReg : ARRAY[0..999] OF Word(该 DB 设为标准访问)。
  3. 把 FB 里这几处改掉:
    // 状态机 STEP 5
    MB_DB := "DB_MbSlave".MB_DB,
    
    // 状态机 STEP 10
    MB_HOLD_REG := "DB_MbSlave".aHoldReg,
    
    // 诊断计数器段
    #wReqTotal := "DB_MbSlave".Request_Count;
    #wCrcErr   := "DB_MbSlave".Bad_CRC_Count;
    #wExcCnt   := "DB_MbSlave".Exception_Count;
    #wSuccCnt  := "DB_MbSlave".Success_Count;
  4. 数据追踪那段也要跟着改成扫 "DB_MbSlave".aHoldReg。
另一条退路:如果你的库是老版本(指令叫 MB_COMM_LOAD / MB_SLAVE), 把 VAR 里两个类型名换掉即可,不要两个版本混用(手册明令禁止)。

七、和自由口从站怎么选

本块(库指令版)自由口版
依赖需要 Modbus RTU 指令库不需要库,纯 SCL 自己组帧
保持寄存器MB_SLAVE 管,1000 个自己管,2000 点,随便改
诊断库自带 CRC / 异常 / 请求计数全靠自己加
线圈(FC01/05/15)库直接映射到过程映像自己实现
改协议细节(非标波特率等)改不了随便改
推荐场景常规 Modbus 从站(省事、稳、诊断全)非标协议、库不可用、要 8 条功能码全支持

典型网关架构(两个块一起用)

[现场仪表 1..N] --485--> CM1241 --主站轮询块--> aData[]
                                                    |
                                         (拷贝,自己写一行赋值)
                                                    v
[上位机 DCS] <--485-- CB1241 <--FB_ModbusRtuSlave-- aHoldReg[40001..41000]

两个块各占一个口、各用一个实例,互不干扰。中间的拷贝就一行赋值,别在拷贝里做复杂运算。


八、容易踩的坑(按现场出现频率排序)

  1. 背景 DB 被顺手设成优化访问 → 818C,一条不通。
  2. uPortHwId 填了槽位号 → 0001。要去设备组态抄「硬件标识符」。
  3. 端口 MODE 没设成 4 → 参数全对却静默不通,STATUS 挂 818B。
  4. 调用周期太长 → 上位机超时。手册要求 RESP_TO 内至少执行 2 次。
  5. 主站地址差 1 → 40001 是下标 0,不是 1。
  6. 上位机用 FC01/FC05/FC15 读「线圈」 → 那些走 CPU 过程映像(Q0.0 起),跟 aHoldReg 没关系。
  7. 同一口上跑了两个实例 → 抢线,两边都不通。一个 485 口只能有一个本块实例。
  8. 改了波特率没给 bReload → 改动不生效,要上升沿重新初始化一次。
  9. PLC 自己改 aHoldReg 也会触发 bDataChanged → 以为是主站写的,其实是自己写的。

_最后更新:2026-10-10_

在现场遇到搞不定的情况?
把「现象 + wLastErr 的值 + 报文的十六进制」发到 26985331@qq.com,对着排错表基本都能定位。 用着有问题、或者想要的功能,也欢迎来信。
自由口 Modbus RTU 从站Modbus 调试助手浮点数位模式转换器局域网设备扫描器← 工具箱