一个实例管最多 8 个从站、32 条任务;读自动分片;同一从站可又读又写,共用一条连接。
西门子 S7-1500 · TIA Portal SCL · 基于系统指令 MB_CLIENT 封装 · 版本 v3.5
3 种编码任选其一导入 TIA Portal · 包内 10 个文件
aSta / aTask / aBuf 是 VAR_IN_OUT,
块自己不存数据 —— 数组得从外面的全局 DB 传进来。包里已经备好一张现成的
DB_MbMultiRead,直接导入就能用,不用自己照着 UDT 手工建。
uStaNo 就共用一条连接,块内串行交替执行。不要为写单独开实例。80A1/80A3 自动换号重建,并新增首次失败取证(wStatusFirst / uFirstErrTask);从站协议异常 838x 不再拆连接。一个 PLC 要同时采几十台 Modbus TCP 从站,每个从站好几个数据区,还得往回写数据。用官方指令裸写,代码量能顶掉半个项目;用一个实例写死一个从站,实例数会失控。
| 对比项 | 单任务版 | FB_MbMultiRead(本块) |
|---|---|---|
| 一个实例管几个从站 | 1 个 | 最多 8 个 |
| 换从站要重连吗 | 要(改 IP 得断开重连) | 不用,每个从站一条常连接 |
| 单次能处理的寄存器数 | 125(超了要自己建多个实例) | 读自动分片,填 600 就分 5 帧 |
| 一个实例能配几条任务 | 1 条 | 最多 32 条 |
| 同一从站能否又读又写 | 不能(要开两个实例=两条连接) | 能,共用一条连接 |
| 数据放哪 | 外部数组 aData[0..124](InOut) | 外部数组 aBuf[0..999],各任务按 uBufOff 落位 |
| 适用平台 | S7-1500 / S7-1200 | S7-1500(用了 8 个 MB_CLIENT 实例) |
这个包只发 FB_MbMultiRead 一个功能块(外加它必须的配套全局 DB)。同一套里的自由口主站、轮询调度、写缓存队列等块还在本地整理中,没测完的不往站上放。
从站表:aSta[1]=[192,168,0,51] aSta[2]=[192,168,0,52] aSta[3]=[192,168,0,53]
uTaskCount := 3
任务表:
aTask[1]: uStaNo=1 bWrite=FALSE uFunc=3 uAddr=0 uCount=100 uBufOff=0
aTask[2]: uStaNo=2 bWrite=FALSE uFunc=3 uAddr=0 uCount=100 uBufOff=100
aTask[3]: uStaNo=3 bWrite=FALSE uFunc=3 uAddr=0 uCount=100 uBufOff=200
读回:aBuf[0..99] / aBuf[100..199] / aBuf[200..299]
从站表:aSta[1]=[192,168,0,51],uTaskCount := 1
任务表:
aTask[1]: uStaNo=1 bWrite=FALSE uFunc=3 uAddr=0 uCount=600 uBufOff=0
块内自动切成 5 帧:125 + 125 + 125 + 125 + 100
读回:aBuf[0..599](aBuf[0] 就是寄存器 0)
有些网关只认 100 个/帧,把 uMaxRegs 从 125 降到 100,600 个就自动变 6 帧。
关键是两条任务填同一个 uStaNo。它们共用那一条 TCP 连接,块内串行交替执行:读一帧 → 写一帧 → 再读一帧。
| 文件 | 编码 | 用途 |
|---|---|---|
FB_MbMultiRead.scl | UTF-8 + BOM | 阅读与留档(完整中文注释) |
FB_MbMultiRead_EN.scl | 纯 ASCII | 首推导入版,最稳 |
FB_MbMultiRead_GB2312.scl | GBK 无 BOM | 想看中文注释时用这版导入 |
_GB2312 是派生文件,由脚本自动生成。改了逻辑要改主文件后重新派生 —— 永远不要手工改 _GB2312,下次派生会全部覆盖。
.scl → 从源生成块。UDT_MbStation(从站表,下标 1..8 对应连接槽位)、UDT_MbTask(任务表,最多 32 条)。DB_MbMultiRead.scl)—— 顺序不能反,见下节。DB_MbMultiRead本块的三个数据引脚是 VAR_IN_OUT,数组要从外部全局 DB 传进来。
包里备了一张现成的,结构和本块严格对得上:
| DB 成员 | 类型 | 对应引脚 | 用途 |
|---|---|---|---|
Station | Array[1..8] of "UDT_MbStation" | aSta | 从站表,下标即连接槽位号 |
Task | Array[1..32] of "UDT_MbTask" | aTask | 任务表,最多 32 条 |
Buffer | Array[0..999] of Word | aBuf | 数据总缓冲(读落点 / 写来源) |
DB_MbMultiRead.scl → 从源生成块 → 只会列出 DB_MbMultiRead 一项 → 生成。"DB_MbMultiRead".Station / .Task / .Buffer。FB_MbMultiRead_DB)由博图自动生成,不用手工建。成员名和引脚名不一样(Station↔aSta)是正常的 —— IN_OUT 按类型和长度匹配,不按名字匹配。
这张 DB 用了NON_RETAIN(断电重启回到初始值)—— 这是故意的:IP、端口、任务表不该被留在 PLC 里。另外Station[n].uUnitId默认是16#FF(255,MB_CLIENT 出厂值), 直连设备一般要改成 1。
配套的 verify_db_multiread.py 会逐项比对 DB 与 FB 的接口定义
(数组上下限、元素类型、两个 UDT 的全部字段名、边界),27 项检查全部通过。
UDT 结构改过,任务 DB 必须重建再下载,不能只替换 FB 就完事 —— 老结构里字段对不上位置,运行时会读到错位的值。
槽位号就是 TCP 连接 ID(TCON_IP_v4.ID)。如果项目里别的通信已经在用这几个号(比如单任务版的 wConnId),要改 FB 里 INT_TO_WORD(#iSlot) 那一行,换成别的号段。
aBuf 默认 1000 个字要处理更多,得把两处一起改大:VAR_IN_OUT 里的 ARRAY[0..999],以及段 1 体检和段 6 结果处理里的边界常数 1000 / 999。只改一处会越界。
它是指令的静态变量 MB_Unit_ID(写在实例 DB 里),默认是 16#FF(255) —— 这是 MB_CLIENT 的出厂默认值,而 v3.2 及之前块里没写这个变量,报文里恒为 255,于是「采不回来」。
v3.3 起 DB100 每个从站多了 uUnitId:直连设备一般填 1;经网关挂 RTU 从站的填 RTU 站号(1..247)。
byVer 应为 16#08。
bWrite 和 uFunc 的方向必须一致 —— 标志说写、功能码却是读,会被直接拒掉。wStatus = 16#80C8 / 16#80A4接收超时 / 从站无响应。先 ping 从站 IP,再用 Modbus 调试工具单独连一次确认 502 端口通。从站 IP 必须和 CPU 同网段。
wStatus = 16#80C5对端把 TCP 连接拒了或断了 —— 这是 TCP 层的事,跟 Modbus 协议和块逻辑无关。九成是这三样:① 从站 IP 或端口填错;② 对端根本没起 Modbus TCP 服务;③ 防火墙/网段挡着。
先在电脑上 ping 通 IP,再用调试工具单独连一次 502 端口;工具能连通了,再回来查块里的配置。
80A1 / 8200 循环两个都是连接层的码:80A1 = 连接被拒绝(本地端口或连接 ID 被占用);8200 = 这条连接正在处理另一个 Modbus 请求。
先确认 byVer 是不是 16#08 —— 不是就升到 v3.5。v3.4 及之前本块有两个时序缺陷会造出这个循环:拆连接只等两拍(10 ms 扫描才 20 ms,而 TCP 真实拆除要上百毫秒),以及连接参数每帧重写一遍(MB_CLIENT 每次都以为参数被改而要重新建连)。
不是 1,默认 16#FF。临时验证:监视表把 FB52.astMB[1].MB_Unit_ID 强制成 1,读数马上回来就是它。
配套仿真脚本用 Python 1:1 复刻了块的递推逻辑(同样的连接状态机、同样的任务轮转、同样的分片与落位规则),155 项断言全部通过:
python test_MbMultiRead_simulation.py配套的全局 DB 另有一份接口一致性校验脚本,逐项比对 DB 与 FB 的定义 (三个成员的数组上下限与元素类型、两个 UDT 的全部字段名、边界),27 项检查全部通过:
python verify_db_multiread.py站上的规矩没变 —— 没跑通的不放上来。同批的其余几个块还在整理,没测完的我不往站上放。
MB_CLIENT 指令与相应版本授权,
部分场景需要 S7-1500 且 CPU 处于相应固件版本。用着有问题、或者想要什么功能,欢迎
来信。