简介:这份资源面向工业自动化领域的PLC程序员与电气工程师,聚焦SCL结构化文本的标准化编程实践,围绕多功能阀门控制这一典型场景,解决电磁阀、气动阀、电动阀等多种驱动类型阀体控制逻辑重复、接口不统一、代码复用性差的问题,适合已具备一定西门子PLC基础、希望提升程序规范性的进阶学习者。资源包共1个文件,为docx格式技术文档,体积约101KB,篇幅精炼却结构完整。文档以SFC_SCL_Vavle函数为主线,先梳理阀门驱动类型、控制开关位置与方式、状态输入点及控制输出点等基础概念,再逐项讲解函数接口设计,包括输入参数io_STW状态字、io_CTW控制字、模式控制字i_Mode与输出状态字o_Status,并给出本地变量与临时变量的使用约定。核心的函数逻辑部分按LCC信号、MCC信号、阀体信号、开关到位延时、开关超时、综合故障、综合报警、故障复位、开关就绪等模块展开,呈现了一套可直接借鉴的标准化封装思路。目前已有427人学习,读者可借此掌握统一接口下的阀门控制模板,减少重复编码与出错概率,提升项目交付效率与代码可维护性。
1. 阀门逻辑为什么要做成SCL标准化函数
一个常见场景:水处理站两条产线八十多台气动阀,原来的程序里每台阀门都是二十几个梯形图网络,开阀、关阀、到位检测、超时报警、互锁,逐条重写一遍。改一个动作延时,要顺着交叉引用改几十处;换人接手,同一个信号有的叫 Y1,有的叫 V01_Open,拿着图纸对线对到半夜。
PLC 标准化编程要解决的就是这件事:把一台阀门的逻辑收敛成一个 SCL 功能块,指令、反馈、参数用 UDT 描述,动作流程用状态机描述,对外只留一组固定接口。现场要改的是参数,不是程序结构。多功能阀门函数的“多功能”不是把各种阀门类型堆进一个 FB,而是同一个 FB 通过一个类型字段去适配双作用开关阀、单作用气缸阀、脉冲阀这几类常见对象。
这套做法对三类人收益最明显:阀门数量上双的非标设备程序员、需要长期交接维护的厂务电气团队,以及正在博途里把 SCL 和梯形图混着用的工程师。
2. 用SCL写阀门函数的边界与接口约定
2.1 梯形图和SCL在阀门逻辑上的分工
梯形图的强项是位逻辑直观、电气人员能在线看懂、安全链改动少;SCL 的强项是 CASE 状态机、FOR 批量、结构体运算、报警文本拼接。阀门逻辑的本质是一段带时间的状态迁移:指令发出、等待到位反馈、超时报故障、复位回到初始。这段用梯形图写要 SET/RESET 一堆中间位,隔两周自己都要重新捋;用 SCL 的 CASE 写,看一眼就知道当前在哪一步。
数量也是分界线。三五台阀门,梯形图完全够用;上到十几台、几十台,需要同一个 FB 反复调用、参数从数组里取,只有 SCL 能做到干净利落。这就是为什么“PLC 控制 32 台变频器程序设计”这类题目最后都会落到实例数组加循环调用上,阀门同理。
边界要划清楚。安全相关的最终切断、急停回路、安全阀输出,我一般留在梯形图或安全程序里做,SCL 算出来的结果只负责驱动这些线圈。混编的常见形式有两种:SCL 写的 FB 在 LAD 网络里被调用;或者在 SCL FB 内部调用已有的 LAD FC。两种都行,关键是别让一个输出点被两处逻辑同时写。
2.2 多功能阀门函数的接口与命名规范
接口一旦定下来就要当合同用,后面几十台阀门都依赖它。我的习惯是前缀分类,方向一眼能认出来。
| 参数方向 | 前缀 | 数据类型 | 示例 |
|---|---|---|---|
| 指令输入 | Cmd_ | Bool | Cmd_Open |
| 现场反馈 | Fbk_ | Bool | Fbk_OpenLimit |
| 输出驱动 | Out_ | Bool | Out_Open |
| 时间参数 | Par_T | Time | Par_TmOut |
| 状态字 | Sts_ | Int / Bool | Sts_State |
| 报警 | Alm_ | Bool | Alm_Timeout |
命名规范里还有几条硬约束:UDT 统一UDT_前缀,功能块统一FB_前缀,背景数据块统一DB_前缀;同一个工程内不允许出现两种叫法,比如既有Fbk_Open又有Open_Feedback。下标统一从 1 开始,别一半从 0 一半从 1,批量循环时下标越界几乎都是这个原因造成的。
2.3 先定需求再写代码:阀门函数的最小需求清单
很多人写阀门 FB 是从代码开始的,写到一半发现漏了复位逻辑、漏了就地模式,回头改接口,所有调用点全部报错。顺序应该是先列需求,再定接口,最后写实现。
| 需求 | 说明 | 是否可裁剪 |
|---|---|---|
| 开 / 关指令 | 手动或自动的综合结果 | 必须 |
| 动作超时监视 | 指令发出后限时等反馈 | 必须 |
| 反馈丢失报警 | 与超时分开,便于区分故障 | 必须 |
| 独立复位沿 | 不能用持续电平复位 | 必须 |
| 双反馈互斥监视 | 开到位与关到位同时为 1 | 可选 |
| 就地 / 远程模式 | 就地优先于远程 | 可选 |
| 运行时间累计 | 用于维护提醒 | 可选 |
接口骨架大致长这样,后面所有实现都基于它。
FUNCTION_BLOCK "FB_Valve" VAR_INPUT En : Bool; // 使能,整台阀门的投入/退出 Cmd_Open : Bool; // 开阀指令(手动或自动综合后) Cmd_Close : Bool; // 关阀指令 Cmd_Reset : Bool; // 报警复位,内部取上升沿 Fbk_Open : Bool; // 开到位反馈 Fbk_Close : Bool; // 关到位反馈 Fbk_Fault : Bool; // 气源/电机故障反馈 Par_TmOut : Time := T#20S; // 动作超时时间 Par_PlsLen : Time := T#0S; // 脉冲阀输出宽度,0 表示持续输出 END_VAR VAR_OUTPUT Out_Open : Bool; Out_Close : Bool; Sts_State : Int; // 对外暴露状态码,便于上位机监视 Alm_Timeout : Bool; Alm_Feedback: Bool; END_VAR VAR Rst : R_TRIG; // 复位沿检测 Ton_Act : TON; // 动作超时定时器 Timer_En : Bool; END_VAR这段声明的逻辑说明:所有现场信号进VAR_INPUT,所有去驱动的信号进VAR_OUTPUT,中间状态和定时器放VAR。参数说明上,Par_TmOut给默认值 T#20S 只是为了让单点测试能跑起来,实际投运必须按阀门实测动作时间重设;Par_PlsLen是给脉冲阀预留的,普通开关阀保持 0 即可,实现里用 0 作为“持续输出”的判断条件。
提示:接口里不要出现任何全局 DB 名字。一旦 FB 内部直接引用全局变量,这个函数就不能被复用,标准化就断了。
3. 多功能阀门函数的UDT与状态机设计
3.1 用UDT把阀门的指令、反馈、参数装进一个结构
UDT 怎么用 SCL 这件事,核心就一句话:UDT 定义“一台阀门有哪些数据”,FB 定义“这些数据怎么变化”。两台以上阀门用数组装 UDT,就能批量处理。
TYPE "UDT_Valve" VERSION : 0.1 STRUCT En : Bool; // 投入 Cmd_Open : Bool; Cmd_Close : Bool; Cmd_Reset : Bool; Fbk_Open : Bool; Fbk_Close : Bool; Fbk_Fault : Bool; Par_TmOut : Time := T#20S; Par_PlsLen : Time := T#0S; Valve_Type : Int := 0; // 0=双作用 1=单作用 2=脉冲阀 Out_Open : Bool; Out_Close : Bool; Sts_State : Int; Alm_Timeout : Bool; Alm_Feedback : Bool; RunTime_s : DInt; // 累计运行秒数 END_STRUCT; END_TYPE结构说明:把这个 UDT 作为 FB 的VAR_IN_OUT参数,调用时传"DB_ValveCfg"[i]这样的数组元素,FB 内部改的就是这份数据本身,不需要额外的输出映射。参数说明上,Valve_Type决定反馈判断方式,双作用阀要求开到位和关到位都有信号,单作用阀只有弹簧复位反馈,脉冲阀靠输出宽度控制;RunTime_s是维护用的累计量,动作期间每扫描周期加一个扫描周期时间即可,不必精确到毫秒。
注意:UDT 里带初值会让每个实例占用额外的工作存储区。几十个实例叠加后,建议在博途的资源分配视图里核对一次 PLC 资源使用情况,别等到下载时才发现存储区吃紧。
3.2 阀门状态机的状态划分与迁移条件
状态码不要用 1、2、3 连号,用十位间隔,中间要插状态时不用重排。这是我踩过坑之后的固定习惯。
| 状态码 | 名称 | 输出 | 迁移条件 |
|---|---|---|---|
| 0 | 空闲 / 关闭 | 全 0 | En 且 Cmd_Open → 10 |
| 10 | 开阀中 | Out_Open=1 | Fbk_Open → 11;超时或 Fbk_Fault → 90 |
| 11 | 已开到位 | 全 0 | Cmd_Close → 20 |
| 20 | 关阀中 | Out_Close=1 | Fbk_Close → 21;超时或 Fbk_Fault → 90 |
| 21 | 已关到位 | 全 0 | Cmd_Open → 10 |
| 30 | 脉冲输出中 | Out_Open 脉冲 | 计时到 → 11 |
| 90 | 故障锁定 | 全 0 | Cmd_Reset 上升沿 → 0 |
迁移条件里有两条容易写错。第一,进入 90 之后不要再自动回 0,必须等复位沿,否则一个抖动的反馈信号会让状态在 10 和 90 之间反复横跳,上位机看到的状态码就开始闪。第二,11 和 21 之间来回切换只由指令驱动,不要让反馈信号参与,反馈只用于确认到达。
3.3 手动、自动与故障模式的优先级处理
优先级不写清楚,现场调试一定会出现“自动在开、手动手柄又在关”的扯皮。我一般按这个顺序在 FB 入口处先归算一次指令:故障 > 就地 > 手动 > 自动。归算的结果再送进状态机,状态机内部不再判断来源。
具体做法是在 CASE 之前加一小段归算逻辑,把各来源的请求合成一个Req_Open和一个Req_Close,两者同时为真时以关闭优先,这是阀门类设备的通用安全约定。就地模式通常用一个面板上的两位开关,接进来占一个 Bool,就地有效时自动指令被屏蔽,但超时监视和报警仍然生效,不能因为切到就地就把监视丢了。
模式切换的瞬间要处理沿。手动切自动时如果指令状态刚好相反,会出现一次多余的开关动作。常见的处理办法是记录上一次的模式值,检测到模式变化时把状态机强制回到 0,让它按新指令重新走一次。这套逻辑写进 FB 之后,八十台阀门的行为完全一致,现场排查只需要看状态码。
4. 用SCL实现多功能阀门功能块
4.1 FB接口定义与多重实例的调用方式
接口用VAR_IN_OUT接 UDT,内部只放定时器和沿检测这类必须按实例独立保存的元件。多重实例的写法是把这个 FB 声明成另一个 FB 的静态变量,这样背景数据块跟着上层 FB 走,不会在项目里散落几十个 DB。
FUNCTION_BLOCK "FB_Valve_Multi" VAR_IN_OUT Cfg : "UDT_Valve"; // 一台阀门的全部数据 END_VAR VAR Rst : R_TRIG; Ton_Act : TON; Timer_En : Bool; Tm_Use : Time; END_VAR BEGIN // 1. 复位沿与定时器统一调用,避免在 CASE 分支里调用 TON #Rst(CLK := #Cfg.Cmd_Reset); #Tm_Use := #Cfg.Par_TmOut; #Ton_Act(IN := #Timer_En, PT := #Tm_Use); // 2. 状态机主体 CASE #Cfg.Sts_State OF 0: #Cfg.Out_Open := FALSE; #Cfg.Out_Close := FALSE; #Cfg.Alm_Timeout := FALSE; #Timer_En := FALSE; IF #Cfg.En THEN IF #Cfg.Cmd_Open THEN #Cfg.Sts_State := 10; ELSIF #Cfg.Cmd_Close THEN #Cfg.Sts_State := 20; END_IF; END_IF; 10: // 开阀中 #Cfg.Out_Open := TRUE; #Cfg.Out_Close := FALSE; #Timer_En := TRUE; IF #Cfg.Fbk_Open THEN #Cfg.Sts_State := 11; ELSIF #Ton_Act.Q OR #Cfg.Fbk_Fault THEN #Cfg.Alm_Timeout := TRUE; #Cfg.Sts_State := 90; END_IF; 11: // 已开到位 #Cfg.Out_Open := FALSE; #Cfg.Out_Close := FALSE; #Timer_En := FALSE; IF #Cfg.Cmd_Close THEN #Cfg.Sts_State := 20; END_IF; 20: // 关阀中 #Cfg.Out_Open := FALSE; #Cfg.Out_Close := TRUE; #Timer_En := TRUE; IF #Cfg.Fbk_Close THEN #Cfg.Sts_State := 21; ELSIF #Ton_Act.Q OR #Cfg.Fbk_Fault THEN #Cfg.Alm_Timeout := TRUE; #Cfg.Sts_State := 90; END_IF; 21: // 已关到位 #Cfg.Out_Open := FALSE; #Cfg.Out_Close := FALSE; #Timer_En := FALSE; IF #Cfg.Cmd_Open THEN #Cfg.Sts_State := 10; END_IF; 90: // 故障锁定 #Cfg.Out_Open := FALSE; #Cfg.Out_Close := FALSE; #Timer_En := FALSE; IF #Rst.Q THEN #Cfg.Alm_Timeout := FALSE; #Cfg.Sts_State := 0; END_IF; END_CASE; END_FUNCTION_BLOCK逻辑说明:定时器在 CASE 之前统一调用一次,这是 IEC 定时器的硬性要求,写在 CASE 分支里会出现某周期不执行、计时被拉长,或者在多个分支里各调一次、计时被清零。Timer_En作为统一的使能变量,进入动作状态置真,到达或故障时置假。参数说明:Par_TmOut真正生效的位置是#Tm_Use,这样做是为了后续要按阀门类型取不同超时值时,只改这一行即可。
4.2 参数表与常见调参场景
| 参数 | 建议默认值 | 作用 | 调整依据 |
|---|---|---|---|
| Par_TmOut | T#20S | 指令到反馈的最长等待 | 实测动作时间 × 2 |
| Par_PlsLen | T#0S | 脉冲阀输出宽度 | 电磁阀最小动作时间加两成 |
| Valve_Type | 0 | 反馈判断方式 | 按执行机构形式选 |
| RunTime_s 累加周期 | 1s | 维护提醒 | 无特殊要求 |
| 双反馈互斥延时 | T#2S | 防抖 | 反馈信号抖动时加长 |
最常见的调参场景是超时值。默认 20 秒对气动球阀基本够,但大口径蝶阀冬天动作慢,实测可能到 35 秒,这时候把超时设成 70 秒比设成 40 秒更安全,因为超时报警的意义是“反馈线路断了或者气源没了”,不是“动作慢了一点”。反过来,如果这台阀门参与的是节拍设备,超时值要收紧到略大于实测值,让故障尽早暴露。
另一个场景是脉冲阀。Par_PlsLen设 0 时输出是持续电平,设成 T#1S 时输出一个宽度固定的脉冲。实现上只需要在状态 10 里加一个计时分支,计时到就跳到 11,不管反馈有没有回来。
5. 批量实例化与现场调试
5.1 用数组和FOR循环管理几十台阀门
阀门数量上来之后,逐个调用实例是维护灾难。把实例声明成数组,配置数据也放数组,循环调用。
FUNCTION_BLOCK "FB_Valve_Group" VAR Valve : ARRAY[1..32] OF "FB_Valve_Multi"; // 32 台阀门的多重实例 i : Int; END_VAR BEGIN FOR #i := 1 TO 32 DO #Valve[#i](Cfg := "DB_ValveCfg".Valve[#i]); END_FOR; END_FUNCTION_BLOCK逻辑说明:循环体只做一件事,把第 i 号配置结构体和第 i 号实例配对。参数说明:数组上下界必须和DB_ValveCfg里数组的上下界完全一致,不一致时博途不一定报错,但会出现某几台阀门永远不动,排查时先在下标上找原因。32 个实例的工作存储占用并不大,真正的开销在配置结构体的时间量字段上,需要时可以在资源视图里核对一遍。
这种写法和批量处理通讯从站是同一个思路:把差异放进数组,把共性放进功能块。设备数从 32 台变成 48 台,只需要改数组上下界和循环终值,逻辑一行不用动。
5.2 在线调试与典型故障对照
调试期最有价值的动作是同时监视状态码和反馈信号,光看输出点线圈没用。我一般把Sts_State和Fbk_Open、Fbk_Close放到同一张监视表里,触发一次动作,看它走了哪些状态。
| 现象 | 可能原因 | 排查点 |
|---|---|---|
| 输出一闪即断 | 反馈信号抖动 | 监视状态码是否在 10 与 90 之间跳 |
| 停在状态 10 不动 | 反馈未接线或类型选错 | 核对 Valve_Type 与接线 |
| 报警无法复位 | 复位写成电平而非上升沿 | 检查 R_TRIG 是否被重复实例 |
| 状态跳变无规律 | 同一输出点被两处写 | 用交叉引用查 Out_Open |
| 批量中某几台不动 | 数组下标越界或配置未初始化 | 在线表格查看对应下标 |
最后一个技巧:调试期把Par_TmOut临时放大到实际值的五倍,让阀门有充足时间走完行程,此时报警只可能来自真正的接线问题,而不是动作慢;等到单机联动都通过,再按实测值收紧到两倍。这样能把“接线错误”和“动作偏慢”两类问题彻底分开,避免在现场一遍遍改参数猜原因。
本文还有配套的精品资源,点击获取