跳到主要内容

函数与函数块

SCL 支持三种程序组织单元 (POU, Program Organization Unit):FUNCTIONFUNCTION_BLOCKPROGRAM。此外还支持 METHODPROPERTYINTERFACE 等面向对象扩展。正确选择 POU 类型是结构化 PLC 编程的基础。


1. FUNCTION (FC) — 无记忆逻辑函数

FUNCTION(无记忆函数)适合纯组合逻辑运算:输入相同则输出永远相同,不依赖内部状态。

声明语法

FUNCTION 函数名 : 返回数据类型
VAR_INPUT
输入变量名 : 数据类型;
// ...
END_VAR
VAR_OUTPUT
输出变量名 : 数据类型;
// ...
END_VAR
VAR_IN_OUT
输入输出变量名 : 数据类型;
// ...
END_VAR
VAR
临时变量 : 数据类型;
// ...
END_VAR
// 函数体代码
// 必须给函数名赋值
函数名 := 返回值;
END_FUNCTION

示例:温度单位转换

FUNCTION CelsiusToFahrenheit : REAL
VAR_INPUT
celsius : REAL;
END_VAR
CelsiusToFahrenheit := celsius * 9.0 / 5.0 + 32.0;
END_FUNCTION

示例:多输出 FC

FUNCTION MinMax : VOID
VAR_INPUT
value1 : REAL;
value2 : REAL;
value3 : REAL;
END_VAR
VAR_OUTPUT
minVal : REAL;
maxVal : REAL;
END_VAR
minVal := value1;
maxVal := value1;

IF value2 < minVal THEN minVal := value2; END_IF;
IF value3 < minVal THEN minVal := value3; END_IF;
IF value2 > maxVal THEN maxVal := value2; END_IF;
IF value3 > maxVal THEN maxVal := value3; END_IF;
END_FUNCTION

使用指导

  • 适用场景:数学运算、数据转换、校验计算、逻辑判断
  • 无持久记忆:每次调用初始化为默认值,不保留历史状态
  • 全局变量:可通过访问全局变量 "全局变量名" 来获取外部状态
  • 返回值:必须给函数名赋值,否则返回未定义值

2. FUNCTION_BLOCK (FB) — 带记忆函数块

FUNCTION_BLOCK(带记忆函数块)适合需要保持状态的逻辑:定时器、计数器、PID 调节器、轴控制等。

声明语法

FUNCTION_BLOCK 函数块名
VAR_INPUT
输入变量 : 数据类型;
// 由调用方赋值
END_VAR
VAR_OUTPUT
输出变量 : 数据类型;
// 由 FB 内部赋值,调用方读取
END_VAR
VAR_IN_OUT
输入输出变量 : 数据类型;
// 引用传递
END_VAR
VAR
私有变量 : 数据类型;
// 内部状态,保持持久
END_VAR
// 执行逻辑
// 可以访问和修改 VAR 和 VAR_OUTPUT 变量
END_FUNCTION_BLOCK

示例:边缘检测 (R_TRIG)

FUNCTION_BLOCK R_TRIG
VAR_INPUT
CLK : BOOL;
END_VAR
VAR_OUTPUT
Q : BOOL;
END_VAR
VAR
M : BOOL;
END_VAR
Q := CLK AND NOT M;
M := CLK;
END_FUNCTION_BLOCK

示例:累加平均器

FUNCTION_BLOCK MovingAverage
VAR_INPUT
x : REAL;
enable : BOOL;
reset : BOOL;
END_VAR
VAR_OUTPUT
average : REAL;
count : UINT;
END_VAR
VAR
sum : REAL;
n : UINT;
END_VAR
IF reset THEN
sum := 0.0;
n := 0;
average := 0.0;
count := 0;
ELSIF enable THEN
sum := sum + x;
n := n + 1;
count := n;
IF n > 0 THEN
average := sum / INT_TO_REAL(n);
END_IF;
END_IF;
END_FUNCTION_BLOCK

使用指导

  • 适用场景:定时器、计数器、状态机、轴控制、PID 调节、滤波器
  • 持久记忆VARVAR_OUTPUT 变量在两次调用之间保持值不变
  • 实例化:必须创建实例才能使用(见第 8 节)
  • 生命周期:实例数据存储在背景数据块 (Instance DB) 中

3. PROGRAM — 主程序组织单元

PROGRAM 是 PLC 执行循环的顶层入口,由操作系统周期性调用。

声明语法

PROGRAM 程序名
VAR_INPUT
// ...
END_VAR
VAR_OUTPUT
// ...
END_VAR
VAR
// 私有变量,保持持久
END_VAR
// 主循环逻辑
END_PROGRAM

示例:主循环

PROGRAM MainCycle
VAR_INPUT
emergencyStop : BOOL;
END_VAR
VAR_OUTPUT
systemReady : BOOL;
END_VAR
VAR
rTrig1 : R_TRIG;
alarmActive : BOOL;
END_VAR
// 急停检查
IF emergencyStop THEN
systemReady := FALSE;
// 调用安全停止逻辑
StopAllAxes();
RETURN;
END_IF;

// 边缘检测
rTrig1(CLK := emergencyStop);
IF rTrig1.Q THEN
alarmActive := TRUE;
END_IF;

systemReady := TRUE;

// 调用其他 FC/FB
MainSequence();
END_PROGRAM

使用指导

  • 顶级入口:在 Darra PLC 项目中,OB1 是默认的主 PROGRAM
  • 持久记忆VAR 变量在扫描周期之间保持值
  • 不自带参数传递:通常通过全局变量 "全局DB".变量 与其他 POU 通信
  • 可调用:内部可以调用 FC、FB 实例和其他 POU

4. METHODS — FB 方法

声明语法

FUNCTION_BLOCK 函数块名
VAR_INPUT
// ...
END_VAR
VAR
// 私有变量
END_VAR

METHOD 方法名 : 返回类型
VAR_INPUT
参数名 : 数据类型;
END_VAR
VAR_OUTPUT
// ...
END_VAR
// 方法体
方法名 := 计算结果;
END_METHOD

END_FUNCTION_BLOCK

示例:带方法的 FB

FUNCTION_BLOCK AxisControl
VAR_INPUT
position : REAL;
velocity : REAL;
END_VAR
VAR
currentPos : REAL;
targetPos : REAL;
enabled : BOOL;
END_VAR

METHOD Enable : BOOL
VAR_INPUT
enable : BOOL;
END_VAR
enabled := enable;
Enable := enabled;
END_METHOD

METHOD MoveTo : BOOL
VAR_INPUT
target : REAL;
speed : REAL;
END_VAR
targetPos := target;
velocity := speed;
currentPos := currentPos + 0.001; // 模拟运动
MoveTo := ABS(targetPos - currentPos) < 0.01;
END_METHOD

METHOD GetPosition : REAL
GetPosition := currentPos;
END_METHOD

// FB 主执行体(在每个扫描周期被调用)
IF enabled THEN
currentPos := position;
END_IF;
END_FUNCTION_BLOCK

调用方式

VAR
axis1 : AxisControl;
atTarget : BOOL;
END_VAR

axis1.Enable(enable := TRUE);
atTarget := axis1.MoveTo(target := 100.0, speed := 10.0);

方法重载

SCL 支持方法重载,通过参数数量或类型区分:

FUNCTION_BLOCK Calculator

METHOD Add : REAL
VAR_INPUT
a : REAL;
b : REAL;
END_VAR
Add := a + b;
END_METHOD

METHOD Add : REAL
VAR_INPUT
a : REAL;
b : REAL;
c : REAL;
END_VAR
Add := a + b + c;
END_METHOD

END_FUNCTION_BLOCK

5. PROPERTIES — FB 属性 (GET / SET)

声明语法

FUNCTION_BLOCK 函数块名
VAR
_value : REAL;
_min : REAL;
_max : REAL;
END_VAR

PROPERTY Value : REAL
GET
Value := _value;
END_GET
SET
IF _min <= Value AND Value <= _max THEN
_value := Value;
END_IF;
END_SET
END_PROPERTY

PROPERTY ClampedValue : REAL
GET
ClampedValue := _value;
END_GET
END_PROPERTY

END_FUNCTION_BLOCK

使用示例

VAR
sensor : FB_Sensor;
v : REAL;
END_VAR

// SET——调用属性的 SET 访问器
sensor.Value := 25.5;

// GET——调用属性的 GET 访问器
v := sensor.Value;

使用指导

  • GET 只读:只声明 GET 不声明 SET 即为只读属性
  • SET 验证:可在 SET 中添加范围检查、触发事件等额外逻辑
  • 命名约定:私有变量建议加 _ 前缀(如 _value)以区别于属性名

6. VAR_IN_OUT — 引用参数传递

声明语法

VAR_IN_OUT 实现引用传递 (pass by reference),调用方必须传入变量(非字面量),FB/FC 内部对该变量的修改会直接反映到调用方。

FUNCTION_BLOCK FB_Controller
VAR_IN_OUT
// 引用传递实际变量,函数内修改直接反映到外部
refValue : REAL;
refArray : ARRAY[0..9] OF INT;
END_VAR
VAR_INPUT
gain : REAL;
END_VAR
refValue := refValue * gain;
END_FUNCTION_BLOCK

调用方式

VAR
ctrl : FB_Controller;
processVar : REAL := 10.0;
data : ARRAY[0..9] OF INT;
END_VAR

// 必须传入变量,不能传常量
ctrl(refValue := processVar,
refArray := data,
gain := 2.0);
// processVar 已被修改为 20.0

与 VAR_OUTPUT 区别

特性VAR_OUTPUTVAR_IN_OUT
传递方式值传递(内部复制)引用传递(直接操作原变量)
可传字面量否(必须传变量)
内部修改影响 FB 内部副本直接修改调用方变量
适用场景计算结果返回需要修改调用方数据

7. 调用约定 — 位置绑定 vs 命名绑定

位置绑定 (Positional)

按参数声明顺序直接填入值,不写参数名:

// 假设 FC 声明为:FUNCTION Calc : REAL (a:REAL, b:REAL, op:INT)
result := Calc(10.0, 20.0, 1); // 位置绑定

优点:简洁 缺点:参数顺序必须完全正确,参数多时易错,可读性差

命名绑定 (Named)

显式指定参数名,不依赖顺序:

result := Calc(a := 10.0, b := 20.0, op := 1);
result := Calc(op := 1, a := 10.0, b := 20.0); // 顺序无关

优点:可读性好,允许中间参数使用默认值,顺序任意 缺点:稍长

混合使用

// 允许前几个参数位置绑定,其余命名绑定
result := Calc(10.0, 20.0, op := 1);

推荐做法

  • 参数 ≤ 3 个:可选位置绑定,适合简单辅助函数
  • 参数 ≥ 4 个:强制命名绑定,提高可维护性
  • FB 调用:始终使用命名绑定,提升代码可读性

8. FB 实例化

单实例 (Single Instance)

VAR
// 声明一个 FB 实例
myTimer : TON;
elapsed : BOOL;
END_VAR

// 调用实例(隐式传递输入参数)
myTimer(IN := trigger, PT := T#5S);
elapsed := myTimer.Q;

多实例 (Multi-Instance)

在另一个 FB 中声明多个实例:

FUNCTION_BLOCK FB_MultiAxisController
VAR
// 多实例声明
axisX : AxisControl;
axisY : AxisControl;
axisZ : AxisControl;
END_VAR
// 分别调用每个实例
axisX(position := xPos, velocity := 100.0);
axisY(position := yPos, velocity := 100.0);
axisZ(position := zPos, velocity := 100.0);
END_FUNCTION_BLOCK

实例数组 (Array of Instances)

FUNCTION_BLOCK FB_ArrayController
VAR
// 实例数组(最多 8 个轴)
axes : ARRAY[1..8] OF AxisControl;
i : INT;
END_VAR
FOR i := 1 TO 8 DO
axes[i](position := posArray[i],
velocity := velArray[i]);
END_FOR;
END_FUNCTION_BLOCK

实例化指导

方式语法存储位置适用场景
单实例timer : TON;调用方背景 DB少量使用
多实例在 FB 内声明其他 FB当前 FB 的背景 DB模块化封装
实例数组axes : ARRAY[1..N] OF FB_Type;当前 FB 的背景 DB同类设备批量控制

9. 递归支持

递归函数

FUNCTION Factorial : DINT
VAR_INPUT
n : DINT;
END_VAR
IF n <= 1 THEN
Factorial := 1;
ELSE
Factorial := n * Factorial(n := n - 1);
END_IF;
END_FUNCTION

限制与注意事项

  • 有限递归深度:Darra PLC 运行时限制递归深度,默认上限为 32 层(可通过编译器选项调整)
  • 无栈溢出保护:深度超过限制时行为未定义,设计时需确保递归深度可控
  • 不建议在循环中断中使用:递归可能增加扫描周期抖动
  • 替代方案:对于深度不确定的递归,优先使用迭代(FOR / WHILE 循环)替代

迭代替代示例

FUNCTION FactorialIter : DINT
VAR_INPUT
n : DINT;
END_VAR
VAR
i : DINT;
result : DINT;
END_VAR
result := 1;
FOR i := 2 TO n DO
result := result * i;
END_FOR;
FactorialIter := result;
END_FUNCTION

10. 内联函数

当前版本不支持内联函数(inline 关键字)。以下替代方案可供参考:

需求替代方案
避免函数调用开销将逻辑直接写在调用处
代码复用使用 FUNCTION 封装,编译器优化
宏定义效果使用全局常量 + 表达式

11. INTERFACE / IMPLEMENTS

接口声明

INTERFACE I_MotionControl
METHOD
Enable : BOOL;
Disable : BOOL;
MoveTo : BOOL;
GetState : INT;
END_INTERFACE

实现接口

FUNCTION_BLOCK FB_ServoAxis IMPLEMENTS I_MotionControl
VAR
enabled : BOOL;
state : INT;
END_VAR

METHOD Enable : BOOL
enabled := TRUE;
state := 1;
Enable := TRUE;
END_METHOD

METHOD Disable : BOOL
enabled := FALSE;
state := 0;
Disable := TRUE;
END_METHOD

METHOD MoveTo : BOOL
VAR_INPUT
target : REAL;
speed : REAL;
END_VAR
// 运动控制逻辑
MoveTo := TRUE;
END_METHOD

METHOD GetState : INT
GetState := state;
END_METHOD

// FB 主执行体
IF enabled THEN
// 周期执行逻辑
END_IF;
END_FUNCTION_BLOCK

多态调用

VAR
// 接口引用变量
motion : I_MotionControl;
servo1 : FB_ServoAxis;
status : BOOL;
END_VAR

// 将 FB 实例赋给接口引用
motion := servo1;
status := motion.Enable();

12. 访问修饰符 (Access Specifiers)

修饰符作用域说明
PUBLIC任意位置默认访问级别,外部可读写
PRIVATE本 FB 内部仅当前 FB 的方法可访问,子类不可见
PROTECTED本 FB 及其子类当前 FB 和继承它的子类可访问,外部不可见

示例

FUNCTION_BLOCK FB_BaseController
VAR
// 默认 PUBLIC
command : INT;

PRIVATE
_internalState : INT;
_errorCode : INT;

PROTECTED
_protectedCounter : INT;
_mode : INT;
END_VAR

METHOD PUBLIC Reset : BOOL
_internalState := 0; // OK:本 FB 内可访问 PRIVATE
_errorCode := 0;
Reset := TRUE;
END_METHOD

METHOD PROTECTED CalcError : BOOL
// 仅供本 FB 和子类调用
CalcError := _errorCode <> 0;
END_METHOD
END_FUNCTION_BLOCK

使用指导

  • PRIVATE:封装内部实现细节,防止外部意外修改
  • PROTECTED:为子类预留扩展点,同时对外部隐藏实现
  • PUBLIC:公开 API 接口,保持稳定

13. 对比总结表

特性FUNCTION (FC)FUNCTION_BLOCK (FB)PROGRAMMETHOD
持久记忆有(VAR 变量保持)继承 FB 记忆
实例化不需要必须实例化编译时自动绑定 FB 实例
背景数据块有(Instance DB)共享 FB 的 DB
返回值必须(可 VOID可选
调用方式直接调用实例调用系统调用实例.方法()
适用场景纯函数运算带状态模块顶层循环模块化操作
重载不支持不支持不支持支持
接口实现不支持支持 IMPLEMENTS不支持接口成员
属性不支持支持 PROPERTY不支持不支持
访问修饰符不适用支持不适用支持
递归有限支持有限支持有限支持有限支持
VAR_IN_OUT支持支持支持支持

选择指南

如果...选择...
只需要一个计算结果,不依赖历史值FUNCTION
需要保持状态(计时、计数、累计)FUNCTION_BLOCK
需要多个相同模块(多轴、多温区)FUNCTION_BLOCK + 多实例
需要顶层循环入口PROGRAM
需要封装 FB 的特定操作METHOD
需要对外暴露可控属性PROPERTY
需要多态和接口契约INTERFACE + IMPLEMENTS

附:完整示例 — 带属性的限幅器 FB

FUNCTION_BLOCK FB_Limiter
VAR_INPUT
rawValue : REAL;
reset : BOOL;
END_VAR
VAR_OUTPUT
clampedValue : REAL;
limitActive : BOOL;
END_VAR
VAR
PRIVATE
_lower : REAL := 0.0;
_upper : REAL := 100.0;
PROTECTED
_clampCount : UDINT;
END_VAR

PROPERTY LowerLimit : REAL
GET
LowerLimit := _lower;
END_GET
SET
_lower := Value;
END_SET
END_PROPERTY

PROPERTY UpperLimit : REAL
GET
UpperLimit := _upper;
END_GET
SET
_upper := Value;
END_SET
END_PROPERTY

METHOD PUBLIC ResetCount : BOOL
_clampCount := 0;
ResetCount := TRUE;
END_METHOD

METHOD PROTECTED GetClampCount : UDINT
GetClampCount := _clampCount;
END_METHOD

// 主执行体
IF reset THEN
_clampCount := 0;
END_IF;

IF rawValue < _lower THEN
clampedValue := _lower;
limitActive := TRUE;
_clampCount := _clampCount + 1;
ELSIF rawValue > _upper THEN
clampedValue := _upper;
limitActive := TRUE;
_clampCount := _clampCount + 1;
ELSE
clampedValue := rawValue;
limitActive := FALSE;
END_IF;
END_FUNCTION_BLOCK

调用示例

VAR
limiter : FB_Limiter;
result : REAL;
active : BOOL;
END_VAR

// 配置限幅
limiter.LowerLimit := -50.0;
limiter.UpperLimit := 150.0;

// 周期调用
limiter(rawValue := RawSignal,
reset := FALSE);
result := limiter.clampedValue;
active := limiter.limitActive;

// 读取钳位次数(需要 PROTECTED 方法——仅供子类扩展)
// limiter.GetClampCount(); // 外部不可调用,编译错误