ST函数与功能块是PLC编程中实现模块化、可复用逻辑的核心构件。它们都使用结构化文本(Structured Text,ST)语言编写,但设计理念、数据管理方式和调用机制存在本质差异。掌握二者区别及正确实例化方法,是避免逻辑错误、提升程序可维护性的关键。
一、核心概念辨析:FC与FB的本质差异
| 特性 | FC(Function Call,函数) | FB(Function Block,功能块) |
|---|---|---|
| 数据存储能力 | 无内部存储区;每次调用均从输入参数读取,不保留上次状态 | 自带背景数据块(DB);自动保存静态变量(STATIC)、输出参数(OUTPUT)及内部变量状态 |
| 调用前提 | 可直接调用,无需额外配置 | 必须先关联一个背景数据块(DB),否则编译报错 |
| 变量声明域 | 仅支持 VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR(临时变量) |
支持 VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR, VAR_STATIC, VAR_TEMP |
| 执行结果依赖性 | 纯函数式:相同输入 → 相同输出,无历史依赖 | 状态相关:输出不仅取决于当前输入,还取决于上一次执行后的 STATIC 变量值(如计数器、延时标志) |
| 典型应用场景 | 数学计算(如 $sqrt(x^2 + y^2)$)、逻辑判断、信号转换 |
带记忆的控制单元(如 TON 定时器、CTU 计数器、电机启停控制单元) |
⚠️ 关键结论:FB = 可实例化的“有状态对象”;FC = 无状态的“纯计算工具”。
这一区别直接决定了:能否用同一个FB实现多个独立设备的并行控制(如3台电机各用1个FB实例),而FC无法做到。
二、ST语言中FC的定义与调用(无状态计算)
1. 定义一个FC:计算两点间欧氏距离
FUNCTION FC10 : REAL
// 输入坐标
VAR_INPUT
x1 : REAL;
y1 : REAL;
x2 : REAL;
y2 : REAL;
END_VAR
// 内部临时变量(每次调用新建,调用结束即销毁)
VAR
dx : REAL;
dy : REAL;
END_VAR
dx := x2 - x1;
dy := y2 - y1;
FC10 := SQRT(dx * dx + dy * dy); // ST标准库函数
- 函数名
FC10是标识符,返回值类型为REAL。 - 所有
VAR声明的变量均为临时变量(TEMP),生命周期仅限本次执行。 - 无
VAR_STATIC,无背景数据块依赖。
2. 在主程序(如 OB1)中调用FC
PROGRAM OB1
VAR
dist_A : REAL;
dist_B : REAL;
END_VAR
// 调用FC10计算两组点的距离(完全独立,互不影响)
dist_A := FC10(0.0, 0.0, 3.0, 4.0); // 返回5.0
dist_B := FC10(-1.0, 2.0, 1.0, 5.0); // 返回√13 ≈ 3.606
- 调用语法:
变量 := FC编号(实参1, 实参2, ...)。 - 每次调用都是全新计算,不占用DB资源,适合高频、轻量运算。
三、ST语言中FB的定义与实例化(有状态控制)
1. 定义一个FB:带使能控制的脉冲发生器(类似TP定时器简化版)
FUNCTION_BLOCK FB20
// 输入
VAR_INPUT
IN : BOOL; // 启动脉冲输入(上升沿触发)
TON_TIME : TIME; // 脉冲持续时间
END_VAR
// 输出
VAR_OUTPUT
Q : BOOL; // 脉冲输出
ET : TIME; // 已计时长(运行中更新)
END_VAR
// 静态变量(保留在背景DB中,跨周期保持)
VAR_STATIC
timer_state : BOOL := FALSE; // 当前是否在计时
elapsed : TIME := T#0s; // 已累计时间
start_time : DATE_AND_TIME; // 上次触发时刻
END_VAR
// 临时变量(本周期内使用,不保存)
VAR_TEMP
now : DATE_AND_TIME;
diff : TIME;
END_VAR
// 主逻辑
now := GET_SYSTEM_TIME(); // 获取当前系统时间
IF IN AND NOT timer_state THEN
// 检测上升沿:IN由FALSE→TRUE
timer_state := TRUE;
start_time := now;
elapsed := T#0s;
ELSIF timer_state THEN
// 计算已过时间
diff := DIFF_TIME(start_time, now);
elapsed := diff;
IF elapsed >= TON_TIME THEN
timer_state := FALSE;
Q := FALSE;
ELSE
Q := TRUE;
END_IF;
ELSE
Q := FALSE;
END_IF;
ET := elapsed;
VAR_STATIC区域声明的变量(timer_state,elapsed,start_time)不会随每次调用重置,而是持久保存在背景数据块中。Q和ET是VAR_OUTPUT,既可被外部读取,其值也参与内部逻辑(但通常不建议在FB内部写入OUTPUT变量)。
2. FB的实例化:必须绑定背景数据块(DB)
在TIA Portal等工程软件中,实例化FB需两步:
-
创建背景数据块(DB):
- 右键项目树中的
FB20→ “创建背景数据块”。 - 系统自动生成
DB1(或指定名称如DB_MOTOR1_PULSE),其结构严格匹配FB20的接口(含STATIC变量)。
- 右键项目树中的
-
在调用处声明FB实例变量:
PROGRAM OB1 VAR pulse_inst_1 : FB20; // 声明一个FB20类型的变量(即实例) pulse_inst_2 : FB20; // 注意:此处未指定DB!实际编译时会自动关联默认DB(如DB1、DB2) // 更规范做法:显式指定DB(见下文) END_VAR
3. 显式绑定DB的调用方式(推荐,清晰可控)
PROGRAM OB1
VAR
// 方式1:声明时直接指定DB(TIA Portal语法)
pulse_a : FB20 WITH DB_MOTOR_A; // DB_MOTOR_A 必须已存在且类型匹配FB20
pulse_b : FB20 WITH DB_MOTOR_B;
// 方式2:在调用语句中传入DB(兼容老版本/部分平台)
// (注:此语法因平台而异,主流S7-1500/TIA Portal以方式1为准)
END_VAR
// 执行调用(必须提供所有INPUT参数)
pulse_a(IN := "Start_Button", TON_TIME := T#2s);
pulse_b(IN := "Remote_Start", TON_TIME := T#500ms);
// 读取输出
"Motor_A_Run" := pulse_a.Q;
"Motor_B_Run" := pulse_b.Q;
pulse_a和pulse_b是两个完全独立的实例,各自拥有专属的DB_MOTOR_A和DB_MOTOR_B,其中的timer_state、elapsed等STATIC变量互不干扰。- 若错误地让两个实例共用同一个DB(如都绑定
DB1),则它们将共享全部状态——导致逻辑混乱(例如启动pulse_a会意外影响pulse_b的计时)。
四、FB与FC混用实战:电机启停控制单元
构建一个完整控制单元,包含:启动条件判断(FC)、启停逻辑(FB)、故障处理(FC)。
1. FC:安全条件校验(无状态)
FUNCTION FC30 : BOOL
VAR_INPUT
e_stop : BOOL; // 急停信号(TRUE=有效)
door_open : BOOL; // 防护门(TRUE=开启)
temp_high : BOOL; // 温度超限
END_VAR
FC30 := NOT (e_stop OR door_open OR temp_high); // 全为FALSE才允许运行
2. FB:电机控制器(有状态)
FUNCTION_BLOCK FB40
VAR_INPUT
START : BOOL;
STOP : BOOL;
SAFE_OK : BOOL; // 由FC30提供
END_VAR
VAR_OUTPUT
RUNNING : BOOL;
FAULT : BOOL;
END_VAR
VAR_STATIC
motor_state : BOOL := FALSE;
fault_latch : BOOL := FALSE;
END_VAR
// 故障清除:STOP按下且SAFE_OK恢复时复位故障
IF STOP AND SAFE_OK THEN
fault_latch := FALSE;
END_IF;
// 启停逻辑(带互锁)
IF START AND SAFE_OK AND NOT fault_latch THEN
motor_state := TRUE;
ELSIF STOP OR NOT SAFE_OK THEN
motor_state := FALSE;
IF NOT SAFE_OK THEN
fault_latch := TRUE;
END_IF;
END_IF;
RUNNING := motor_state;
FAULT := fault_latch;
3. 主程序整合调用
PROGRAM OB1
VAR
// 实例化两个电机控制器(完全独立)
motor1_ctrl : FB40 WITH DB_MOTOR1;
motor2_ctrl : FB40 WITH DB_MOTOR2;
// 中间变量
safe1 : BOOL;
safe2 : BOOL;
END_VAR
// 步骤1:分别校验每台电机的安全条件(调用FC)
safe1 := FC30("E_Stop_1", "Door_1", "Temp_1");
safe2 := FC30("E_Stop_2", "Door_2", "Temp_2");
// 步骤2:将安全结果传入各自FB实例(驱动状态机)
motor1_ctrl(START := "Start_1", STOP := "Stop_1", SAFE_OK := safe1);
motor2_ctrl(START := "Start_2", STOP := "Stop_2", SAFE_OK := safe2);
// 步骤3:输出结果
"Motor1_Running" := motor1_ctrl.RUNNING;
"Motor1_Fault" := motor1_ctrl.FAULT;
"Motor2_Running" := motor2_ctrl.RUNNING;
"Motor2_Fault" := motor2_ctrl.FAULT;
- ✅
FC30被复用两次,节省代码空间,且无状态冲突风险。 - ✅
FB40两个实例通过不同DB隔离,一台电机故障(fault_latch = TRUE)绝不影响另一台。 - ✅ 所有逻辑清晰分层:FC做“瞬时判决”,FB做“状态演进”。
五、常见错误与避坑指南
-
误将FB当FC调用
❌ 错误写法:result := FB20(IN := TRUE, TON_TIME := T#1s);
→ 编译失败:FB不能作为表达式返回值调用,必须通过实例变量调用。 -
忘记为FB分配背景DB
❌ 在变量声明pulse : FB20;后未创建DB → 下载时PLC报错Invalid instance DB。
✅ 解决:右键该变量 → “创建背景数据块”。 -
多个FB实例共用同一DB
❌inst1 : FB20 WITH DB_COMMON; inst2 : FB20 WITH DB_COMMON;
→inst1修改timer_state会立即覆盖inst2的同名变量,造成不可预测行为。 -
在FB内部修改OUTPUT变量后再读取
❌Q := TRUE; IF Q THEN ... END_IF;
→ ST中OUTPUT变量在调用结束前不保证可读,应改用中间VAR或VAR_STATIC变量。 -
混淆VAR_TEMP与VAR_STATIC
❌ 将计数器值声明为VAR_TEMP count : INT;→ 每次调用归零,无法计数。
✅ 必须用VAR_STATIC count : INT := 0;。
六、性能与资源权衡建议
-
优先用FC:当逻辑满足以下任一条件:
- 纯数学/布尔运算;
- 不需要记忆上一周期值;
- 被高频调用(如每10ms执行一次的滤波算法);
- 希望最小化DB数量(节省PLC内存)。
-
必须用FB:当逻辑涉及以下任一需求:
- 时间累积(定时器、积分器);
- 事件计数(启动次数、故障次数);
- 多状态机(手动/自动/急停模式切换);
- 需要为同类设备创建多个独立副本(如N台泵、M个阀门)。
-
资源开销对比(以S7-1500为例):
- 一个FC调用:约消耗 < 100 字节代码+栈空间;
- 一个FB实例:除代码外,额外占用DB空间(按
VAR_STATIC成员总字节数计算)。例如FB40约占20字节DB,100个实例即2KB DB内存。
最终选择依据不是“哪个更高级”,而是“哪个最贴合控制对象的本质属性”:无记忆的计算选FC,有记忆的状态机选FB。

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