Modbus 写缓存队列
写任务排队 · 失败不堵车

写请求先进队列,调度 FC 逐条发出去。写总闸一关任务照进队列不丢;某一帧失败、超时、掉线,队列自己往下走。

西门子 S7-1200 / S7-1500 · TIA Portal SCL · 基于系统指令 MB_CLIENT 封装 · 版本 v2.1

✅ 实机跑通:已在 S7-1510SP-1 PN 上导入 TIA 工程实跑, 验证了「正常写入通路」与「同一扫描周期多次提交」两项(v2.1 的核心改动); 配套 Python 离散仿真 57 项断言全部通过。站上的规矩没变 —— 没跑通的不放上来。
下载完整包(zip · 3 个 SCL 块 × 3 种编码 + 仿真脚本 + 评审说明)

3 种编码任选其一导入 TIA Portal · 包内 12 个文件

三个文件,顺序不能反
ModbusWriteCache_Types(生成 UDT + 全局 DB) → FC_ModbusWrite_PutTask(入队) → FC_ModbusWrite_CacheRun(出队调度)。 两个 FC 的接口引用了 Types 里定义的 UDT,顺序反了报「未定义数据类型」。

一、它解决什么问题

往从站写数据,最朴素的做法是直接调 MB_CLIENT。设备不在线的时候,这一调就等于把整个程序吊死:指令一直等不到 DONE,后面的写请求全堵在后面,一个都发不出去,而 HMI 上看起来一切正常。

写缓存队列的做法是把"提交"和"发送"拆开:

场景直接调 MB_CLIENT用本写缓存队列
对端 TCP 半开(受理了但永不返回)状态机永久停在等结果,后续写请求全部滞留兜底超时 → 断线重连 → 丢弃并锁存错误码,状态机回 0
请求未被受理(16#8200 端口占用)同样永久卡住等受理超时 → 报自定义码 16#FFFE,回 0
从站回异常码(16#8383)重试策略一刀切,白试 3 帧协议层异常不重试,1 帧就判定失败
同一扫描周期提交 5 条——5 条全进队列,逐条发出
想临时停写只能靠外面不再调置 ENABLE_WRITE=FALSE,任务照进队列不丢
本包只发写缓存队列这一个功能块(三件套)。同批的另外几个块 (多从站轮询主站、自由口主站、RTU 从站等)各有各的测试状态,没测完的我不往站上放。

二、三个文件各干什么

文件生成的块职责
ModbusWriteCache_TypesUDT_ModbusWriteTask
DB_ModbusWriteCache
数据层:一条写任务的结构 + 全局 DB(队列、状态机、组态、统计、定时器)
FC_ModbusWrite_PutTaskFC_ModbusWrite_PutTask入队:调用方提交写请求,校验参数后写进环形队列
FC_ModbusWrite_CacheRunFC_ModbusWrite_CacheRun出队:状态机驱动 MB_CLIENT,处理超时、重试、断线与统计

在 OB1 里怎么调

// ① 提交写请求:每周期可以调多次
"FC_ModbusWrite_PutTask"(
    SlaveID   := 1,            // 从站号 1..247,0 是广播(拒收)
    RegAddr   := 40101,        // 保持寄存器地址(带区号)
    RegValue  := 16#1234,      // 要写的值
    AddTrigger:= "写启动脉冲");  // 必须单周期脉冲!

// ② 调度:每周期调一次,全工程只调一次
"FC_ModbusWrite_CacheRun"(
    InterfaceId := 64,         // PLC 的 PROFINET 口硬件标识符
    ID          := 21,         // 连接号,必须唯一
    ADDR1       := 192, ADDR2 := 168,
    ADDR3       := 0,   ADDR4 := 51,
    RemotePort  := 502);

三、状态机

        队列非空且总闸开
   0 空闲 ──────────────► 10 等受理 ──BUSY──► 20 等结果 ──DONE──► 出队(成功) ──┐
       ▲                    │                  │                              │
       │                    │T_ACCEPT 超时     │T_REQ 超时 / ERROR            │
       │                    ▼                  ▼                              │
       │                  ┌──────────────────────┐                            │
       │                  │ 30 判定               │                           │
       │                  │  · 818x/838x → 丢弃   │                           │
       │                  │  · 80Cx/超时 → 60 断线│                           │
       │                  │  · 其余 → 原地重试    │                           │
       │                  └────────┬─────────────┘                            │
       │                           ▼                                          │
       └───────────────────── 60 断开 ──► 65 冷却 ──► 回到 10(重试)          │
       └──────────────────────────── 丢弃(失败) ◄─────────────────────────────┘
状态含义兜底
0空闲,队列空或总闸关—
10已置 REQ,等 MB_CLIENT 受理T_ACCEPT 超时 → 16#FFFE → 30
20已受理,REQ 落低,等 DONE/ERRORT_REQ 超时 → 16#FFFF → 30
30判定:丢弃 / 原地重试 / 先重连保证 REQ 至少一拍低电平
60 / 65断开保持 T_DISC → 释放 → 冷却 T_RECONN之后回 10 重试

成功和丢弃都不单占状态,就地出队 —— 每个任务固定 4 拍。

四、三个版本文件怎么选

文件编码用途
ModbusWriteCache_Types.sclUTF-8 + BOM阅读与留档(完整中文注释)
ModbusWriteCache_Types_EN.scl纯 ASCII首推导入版,最稳
ModbusWriteCache_Types_GB2312.sclGBK 无 BOM想看中文注释时用这版导入

三个块都是这三份编码,同一个块要导同一编码版本,别混着导。

_GB2312 是派生文件,由脚本自动生成。改了逻辑要改主文件后重新派生 —— 永远不要手工改 _GB2312,下次派生会全部覆盖。

五、导入步骤

  1. 项目树 → 外部源文件 → 添加新的外部源文件 → 选中 .scl → 从源生成块。
  2. 先导 ModbusWriteCache_Types → 生成 UDT + 全局 DB 两项。
  3. 再导 FC_ModbusWrite_PutTask,最后导 FC_ModbusWrite_CacheRun。
  4. 不要先手动新建同名块,否则会冲突。
  5. 不要重导 MB_CLIENT_DB / MB_CLIENT_PARA —— 本包故意不含,那两个 DB 里是现场 IP 与连接号,重导会冲掉。
  6. OB1 里:CacheRun 每周期调一次、全工程只调一次;PutTask 每周期可调多次。
  7. 在线看 DB_ModbusWriteCache.VER,是 16#21 就说明新代码生效了。
导入 ModbusWriteCache_Types 会覆盖现有的 DB_ModbusWriteCache: 队列清空、统计清零、组态值回到默认。队列本来就是非保持的,但请在停机试车的窗口里做, 别在线路上正跑着东西的时候导。

从零建工程还缺两个 DB

MB_CLIENT_DB(在 OB 里拖一次 MB_CLIENT 指令,让 TIA 自动生成)+ MB_CLIENT_PARA(一个 T_CON_IP_v4 结构: InterfaceId=64 / ID=21 / ActiveEstablished=1 / RemotePort=502 / LocalPort=0)。

两个 FC 的输入接口完全没变,原有的调用语句不用改;新增的都是输出引脚,可以全部不接(状态在 DB 里都有)。

六、参数速查

全部在 DB_ModbusWriteCache 里,HMI 或监视表直接改(DB 是非保持的,掉电回默认值)。

参数默认什么时候要改
QUEUE_MAX_LEN / QUEUE_CAPACITY100 / 99改队列深度时,同时改 TaskQueue 数组长度
RETRY_MAX3总尝试次数(含第一次)。局域网 3 够,别贪大
ENABLE_WRITETRUE临时停写(检修 / 联调)置 FALSE,任务仍会入队不丢
RETRY_ON_PROTO_ERRFALSE网关偶发帧错、想连异常码也重试时置 TRUE
ADDR_OFFSET0地址习惯是 0 基寄存器号就填 40001
TRIG_MODE0v2.1 起已废弃,FC 不再读它。保留字段只为不动 DB 结构
T_ACCEPTT#1s跨网段 / 无线可放宽到 T#2s
T_REQT#6s必须比 MB_CLIENT 背景 DB 里的响应超时长(那里默认 5s),否则本块先报超时,拿不到真正的错误码
T_DISC / T_RECONN300ms / 500ms一般不用动

七、四个必须知道的坑

1. DB 的「优化的块访问」必须关

DB_ModbusWriteCache 的 S7_Optimized_Access 必须是 'FALSE'。 MB_CLIENT 的 MB_DATA_PTR 只能指向非优化 DB 或 M 区;一旦被「顺手优化」成 TRUE, MB_CLIENT 会稳定报 16#818C 并且一条都写不出去。

2. AddTrigger 必须是单周期脉冲

用 R_TRIG 把电平取成单周期脉冲再喂给每个调用点。接成电平会每周期重复写同一个值, TrigHoldWarn 输出会亮一拍提示。同一个脉冲可以喂给多个调用点, v2.1 起同一扫描周期里多处提交互不干扰。

3. 写出去的是线圈不是寄存器

RegAddr 直接对应 MB_CLIENT 的 MB_DATA_ADDR: 写保持寄存器要填 40101 这种带区号的地址;填 100 会被当成线圈区,发出去的是 FC05 而不是 FC16。 地址习惯是 0 基寄存器号,就在 ADDR_OFFSET 里填 40001。

4. 升级 v2.1 只要导一个文件

v2.1 只改了 FC_ModbusWrite_PutTask 的入队触发语义,ModbusWriteCache_Types 本次只改了注释、DB 结构一个字节没变。 不要重导 Types —— 重导会把在线整定过的 T_ACCEPT / T_REQ / RETRY_MAX 一起重置。

八、常见问题

一条都写不出去,MB_StatusLatched = 16#818C

MB_DATA_PTR 指向了优化访问的 DB。把 DB_ModbusWriteCache 的「优化的块访问」关掉 —— 别把它改成优化。

队列只入不发,WriteLocked = TRUE

ENABLE_WRITE 是 FALSE。打开写总闸。

PutTask 的 MultiCall 亮了

同一周期不是第一次调用。v2.1 起这只是诊断位,不是故障,不用管。v2.0 时期接了报警的,这次要拆掉。

StatusLive 一直 16#7004

这是正常的(连接已建立、无作业在跑)。不是故障,别当错误码查。真卡住时 STATUS 是 16#7005 或 16#8200。

16#8200 端口正在处理另一个请求

同一连接号被两处用了,或者上一作业没收尾。检查全工程是不是有第二个 MB_CLIENT 用了同一个 ID。

时间戳 / 计数掉电就归零

DB 是 NON_RETAIN。有意的:写任务绝不能在重启后被重放 —— 那等于把设备写回历史状态,比重发一条新写请求危险得多。

九、验证记录

实机验证

已在 S7-1510SP-1 PN 上导入 TIA 工程实跑,验证了两项:

其余边界场景(兜底超时、断线重连、失败丢弃、协议异常码分类)目前是仿真结论,没有在真机上逐条复现 —— 页面上不把它们说成实测过。

仿真验证

配套仿真脚本用 Python 1:1 复刻了两套状态机的离散递推,并对 MB_CLIENT 建模了四种搭档行为(正常 / 报错 / 受理后永不返回 / 根本不受理)—— 只跑成功路径是测不出问题的。

关键对照数据

场景原稿本版 v2.1
同周期调 5 次(同一个单周期脉冲)5 帧5 帧全进
触发保持 20 拍(没取沿,属于接错)发 20 帧同样的写发 4 帧 + TrigHoldWarn 报警 19 拍
对端半开 5000 拍后状态机永久停在 2,失败计数 0,后续 5 条全滞留超时 → 重连 → 丢弃并锁存 16#FFFF,状态机回 0
请求未被受理永久停在 2,7004 重发分支触发 0 次报 16#FFFE,回 0
从站回 16#8383发 3 帧白试发 1 帧
满队列里「刚好空出一格」那一拍提交静默丢弃正常入队
诚实声明:写缓存队列本身已在真机上跑通(S7-1510SP-1 PN,验证了正常写入通路与同周期多次提交)。 但上面那张对照表里的「原稿」那一列,以及超时兜底、断线重连、失败丢弃这些边界分支,是仿真 + 手册核对的结论, 没在真机上逐条复现 —— 这里不把它们说成实测过。
换到自己的工程时,仍建议在停机窗口做,先置 ENABLE_WRITE=FALSE 观察状态机跑通,再开闸。
站上的规矩没变 —— 没跑通的不放上来。同批的其余几个块还在整理,没测完的我不往站上放。
使用与免责
源码按「现状」免费提供,仅供学习交流与合法用途。设备型号、模块版本、现场条件各不相同, 落地前还得对着厂家手册和图纸核实 —— 这里能帮少走弯路,不能替你签字。 用到项目上请自行验证,关键工况务必先在测试台上跑一遍。

这个块依赖西门子官方 MB_CLIENT 指令与相应版本授权。 用着有问题、或者想要什么功能,欢迎 来信。
FB_MbMultiRead 多从站主站 → ← 自由口 Modbus RTU 从站 FB_ModbusRtuSlave 从站 FB_DiagOB 诊断三件套 ← 返回程序源码