文章目录

ST代码复用策略:创建通用库函数块(Library)的步骤

发布于 2026-03-18 18:31:52 · 浏览 8 次 · 评论 0 条

在电气自动化系统中,使用结构化文本(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 单次上升沿 bDonetDelay 后输出1个扫描周期脉冲,nState 经历 0→1→2→3
中途禁用 bEnable 在计时期间变为 FALSE bRunning 立即为 FALSEnState 回到 0,所有定时器 .Q 清零
超时触发 bStart=TRUEbEnable 保持 TRUE,但主延时未到而 tTimeout bTimeout=TRUEnState=4bRunning=FALSE
快速重触发 bStart 连续两次上升沿间隔 < tDelay 第二次触发覆盖第一次,计时重新开始(状态机自动处理)

通过在线监控nState和各输出位,确认所有路径无遗漏。任一失败,立即回溯代码中的IF分支条件。


五、封装为库:导出可移植的.LIB文件

完成验证后,将该函数块及所有依赖(本例无额外依赖)打包为标准库:

  1. 右键点击 FB_DelayStart → “导出为库…”
  2. 在导出向导中:
    • 勾选 “包含所有依赖项”(即使当前无依赖,也养成习惯);
    • 设置 “库名称”Lib_MachineLogic(前缀Lib_标识库类型);
    • 取消勾选“导出为源代码”(选择二进制.lib格式,保护知识产权且体积更小);
    • 点击“导出”,保存为 Lib_MachineLogic.lib

生成的.lib文件是平台无关的二进制包,可在Codesys V3.5+、TIA Portal V17+(需安装Codesys兼容插件)、Unity Pro XLS等支持IEC 61131-3库标准的环境中直接导入。


六、在新项目中复用:3步接入,零学习成本

在另一个工程中复用该库:

  1. 项目树 → 库 → 右键“添加库…” → 选择 Lib_MachineLogic.lib
  2. 在POU中声明实例(无需VAR_GLOBAL):
    PROGRAM PLC_PRG
    VAR
        PumpStart : Lib_MachineLogic.FB_DelayStart(tDelay:=t#4s);
    END_VAR
  3. 在主循环中调用
    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()防负值漏判) 浮点计算微小误差导致条件判断失效

九、持续演进:库的版本管理与文档

每次更新库后,必须同步三件事:

  1. 更新库内VERSION属性(如从2.1升至2.2);
  2. 在库描述字段填写变更日志(Codesys中右键库 → 属性 → 描述):
    v2.2 (2024-06-15): 
    - 修复FB_DelayStart在tTimeout=tDelay时状态卡滞问题
    - 新增nState=5(手动复位)状态码
  3. 生成调用示例文档(纯文本,随库文件同目录存放):
    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个问题

在发布任何库前,自问并确认:

  1. 能否在无网络、无调试器的离线PLC上稳定运行72小时?
    → 意味着代码无内存泄漏、无未初始化变量、无除零风险(检查所有/运算前加IF Denominator <> 0 THEN);
  2. 能否被实习生在10分钟内学会调用?
    → 检查接口参数是否少于5个、命名是否见名知义(bStart而非Input1)、是否有调用示例;
  3. 能否在TIA Portal和Codesys中不改一行代码直接复用?
    → 意味着100%使用IEC 61131-3标准语法,零私有指令,零硬件相关关键字(如%QX0.0)。

全部回答“是”,这个库才真正具备工业化复用价值。


将重复劳动压缩为一次高质量封装,把调试时间转化为知识沉淀——这才是电气自动化工程师的核心杠杆。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文