在结构化文本(Structured Text,ST)编程中,函数调用是实现模块化、可复用逻辑的核心手段。Result := MyFunction(Input1, Input2); 这一行代码看似简单,但其背后涉及语法规范、数据类型匹配、执行时序、错误处理、调试验证等完整工程实践链条。以下为零基础到工业现场级的全流程实操指南,所有步骤均可直接在主流PLC开发环境(如TIA Portal、Codesys、Unity Pro)中验证。
一、理解这行代码的四个关键角色
ST语言遵循“赋值语句 = 左值 := 右值;”结构。拆解 Result := MyFunction(Input1, Input2);:
Result:左值(L-value),即接收返回值的变量。它必须已声明,且数据类型必须与函数声明的返回类型严格一致(例如函数返回INT,则Result也必须是INT或兼容类型)。:=:ST专用赋值运算符(不可写作=或==)。它是阻塞式同步操作:CPU执行至此,会等待MyFunction完全执行完毕并返回结果后,才将值写入Result。MyFunction:函数名。它不是内置指令,而是你或他人定义的用户自定义函数(UDF),必须存在于当前项目符号表中,且已在调用前完成编译。(Input1, Input2):实参列表。每个参数必须与函数声明中的形参顺序、数量、类型一一对应。若类型不匹配(如传入REAL给期望INT的形参),编译器将报错,无法下载到PLC。
✅ 正确示例(在TIA Portal中):
函数声明:FUNCTION MyFunction : INT
VAR_INPUT
a : INT;
b : INT;
END_VAR
MyFunction := a + b;
调用处:Result := MyFunction(10, 25);→Result值为35
❌ 常见错误:
Result := MyFunction(10.5, 25);→ 编译失败:实参10.5是REAL,形参a是INT,类型不兼容。Result := MyFunction(10);→ 编译失败:少传1个参数。Result = MyFunction(10, 25);→ 编译失败:误用=而非:=。
二、从零开始:定义一个可调用的函数
函数不能凭空调用。你必须先创建它。以下是标准五步法:
- 新建函数块:在项目树中右键
Program Blocks→Add New Block→ 选择Function→ 输入名称MyFunction→ 点击OK。 - 声明返回类型:在函数编辑区顶部,将默认的
FUNCTION MyFunction : VOID改为明确类型,例如FUNCTION MyFunction : BOOL(返回布尔值)或FUNCTION MyFunction : DINT(返回双整数)。 - 声明输入参数:在
VAR_INPUT区域逐行定义:VAR_INPUT Input1 : REAL; Input2 : REAL; Enable : BOOL := TRUE; // 可选使能端,带默认值 END_VAR注意:
Enable参数加了:= TRUE,表示若调用时未传该参数,其默认值为TRUE。这是ST支持的可选参数机制,但仅适用于带默认值的VAR_INPUT。 - 编写函数体逻辑:在
BEGIN和END_FUNCTION之间写计算逻辑。必须给函数名赋值(即返回值):BEGIN IF Enable THEN MyFunction := ROUND((Input1 + Input2) * 0.5); // 计算平均值并取整 ELSE MyFunction := 0; // 禁用时返回0 END_IF; END_FUNCTION关键规则:函数名
MyFunction在此处既是函数标识符,也是隐式声明的返回变量。你必须在函数体内至少一次对其赋值,否则编译警告“Function return value not assigned”。 - 保存并编译:按
Ctrl + B编译。无错误后,该函数即可在任何ST程序段中被调用。
三、调用函数的三种合法场景及写法
函数调用位置决定其行为特征。务必根据控制逻辑需求选择:
场景1:在主程序(OB1)中作为独立计算语句
最常见用法,用于周期性计算。
PROGRAM PLC_PRG
VAR
Result : INT;
Temp1 : REAL := 100.3;
Temp2 : REAL := 200.7;
END_VAR
// 每个扫描周期执行一次
Result := MyFunction(Temp1, Temp2, TRUE);
✅ 特点:简单直接;
Result值在每个扫描周期更新。
⚠️ 注意:若MyFunction内部含延时(如TON)、状态机等非纯计算逻辑,其行为将依赖于调用频次(即PLC扫描周期)。
场景2:作为条件表达式的一部分(嵌套调用)
用于简化判断逻辑。
IF MyFunction(SensorValue, Setpoint) > 50 THEN
**启动** 报警灯;
ELSE
**关闭** 报警灯;
END_IF;
✅ 特点:无需中间变量;函数返回值直接参与比较。
⚠️ 注意:此时函数仍会执行,但返回值未被显式保存。适合只关心真/假或阈值判断的场合。
场景3:在函数块(FB)内作为局部计算
实现高内聚逻辑封装。
FUNCTION_BLOCK MotorControl
VAR
SpeedCmd : REAL;
MaxSpeed : REAL := 1500.0;
LimitOk : BOOL;
END_VAR
// 在FB的代码区
LimitOk := MyFunction(SpeedCmd, MaxSpeed) = 1; // 返回1表示限幅通过
✅ 特点:逻辑隔离,便于复用和测试;
LimitOk是FB内部变量,不影响全局命名空间。
四、类型匹配:绕不开的硬性规则
ST是强类型语言。函数调用时的类型检查发生在编译期,而非运行期。这意味着:
- 形参
Input1 : INT只接受INT、SINT、USINT(子范围整数)等可隐式转换的整数类型。 - 形参
Input1 : REAL接受REAL、LREAL(长实数),但不接受INT—— 即使数值相同,也需显式转换。
正确的类型转换写法:
| 需求 | 写法 | 说明 |
|---|---|---|
将 INT 转为 REAL |
Result := MyFunction(REAL#Input1, REAL#Input2); |
使用 # 强制类型转换符,REAL# 表示“将后面的值解释为REAL” |
将 REAL 转为 INT(截断) |
Result := MyFunction(INT#Temp1, INT#Temp2); |
INT# 截去小数部分,10.9 → 10 |
将 REAL 转为 INT(四舍五入) |
Result := MyFunction(ROUND(Temp1), ROUND(Temp2)); |
ROUND() 是标准库函数,返回 DINT,需再转 INT:INT#ROUND(Temp1) |
❗ 绝对禁止:
Result := MyFunction(Input1, Input2);其中Input1是STRING类型,而函数期望INT—— 编译器立即报错:“Cannot convert STRING to INT”。
五、调试:如何确认函数真的执行了?
光看语法正确不够,必须验证运行时行为:
- 在线监控:下载程序后,在TIA Portal中打开
PLC_PRG,将光标停在Result := MyFunction(...);行,右键 →Monitor→Monitor Once。观察Result值是否随输入变化实时更新。 - 断点调试:右键该行 →
Toggle Breakpoint。运行时CPU会在此暂停,可查看Input1、Input2当前值,并单步进入MyFunction内部(需函数源码已下载)。 - 添加日志标记:在函数体首尾加入临时输出:
BEGIN DebugFlag := TRUE; // 全局BOOL变量,连接到HMI指示灯 MyFunction := ... ; DebugFlag := FALSE; END_FUNCTION若HMI灯闪烁,证明函数被调用;若常亮,说明卡在函数内(如死循环)。
六、高级技巧:处理复杂返回值
当函数需返回多个值时,ST不支持多返回值,但有三种工业级解决方案:
方案1:使用结构体(STRUCT)作为返回类型
定义结构体类型:
TYPE MotorStatus :
STRUCT
Ready : BOOL;
FaultCode : WORD;
ActualSpeed : REAL;
END_STRUCT
END_TYPE
函数声明改为:
FUNCTION GetMotorState : MotorStatus
VAR_INPUT
AxisID : BYTE;
END_VAR
调用:
Status := GetMotorState(1);
IF Status.Ready THEN
**启动** 轴1;
END_IF;
方案2:使用函数块(FB)替代函数(FC)
FB自带背景数据块(DB),可保存内部状态,并通过输出引脚暴露多个值:
FUNCTION_BLOCK FB_CalcAverage
VAR_OUTPUT
AvgValue : REAL;
Valid : BOOL;
Count : INT;
END_VAR
调用:
fbAvg(IN := SensorValue);
Result := fbAvg.AvgValue;
方案3:通过VAR_IN_OUT参数“返回”额外值
FUNCTION CalcWithStats : REAL
VAR_INPUT
Data : ARRAY[0..99] OF REAL;
END_VAR
VAR_IN_OUT
MinVal : REAL;
MaxVal : REAL;
END_VAR
调用:
Min := 0.0; Max := 0.0;
Avg := CalcWithStats(MyArray, Min, Max); // Min/Max被函数修改
✅ 推荐顺序:优先用STRUCT(清晰、安全)→ 次选FB(需状态保持)→ 谨慎用VAR_IN_OUT(易引发副作用)。
七、性能与安全红线
在自动化系统中,函数滥用会导致严重后果:
- 严禁在函数内调用非可重入函数:如
ADR()(取地址)、MOVE(内存块移动)、TIME_TO_DT(时间转日期)等。这些函数依赖全局状态,多处并发调用可能冲突。 - 避免递归调用:
FUNCTION F : INT; BEGIN F := F(1)+1; END_FUNCTION→ 编译器禁止,因ST不支持递归。 - 循环调用检测:若
F1调用F2,F2又调用F1,TIA Portal会在编译时报“Cyclic reference detected”,必须解耦。 - 实时性保障:单个函数执行时间应 < 1ms(典型PLC扫描周期为10ms)。若函数含复杂浮点运算或大数组遍历,需用
TIME类型变量测量:StartTime := TON_TIME(); Result := HeavyCalculation(Inputs); Elapsed := TON_TIME() - StartTime; IF Elapsed > T#5ms THEN **触发** 性能告警; END_IF;
八、错误处理:让函数更健壮
ST本身无异常机制,但可通过返回约定实现容错:
-
返回状态码:函数返回
INT,0=成功,负数=错误码:FUNCTION SafeDivide : INT VAR_INPUT Numerator, Denominator : REAL; END_VAR VAR_OUTPUT Quotient : REAL; END_VAR BEGIN IF Denominator = 0.0 THEN SafeDivide := -1; // 除零错误 ELSE Quotient := Numerator / Denominator; SafeDivide := 0; // 成功 END_IF; END_FUNCTION调用:
ErrorCode := SafeDivide(10.0, 0.0, Quotient); IF ErrorCode <> 0 THEN **记录** 错误日志; END_IF; -
使用标准库的
ERROR类型:Codesys支持ERROR枚举,可返回预定义错误(如ERR_DIV_BY_ZERO)。 -
前置校验:在调用前由调用方检查输入有效性,比在函数内处理更高效:
IF Input1 >= 0 AND Input2 <= 100 THEN Result := MyFunction(Input1, Input2); ELSE Result := -999; // 无效输入占位符 END_IF;
Result := MyFunction(Input1, Input2); 不是一行孤立的代码,而是整个自动化软件架构的最小执行单元。它的正确性,取决于你是否严格遵守类型契约、是否理解执行上下文、是否为边界条件预设防御逻辑。现在,你可以打开PLC编程软件,新建函数、声明变量、输入这一行,并亲眼看到 Result 如何从0变为真实物理量——这就是电气自动化的起点。

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