在结构化文本(ST)编程中,当同一个功能块(FB)被多次实例化时,每个实例必须拥有完全独立的数据空间。这是电气自动化系统稳定运行的底层前提。一旦数据混淆,轻则逻辑错乱、输出异常,重则引发设备误动作或连锁停机。本指南聚焦解决 ST 中多重实例调用的核心陷阱:确保每个 FB 实例的数据绝对隔离。
一、问题本质:FB 实例 ≠ 代码复用,而是“独立副本”
功能块(FB)与函数(FC)的根本区别在于:FB 拥有专属的背景数据块(DB),而 FC 没有。当你写:
fbMotor1 : FB_Motor;
fbMotor2 : FB_Motor;
编译器并非“共享一份 FB_Motor 的代码”,而是为 fbMotor1 和 fbMotor2 各自生成一个独立的背景 DB(例如 DB101 和 DB102),并将它们分别绑定到对应实例。所有 IN、OUT、IN_OUT 及静态变量(STATIC)都存储在各自 DB 中。
⚠️ 关键误区:
如果在 FB 内部错误地使用了全局变量(如 M 区、DB 全局访问)或未声明为 STATIC 的局部变量(VAR),这些变量将被所有实例共用,导致数据污染。
例如,在 FB_Motor 中这样写是危险的:
// ❌ 错误:使用全局 M 区 — 所有实例读写同一地址
bEnableAll : BOOL := %M10.0;
// ❌ 错误:使用非 STATIC 的 VAR — 编译器可能将其优化为全局或复用栈区
VAR
iCounter : INT; // 无 STATIC 修饰 → 数据不属实例独有
END_VAR
此时,fbMotor1.iCounter 与 fbMotor2.iCounter 实际指向同一内存位置,fbMotor1 执行一次计数,fbMotor2 的值立即改变。
✅ 正确做法:
所有属于实例状态的变量,必须声明为 STATIC,并确保其初始化逻辑可重入:
FUNCTION_BLOCK FB_Motor
VAR_INPUT
bStart : BOOL;
bStop : BOOL;
nSpeed : REAL;
END_VAR
VAR_OUTPUT
bRunning : BOOL;
nActual : REAL;
END_VAR
VAR
// ❌ VAR 区变量:仅用于临时计算,不可存状态
rTemp : REAL;
END_VAR
VAR_STATIC
// ✅ STATIC 区变量:每个实例独占,自动映射至各自 DB
bIsRunning : BOOL := FALSE;
iStep : INT := 0;
tTimer : TON;
END_VAR
VAR_STATIC 是实现数据独立性的语法基石。它强制编译器为每个实例在背景 DB 中分配固定偏移地址,且绝不跨实例复用。
二、实操验证:三步确认实例数据是否真正独立
以下方法无需示波器或调试器,仅靠在线监控即可完成验证。
-
在线查看背景 DB 结构
在 TIA Portal 中双击任一 FB 实例(如fbMotor1),打开“属性” → “常规” → 点击“背景数据块”右侧的放大镜图标。观察其 DB 号(如DB101)。再对fbMotor2执行相同操作,确认 DB 号不同(如DB102)。
若两个实例共用同一个 DB 号,则说明实例化方式错误(如误用FB_Motor()函数式调用而非声明变量)。 -
强制写入并交叉观测
- 在线连接 PLC,进入
DB101监控界面,找到bIsRunning地址(如DB101.DBX0.0),手动设为TRUE; - 切换至
DB102监控界面,检查同名变量bIsRunning(DB102.DBX0.0)——必须仍为FALSE; - 反向操作:将
DB102.DBX0.0设为TRUE,确认DB101.DBX0.0不变。
若任一操作导致对方状态同步变化,即存在隐式共享(如通过POINTER或ANY类型误传地址)。
- 在线连接 PLC,进入
-
检查 FB 调用链中的“地址穿透”
常见风险点:FB 内部调用另一个 FB,并将THIS指针或ADR()获取的地址作为IN_OUT传入。例如:// ❌ 危险:将本实例 STATIC 变量地址传给子 FB,可能被多实例共用 fbSafetyCtrl( bEnable := bIsRunning, pState := ADR(iStep) // ← 此地址来自当前实例 STATIC,但若子 FB 未正确处理,可能出错 );正确做法:避免传递
ADR(),改用值传递;若必须传地址,确保子 FB 的对应参数声明为IN_OUT且其背后变量也为STATIC。
三、高危场景与规避方案
| 风险场景 | 错误代码示例 | 后果 | 安全替代方案 |
|---|---|---|---|
| 静态数组越界共享 | VAR_STATIC arrData : ARRAY[0..9] OF INT; <br> 在 FB 中用 arrData[i] 且 i 来自外部输入未校验 |
多实例可能因 i 超出范围写入相邻实例的 STATIC 区域 |
始终校验索引:<br>IF i >= 0 AND i <= 9 THEN arrData[i] := value; END_IF; |
| UDT 成员未隔离 | 自定义 UDT stConfig 含 nKp : REAL;,在 FB 中声明 config : stConfig;(未加 STATIC) |
所有实例共享同一份 config 数据 |
UDT 变量必须声明为 STATIC:<br>VAR_STATIC config : stConfig; |
| 定时器/计数器混用 | 使用 TON 或 CTU 时,直接声明在 VAR 区(如 tDelay : TON;) |
tDelay 成为全局单例,fbMotor1 启动后,fbMotor2 调用会复位/覆盖其状态 |
所有功能元件必须置于 VAR_STATIC:<br>VAR_STATIC tDelay : TON; |
| FB 内调用 FC 并写全局 DB | fcLogMsg(sMsg := 'Motor1 running'); 内部将消息写入 DB_LOG.DBW0 |
所有实例日志覆盖同一地址,丢失上下文 | FC 改为接收 DB 指针:<br>fcLogMsg(pLogDB := ADR(DB101), sMsg := 'running'); |
四、终极防护:实例化模板与命名规范
建立团队级编码约定,从源头杜绝问题:
-
强制使用带注释的声明模板
每次声明 FB 实例时,按此格式书写:// [FB_Motor] - 实例1:主传送带驱动 fbConveyorMain : FB_Motor; // [FB_Motor] - 实例2:分拣臂定位轴 fbSortArmAxis : FB_Motor; -
背景 DB 命名自动化
在 TIA Portal 中,右键 FB 实例 → “属性” → “常规” → 勾选“启用背景数据块名称” → 输入有意义名称,如DB_ConveyorMain、DB_SortArmAxis。这能避免DB101、DB102等无意义编号带来的管理混乱。 -
禁用“无背景 DB”调用模式
绝对禁止使用如下语法(它创建的是无状态的 FC 式调用):FB_Motor(bStart := bStart1, nSpeed := 50.0); // ❌ 无实例,无 STATIC,无数据此写法实际调用的是 FB 的“函数接口”,所有内部变量均无持久性,等同于每次执行都是全新初始化——无法维持
bIsRunning等状态,完全失去 FB 意义。
五、故障排查速查表
当发现多个 FB 实例行为相互干扰时,按顺序执行以下检查:
-
检查声明方式
fbX : FB_Type;✔️(正确)
fbX();❌(错误,是函数调用) -
确认 STATIC 区覆盖率
打开 FB 编辑界面,检查VAR_STATIC区是否包含所有需保持状态的变量(包括TON、CTU、STRING、UDT 实例等)。遗漏任意一个,即存在泄漏点。 -
扫描全局访问
全文搜索%M、%DBX、%DBW、%DBD、"GlobalDB".等硬编码地址。若有,改为通过IN参数传入 DB 指针。 -
验证指针操作安全性
搜索ADR(、REF(、POINTER TO。确认所有被取址的变量均属于当前实例的VAR_STATIC区,且接收方 FB 的对应参数为IN_OUT并做了边界保护。 -
交叉测试 DB 内容
如第二节所述,强制修改一个实例 DB 的某STATIC变量,观察其他实例 DB 对应偏移地址内容是否变化。变则必有共享路径。
六、原理延伸:为什么 VAR 和 VAR_TEMP 不能用于状态保持?
VAR(局部变量):存储在块调用时的栈帧中。FB 每次被调用,栈帧重新分配,变量值不保留。若 FB 被周期性调用(如 OB1),VAR变量每次都是新值(除非显式初始化),无法维持跨周期状态。VAR_TEMP(临时变量):生命周期更短,仅存在于单次调用内,甚至可能被编译器优化掉,绝不可用于任何需要记忆的场景。VAR_STATIC:唯一被编译器保证“跨调用持久化 + 实例间隔离”的区域。其内存布局在编译时静态确定,每个实例的VAR_STATIC段在各自背景 DB 中占据独占连续空间。
因此,VAR_STATIC 不是“可选项”,而是 FB 实现数据独立性的唯一合法容器。
确保每个 FB 实例拥有独立 VAR_STATIC 区、专属背景 DB、无全局地址硬编码,即可彻底消除多重实例间的数据串扰。

暂无评论,快来抢沙发吧!