1. 为什么要在同一个项目里混用ST和梯形图
1.1 先搞清楚这两种写法各自的脾气
搞PLC编程的人,迟早会碰到一个绕不开的选择:这段逻辑到底用梯形图写,还是用ST写。我刚入行那会儿,师傅跟我说“梯形图是根,先把梯形图吃透”,这话没错,但干了几年之后你会发现,有些场景硬用梯形图去堆,纯粹是跟自己过不去。
梯形图(Ladder Diagram,简称LD)的本质是什么?它是一套基于继电器逻辑的图形化表达。触点、线圈、定时器、计数器,这些东西在电气原理图上长什么样,在梯形图里就长什么样。它的最大优势是直观——一个电工老师傅,没学过编程,拿着梯形图也能大概看懂这条线是干嘛的。所以在处理开关量逻辑、互锁、顺序控制这些场景时,梯形图几乎是无敌的。
但梯形图的短板也很明显。一旦涉及到复杂运算、数据处理、循环、数组操作、字符串处理,梯形图就开始变得极其臃肿。你试过用梯形图去写一个冒泡排序吗?我试过,那画面太美我不敢看——几十个比较指令和传送指令堆在一起,别说别人看不懂,过两个月我自己都看不懂。
ST(Structured Text)就不一样了。它是高级语言,语法接近Pascal,有IF、CASE、FOR、WHILE这些结构化语句,做数学运算、逻辑判断、数据处理非常顺手。一条FOR循环能搞定的事情,梯形图可能要写几十行。但ST的问题是不够直观——对于习惯了看电路图的现场调试人员来说,一段ST代码就像看天书,排查故障的时候不如梯形图那么一目了然。
所以结论很清晰:两者不是替代关系,而是互补关系。混编的核心思路就是——让梯形图干它擅长的事,让ST干它擅长的事。
1.2 混编到底解决什么问题
我拿一个实际项目举例。之前做过一条包装线,控制逻辑大概分三块:
第一块是安全回路和急停逻辑。这部分涉及安全门、光幕、急停按钮的互锁,要求逻辑绝对清晰、可审查。这种场景用梯形图写,每一路互锁都画得明明白白,安全审查的时候一眼就能过。
第二块是配方管理。不同产品对应不同的包装参数——温度、时间、速度、数量,加起来几十个参数,还要支持配方切换、参数存储、掉电保持。这种场景用ST写,一个结构体数组就搞定了,用梯形图写简直是灾难。
第三块是运动控制。轴的回零、定位、插补,这部分通常用厂商提供的功能块(FB)来调用,功能块内部可能是ST写的,但调用的时候在梯形图里拖一个方块出来就行。
你看,同一个项目里,三种不同的需求,三种不同的最优写法。如果强行统一用一种语言,要么安全逻辑写得不够清晰,要么数据处理写得极其痛苦。混编就是让每部分都用最合适的工具去做。
1.3 三菱GX Works3里的混编机制
我用的是三菱的GX Works3(目前比较新的版本是1.123d),它原生支持ST和梯形图混编。具体来说有几种方式:
方式一:同一个程序块内混编。在梯形图程序块里,你可以插入ST代码段。GX Works3支持在梯形图中直接嵌入ST语句,用“ST插入”功能就能实现。反过来,在ST程序块里也可以调用梯形图写的功能块。
方式二:不同程序块分别用不同语言。这是最常用的方式。比如主程序用梯形图,数据处理子程序用ST,然后通过功能块调用或者标签引用来交互。GX Works3允许你在同一个项目中创建多个程序块,每个程序块可以独立选择语言。
方式三:功能块(FB)封装。把ST写的复杂逻辑封装成一个FB,然后在梯形图里像调用普通指令一样调用它。这是我最推荐的方式,后面会详细讲。
注意:混编不是随便混的。不同程序块之间的变量交互需要通过全局标签或者参数传递来实现,不能直接访问对方的局部变量。这一点在项目规划阶段就要想清楚。
2. 核心细节拆解:ST和梯形图各自的关键写法
2.1 梯形图的核心要素与常见误区
梯形图看起来简单,但真正写好并不容易。我见过太多人梯形图写得乱七八糟,主要问题集中在几个地方。
触点逻辑的优化。很多人写梯形图喜欢“一行到底”,把所有条件都串在一条线上。比如一个输出需要满足A、B、C、D四个条件,他就画一条线串四个触点。这本身没问题,但如果条件之间有嵌套关系,比如“A和B同时满足,或者C和D同时满足”,这时候就要用并联和串联的组合。我的习惯是把逻辑分组,用中间继电器(M点)把复杂条件拆开,每一组条件单独一行,最后再汇总。这样调试的时候可以逐个M点去查,效率高很多。
定时器和计数器的使用。梯形图里定时器(T)和计数器(C)是独立占地址的,用的时候要注意地址分配。我一般会在程序开头做一个地址规划表,把T0-T199分配给不同的功能模块,避免冲突。另外,三菱的定时器默认是100ms精度,如果需要更高精度,要用高速定时器或者用ST里的时间函数。
上升沿和下降沿的处理。梯形图里用LDP、LDF指令来捕捉上升沿和下降沿。这里有个坑:上升沿指令必须直接接在母线上或者紧跟在其他触点后面,不能放在线圈后面。我见过有人把LDP放在输出线圈后面,结果完全不工作。正确的做法是在需要捕捉沿信号的触点前面加LDP。
双线圈问题。这是梯形图最经典的坑。同一个输出线圈(比如Y0)在程序里出现了两次,后面的会覆盖前面的。GX Works3编译的时候会给出警告,但很多人不看警告。我的建议是每个输出线圈只出现一次,如果需要多个条件控制同一个输出,用中间继电器汇总后再输出。
2.2 ST语言的核心语法与实战技巧
ST语言的语法不复杂,但要用好需要一些编程思维。我挑几个最常用的语法点讲。
IF语句。这是ST里最常用的条件判断。基本结构是:
IF 条件1 THEN // 执行语句 ELSIF 条件2 THEN // 执行语句 ELSE // 执行语句 END_IF;注意ELSIF的写法,不是ELSE IF。这个坑我踩过,编译报错的时候找半天。
CASE语句。状态机的最佳搭档。比如设备有手动、自动、报警、停机几个状态,用CASE写非常清晰:
CASE 设备状态 OF 0: // 手动模式 手动逻辑(); 1: // 自动模式 自动逻辑(); 2: // 报警 报警处理(); 3: // 停机 停机逻辑(); END_CASE;FOR循环。处理数组的利器。比如要计算一个长度为10的数组的总和:
sum := 0; FOR i := 0 TO 9 DO sum := sum + 数组[i]; END_FOR;这里要注意循环变量的类型,一般用INT或者DINT。如果数组很大,循环次数很多,要考虑扫描周期的影响。我一般会把大循环拆成多次扫描执行,避免单次扫描时间过长导致看门狗报警。
结构体(STRUCT)。ST最强大的功能之一。你可以把一组相关的变量打包成一个结构体,比如配方参数:
TYPE 配方参数 : STRUCT 温度 : REAL; 时间 : INT; 速度 : REAL; 数量 : INT; END_STRUCT END_TYPE然后定义一个数组配方列表 : ARRAY[0..9] OF 配方参数;,配方切换的时候直接改索引就行,非常方便。
2.3 两种写法在数据处理上的效率对比
我做过一个实测,同样的数据处理任务,分别用梯形图和ST实现,对比一下代码量和执行效率。
任务描述:读取一个长度为20的INT数组,找出最大值和最小值,并计算平均值。
用梯形图实现:需要用到变址寄存器(Z/V),配合循环指令(FOR/NEXT),大概需要30-40行梯形图。代码可读性很差,调试的时候基本靠猜。
用ST实现:
max := 数组[0]; min := 数组[0]; sum := 0; FOR i := 0 TO 19 DO IF 数组[i] > max THEN max := 数组[i]; END_IF; IF 数组[i] < min THEN min := 数组[i]; END_IF; sum := sum + 数组[i]; END_FOR; avg := sum / 20;不到15行,逻辑一目了然。执行效率方面,ST编译后的代码和梯形图编译后的代码在PLC里跑起来差别不大,因为最终都是编译成机器码。但开发效率和可维护性差距巨大。
实操心得:数据处理类的逻辑,能用ST就用ST,别跟自己过不去。梯形图留给开关量逻辑和互锁,这是它的主场。
3. 混编实操:从项目搭建到联调的全过程
3.1 项目结构规划与程序块划分
混编项目的第一步不是写代码,而是规划结构。我一般的做法是按功能划分程序块,每个程序块选择最合适的语言。
以一个典型的设备控制项目为例,我会这样划分:
| 程序块名称 | 语言 | 负责内容 | 调用方式 |
|---|---|---|---|
| Main | 梯形图 | 主循环、模式切换、安全逻辑 | 初始程序 |
| Safety | 梯形图 | 急停、安全门、光幕互锁 | Main中调用 |
| AutoSeq | 梯形图 | 自动流程步进控制 | Main中调用 |
| RecipeMgr | ST | 配方管理、参数计算 | Main中调用 |
| DataProcess | ST | 数据采集、统计、报警判断 | Main中调用 |
| AxisCtrl | 梯形图 | 轴使能、回零、定位触发 | Main中调用 |
| Comm | ST | 通信数据处理、协议解析 | Main中调用 |
这个划分的核心逻辑是:安全相关的、流程相关的用梯形图,数据相关的、计算相关的用ST。安全逻辑用梯形图是因为审查方便,流程控制用梯形图是因为步进逻辑用梯形图写最清晰。数据处理用ST是因为效率高,通信协议解析用ST是因为涉及大量位操作和数组处理。
在GX Works3里创建程序块的时候,每个程序块可以独立选择语言。创建好之后,在Main程序块里通过“程序块调用”指令来调用其他程序块。
3.2 ST功能块的封装与梯形图调用
这是混编最核心的技术点。把ST写的复杂逻辑封装成功能块(FB),然后在梯形图里调用。
具体步骤:
第一步:创建功能块定义。在GX Works3的导航窗口里,右键“功能块”->“新建”,选择ST语言。定义输入输出变量:
// 功能块:配方切换 // 输入 输入_配方号 : INT; 输入_切换触发 : BOOL; // 输出 输出_当前温度 : REAL; 输出_当前时间 : INT; 输出_切换完成 : BOOL; // 内部变量 配方列表 : ARRAY[0..9] OF 配方参数; 当前索引 : INT;第二步:编写FB内部逻辑。用ST写配方切换的逻辑,包括参数读取、边界检查、切换完成信号等。
第三步:在梯形图中调用。在梯形图程序块里,从指令栏里找到你创建的功能块,拖到梯形图上,就会出现一个方块。方块的左边是输入引脚,右边是输出引脚。把对应的变量连上去就行。
这种方式的优势非常明显:梯形图部分保持了一贯的直观性,ST部分被封装在黑盒子里,调试的时候只需要关注输入输出对不对,不需要关心内部实现。而且FB可以重复调用,比如你有10个轴,只需要写一个轴控FB,调用10次就行。
注意事项:FB的输入输出变量类型要匹配。比如你在梯形图里连了一个INT变量到FB的REAL输入引脚,编译会报错。GX Works3有自动类型转换的功能,但我不建议依赖它,最好手动转换,避免精度丢失。
3.3 全局标签与跨程序块数据交互
不同程序块之间需要共享数据,比如梯形图程序块里的模式切换信号,ST程序块里需要读取。这时候就要用到全局标签。
在GX Works3里,全局标签在“标签”->“全局标签”里定义。定义的时候要指定数据类型和分配地址(或者选择“自动分配”)。
我的习惯是按功能模块分组定义全局标签,比如:
- 模式相关:
g_手动模式、g_自动模式、g_报警模式 - 配方相关:
g_当前配方号、g_配方切换请求 - 状态相关:
g_设备运行中、g_设备故障
全局标签定义好之后,在任何程序块里都可以直接使用。梯形图里直接输入标签名,ST里也直接输入标签名,GX Works3会自动关联。
这里有个坑:全局标签的地址分配。如果选择自动分配,GX Works3会帮你分配软元件地址,但有时候会分配到你不想要的区域。我一般会手动指定地址,比如模式相关的分配到M100-M199,配方相关的分配到D1000-D1999。这样在监控的时候一目了然。
3.4 编译与下载的注意事项
混编项目的编译和普通项目略有不同。GX Works3在编译的时候会检查所有程序块,包括ST和梯形图。常见的编译错误有:
- ST语法错误:比如漏了分号、
END_IF写成了ENDIF、变量未定义等。GX Works3的错误提示还算友好,会告诉你哪一行出了问题。 - 标签未定义:在ST里用了一个没有定义的变量。检查全局标签和局部标签的定义。
- 类型不匹配:比如把INT赋给BOOL,或者把REAL赋给INT。需要显式转换。
- FB调用参数不匹配:输入输出的数量和类型要对上。
下载的时候,GX Works3会把所有程序块一起下载到PLC。如果PLC里已经有程序在运行,下载前会提示你是否停止PLC。一定要确认设备处于安全状态再下载,这是基本的安全意识。
4. 常见问题与排查技巧实录
4.1 混编项目中最容易踩的五个坑
坑一:ST里的定时器不能用梯形图的T软元件。梯形图里的T0、T1是硬件定时器,ST里不能直接用。ST里要用定时器功能块,比如TON、TOF。我一开始不知道,在ST里写T0 := 100;,编译直接报错。
坑二:ST的扫描周期和梯形图不一样。梯形图是自上而下、从左到右执行的,ST是顺序执行的。但两者都在同一个扫描周期内。如果ST里有一个很长的FOR循环,会拖慢整个扫描周期。我一般会把大循环拆成小循环,分多次扫描执行。
坑三:梯形图里的上升沿在ST里没有直接对应。梯形图有LDP指令,ST里需要用变量记录上一次的状态来实现:
IF 触发信号 AND NOT 上次触发信号 THEN // 上升沿逻辑 END_IF; 上次触发信号 := 触发信号;坑四:FB的局部变量在每次调用时不会自动清零。如果你在FB里用了一个局部变量做累加,下次调用的时候它还是上次的值。需要在FB开头手动初始化,或者用RST指令清零。
坑五:全局标签和局部标签重名。GX Works3允许全局标签和局部标签同名,但实际使用时局部标签会覆盖全局标签。这会导致你在ST里以为用的是全局变量,实际上用的是局部变量。我的建议是全局标签加前缀g_,局部标签不加前缀,这样一眼就能区分。
4.2 调试混编程序的实用方法
调试混编程序,我一般分三步走。
第一步:单独调试ST程序块。在GX Works3的模拟器里,可以单独运行ST程序块,用“监视”功能查看变量值。我一般会写一个简单的测试程序,给ST程序块的输入赋固定值,看输出对不对。
第二步:单独调试梯形图程序块。梯形图的调试更直观,直接在监视模式下看触点和线圈的通断状态。GX Works3的梯形图监视功能很好用,能实时显示每个触点的ON/OFF状态。
第三步:联调。把ST和梯形图一起跑,重点看跨程序块的数据交互对不对。我一般会在关键的数据交互点加一些“监视标签”,在Watch窗口里实时查看。
实操心得:GX Works3的“采样跟踪”功能非常好用。你可以设置触发条件,让PLC在特定条件下记录变量值的变化过程。对于排查偶发性的逻辑问题特别有效。
4.3 常见编译错误速查表
| 错误提示 | 可能原因 | 解决方法 |
|---|---|---|
| “未定义的标签” | 变量未声明 | 检查全局标签和局部标签定义 |
| “类型不匹配” | 赋值类型不一致 | 用INT_TO_REAL等转换函数 |
| “FB调用参数错误” | 输入输出数量或类型不对 | 检查FB定义和调用 |
| “语法错误” | ST语句缺少分号或关键字拼写错误 | 检查报错行附近 |
| “双线圈” | 同一输出线圈出现多次 | 用中间继电器汇总 |
| “扫描周期超时” | ST循环太大 | 拆分循环,分多次扫描 |
4.4 性能优化的几个实用技巧
技巧一:把频繁调用的ST逻辑放在FB里。FB的局部变量访问速度比全局变量快,而且FB可以重复调用,代码更简洁。
技巧二:梯形图里的复杂运算用ST替代。比如浮点数运算、三角函数、PID计算,这些用梯形图写又长又容易出错,用ST写几行就搞定。
技巧三:合理规划扫描周期。如果项目对实时性要求高,可以把关键逻辑放在中断程序里。GX Works3支持固定周期中断和事件中断。
技巧四:用结构体组织数据。把相关的变量打包成结构体,不仅代码清晰,而且访问效率高。比如把轴的所有参数打包成一个结构体,用数组管理多个轴。
技巧五:注释要写清楚。混编项目里,ST和梯形图交替出现,注释尤其重要。我一般会在每个程序块开头写清楚功能说明,在关键的ST代码段前面写清楚逻辑意图。
5. 我的混编实践体会
干了这么多年PLC编程,我对混编的理解也在不断变化。刚开始的时候,我总想着“用一种语言搞定所有事情”,要么全用梯形图,要么全用ST。后来发现,这种想法本身就是错的。工具没有好坏,只有合不合适。
梯形图就像一把锤子,简单、直接、可靠,敲钉子的时候无可替代。ST就像一把瑞士军刀,功能多、灵活,但你不能拿它去敲钉子。混编就是把锤子和瑞士军刀都放在工具箱里,需要哪个用哪个。
我现在做项目的习惯是:先画流程图,再决定每部分用什么语言。流程图上标注清楚,哪些是开关量逻辑,哪些是数据处理,哪些是通信交互。然后按功能划分程序块,每个程序块选择最合适的语言。最后在Main程序块里把它们串起来。
这样做的最大好处是可维护性。设备交付之后,现场调试人员看梯形图部分就能处理大部分故障,遇到数据处理的问题再找ST部分。分工明确,各司其职。
最后分享一个小技巧:在GX Works3里,你可以给每个程序块设置不同的颜色标签。我给梯形图程序块设成蓝色,ST程序块设成绿色,FB设成橙色。这样在导航窗口里一眼就能看出哪个程序块是什么语言,找起来特别快。
混编不是炫技,而是务实。用最合适的工具做最合适的事,这才是工程师该有的思维方式。