1. 为什么混编而不是二选一:梯形图和ST各自的边界
1.1 梯形图的舒适区其实很窄
梯形图是PLC最古老也最普及的编程语言,本质是继电器控制回路的图形化表达。接触器、继电器怎么接,梯形图就怎么画:左母线是火线,右母线是零线,常开常闭触点串联并联,最后驱动线圈。老一辈电气工程师看梯形图就像看电气原理图,几乎不需要额外培训。
这种图形化特性决定了梯形图的优势场景非常集中:正反转互锁、星三角启动、安全门联锁、急停回路、气缸到位检测、电机启停控制这类“触点逻辑密集”的程序,梯形图是当之无愧的第一选择。一个完整的电机正反转回路,梯形图十几行就画完了,而且现场电工肉眼就能排查哪里有故障。
但梯形图的短板也同样明显。只要逻辑里出现“循环”“数组”“批量计算”“复杂公式”,梯形图就开始露出丑态。我印象最深的是几年前做一台老设备改造,客户要求在触摸屏上统计当日产量、良品率、设备OEE,还要按班次存到数组里方便查询。用梯形图写这套数据统计,我写了将近三百行,全是MOV、ADD、CMP、INC的堆叠,光数组下标的管理就用了七八个变址寄存器,改一个统计口径要翻半天程序在线查找触点。那个过程非常痛苦,调完以后我自己都不太想去维护它。
1.2 ST语言的两面性
ST语言(结构化文本)是IEC 61131-3标准里最接近高级编程语言的一种PLC语言。它的语法类似Pascal或者C语言,支持变量声明、IF/ELSE分支、FOR/WHILE循环、CASE状态机、数组和结构体。凡是你觉得梯形图写起来别扭的逻辑,换到ST里往往几行就搞定。
举一个非常普遍的例子:S7-1200里把十进制转十六进制的拆分、字节拼接、字符串拼装这类数据处理程序,用梯形图写要一个字节一个字节地拆出来,然后用MOV指令拼到目标寄存器里,既啰嗦又容易错。用SCL(西门子对ST的实现)写,就是两三句赋值语句的事。这也是为什么很多西门子工程师在做配方管理、数据块搬运、通信协议解析时都会转向SCL。
但ST有个致命问题:它不直观。梯形图里一个触点从上到下扫一眼就知道回路通不通,而ST的IF条件嵌在三行代码里,要靠在线监视才能确认当前走了哪个分支。纯ST项目交付以后,客户电气维修人员往往不敢下手。我之前接过一个外包维护的项目,前一家公司全套程序用ST写的,设备一出报警,业主的电工只能干瞪眼,必须打电话找原开发公司远程处理。这绝对不是代码水平的问题,而是ST这种文本形式天然就不适合现场快速排查。
1.3 混编的本质是接口管理
“混编”这两个字容易让人误解,以为是把ST代码和梯形图画在同一个屏幕里,实际上不是这样。IEC 61131-3标准本身支持多种语言共存,工程里既可以有梯形图程序块,也可以有ST程序块,它们之间的协作靠的是“程序组织单元(POU)”和“变量接口”。
换句话说,混编的核心不是语言本身,而是接口管理。一个ST功能块对外暴露哪些输入变量、哪些输出变量,内部逻辑怎么实现,梯形图侧完全不需要关心;反过来,ST主控程序调用一个梯形图子程序时,只需要给它需要的状态和使能信号,梯形图内部怎么联锁也不关ST的事。
基于这个思路,我在实际项目里总结出了三种主流混编写法,分别适用于不同的控制系统架构和团队配置,后面一个个展开讲。
2. 写法一:梯形图作主程序,ST功能块作算法黑盒
2.1 适用场景和整体思路
这是最稳妥、最容易向客户解释的混编方式,适合设备逻辑以联锁控制为主、算法数据量为辅的项目。整体结构是:梯形图写主程序,负责设备启停、互锁、报警、手动自动切换;所有需要计算的东西——数据滤波、平均值、数组排序、流量累积、校验码计算——全部封装成ST功能块,在梯形图里通过一个“功能块调用”指令即可使用。
这种写法的最大好处是维护边界清晰:懂梯形图的人看主程序就能明白设备动作流程,ST功能块像一颗颗螺丝钉插在梯形图框架上。功能块内部写错了,只需要打开对应的ST功能块修改,不影响其他页面。
2.2 以Codesys生态为例的完整实操步骤
我平时用得最多的平台是Codesys以及基于Codesys二次开发的国产PLC,比如信捷XD系列、汇川H系列,操作逻辑基本一致。下面以一个“十通道温度采集求平均值并找最大值”的功能块为例,把步骤完整走一遍。
第一步,建立ST功能块。在工程树里右键选择“添加对象→功能块”,语言类型选ST。然后定义接口变量:
FUNCTION_BLOCK FB_ArrayCalc VAR_INPUT bEnable : BOOL; // 允许计算 arrRawData : ARRAY[0..9] OF REAL; // 十通道原始数据 END_VAR VAR_OUTPUT rAverage : REAL; // 平均值 rMaxValue : REAL; // 最大值 bValid : BOOL; // 数据有效标志 END_VAR VAR i : INT; // 循环计数 END_VAR第二步,写内部逻辑。这里用循环实现累加和最大值判断,梯形图最头疼的部分在这几行里解决:
IF bEnable THEN rAverage := 0; rMaxValue := arrRawData[0]; bValid := TRUE; FOR i := 0 TO 9 DO rAverage := rAverage + arrRawData[i]; IF arrRawData[i] > rMaxValue THEN rMaxValue := arrRawData[i]; END_IF; END_FOR; rAverage := rAverage / 10; ELSE bValid := FALSE; END_IF;第三步,编译这个功能块,然后回到梯形图页面。在指令树的“功能块调用”类目下找到FB_ArrayCalc,拖进梯形图网络里。此时IDE会自动生成一个功能块调用线圈,顶部是输入引脚,底部是输出引脚。你需要做的是:
- bEnable接一个控制字BOOL变量,比如“计算使能”;
- arrRawData接数据块里的温度数组变量;
- rAverage接到一个REAL变量,用于触摸屏显示;
- rMaxValue接到最大值显示变量;
- bValid接到一个内部BOOL,可以参与报警判断。
第四步,注意数据类型匹配。数组变量必须和功能块声明的类型完全一致,差一点都不通。很多新手在这里卡住,比如梯形图里的数据是从模拟量通道采集的INT型,而功能块声明的是REAL数组,就需要在数据块里先做一次类型转换再接入。
2.3 这个写法里最容易踩的坑
功能块没有“门控”导致每个扫描周期都执行。这个问题很隐蔽。如果梯形图侧没有给bEnable加前置条件,而是让bEnable常量置TRUE,那功能块每周期都会循环十次。在Codesys里这不算问题,但在一些老式PLC上会影响扫描周期,甚至触发看门狗。我在信捷XD系列上就遇到过类似情况,解决方式是bEnable只在数据刷新完成沿之后置TRUE,计算完立即复位,既保证了数据新鲜度,又减少不必要的CPU开销。
数组越界是另一个高频事故。ST功能块内部如果引用了arrRawData下标为10的元素,在线运行时不会像PC程序那样直接报错,而是可能读到前后变量。这个错误极其隐蔽,因为功能块输出看起来还正常,但数据已经错了。排查方法很简单:FOR循环的下标范围必须和数组声明一致,任何常量下标都建议写成数组的上限值,不要写死数字。
在线监控也要提前规划。梯形图页面只能看到功能块的输入输出引脚,看不到内部变量。如果你希望在调试时监视功能块内部的i循环计数或者中间变量,最好在ST功能块的接口区额外加几个“监视变量”,或者在IO映射里建立测试点。等程序下载运行后再想加变量,就得停机重新编译下载,成本很高。
3. 写法二:ST做状态机大脑,梯形图做设备四肢
3.1 哪些设备最适合“ST主控+梯形图执行”
如果你面对的是一台多工位自动化设备,自动运行流程有十几个甚至几十个状态,每种状态都要协调一堆伺服、气缸、夹紧机构,那么梯形图硬写状态机就是一场噩梦。
举个例子,一台三轴搬运设备,自动流程包括回零、等待取料、取料确认、移送、放料确认、复位,每个工位还有独立的伺服动作和气缸联锁。如果用梯形图写状态跳转,每一行都要判断“当前状态+完成条件+下一状态”,几十个状态的跳转矩阵轻松超过五百行,检查和修改都是一场灾难。
但如果你把一个工位的气缸联锁逻辑用ST写,又会陷入前面说的可维护性困局。所以这个场景下最佳方案是:ST写状态机主控,梯形图写硬件联锁回路。我把这种架构叫作“大脑和四肢分离”——ST是大脑,负责判断状态、下发指令、收集反馈;梯形图是四肢,负责把设备动作和硬件安全回路落到实处。
3.2 具体实现:CASE语句驱动梯形图子程序
在这种写法里,ST主控程序通常用CASE语句组织状态跳转:
CASE byStep OF 0: // 回零状态 fbHomeREQ(bEnable := TRUE); IF bHomeDone THEN byStep := 1; END_IF; 1: // 等待取料 fbWaitReady(bEnable := TRUE); IF bPickReady THEN byStep := 2; END_IF; 2: // 取料执行 fbPickInProgress(bEnable := TRUE); IF bPickDone THEN byStep := 3; END_IF; END_CASE;这里的关键是把每个状态对应的设备动作封装成一个个动作功能块,动作功能块内部再调用梯形图子程序。或者更简单一点:在ST主控里把当前状态对应的“允许动作标志位”置TRUE,梯形图页面的每一个网络都依赖这些标志位来输出。
梯形图那一侧写什么?写最纯粹的逻辑:伺服使能必须同时满足伺服报警未触发且急停释放且状态机的取料允许标志为TRUE;气缸伸出必须同时满足夹具到位且没有工件触碰报警。这些逻辑放在梯形图里,电气工程师现场拿万用表都能顺着线路查,而不用去理解ST里的CASE跳转逻辑。
3.3 实例展开:三轴搬运设备的自动运行状态
状态机和梯形图的协作细节,我拿一个具体的取料工位来说。ST主控在“取料”状态下发bPickAllow为TRUE,此时梯形图侧的取料输出网络才允许动作:
安全门关闭、急停未触发、伺服就绪、取料允许标志为TRUE,这四个条件串联在一起,只有在全部满足时,输出Q0.0才给伺服使能线圈通电。再往下,伺服使能信号加上夹具夹紧到位反馈,才输出气缸动作。
这套结构的调试体验我很喜欢。手动模式可以完全绕过ST状态机——手动按钮直接驱动梯形图输出,不需要状态机介入。自动模式时则是ST先动作,把允许标志翻转后再等待梯形图的执行结果。只要把手动输出和自动输出做了互锁,现场的“手自动切换”就会无比顺畅,因为梯形图负责的是最底层的输出线圈,ST永远不直接驱动物理输出。
3.4 这种写法的三个关键要点
第一,状态号一定要定义成符号常量,不要直接用裸数字。我习惯在最前面把状态枚举写清楚:
VAR CONSTANT STATE_HOME : INT := 0; STATE_WAIT_PICK : INT := 1; STATE_PICK : INT := 2; STATE_MOVE : INT := 3; STATE_PLACE : INT := 4; END_VAR这样CASE里写的就不是跳转数字,而是状态名称,别人看你代码的时候不需要拿一张状态表来回对照。
第二,ST侧下发的是“允许”信号,不是“动作”信号。同样是驱动一个气缸,让ST输出一个“请伸出”的BOOL,不如让ST输出一个“允许伸出”的标志,然后由梯形图综合判断安全条件后输出。区别很大:“请伸出”是命令,梯形图必须无条件执行;而“允许伸出”是授权,梯形图有权因为安全条件不满足而拒绝伸出。这个设计思想保住了安全底线。
第三,状态跳转的条件尽量使用沿信号。一台多工位设备,各工位动作时间不同,状态机跳转过快会导致下一工序还没准备好就开始执行。我一般会在梯形图侧把所有到位信号汇总后,用一个上升沿触发状态机的“工序完成”标志。这样即使某个传感器短暂闪断,也不会造成状态误跳。
4. 写法三:同一逻辑内部的“内嵌式”混编
4.1 什么才算内嵌式混编
前两种写法本质上都是“块级混编”——一个块是ST,另一个块是梯形图,靠调用关系协作。但有些PLC平台还支持在同一个逻辑单元内部,把ST语句直接嵌到梯形图网络里。这种写法的变化多、平台差异大,所以单独拿出来讲。
最典型的实现是三菱GX Works3。在结构化梯形图工程里,你可以把一个梯形图网络的输出部分替代为一个“ST语句盒”,梯形图的触点条件控制这个ST语句盒是否执行。这样既保留了梯形图的条件判断,又在同一行里完成了复杂表达式。
另一个常见场景是Codesys生态下的SFC(顺序功能图)编程:SFC的“转换条件”可以选择ST语言实现。一个转换条件的判断逻辑如果是“取料完成 AND 无报警 AND 定位偏差小于0.5”,用梯形图要画三个比较器再加AND串联,但用ST表达式一行就写完了。
4.2 两个真实场景的展开
先看GX Works3里的ST语句盒。假设我有一个拨码开关读取的十六进制数据存在D200里,想在设备启动时把它转换成十进制数值并判断是否在正常范围内。梯形图网络前面是启动条件触点,触点后面插入一个ST语句盒,盒内写:
D100 := BCD_TO_INT(D200); IF D100 > 0 THEN M20 := TRUE; END_IF;梯形图的触点条件满足时,这个语句盒才会执行;不满足时,D100保持原值,M20不动作。这种写法把一个百来行的、由各种CMP、BCD、MOV指令组合起来的网络,压缩成了两行可读性极高的ST语句。调试效率提升非常明显。
再看Codesys SFC的转换条件。你在SFC图表里拖动一个转换元素,双击打开编辑器,语言选ST,然后输入:
bPickDone AND NOT bAlarm AND ABS(rActualPos - rTargetPos) < 0.5这个表达式的计算结果就是转换条件。如果用梯形图实现同样的判断,得插入三个比较指令再串起来,尤其在变量名很长的时候,梯形图连线会非常拥挤,图形错位一调整就是半小时。
4.3 内嵌式写法的限制和注意事项
在线监控能力弱是内嵌式写法最大的短板。普通梯形图网络在线监视时,每个触点和线圈都有绿/灰色状态显示;而ST语句盒只能看到整体执行结果变量,语句盒内部每一行的条件分支并不能像ST程序块那样逐行高亮。如果语句盒里写了一个复杂的IF嵌套,中途出错时不太好定位。
另一个要注意的风险是执行级别。梯形图触点控制的ST语句盒本质上是每个扫描周期执行一次,它内部如果写了很大的循环体,会直接影响扫描周期。我之前见过一位同事在内嵌ST盒里写了一个FOR 0 TO 10000的空循环用于延时,结果PLC扫描周期直接爆表,CPU直接亮红灯。这种用途应该用延时定时器,而不是循环硬跑。
还有一个务实建议:内嵌ST语句盒里的代码尽量不依赖PLC具体品牌指令。BCD_TO_INT这种指令换一个平台就不存在了,移植性极差。我一般只在内嵌位置写ADD、SUB、IF、CASE这种标准语句,特殊指令还是放到功能块里封装。
4.4 各平台混编支持程度的真实差异
这部分信息很重要,因为很多工程师拿着一个平台的习惯去另一个平台找功能,结果一脚踩空。
| 平台/软件 | 混编支持方式 | 主要限制 |
|---|---|---|
| 西门子TIA Portal(S7-1200/1500) | 块级混编:梯形图OB调用SCL编写的FC/FB,反之亦可 | 同一块内不支持混写,不能在一个梯形图网络里嵌入SCL文本;SCL中调用梯形图块也需要先定义实例 |
| Codesys及国产Codesys类(汇川、信捷部分型号等) | 块级混编为主;SFC动作/转换条件可选择不同语言 | 普通LD梯形图内不提供ST语句盒,但SFC内混编很灵活 |
| 三菱GX Works3(FX5U及以上) | 支持梯形图网络内插入ST语句盒 | ST语句盒内不能调用梯形图块,代码过长时监控困难 |
| 传统三菱FX3U(GX Works2) | 基本不支持真正的ST混编 | FX3U需用结构化梯形图+变址间接实现,复杂数据处理仍然别扭 |
| 信捷XD系列(XDPPro) | 支持ST功能块与梯形图互相调用 | 块级混编成熟,内嵌式表达支持程度取决于具体版本,建议先做编译验证 |
5. 三种写法怎么选:选型建议与实际体会
5.1 关键差异一览表
三种写法不是替代关系,而是适用于不同项目阶段和控制架构。我用一张表把核心差异列出来,方便你按自己手里的项目去套:
| 对比维度 | 写法一:梯形图壳+ST芯 | 写法二:ST大脑+梯形图四肢 | 写法三:内嵌式混编 |
|---|---|---|---|
| 程序组织 | 梯形图为主,ST功能块按需插入 | ST作为主控层调用梯形图子程序 | 同一网络内梯形图触点+ST语句 |
| 可维护性 | 很好,边界清晰 | 较好,但要求维护人员理解状态机概念 | 一般,在线监控能力弱 |
| 调试难度 | 较低,ST块内部可用监视变量观察 | 中等,状态跳转要靠变量表辅助 | 较高,语句盒内分支不如梯形图直观 |
| 适合场景 | 传统设备控制+算法辅助 | 多工位自动化、流程复杂设备 | 协议解析、条件判断密集的小型逻辑 |
| 对团队要求 | 电工能看梯形图,编程人员熟悉ST | 需要一个能写ST状态机的工程师 | 调试工具熟练,熟悉平台特别功能 |
5.2 我的固定套路
踩了不少坑之后,我现在接手新项目基本会按下面这套惯例划分语言:
凡是和安全、急停、互锁、门禁相关的逻辑,一律梯形图,没得商量。这类逻辑出问题的代价是安全事故和人身伤害,必须让设备现场任何一个电气人员都能直观检查。
凡是批量计算、数据处理、协议解析、复杂数学运算,一律ST功能块。这些逻辑不追求可视化,只追求代码简洁和修改方便。二十个浮点数求平均值,梯形图要写一屏,ST只要一个FOR循环。
凡是多工位、自动流程、状态机类型的逻辑,用ST主控,但只下发给梯形图“允许”信号,绝不直接输出到物理点。这样状态流程的修改不会影响安全回路,安全回路的变更也不会导致状态机逻辑崩溃。
凡是伺服、气缸、变频器的底层驱动和反馈处理,放在梯形图里。这些点位多、接线杂、回路逻辑相似,梯形图的触点和线圈形式最方便排查IO信号。
5.3 混编项目的工程化管理建议
语言混编之后,程序的可读性瓶颈往往不在代码本身,而在接口变量命名混乱。我见过太多项目里ST功能块的输入输出叫A1、B2、TEMP这种名字,梯形图侧根本分不清谁是谁。现在我强制自己用一套简单前缀:来自梯形图的输入变量名以LD_开头,来自ST的变量名以ST_开头,跨层传递的全局标志统一放数据块并写注释说明来源和去向。
接口文档也建议提前做。我在项目启动阶段会维护一张Excel表格,每个ST功能块一行,列清楚功能块名称、输入变量、类型、来源块、输出变量、去向块、备注说明。这张表在前期设计时看起来像多余工作,到了调试后期联调阶段,你会发现它的价值——不用翻十几个文件就能知道某个BOOL变量是从哪来的,是谁置位的。
最后提醒一个在线改动的问题。梯形图支持在线修改部分页面并下载,但在ST功能块内部修改了逻辑之后,绝大多数PLC都需要整体编译并下载整个程序块。如果你在自动运行状态下改了一个ST块的参数,一定要确认当前设备处于安全位置,否则下载瞬间的输出状态跳变可能会让设备来个意外动作。我在现场有一次深夜加班改配方程序,就是在自动状态下下载了ST块,结果气缸瞬间动作,幸好对着空料位,不然后果不敢想。
三种写法我实际都用过,没有一种能包打天下。真正好用的混编方案,是在项目需求阶段就想清楚每一段逻辑该用什么语言。语言只是工具,设备稳定运行、维护人员能接手,才是最后要交的答卷。