在电气自动化系统中,使用结构化文本(Structured Text,ST)编程时,重复编写相同功能的逻辑(如PID计算、报警判断、电机启停保护等)会显著降低开发效率、增加调试难度,并导致后期维护成本飙升。解决这一问题的核心方法是:将经过验证的功能封装为可复用的库函数块(Library Function Block),供多个项目、多个POU(Program Organization Unit)统一调用。本文以符合IEC 61131-3标准的主流PLC平台(如Codesys、TIA Portal、Unity Pro)为背景,手把手演示如何从零创建一个真正可用、可移植、可维护的ST通用库。
一、明确复用目标:选对第一个函数块
不要一上来就封装“所有功能”。优先选择同时满足以下四个条件的逻辑:
- 高频使用:同一项目中被调用 ≥3 次,或跨 ≥2 个项目使用;
- 逻辑稳定:算法已验证无误,输入输出边界清晰,不依赖特定硬件地址;
- 接口简洁:输入参数 ≤5 个,输出参数 ≤3 个,无隐式状态依赖(如未声明的全局变量);
- 无平台锁死:不调用厂商私有指令(如
ADR()、MOVE_BLK等非标准扩展),仅使用IEC 61131-3标准函数(ABS()、SQRT()、TON()等)和基本运算符。
✅ 推荐首个实践对象:带使能控制与超时保护的延时启动函数块 FB_DelayStart
它替代了常见但易出错的手写延时逻辑(如IF StartBtn AND NOT Timer.Q THEN Timer(IN:=TRUE, PT:=t#5s); END_IF;),并内置防抖、超时失败反馈,可直接用于泵、风机、输送机等设备启停。
二、设计函数块接口:用变量名说话
在Codesys中新建一个Function Block类型POU,命名为FB_DelayStart。其接口必须严格遵循“输入即命令、输出即状态”原则,禁止在VAR_IN_OUT中存放中间变量。
FUNCTION_BLOCK FB_DelayStart
VAR_INPUT
bEnable : BOOL; // 使能信号:FALSE时立即复位所有内部状态
bStart : BOOL; // 启动请求:上升沿触发计时(自动消抖)
tDelay : TIME; // 目标延时时间(例:t#3s)
tTimeout: TIME := t#10s; // 超时阈值(默认10秒,超时则置位bTimeout)
END_VAR
VAR_OUTPUT
bRunning: BOOL; // 运行中:计时开始后为TRUE,完成或复位后为FALSE
bDone : BOOL; // 启动完成:仅在延时到达且bEnable仍为TRUE时置位1次脉冲
bTimeout: BOOL; // 超时失败:tDelay未到达但tTimeout已到,且bEnable仍为TRUE
nState : INT; // 状态码:0=闲置, 1=等待启动, 2=计时中, 3=完成, 4=超时
END_VAR
VAR
fbRTrig_Start : R_TRIG; // 内部上升沿检测(标准IEC函数块)
fbTON_Delay : TON; // 标准延时接通定时器
fbTON_Timeout : TON; // 超时监控定时器
bLastStart : BOOL; // 上次bStart值(用于边沿检测)
END_VAR
⚠️ 关键细节说明:
tTimeout设为:= t#10s是带默认值的可配置参数,调用时可覆盖(如Inst1(tTimeout:=t#15s)),增强复用性;nState输出为调试提供透明度,生产环境可删减,但开发期强烈建议保留;- 所有内部定时器(
fbTON_Delay,fbTON_Timeout)均为实例变量(非全局),确保多实例并发运行互不干扰; bLastStart是唯一需要手动管理的状态变量,用于实现无R_TRIG时的兼容逻辑(此处为演示完整性保留,实际推荐直接用标准R_TRIG)。
三、编写核心逻辑:ST代码零冗余
全部逻辑写在函数块主体中,不使用ANY指针、不访问全局DB、不调用非标库。代码按状态流组织,每行只做一件事:
// 1. 使能失效:强制复位所有状态
IF NOT bEnable THEN
fbRTrig_Start.CLK := FALSE;
fbTON_Delay.IN := FALSE;
fbTON_Timeout.IN := FALSE;
bRunning := FALSE;
bDone := FALSE;
bTimeout := FALSE;
nState := 0;
bLastStart := FALSE;
EXIT; // 立即退出,避免后续逻辑执行
END_IF;
// 2. 检测启动请求上升沿(消抖处理)
fbRTrig_Start(CLK := bStart);
IF fbRTrig_Start.Q THEN
// 清除超时定时器,启动主延时与超时监控
fbTON_Timeout.IN := TRUE;
fbTON_Timeout.PT := tTimeout;
fbTON_Delay.IN := TRUE;
fbTON_Delay.PT := tDelay;
nState := 1;
END_IF;
// 3. 主延时进行中
IF fbTON_Delay.Q THEN
bRunning := TRUE;
nState := 2;
// 延时完成:仅当bEnable仍有效时才确认成功
IF bEnable THEN
bDone := TRUE;
nState := 3;
END_IF;
END_IF;
// 4. 超时判定(主延时未完成,但超时定时器已到)
IF fbTON_Timeout.Q AND NOT fbTON_Delay.Q THEN
bTimeout := TRUE;
bRunning := FALSE;
nState := 4;
END_IF;
// 5. 状态清理:成功或超时后自动复位定时器,等待下次触发
IF bDone OR bTimeout THEN
fbTON_Delay.IN := FALSE;
fbTON_Timeout.IN := FALSE;
bRunning := FALSE;
END_IF;
✅ 验证要点:
EXIT语句确保使能关闭时彻底切断后续逻辑,避免状态残留;bDone使用布尔脉冲输出(由bEnable守卫),防止多次触发;- 所有定时器
.IN引脚均通过显式赋值控制,绝不用fbTON(PT:=...)省略.IN(否则无法手动复位); - 无
GOTO、无嵌套过深IF(最大2层),便于Code Review。
四、测试与验证:三步走确保健壮性
在Codesys中新建测试程序PRG_Test_DelayStart,实例化3个不同配置的FB_DelayStart:
PROGRAM PRG_Test_DelayStart
VAR
Test1 : FB_DelayStart(tDelay:=t#2s, tTimeout:=t#8s);
Test2 : FB_DelayStart(tDelay:=t#5s); // 使用默认tTimeout
Test3 : FB_DelayStart; // 全部使用默认值
iCycle: INT := 0;
END_VAR
// 模拟周期性测试信号(1Hz)
iCycle := iCycle + 1;
IF iCycle > 100 THEN iCycle := 0; END_IF;
// Test1:每10秒触发一次启动
Test1(bEnable := TRUE, bStart := (iCycle = 1));
// Test2:每20秒触发,延时5秒
Test2(bEnable := TRUE, bStart := (iCycle = 10));
// Test3:仅在使能为FALSE时测试复位行为
Test3(bEnable := (iCycle < 50), bStart := (iCycle = 20));
测试用例清单(必须全部通过):
| 测试场景 | 操作步骤 | 期望结果 |
|---|---|---|
| 正常启动 | bEnable=TRUE, bStart 单次上升沿 |
bDone 在 tDelay 后输出1个扫描周期脉冲,nState 经历 0→1→2→3 |
| 中途禁用 | bEnable 在计时期间变为 FALSE |
bRunning 立即为 FALSE,nState 回到 0,所有定时器 .Q 清零 |
| 超时触发 | bStart=TRUE 后 bEnable 保持 TRUE,但主延时未到而 tTimeout 到 |
bTimeout=TRUE,nState=4,bRunning=FALSE |
| 快速重触发 | bStart 连续两次上升沿间隔 < tDelay |
第二次触发覆盖第一次,计时重新开始(状态机自动处理) |
通过在线监控nState和各输出位,确认所有路径无遗漏。任一失败,立即回溯代码中的IF分支条件。
五、封装为库:导出可移植的.LIB文件
完成验证后,将该函数块及所有依赖(本例无额外依赖)打包为标准库:
- 右键点击
FB_DelayStart→ “导出为库…”; - 在导出向导中:
- 勾选 “包含所有依赖项”(即使当前无依赖,也养成习惯);
- 设置 “库名称” 为
Lib_MachineLogic(前缀Lib_标识库类型); - 取消勾选“导出为源代码”(选择二进制
.lib格式,保护知识产权且体积更小); - 点击“导出”,保存为
Lib_MachineLogic.lib。
生成的.lib文件是平台无关的二进制包,可在Codesys V3.5+、TIA Portal V17+(需安装Codesys兼容插件)、Unity Pro XLS等支持IEC 61131-3库标准的环境中直接导入。
六、在新项目中复用:3步接入,零学习成本
在另一个工程中复用该库:
- 项目树 → 库 → 右键“添加库…” → 选择
Lib_MachineLogic.lib; - 在POU中声明实例(无需
VAR_GLOBAL):PROGRAM PLC_PRG VAR PumpStart : Lib_MachineLogic.FB_DelayStart(tDelay:=t#4s); END_VAR - 在主循环中调用:
PumpStart( bEnable := Motor_Ready, bStart := CMD_Pump_Start, tDelay := t#4s // 此处覆盖默认值,体现灵活性 );
✅ 复用优势即时体现:
- 新增一台泵?复制粘贴2行代码,无需重写延时逻辑;
- 客户要求将延时从3秒改为5秒?仅修改调用处
tDelay参数,全项目批量替换; - 发现超时逻辑缺陷?只需更新库文件并重新导入,所有引用自动生效。
七、进阶策略:构建分层库体系
单个函数块只是起点。规模化复用需建立三层库结构:
| 层级 | 名称示例 | 内容特点 | 复用粒度 |
|---|---|---|---|
基础层 Lib_Basic |
FB_LimitCheck, FC_ClampReal, FB_EmergStop |
原子操作:限幅、钳位、急停链 | 单功能,无业务语义 |
设备层 Lib_Drives |
FB_VSD_Control, FB_Servo_Home |
设备驱动:变频器启停、伺服回原点 | 绑定硬件协议(Modbus/CanOpen),但接口标准化 |
工艺层 Lib_Process |
FB_FillControl, FB_MixSequence |
工艺单元:灌装控制、混合时序 | 封装完整工艺逻辑,输入为传感器信号,输出为执行器命令 |
📌 关键规则:
- 下层库不可引用上层库(避免循环依赖);
- 每个库单独导出为
.lib,命名含版本号(如Lib_Basic_v2.1.lib); - 使用
VERSION属性在库内声明兼容性(Codesys中右键库 → 属性 → 版本)。
八、避坑指南:90%工程师踩过的5个雷区
| 雷区 | 错误写法 | 正确做法 | 后果 |
|---|---|---|---|
| 全局变量污染 | VAR_GLOBAL 中声明 g_bMotorRun |
所有状态存于函数块 VAR 内部 |
多实例调用时相互覆盖,逻辑紊乱 |
| 硬编码地址 | Q0.0 := bDone; |
输出仅通过 bDone: BOOL 接口,由调用者连接物理地址 |
库无法移植到其他IO配置项目 |
| 忽略使能守卫 | fbTON(IN:=bStart, PT:=t#3s); |
所有执行逻辑前加 IF bEnable THEN ... END_IF |
使能断开时定时器继续计时,造成误动作 |
| 参数类型错误 | tDelay: INT(单位毫秒) |
tDelay: TIME(使用 t#3s 字面量) |
不同PLC平台INT范围不同,TIME类型保证语义一致 |
| 未处理浮点精度 | IF fValue > 0.001 THEN |
IF ABS(fValue) > 0.001 THEN(使用ABS()防负值漏判) |
浮点计算微小误差导致条件判断失效 |
九、持续演进:库的版本管理与文档
每次更新库后,必须同步三件事:
- 更新库内
VERSION属性(如从2.1升至2.2); - 在库描述字段填写变更日志(Codesys中右键库 → 属性 → 描述):
v2.2 (2024-06-15): - 修复FB_DelayStart在tTimeout=tDelay时状态卡滞问题 - 新增nState=5(手动复位)状态码 - 生成调用示例文档(纯文本,随库文件同目录存放):
Lib_MachineLogic_USAGE.txt内容示例:【调用范例】实现输送机延时启动(带超时): ConveyorStart( bEnable := Conveyor_Ready AND Safety_Gate_Closed, bStart := CMD_Conveyor_Start, tDelay := t#8s, tTimeout:= t#12s ); // 输出处理: IF ConveyorStart.bDone THEN Start_Conveyor_Motor(); END_IF; IF ConveyorStart.bTimeout THEN Raise_Alarm(105); END_IF;
文档不求华丽,但必须让新成员5分钟内看懂怎么用。
十、终极检验:库是否合格的3个问题
在发布任何库前,自问并确认:
- 能否在无网络、无调试器的离线PLC上稳定运行72小时?
→ 意味着代码无内存泄漏、无未初始化变量、无除零风险(检查所有/运算前加IF Denominator <> 0 THEN); - 能否被实习生在10分钟内学会调用?
→ 检查接口参数是否少于5个、命名是否见名知义(bStart而非Input1)、是否有调用示例; - 能否在TIA Portal和Codesys中不改一行代码直接复用?
→ 意味着100%使用IEC 61131-3标准语法,零私有指令,零硬件相关关键字(如%QX0.0)。
全部回答“是”,这个库才真正具备工业化复用价值。
将重复劳动压缩为一次高质量封装,把调试时间转化为知识沉淀——这才是电气自动化工程师的核心杠杆。

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