咱们搞工控的,多少都有点执念:有人写博途程序,从头到尾全是梯形图,看着继电器电路特别亲切;有人一上手就是SCL,觉得这才叫“高级编程”。我在S7-1200/1500上折腾了这么多年,越来越觉得,真正常用的原创程序,反而是LAD和SCL混着来的。今天就把我一套自用的双模式顺序控制程序摊开聊,从架构、代码到调试踩坑,能让你少走几周的弯路。
这篇东西适合刚把博途软件摸熟、想提升程序质量的新手,也适合正纠结“用什么语言写”的同行。你不用完全照搬我的程序,但里面关于语言分工、变量规划、接口设计、调试习惯的思路,是可以直接拿过去用的。
1. 为什么我要把SCL和LAD混着写——两种语言的选型思路
很多初学博途的朋友最爱问一个问题:“SCL和LAD到底哪个好?”我的回答一直是:看逻辑类型,别搞“信仰之争”。现场设备对程序的需求从来不是单一的,有简单直接的连锁,也有绕来绕去的数据处理,用一种语言硬扛,迟早会别扭。
1.1 LAD的优势与局限
梯形图这东西,本质是把电气控制柜里的硬接线映射到软件里。触点、线圈、置位、复位,一眼看过去就知道哪个条件驱动哪个输出,尤其是急停、安全门、互锁这类逻辑,LAD的可视化优势是无敌的。
但LAD的短板也很明显。稍微复杂一点的逻辑就开始“蜘蛛网化”:一长串网络、跳来跳去的母线连线,维护的人一多,谁改谁知道。更别说做数组循环、字符串处理、批量计算,LAD要硬写也能写,但那个复杂度,真的会让人想砸键盘。我这人有个原则:能用LAD直观看清楚的地方就用LAD,一旦逻辑里面出现“计数、遍历、条件分支很多”的情况,马上切SCL,绝不勉强。
1.2 SCL适合处理什么逻辑
SCL在我们这行,习惯上叫结构化控制语言,语法接近Pascal,博途还支持C风格的某些写法。它对数据类型敏感度比LAD高,但换来的是一等一的表达能力:循环、数组、多层嵌套、块操作,在SCL里都是几句话的事。
我习惯把SCL用在三个地方:第一是顺序控制步序,因为步数多、转移条件杂,用步号加CASE结构管理最清晰;第二是数据处理,比如模拟量滤波、多个产品的参数选择、统计计数;第三是故障处理逻辑,复杂的报警优先、互斥、批量复位,SCL写出来基本就是一张决策表,非常直观。
1.3 我的混搭原则与整体分工
说穿了就一句话:画得清楚的用LAD,算得清楚的用SCL。硬件信号进来的地方,比如物理按钮、传感器、接触器反馈,我用LAD做第一道处理;逻辑计算和流程控制,用SCL写功能块;最后要输出到物理点的时候,再用LAD做输出互锁和手动强制。这么一分工,电气维修人员看LAD能看懂动作流程,程序员看SCL能维护算法,谁都不会太难受。
这套方式也直接决定了我下面要讲的原创程序长什么样:一个SCL编写的主控功能块,加上外围几个LAD网络做保护和手动操作,组合起来一套完整的设备控制逻辑。
2. 原创程序架构拆解——一个双模式顺序控制FB的设计
做了这么多项目,我觉得最能体现混搭价值的是典型的双工位压装设备:有手动调试、有自动循环、有大量传感器反馈、有气缸和液压动作。这类设备不大不小,逻辑复杂度中等,正好把LAD和SCL的优势都调动起来。下面这个方案是我在几个类似项目里总结出来的模板,我给它起名叫“双模式顺序控制块”。
2.1 程序总体框架
整个程序放在一个1500 CPU里,OB1里调用一个主FC,主FC负责调用若干个FB。核心的自动流程封装在一个SCL写的FB里,我叫它FB_AutoSeq。手动控制和安全互锁直接用LAD写在FC_MAN里。这么做的好处是:自动逻辑和手动逻辑物理分开,故障互锁不依赖自动程序扫描顺序,安全性更高。
顶层调用关系大概是:
OB1 └─ FC_Main ├─ FC_Manual(LAD:手动操作、输出互锁) ├─ FB_AutoSeq(SCL:自动步序、报警管理) └─ FC_IO_Mapping(LAD:物理IO映射)OB1里我只是简单调用FC_Main,扫描周期短,逻辑块内部自己管理状态。有人喜欢把所有IO都放在FB接口上,我更喜欢先做一层IO映射,把实际IO地址和逻辑变量分开。原因很简单:如果接线改了,只改映射FC,不动逻辑块,不影响自动程序里的变量名。
2.2 数据块与变量规划
SCL和LAD混用的工程里,数据块规划是灵魂。我最怕看到有人把所有东西都塞到共享DB里,变量名还没规律,程序一多乱成一锅粥。
我的习惯是每个FB都带自己的背景数据块,背景DB里的变量分为三组:接口参数、静态变量、临时变量。接口参数里再细分输入、输出、输入输出。下面是我这个FB_AutoSeq的变量规划表:
| 分类 | 典型变量 | 说明 |
|---|---|---|
| VAR_INPUT | bStart、bStop、bReset | 启动、停止、复位按钮信号 |
| VAR_INPUT | bCylFwdDone、bCylBwdDone | 气缸到位反馈 |
| VAR_INPUT | bEmergencyStop、bSafetyDoor | 急停、安全门反馈 |
| VAR_OUTPUT | bCylFwdCmd、bCylBwdCmd | 气缸动作指令 |
| VAR_OUTPUT | bRunState、bAutoState | 运行状态指示 |
| VAR_OUTPUT | iCurStep、iAlarmCode | 当前步号、报警代码 |
| VAR_STATIC | iStep、tonDelay、bEdgeBuff | 步序、定时器、边沿缓存 |
接口参数我用的是BOOL、INT、TIME这些基础类型,尽量避免在接口上直接申明复杂结构体。因为LAD调用FB填实参时,复杂结构体在接口列表里显示起来不够直观,出问题不好查。真要传数组,我一般做成OPTIMIZED访问的DB,在SCL里直接按符号寻址。
2.3 LAD侧:硬件联锁与急停回路
这部分的思路和电工硬接线逻辑是一脉相承的。手动控制FC里,我把输出控制分成两类:快速动作和保持动作。
快速动作就是点动,比如按着手动按钮气缸前进,松手就停。这个在LAD里就是标准的触点串联线圈,没什么花样。保持动作就得多一层考虑:比如手动模式下要让某一路输出保持,必须用带记忆功能的置位复位,并且要确保复位条件覆盖所有可能的停止情况。
急停和安全联锁我在LAD里做了一个统一的“允许输出”网络:急停没触发、安全门关闭、模式不在手动挡位,这三个条件串联成一个中间变量bSafeOK。所有的输出线圈串上这个变量再输出。用LAD画这种网络的优点,是维修电工看到就能马上理解“为什么这台机器没动作”,顺着bSafeOK一查就定位了,不需要打开SCL去翻逻辑。
2.4 SCL侧:步序逻辑与故障处理
自动程序的核心在FB_AutoSeq里,我用一个INT变量iStep表示当前步号。每个步号对应设备的一个稳定状态,转移条件满足后步号切换到下一个。SCL里这玩意写起来非常舒服,分支条件一眼能看全。
CASE iStep OF 0: // 初始空闲 bCylFwdCmd := FALSE; bCylBwdCmd := FALSE; IF bStart THEN iStep := 10; END_IF; 10: // 气缸前进 bCylFwdCmd := TRUE; IF bCylFwdDone THEN tonDelay(IN := TRUE, PT := tWaitTime); IF tonDelay.Q THEN iStep := 20; END_IF; END_IF; 20: // 保压完成,气缸后退 bCylFwdCmd := FALSE; bCylBwdCmd := TRUE; IF bCylBwdDone THEN iStep := 0; bRunState := FALSE; END_IF; ELSE iStep := 0; END_CASE;这段代码的关键是:每个步号的进入和退出条件都非常明确,动作指令是临时输出的组合,而步序状态是唯一的记忆变量。调这种程序,你看iStep就知道了设备走到哪了。故障处理我也放到这个FB里写:比如定位超时、夹紧无压力、某一步反馈错乱,统一在一个定时器窗口内判断,超时就把bFault置位,同时把iAlarmCode写入对应的步号,然后强制步序号回初始。这样报警处理就是SCL里几行IF语句的事,不需要在LAD里拉几条母线。
3. 原创SCL代码解析与博途实操要点
上一节给了骨架,这一节我把细节打满。很多人SCL卡壳,不是看不懂语法,而是不知道怎么把语法用到正确的位置。我结合这套程序讲几个关键点,都是实际调试中反复验证过的做法。
3.1 核心SCL代码分段讲解
边沿检测和启动联锁
在自动程序里,启动按钮不能是简单的LEVEL信号,必须是上升沿,否则操作员按着不放,程序就会反复启动。SCL里做上升沿不复杂,但要记得在静态变量里声明一个缓存BOOL。我的习惯是在OB系列里写一个统一的边沿采集,借着FB的常规输入接口把边沿块实例传进去。实际代码类似:
IF bStart AND NOT bEdgeBuff THEN bEdgeBuff := TRUE; bAutoState := TRUE; END_IF; IF NOT bStart THEN bEdgeBuff := FALSE; END_IF;用bEdgeBuff记录上一次扫描周期的值,当前是上升沿才触发。这个写法比用R_TRIG实例更直观,也更方便在程序里同时做多个条件。真正复杂的大型项目建议用R_TRIG标准块,但中小型设备这样写完全够用。
定时器在SCL里的用法
SCL的定时器,我的做法是每次都实例化一个定时器块,然后用TON的方式调用。上面代码里已经演示了,关键是定时器复位条件。很多人定时器不复位,出问题就表现为“第二次循环的时候定时器直接通了”。标准套路是:定时器输入条件断开的时候,要把定时器块重新初始化。我在步序切换之前会统一把这一步的定时器清零:
IF iStep <> 10 THEN tonDelay(IN := FALSE, PT := tWaitTime); END_IF;这部分逻辑看着不起眼,但现场设备“卡住不动”的案例里,十个有六七个是定时器没复位导致的。
3.2 LAD调用SCL功能块的接口注意事项
SCL的FB写完了,最后还得拉到LAD里调用。这里有个很容易踩的坑:FB的接口变量命名和调用窗口显示名。博途里FB的VAR_INPUT、VAR_OUTPUT,在LAD调用界面是直接显示参数名的,所以名字一定要起得自然。我见过有人起I0_0这种名字,结果填实参的时候自己都不知道该填哪个。我给自己定了条规矩:接口变量名必须一问就知道是干什么的。bStart、bReset这种算合格,x、y这种直接删掉重写。
调用时的实参,我建议用符号名,不要用绝对地址%I0.0。符号名在LAD里显示成“块名变量名”,维护的时候能直接看到逻辑意思。绝对地址只有在IO映射FC里才用,因为那本来就是干地址对接的活。
还有一个细节:LAD调用SCL功能块,INOUT和OUT的区别要分清楚。OUT类型在LAD调用时,实参位置显示为浅色,可以填变量也可以填DB地址;INOUT一填,就直接带地址引用了,不能瞎填。写SCL块的时候我尽量少用INOUT,能拆成单独的IN和OUT就拆。INOUT多了以后,调用关系复杂了,调试想强制某个输出特别麻烦。
3.3 调试技巧:步序监视与强制变量
程序下载进去,不能光看灯,得学会用博途的监视功能。我的调试习惯是三步走:
第一步,看FB的背景DB里iStep的值。博途打开背景DB,监视模式下直接看INT变量等于几,对应设备现在应该执行到哪个动作。如果步号乱跳,就去查转移条件。
第二步,看LAD里那个bSafeOK输出线圈。设备不动作,先问安全联锁通了没有。这个线圈是LAD侧的统一闸门,它不亮后面全免谈。
第三步,用“强制”功能做单步测试。注意,强制是在CPU侧强制变量值,不是改程序下载。我在自动状态跑不起来的时候,会把bCylFwdDone强制成1,模拟气缸到位信号,观察步序是否继续向后走。这样排除了传感器接线问题后,程序逻辑对不对马上就暴露了。
这里要提醒一句:强制变量的操作要非常小心,尤其设备连接着实际输出的时候。强制必须逐个变量来,用完立刻取消强制。我都是把强制表先清空,再填新的,避免一堆强制值留在CPU里,重启之后逻辑突然莫名其妙。
4. 实际调试中踩过的坑与排查速查
程序跑不起来的原因千奇百怪,但大部分坑是可以总结成规律的。我把这几年调设备过程中最容易出问题的点整理了一下,算是给这份原创程序做个配套速查。
4.1 典型问题清单
| 现象 | 根因 | 排查与解法 |
|---|---|---|
| 自动启动没反应 | 启动信号被安全联锁挡住 | 查bSafeOK,查急停回路与模式切换 |
| 步序卡在某一站不动 | 定时器未复位或到位信号未到达 | 查该步骤的转移条件,强制反馈信号看是否跳步 |
| 气缸动作输出跟手不一致 | LAD输出互锁与SCL指令冲突 | 查输出线圈是否串入了反向互锁条件 |
| 某个报警总是不复位 | 故障标志位被重复置1 | 查故障复位条件是否包含了所有置位路径 |
| 程序偶尔不执行某段 | 变量类型隐式转换导致比较失败 | 检查INT与REAL、TIME与DINT的赋值位置 |
| 下载后设备位置丢失 | 背景DB未设置掉电保持 | 优化访问时勾选“在重新启动时保持” |
| LAD调用SCL时报错 | 实参类型与FB接口不匹配 | 回到FB接口检查方向是IN还是OUT,类型精确对应 |
| 强制变量后程序失控 | 强制表残留未取消 | 清空强制表,断电重启CPU后重新下载 |
这里面我想重点说两个,一个是隐式转换。SCL对类型要求比LAD严格,但也不是完全不让你转。REAL和INT混用的时候,经常出现“比较不出来”的怪问题。我吃过一次亏:有个温度值模拟量缩放后是REAL,我用它和一个INT的设定值做比较,结果90度时就是不触发。后来把比较两端都转成REAL才正常。所以SCL里我养成了一个习惯:所有参与计算的量,要么明确定义成REAL,要么明确转成REAL,不靠编译器默认转换。
另一个是下载后设备位置丢失。这个在S7-1200/1500里特别坑:如果背景DB没设置掉电保持,那每次重新上电,iStep都会回到初值。设备以为自己刚启动,实际上执行机构已经在加工位了。要么步序归零前做一次“回安全位”的动作,要么就把步序相关的变量设成保持。两个方案我倾向后者,因为回安全位不一定每次都条件允许。
4.2 维护阶段的经验心得
程序交付以后才是考验的开始。维护工程师换了一拨又一拨,真正能撑住场子的不是华丽的算法,而是注释和结构。
我写SCL块的时候,每一段CASE分支都会写一行注释说明当前步号的设备状态,比如“步10:夹爪前进,等待到位”。LAD网络标题也全部填上,网络注释里写上“改了互锁逻辑的人必须同时改注释”。这些东西占不了多少时间,但能省下大量现场电话。
我个人做程序还有个习惯:每个FB第一屏写版本号和修改记录。不要小看这个,设备用了五年之后,回来看程序的人首先看的就是修改记录,否则根本不知道哪个逻辑是被谁临时加上的。
这个原创程序我还在持续扩展,比如后面准备把配方管理和数据记录、报表生成加进去。但核心思路一直没变:安全的联锁交给LAD,清晰的流程逻辑交给SCL,两者的接口和数据块一定要规划好。这样一套程序下来,不管设备多复杂,维护起来都能有个清晰的抓手。