我做了这么多年设备程序,发现很多做电气和PLC的人对"ST与梯形图混编"这件事始终有点含糊。倒不是说不会用,而是说起混编时,脑子里没有一个清晰的框架,一会觉得是梯形图里调功能块,一会觉得是一门语言里嵌入另一门。这篇文章我想把这个问题彻底讲透。我平时的工作主要围绕三菱GX Works3、西门子TIA Portal、以及Codesys系软件展开,这三种环境恰好代表了混编的三种不同做法,我会用实际项目里的经验,把"ST与梯形图混编的三种写法"掰开揉碎讲清楚,包括每种写法的适用场景、典型代码、容易踩的坑,以及我个人的选型习惯。
先说一个总观点:ST和梯形图根本不是对立的,它们解决的是不同层面的问题。梯形图本质上是电气原理图的软件化,它和继电器控制回路同源,现场维护人员只要看过电气图,基本一天就能上手。ST则更像C语言和Pascal的混合体,擅长的是循环、数组、结构体、数学运算、状态机这类纯逻辑任务。一台稍微复杂点的设备,比如带称重、伺服、配方、多工位的包装机,你很难只用其中一种语言写得既漂亮又让人敢去改。所以混编不是风格问题,是刚需。接下来我按三种写法的粒度逐步展开。
1. 为什么混编是刚需:两种语言的性格和边界
1.1 梯形图擅长"看得见",ST擅长"算得对"
先说梯形图的优势。梯形图的每个触点、每个线圈都对应一个实体的电气动作,拿一张程序打印图,顺着母线从左往右看,启停、自锁、互锁、急停链路一目了然。这种"可读性"对产线维护来说是无价的。我见过很多设备厂的电工师傅,他们不看注释就能顺着梯形图找到哪个中间继电器没有吸合。
但是梯形图的短板同样突出。一个数据运算逻辑,比如把温度传感器的原始工程量转换成实际温度,再做三段PID运算和上下限报警,放在梯形图里需要二三十个网络,中间结果全是临时标志位,改一个系数要翻十几行图。就算写得再规范,这种程序在调试时也容易看花眼。数组、循环、结构体这类数据结构在纯梯形图里更是地狱难度,你用移位指令做一串数据的队列,比ST里一个FOR循环慢得多,也不利于后期维护。
ST的优势正好补在这里。ST的赋值、IF条件、FOR循环、CASE状态机,几行就能完成梯形图几十个网络才能做完的事情。比如批量读取配方数据、对一组测量值排序、计算统计特征值、拼接通讯报文,用ST写不仅代码量少,出错的概率也低得多。因为程序是按顺序执行的,逻辑流向直接体现在代码文本里,不用像梯形图那样靠网络执行状态去猜。
1.2 一个典型场景:包装线的"梯形图安全链"和"ST算法"
说这些太抽象,我拿一台实际设备举例。一条小包装线,主电机的启停控制、急停回路、安全门联锁、气缸进退,这些属于安全链路和基础设备控制,适合梯形图。机器还有一个称重补偿机构,秤的原始值要经过滑动平均滤波,然后根据连续几个袋子的误差值算出补偿量,再根据配方表查表输出给伺服速度。这套东西如果全用梯形图写,程序里会堆满计算网络和临时变量。我用混编来处理:安全逻辑和主流程用梯形图,称重滤波和补偿计算用ST写成功能块。这样一来,维护电工处理报警时看到的是熟悉的梯形图,遇到计算问题我只需要把ST功能块的接口参数检查一遍。
所以我把"H2"以外的结构想得很清楚:混编的实质,就是给不同性格的逻辑找到各自最合适的宿主语言,再用一套规范把它们串起来。至于怎么串,就是下面要讲的三种写法的问题。
2. 三种混编写法的完整拆解
2.1 写法一:梯形图框架调用ST功能块,算法黑盒化
这是最保守、也是应用最广的写法。整个程序以梯形图为主,类比于一张完整的电气图,遇到需要复杂计算的地方,不在图里硬写,而是新建一个ST语言的功能块(FB)或函数(FC),在梯形图里直接像拖一个普通功能块一样调用。
这种写法用西门子TIA Portal、Codesys、三菱GX Works3都能做,而且工程上兼容性最好。它特别适合老设备改造和程序升级:原有梯形图逻辑不用动,你只要把新算法封装成一个ST功能块,在主程序里增加一行调用,剩下的交给接口数据。
我举个例子。有一台设备需要做测量值的滑动平均滤波,输入是一路模拟量,输出是滤波后的工程值和数据有效标志。ST功能块大致长这样:
FUNCTION_BLOCK FB_MovingAverage VAR_INPUT bEnable : BOOL; // 使能信号 rRawValue : REAL; // 模拟量原始值 END_VAR VAR_OUTPUT rFiltered : REAL; // 滤波后数值 bValid : BOOL; // 数据有效 END_VAR VAR arrBuf : ARRAY[0..9] OF REAL; nIndex : INT; rSum : REAL; i : INT; END_VAR IF NOT bEnable THEN rFiltered := 0.0; bValid := FALSE; nIndex := 0; rSum := 0.0; FOR i := 0 TO 9 DO arrBuf[i] := 0.0; END_FOR; RETURN; END_IF arrBuf[nIndex] := rRawValue; rSum := 0.0; FOR i := 0 TO 9 DO rSum := rSum + arrBuf[i]; END_FOR; rFiltered := rSum / 10.0; bValid := TRUE; IF nIndex < 9 THEN nIndex := nIndex + 1; ELSE nIndex := 0; END_IF在梯形图里调用就是标准的功能块实例化方式,把模拟量通道值接到rRawValue,把滤波结果读取出来。程序扫描时,梯形图运行到该功能块所在网络时,ST块会在同一个扫描周期内同步执行完毕。这种写法最大的好处是边界清晰:梯形图负责"什么时候调用、数据从哪来、结果送到哪去",ST负责"数据怎么算"。两种语言各干各的,互相不污染。
这种写法的一个注意点是功能块的接口设计。VAR_INPUT、VAR_OUTPUT、VAR_IN_OUT要区分清楚,尤其是VAR_IN_OUT,它传的是引用,像指针一样,外部变量和内部变量会实时联动,理解不到位就容易出现数据被意外改写的情况。我的经验是,能不用VAR_IN_OUT就不用,尽量让数据通过VAR_INPUT进、VAR_OUTPUT出,边界越简单越好。
2.2 写法二:ST主程序做调度,梯形图子程序管设备
反过来,当设备的流程逻辑很复杂时,我会把主控制器写成ST,在ST里通过调用梯形图写的子程序来管理实际设备。这种写法适合什么样的情况?多工位、多配方、多种运行模式的设备。
举个例子。一个四工位清洗站,要按配方切换"水洗、碱洗、漂洗、烘干"四个阶段,每个阶段又涉及多个阀、泵、加热器。如果用梯形图做主程序,一套阶段切换和联锁下来,网络数很容易超过上百个,而且配方判断、计时、步骤跳转的代码会被图淹没。我的做法是:主程序用ST写一个CASE状态机,把它当"大脑";具体的阀泵动作、报警复位、电机启保停,封装成梯形图写的子程序或者功能块,由ST的每个状态去调用。
ST主程序的骨架大致是:
CASE nStep OF 0: (* 待机 *) bIdle := TRUE; bAutoRun := FALSE; // 调用梯形图子程序:关闭所有输出 FC_DevClose( bCloseAll := TRUE ); 1: (* 水洗阶段 *) bWash := TRUE; // 调用梯形图写的电机控制功能块 FB_MotorControl( bStart := bWashStart, bStop := bEStop, bOverload := bMotorOverload, bRunning => bPumpRunning ); // 调用梯形图写的阀控制功能块 FB_ValveControl( bOpen := bValveOpenCmd, bClosed => bValveClosedFeedback ); IF bPumpRunning AND bValveClosedFeedback THEN nTimer := nTimer + 1; IF nTimer >= 300 THEN nStep := 2; nTimer := 0; END_IF END_IF 2: (* 碱洗阶段 *) ... END_CASE;实际工程里,梯形图部分的电机控制功能块可能包含完整的启保停逻辑、热继电器反馈、接触器反馈、远程本地切换。这些在梯形图里画出来只有十来个网络,维护电工一眼就能看懂。而ST主程序只关心"现在是第几步,这一步应该向哪些设备发出什么指令"。两者的关注点彻底分离。
写法二的难点在于调度顺序。PID公式、状态机跳转都必须清楚知道"这一段代码在扫描周期的哪个位置"。同一项目中可能存在多个程序块(PRG或OB),它们按系统设定的优先级顺序执行。我的习惯是:在ST主程序里不要指望梯形图子程序"马上"执行完毕后才继续。因为如果梯形图程序在主ST程序之前或之后执行,数据读取会有周期差。要明确写清楚:梯形图设备的当前状态是上一周期更新的,本周期发出的指令要到下一周期才反馈回来。这是顺序控制里很正常的"一拍延迟",只要在状态跳转条件里留足时间余量,不要用边沿去卡太紧凑的条件就行。
2.3 写法三:在梯形图程序段里直接嵌入ST语句块,段级混编
第三种写法粒度最细,不是建独立的ST块,而是直接在一个梯形图程序段里插入ST语句块。这种支持不是所有平台都有。我实际用过并且觉得顺手的是三菱GX Works3。它在梯形图编辑画面中提供了"ST语句块"的插入功能,你可以在整个梯级的某个位置放一个ST框,里面用ST语言写一段逻辑,扫描到该位置时ST语句一次执行完毕,然后再继续往下扫描梯形图。国产的汇川AutoShop等平台也有类似的能力。
这里要强调一下平台差异。西门子TIA Portal不支持直接在LAD段里嵌入SCL语句块,你只能新建一个SCL的FB/FC,再由LAD调用,也就是写法一。Codesys系也不支持在一个POU内混编两种语言,写结构的老师常说的"段级混编"在IEC 61131-3严格意义上是做不到的,只能做到工程级混编。三菱GX Works3的ST语句块是目前我见过的真正意义上"梯形图中间夹ST"的实现。
这种写法适合的场景是:一个小算法,不值得单独建一个功能块,但用梯形图网络写又很啰嗦。比如几个字节的位拼接、一个区间映射、一组数据的求和,在网络里放一个ST语句块,三五行就完事。示意如下:
// 在梯形图网络中嵌入的ST语句块 FOR i := 0 TO 3 DO D400 + i := (D100 + i) * 2; END_FOR; M10 := TRUE;这里ST语句块执行完毕,M10被置位,然后梯形图继续往后扫描。整个逻辑放在同一个梯级里,数据的连续性比单独一个ST块更直观。但这种写法有它的问题,我在第四章会详细说。它的最大限制是:它不是所有平台都支持,代码复用性也很差,没法在另一个项目里直接拖过去用。所以我的定位是"局部顺手用,不作为架构手段"。
3. 三种写法的实测对比与选型逻辑
3.1 从可维护性、扫描周期、可移植性三个维度看差异
我在多个项目里对三种写法做过横向对比,下面这个表算是我自己的经验总结,不一定代表全部情况,但基本可以用作参考:
| 对比维度 | 写法一:LAD框架调ST块 | 写法二:ST主控调LAD子程序 | 写法三:LAD内嵌ST语句块 |
|---|---|---|---|
| 可读性 | 较高,算法黑盒化 | 逻辑集中,但设备状态需要跳转查看 | 局部清晰,整体结构依赖梯形图 |
| 调试便利性 | 好,功能块接口观察方便 | 中,在线监控需要同时看ST和LAD | 差,语句块内部不易逐行单步 |
| 扫描周期开销 | 低,等于一次FB调用 | 低至中,取决于子程序规模 | 中,语句块每次扫描都进入执行 |
| 代码复用性 | 好,功能块可整个迁移复制 | 一般,ST主控和LAD设备程序耦合度较高 | 差,语句块几乎不能复用 |
| 平台兼容性 | 高,所有主流平台都支持 | 高,所有主流平台都支持 | 低,依赖特定平台的段级混编能力 |
| 团队门槛 | 低,维护人员只需理解接口 | 中,需要理解状态机逻辑 | 低至中,维护人员仍需习惯混看两种代码 |
| 典型项目 | 老设备算法改造、PID/滤波/补偿 | 多工位配方设备、清洗机、工作站 | 局部数据处理、单点修补 |
扫描周期方面的差异,实际测下来三种写法并不会造成明显的性能瓶颈。PLC的扫描周期往往被通讯、运动控制、上下位机交互吃掉,混编本身带来的执行时间差异微乎其微。真正影响性能的是数据分布方式:如果ST块里访问了大量全局变量或跨模块数组,总线的数据交换时间会上升。写法一和写法二在数据结构设计合理的前提下,基本感知不出差别。
还有一个容易忽略的点:代码覆盖率和管理粒度。写法一的代码模块边界清晰,很适合做加密保护和版本管理;写法二的主程序是ST,梯形的子程序各自独立,团队协作时分工很明确;写法三的混编段落在梯形图的网络中间,一旦程序加密,在线查看这段ST语句块很可能会受影响,后面排障会非常难受。
3.2 根据项目类型决定写法,而不是根据个人喜好
我在选型时有一个简单的决策流程,分享给读者参考,能减少很多纠结。
第一步,统计整个程序里"设备控制逻辑"和"数据运算逻辑"的占比。如果设备控制占七八成,数据运算只有两成,无脑选写法一;如果流程复杂、配方多,设备控制反而简单,选写法二;如果总体上还是梯形图为主,只是个别网络需要一点ST计算,选写法三。
第二步,看维护团队的构成。设备交付后,如果现场是电工师傅维护为主,整套程序里必须保留一个完整的梯形图可读视图,那就不要用写法二把主流程藏进ST状态机里。维护人员可以不会ST,但他必须能从梯形图上找到启动、停止、急停、复位这些基本操作。反过来说,如果你的团队是工程师主导,大家习惯看文本代码,写法二反而效率更高。
第三步,确认平台能力。你在TIA里做不了真正的段级混编,在Codesys里也做不了POU内多语言混编,所以写法三只有三菱GX Works3和部分日系/国产平台做得好。选型前先确认你的软件版本和CPU是否支持,不要看图写代码,写完了发现目标CPU根本不支持ST语句块,那就尴尬了。
4. 混编时最容易踩的坑和我的排查思路
4.1 变量作用域和FB实例化:名字冲突只是第一层
混编最容易炸的问题,就是变量冲突。梯形图里传统继电器用M、D、X、Y区全局变量,ST功能块也访问这些区,一旦两边都写同一个地址,结果就不可控。我踩过一次很典型的坑:梯形图里用M100作为"自动运行允许"标志,我在ST功能块里需要读写一个数组的指针,顺手把指针偏移量放到了M100,结果整个设备在自动模式下偶发停机,查了很久才看到ST程序把那个标志位覆盖成了0。
这不是名字起得不够长的问题,根源在于两种语言混编之后,"同一块内存"的访问权被两个人管理,而PLC没有编译期的冲突检查。后来我立了条规矩:梯形图给ST块预留数据区,采用地址分离的方式,比如D区高2000字以上是ST专用,M区的中继只服务梯形图,ST和梯形图的交互全部走功能块接口的输入输出参数,不在内部直接访问对方的中继区域。这样冲突从根源上被消灭了。
功能块实例化的坑也要注意。如果你在梯形图里调用了多个同名的ST功能块实例,梯形图软件会提示你实例名不能重复,这算相对安全。真正危险的是把ST功能块的VAR_IN_OUT直接接一个梯形图线圈或内部位,因为VAR_IN_OUT是引用关系,你把一个位地址当成指针传进去,ST块内部任何写入都会实时改掉梯形图这个位的状态。我在排查时遇到过一次伺服使能输出被莫名拉低,最后发现就是ST块里一个临时变量用了VAR_IN_OUT接在了伺服使能位上,程序一执行就把它覆盖。解决方式很简单:把VAR_IN_OUT改成VAR_INPUT+VAR_OUTPUT,数据单向流动,就不会出现双向引用冲突。
4.2 扫描周期的"先有鸡还是先有蛋"问题
混编不是同一时刻发生的,程序总有个先后执行顺序。梯形图程序和ST程序块在同一个扫描周期内,谁先执行,谁后执行,完全取决于工程的程序任务配置或程序步编号。这个执行顺序直接决定了一个问题:ST块读取到的"当前状态",到底是梯形图这一周期改过的,还是上一周期的。
我遇到过一个很典型的故障:设备做位置切换,梯形图在扫描前段根据当前工位号把目标速度写到D区,ST速度规划块在扫描后段读取D区目标速度并计算斜坡。看起来没问题,但梯形图的工位号更新逻辑和ST读取逻辑恰好处于同一个扫描周期,ST实际读到的是上一周期的工位号,导致速度切换总慢一拍。这种问题在梯形图单独编程时很少出现,因为每个人习惯顺着扫描顺序写工程,但混编时两段代码可能分布在不同程序段甚至不同任务里,顺序就容易混乱。
排查这种问题的思路很清晰:先把"这一拍慢"的现象定位到"谁先执行、谁后执行"。打开工程的任务配置看程序执行顺序,如果PLC支持在线监控执行时间,就逐个确认ST块的执行周期。如果数据流是"A块写入数据,B块读取数据",那么A必须排在B之前执行。如果顺序改不了,就在数据写入侧加一个"数据有效"标志位,B块只有在读到有效标志后才使用数据,处理完后复位标志。这个"握手"虽然多花一个周期,但逻辑绝对可靠。
4.3 在线调试时ST块是"黑盒",语句块更难盯
梯形图的调试体验是最好的,每个触点、线圈都有红绿状态,电工师傅完全可以不读代码只盯图就能判断故障点。但ST块一旦被调用,在线监控只能看变量值变化,看不到程序内部的执行细节。如果你在TIA或GX Works3里,ST块内部的每一行不会像梯形图那样逐行点亮,顶多是通过变量的实时值去判断。这就导致混编程序在出问题时,很多人会下意识怀疑ST块,但又看不到内部,只能靠猜。
我的做法是在每个ST功能块里预留一个调试结构体,专门用来"外透"内部关键值。比如在功能块里定义一个VAR_OUTPUT的数组或者自定义结构体,把中间变量、当前状态、最近一次跳转条件是否成立、上一次执行的周期时间全部放进去,在HMI或者监控表里观察。调试完成后可以保留这些输出,不会影响性能。
对于写法三这种段级混编,语句块内部的单步监控更加有限。我在GX Works3的ST语句块里吃过亏:一段写在梯形图中部的ST语句块,在线时只能看到外部接口状态和语句块的执行条件,内部每一行跳转不跳转并不透明。后来我的习惯是,只要这段ST语句块超过八行,就绝不写在梯形图里,一律拆出来做成标准功能块。这个"八行原则"至今还在用,极大减少了调试黑盒的问题。
4.4 平台方言:同一个ST,换家软件就能编译失败
ST虽然有个IEC 61131-3标准,但各平台实现起来都有自己的方言。你把三菱的ST功能块复制到Codesys里,大概率要改语法;把Codesys里写好的ST块拿到西门子TIA里,数组的声明和字符串处理写法也需要调整。这种"ST通用性幻觉"在混编场景下特别容易被放大,因为混编时你往往是在一个成熟项目里做局部替换,根本没有多余预算去做语法移植测试。
我举几个常见差异点。数组声明,Codesys风格是ARRAY[0..9] OF INT,西门子SCL也类似,但三菱GX Works3的ST语法中数组下标范围和数据类型定义方式有区别。赋值符号,标准ST是:=,但部分国产平台兼容=,写习惯了不注意就会踩坑。注释风格,//和(* *)的兼容性在不同平台上也不一样。字符串拼接,三菱的ST和西门子的SCL差异非常大,一个用+或专用函数,一个用CONCAT。布尔量的AND/OR在IEC标准里就是AND/OR,但如果你在梯形图里用常开闭点,到了ST里须改成AND/OR关键字,这条最容易让梯形图老手写混。
所以我的经验是:在一个项目里确定了用混编,就要把"ST的书写规范"也定下来,包括变量命名、注释格式、是否允许访问全局地址、是否允许使用字符串等高标准行为。移植时,逐个平台过一遍语法,千万别盲信"IEC标准"就能一切通吃。
4.5 程序加密和版本管理
最后一个坑跟工具链有关。很多PLC在启用程序加密后,ST功能块在在线状态是无法查看源码的,只能看到调用关系。如果你的项目是给客户做交钥匙工程,程序加密了,现场排障又需要看内部逻辑,到时候会发现梯形图能看到,ST块全黑。所以我的建议是把工程程序分两个版本管理:一个带完整注释和源码的开发版,一个用于发布和加密的发布版。发布版中如果必须保留ST块,也要把功能块接口的注释写清楚,至少让别人明白输入输出各是什么。
另外提醒一点:混编程序在版本对比时,梯形图网络可以逐项对比,ST块只能整个文件对比,第三方版本管理工具对ST的支持参差不齐。实际操作时,我一般把"工程文件导出为文本格式"作为一个保留动作,每次版本变更后导出一份文本存到版本管理目录里,既方便对比,也能在软件崩溃时快速恢复。
5. 我个人现在的混编选型习惯和一些经验总结
聊到这儿,三种写法都说得差不多了。最后说说我现在的固定习惯。
遇到新项目,我会先按"控制逻辑、数据逻辑、流程逻辑"分成三堆。安全链路和硬互锁,永远用梯形图,这部分是用来保命的,必须让任何一个人都能看懂。数据计算、曲线、配方表、通讯报文解析,一律用ST功能块,写成一个一个独立的小块,像积木一样由梯形图或者ST主程序调度。流程状态机,根据复杂度来:状态数量少于十个且切换条件简单,用梯形图步进指令;状态多、条件复杂,就用ST的CASE结构。在这个框架下,大部分项目自然落到了"写法一"和"写法二"的组合,写法三只在极个别"实在不想单独建块"的情况下使用。
我还有一个坚持了很多年的小习惯:每个交互接口,不管规模多大,都在程序注释里写明"调用周期、数据来源、安全联锁关系"。这句话看着不起眼,但它能避免很多事故。有一次设备已经交付三年,客户现场维护人员问某个ST功能块的输入为什么总是0,我远程翻了注释,找到数据来源地址,发现是初始化时把地址写错了,三分钟定位问题。如果没有这条注释,就得对着通讯表格和地址映射慢慢挖。
混编的核心,从来不是谁的语言更高级,而是让最合适的人能看懂最合适的逻辑,让每个逻辑都承载在它最擅长的编程模型里。无论你是梯形图老手还是ST爱好者,多掌握几种混编写法,并在自己常用的平台上亲手验证一遍执行顺序和接口行为,这比看任何文章都有用。以上是我在几个主流PLC平台上做混编的完整经验复盘,希望能给正在纠结怎么混、怎么编的人一点实践上的底气。