先交代一个真实的场景:去年我在一条小型包装线上,把一套原本用梯形图堆了三天的控制逻辑,改用汇川H5U PLC的ST语言重写,整个逻辑部分从600多行缩短到200多行,一个刚入行一年的电气工程师拿过去,两天就能看懂并改参数。这件事让我彻底认了一件事——汇川H5U做中型设备、多轴运动和数据处理时,ST语言不是"要不要学"的问题,而是"迟早要学"的问题。
这篇内容主要面向两类人:一类是之前一直用梯形图、想转ST但找不到完整路径的电气工程师;另一类是已经接触过ST、但在H5U上还没有形成系统写法的工控人。我整理了7个步骤,从建工程、写变量、写语句、写流程、写循环、封装功能块,一直到在线调试和报警排查,每一步都给出可以直接复制到AutoShop里跑的代码片段,以及我实际踩过的一些坑。学完这7步,你写H5U的ST程序就能从"照着例子改"过渡到"按自己的思路搭程序"。
1. 为什么从梯形图转向ST语言:H5U的定位与ST的用武之地
1.1 H5U到底是一台什么样的PLC
汇川H5U系列是汇川面向中高端小型设备市场推出的中型PLC,官方定位是"中小型运动控制与逻辑控制一体化"。它和旧款H1U、H2U这类传统小型机最大的区别,是内核采用了基于CODESYS V3的运行时环境,支持IEC 61131-3标准的全部五种编程语言:LD(梯形图)、FBD(功能块图)、ST(结构化文本)、SFC(顺序功能图)、IL(指令表)。换句话说,H5U不是一台"能写ST的老式PLC",而是一台真正为复杂逻辑、数据处理和运动控制准备的平台。
我最早用的是H5U-1614MTD这个型号,16点输入、14点晶体管输出,带2路EtherCAT总线,最大支持8个从站轴。说实话,如果你只是拿它替代H1U做继电器逻辑,其实有点浪费。H5U真正发力的场景是:需要EtherCAT伺服、需要模拟量算法处理、需要把重复设备逻辑做成标准化功能块、需要和上位机或HMI做大量数据交互。这些场景里,梯形图不是不能做,是做得非常痛苦——一段平均值滤波梯形图要写十几行网段,ST只需要三行。
1.2 ST语言和梯形图的本质差异
很多老师傅对ST的第一反应是"像C语言,离PLC远了,不安全"。这个我理解,但说实话,这种想法更多是习惯问题。
梯形图的本质是"电气回路思维":每个网络就是一条从左母线到右母线的"虚拟电路",程序执行顺序就是网络从上到下、从左到右的扫描。这种思维的优势在于直观,一个懂继电器电路的人就能看懂,排查现场问题时可以顺回路找。但它的劣势也很明显——项目一旦超过300个网络,整个程序的可读性会急剧下降,改一个流程可能要翻好几屏,而且很难做数据的批量运算。
ST语言的本质是"脚本执行思维":它按IEC 61131-3标准定义了一套完整的语句和操作符,包括赋值、条件分支、循环、函数和功能块调用,语法风格接近Pascal。你用ST写程序,实际上是在用文字描述控制逻辑。好处有四个:
- 同样是做10个模拟量的平均值滤波,ST的FOR循环能在10行内解决,梯形图可能要50行以上
- 逻辑结构清晰,注释写好了,后续维护的人不需要逐行看梯形图网段
- 同样的算法代码可以在不同PLC品牌之间跨平台复用,因为IEC 61131-3标准是通用的
- 函数和功能块的封装能力,能让设备程序真正实现"一次封装、到处调用"
以我个人的使用经验来说,H5U上ST和LD的最佳配比不是非此即彼,而是一个设备程序里,逻辑互锁、安全回路这些电气工程师最关心的部分可以用LD保留,而所有涉及数据处理、流程状态机、批量运算的部分全部交给ST。这也是下面7步里贯穿始终的思路。
1.3 7步总览,先建立整体框架
在进入具体操作之前,我先给出一张整体的步骤表,方便你心里有个地图。后面每个章节对应其中一到两步,逐步展开。
| 步骤 | 核心内容 | 对应章节 |
|---|---|---|
| 第1步 | H5U开发环境搭建与新建ST工程 | 第2节 |
| 第2步 | ST变量声明与数据类型基础 | 第2节 |
| 第3步 | ST运算与赋值语句,写最小编程单元 | 第3节 |
| 第4步 | IF / CASE条件分支,让程序认识状态 | 第3节 |
| 第5步 | FOR / WHILE循环与数组批量处理 | 第4节 |
| 第6步 | 功能块(FB)与函数(FUN)封装复用 | 第4节 |
| 第7步 | 在线调试、断点监视与报警排查 | 第5节 |
这7步并不是完全独立的,前面几步是语法地基,后面两步是工程化能力。你学完第5、6步之后,回头看前4步会更有感觉。
2. 第1-2步:从建工程到写出第一段ST程序
2.1 开发环境搭建,最容易忽略的三个细节
H5U的编程软件是AutoShop(汇川统一编程平台),软件本身可以从汇川官网下载,安装过程比较常规,一路下一步就行。但有几个细节我吃过亏,提醒一下。
第一,AutoShop版本和固件版本要对上。H5U的固件更新比较频繁,如果你用旧版AutoShop连接新出厂的H5U,软件会提示固件版本不匹配,这时不要随便点"升级固件",而是先确认你手里的固件文件是不是设备原厂版本。我遇到过现场回传设备,发现固件被上一个调试人员升过级,但程序还是旧版,导致下载时直接报错。稳妥的做法是:新建工程前先到"帮助-关于"里看AutoShop版本,再在PLC上电后用USB连接看固件版本,尽量保证二者是配套的。
第二,通信驱动的安装。AutoShop通过USB连接H5U时,Windows会识别为一个虚拟串口设备,但有时候驱动不会自动装上。如果插上USB线后设备管理器里出现黄色感叹号,去AutoShop安装目录下找驱动文件夹手动更新驱动即可。另外,如果使用以太网连接,需要把电脑IP设为和PLC同一网段,H5U默认IP一般写在机身标签上,首次连接建议直接用USB,确定IP后再切换网络。
第三,工程模板的选择。新建工程时AutoShop会让你选择PLC型号和工程模板,一定要按实际型号选,比如H5U-1614MTD就不要选成H5U-2414MTD。选错型号的后果是I/O地址映射不完整,程序下载后输入输出点对不上,轻则报警,重则误动作。这个错误在新手期特别容易犯。
2.2 ST程序的最小单元:变量声明区与代码区
打开AutoShop后,在左侧工程树里找到"程序组织单元"(POU),右键添加一个POU,语言类型选"结构化文本(ST)"。这时你会看到编辑器里默认有两块区域:上面的变量声明区和下面的代码区。
ST语言和C语言有个很大的区别:C语言可以在函数体任意位置声明变量,而ST要求在程序开头集中声明。H5U的ST变量声明区以VAR开头、END_VAR结尾,中间可以声明BOOL、INT、REAL等数据类型的变量,还能给变量赋初值。
我一般会在代码区第一行写一个程序名称注释,然后空一行再开始写逻辑。比如一个最简单的点动程序:
VAR bStart : BOOL := FALSE; bStop : BOOL := FALSE; bRun : BOOL := FALSE; nCount : INT := 0; rVolt : REAL := 0.0; END_VAR // 主逻辑:点动运行,停止优先 bRun := bStart AND NOT bStop; // 计数演示:单击一次bStart,计数加1 IF bStart AND NOT bRun THEN nCount := nCount + 1; END_IF这段代码你直接复制到AutoShop的ST编辑器里就能编译。注意几个ST的基本规则:语句结尾用分号;,注释用//或者(* *),赋值符号是:=而不是等号=,布尔型变量的值是大写的TRUE、FALSE。
2.3 H5U支持的常用数据类型,选错类型会踩大坑
ST之所以比梯形图更适合数据处理,核心在于它有完整的数据类型体系。H5U的ST里常用的类型如下:
| 数据类型 | 位宽 | 取值范围示例 | 典型用途 |
|---|---|---|---|
| BOOL | 1位 | TRUE / FALSE | 开关量、启停状态 |
| BYTE | 8位 | 0~255 | 小型状态码 |
| WORD | 16位 | 0~65535 | 通讯状态字 |
| DWORD | 32位 | 0~4294967295 | 位组合状态 |
| INT | 16位 | -32768~32767 | 常规整数计数 |
| DINT | 32位 | -2147483648~2147483647 | 大范围整数计数 |
| REAL | 32位 | -3.4E38~3.4E38 | 模拟量、浮点运算 |
| STRING | 可变 | 字符 | 上位机交互文本 |
这里最容易被忽略的坑有两个。
第一个是INT和DINT溢出。H5U的默认整数类型在某些隐式转换时是按INT处理的,当你的计数器计数值可能超过32767时,必须显式声明为DINT。我见过一个设备,产量计数用的是INT,连续生产两个月后计数突然变成负数,就是因为溢出回绕了。这个错位很隐蔽,排查起来也费劲,一定要在声明阶段就规划好。
第二个是REAL和INT混用。ST里 INT和REAL不能直接赋值和参与运算,需要做类型转换。H5U提供了INT_TO_REAL和REAL_TO_INT这样的转换函数,比如:
nCount := REAL_TO_INT(rVolt * 10.0);如果你偷懒直接写nCount := rVolt * 10.0;,编译会直接报类型错误。这其实是好事,意味着你在编译期就能发现类型问题,不会等到现场出故障再查。
2.4 I/O地址映射:H5U的软硬分离机制
H5U的ST程序里访问物理输入输出点,并不是直接写X0、Y0这样的地址,而是通过I/O映射地址。AutoShop会为每个输入点分配一个%I开头的地址,每个输出点分配一个%Q开头的地址。比如H5U-1614MTD的输入点对应%IX0.0到%IX0.7、%IX1.0到%IX1.7,输出点对应%QX0.0到%QX0.7、%QX1.0到%QX1.5。
在ST里最好别直接用%IX0.0这种裸地址,建议在变量声明区建立软变量映射:
VAR bInStart : BOOL := FALSE; // 启动按钮映射 bInStop : BOOL := FALSE; // 停止按钮映射 bOutMotor : BOOL := FALSE; // 电机输出映射 END_VAR // 在代码区开头做地址映射 bInStart := %IX0.0; bInStop := %IX0.2; %QX0.0 := bOutMotor;这样做的核心原因是可维护性。将来如果现场互换输入点,你只需要改映射那一行,不用改全程序的逻辑。这是标准化编程的起点,也是从"会写ST"到"写好ST"的第一道分水岭。
3. 第3-4步:ST语言的运算与流程控制,让程序学会判断
3.1 赋值、算术、逻辑与比较运算,ST的基本功
ST语言的运算体系继承了高级语言的表达能力,这是梯形图完全不具备的。H5U的ST支持以下常用操作符:
- 赋值操作符:
:= - 算术操作符:
+、-、*、/、MOD(取余) - 比较操作符:
=(等于)、<>(不等于)、<、>、<=、>= - 逻辑操作符:
AND、OR、XOR、NOT - 括号优先级:
()最高,算术其次,逻辑最低
一个实际生产中的例子:现场有一个温度模拟量输入rTemp,一个压力模拟量输入rPress,当温度大于80度且压力小于2.5MPa时,需要输出报警信号。ST写出来就三行:
bAlarm := (rTemp > 80.0) AND (rPress < 2.5); %QX2.3 := bAlarm;同样逻辑用梯形图,你要画两个比较块、一个与逻辑块、再加一个输出线圈。这不是说梯形图不能用,而是ST在可读性和效率上明显更优,尤其在模拟量比较这类场景,ST天然就是为这种表达式设计的。
3.2 IF条件分支:从单条件到多状态判断
IF语句是ST里用得最多的流程控制语句,语法结构如下:
IF 条件1 THEN // 条件1成立时的逻辑 ELSIF 条件2 THEN // 条件2成立时的逻辑 ELSE // 以上条件都不成立时的逻辑 END_IF注意ST里是ELSIF,不是C语言的else if,少一个字母。很多从C语言迁移过来的工程师第一次写ST都会在这里拼写错误。
在H5U上我用IF语句最多的场景,是设备的手动/自动模式切换。手工模式需要按按钮直接点动,自动模式则要按固定流程走。我会把模式变量定义成nMode,然后判断:
VAR nMode : INT; // 0=停机 1=手动 2=自动 bHandStart : BOOL; bAutoStart : BOOL; bAutoStop : BOOL; bMotoRun : BOOL := FALSE; END_VAR IF nMode = 0 THEN bMotoRun := FALSE; ELSIF nMode = 1 THEN bMotoRun := bHandStart; ELSIF nMode = 2 THEN bMotoRun := bAutoStart AND NOT bAutoStop; ELSE bMotoRun := FALSE; END_IF这种写法最大的好处是"单一出口"原则——无论什么模式下,最终只维护一个bMotoRun变量,不会出现多个输出点在程序里互相拉扯的情况。这也是我推荐在ST里尽量用"一个控制结果对应一个输出变量"的原因。
3.3 CASE分支:多状态流程的首选,告别青春版梯形图
当判断的分支数量超过三个时,IF嵌套会变得很难读,这时应该用CASE语句。CASE特别适合设备的状态机编程,比如一个自动送料设备的状态:初始化、待机、抓取、移动、放料、复位。
VAR nState : INT := 0; // 0=初始化 10=待机 20=执行中 30=异常 END_VAR CASE nState OF 0: // 初始化 IF %IX0.0 THEN nState := 10; END_IF 10: // 待机,等待启动命令 IF bStartCmd THEN nState := 20; END_IF 20: // 执行中:调用各动作 bMotoRun := TRUE; IF bFinishSignal THEN bMotoRun := FALSE; nState := 10; END_IF 30: // 异常报警 bAlarmLamp := TRUE; IF bResetCmd THEN bAlarmLamp := FALSE; nState := 10; END_IF ELSE // 兜底逻辑:任何未定义状态都回到初始化 nState := 0; END_CASE这里要特别强调ELSE兜底分支。很多新手写CASE时只写几个具体分支,不写ELSE,一旦状态变量被干扰或者传入了未定义的数值,程序就会"卡住"——没有任何分支处理它,输出保持上一次的状态,这在设备控制里很危险。加上ELSE兜底,程序永远有退路。
3.4 定时器TON在ST里的调用方式
H5U的ST里调用定时器,并不是像梯形图那样拖一个TON块放上去,而是需要先声明一个定时器功能块实例,然后调用它。ST调用功能块的本质是"调用一个带内部记忆的代码块",用点操作符访问它的输入输出引脚。
VAR fbStartDelay : TON; // TON:通电延时定时器 bDelayDone : BOOL; END_VAR // TON输入引脚赋值 fbStartDelay(IN := %IX0.0, PT := T#5S); // 读取定时器输出:延时5秒后bDelayDone为TRUE bDelayDone := fbStartDelay.Q;T#5S是ST语言特有的时间字面量写法,表示5秒。PT是预设时间,ET是已计时时间,Q是定时器输出。需要特别注意的是,TON实例在程序里同一时刻只能在一个POU中被调用一次,如果你在多个PRG里使用了同一个fbStartDelay实例,编译虽然能过,但运行时的结果会跟你预想的完全不同。这是新手用ST比较容易忽略的一个大坑。
4. 第5-6步:用循环和功能块把重复劳动交给PLC
4.1 FOR循环:批量处理模拟量的利器
梯形图没有真正意义上的循环语句,ST有。FOR循环的价值在H5U这类设备PLC上,最典型的应用就是模拟量数组的批量处理。
比如现场有8个温度传感器,通过8个模拟量输入通道采集原始值arrAD[0]到arrAD[7],你需要把它们转换成实际的摄氏度,并求平均值和最大值。用FOR循环一行搞定转换逻辑:
VAR arrAD : ARRAY[0..7] OF INT; // 8路AD原始值 arrTemp : ARRAY[0..7] OF REAL; // 8路温度值 i : INT; rSum : REAL := 0.0; rAvg : REAL := 0.0; rMax : REAL := -999.0; END_VAR // 批量转换:AD值乘以0.1加上偏移量,模拟常见的热电偶线性换算 FOR i := 0 TO 7 DO arrTemp[i] := INT_TO_REAL(arrAD[i]) * 0.1 - 20.0; END_FOR // 求平均值和最大值 rSum := 0.0; rMax := -999.0; FOR i := 0 TO 7 DO rSum := rSum + arrTemp[i]; IF arrTemp[i] > rMax THEN rMax := arrTemp[i]; END_IF END_FOR rAvg := rSum / 8.0;注意FOR循环的循环变量i要提前声明为INT,循环范围是0 TO 7,默认步长为1。如果要步长为2,写作FOR i := 0 TO 7 BY 2 DO。这里有一个关键习惯:FOR循环执行完之后,如果后面还要用i做边界判断,记得重新赋值,否则i会是循环结束后的值(8),而不是0,容易导致数组越界。
4.2 WHILE循环:什么时候用它,什么时候慎用
WHILE循环在PLC里不建议轻易使用,这是我在实际项目中吃过亏之后才切身体会到的。
WHILE bCondition DO // 循环体 END_WHILEWHILE在PC编程里很常见,但PLC的程序是周期性扫描执行的,一个扫描周期内如果WHILE条件一直为TRUE,程序就会一直卡在这个循环里不出来。它的后果比FOR严重得多:FOR有明确的循环次数上限,WHILE只要条件成立就无限循环下去,这会导致PLC的扫描周期拉长,看门狗超时,整个PLC直接停机报警。
我的处理原则是:在H5U的ST里,凡是能用FOR解决的问题一律不用WHILE;如果一定要用WHILE,则必须在循环体里加上退出条件计数器,最多执行N次强制退出。下面是一个保险写法:
VAR nLoopGuard : INT := 0; bCondition : BOOL := TRUE; END_VAR nLoopGuard := 0; WHILE bCondition AND (nLoopGuard < 100) DO // 执行逻辑 nLoopGuard := nLoopGuard + 1; END_WHILE这样即使条件一直成立,也最多循环100次,不会拖死扫描周期。
4.3 功能块(FB)与函数(FUN)的本质区别
到了这一步,ST真正拉开梯形图差距的能力才体现出来——封装。H5U的ST支持两种封装手段:FUNCTION(函数)和FUNCTION_BLOCK(功能块)。
它们的本质区别在于:函数(FUN)没有内部记忆能力,同样的输入,每次执行返回相同的结果,比如加法函数、类型转换函数;而功能块(FB)有内部静态变量,可以记忆上次执行的状态,比如定时器、计数器、状态机。判断该用FUN还是FB,就一句话:需要在两次调用之间记住信息吗?需要就用FB,不需要就用FUN。
在AutoShop里添加FUN或FB时,同样是右键POU新建,选择对应类型。一个典型的电机控制功能块可以写成这样:
FUNCTION_BLOCK FB_Motor VAR_INPUT bStart : BOOL := FALSE; // 启动命令 bStop : BOOL := FALSE; // 停止命令 bFault : BOOL := FALSE; // 故障信号 END_VAR VAR_OUTPUT bRun : BOOL := FALSE; // 运行输出 END_VAR IF bFault THEN bRun := FALSE; ELSIF bStop THEN bRun := FALSE; ELSIF bStart THEN bRun := TRUE; END_IF这个功能块的特点是"停止优先"和"故障优先":只要故障或停止信号为TRUE,输出一定是FALSE。这在设备安全逻辑里是必须的,无论启动信号是否一直为TRUE,故障时都绝不能继续运行。
4.4 主程序里实例化功能块,同一套逻辑管十台电机
封装只是一个开始,真正体现价值的是实例化调用。比如一条设备线有8台电机,传统梯形图你就要复制8份电机启停电路,每份改一个地址,改起来费时还容易漏改。用ST功能块,你只需要在程序全局区实例化8个FB_Motor实例,然后在主PRG里分别调用:
VAR fbMotor1 : FB_Motor; fbMotor2 : FB_Motor; fbMotor3 : FB_Motor; // ... 一直到 fbMotor8 END_VAR // 主程序调用 fbMotor1(bStart := %IX0.0, bStop := %IX0.1, bFault := %IX0.2); %QX0.0 := fbMotor1.bRun; fbMotor2(bStart := %IX0.3, bStop := %IX0.4, bFault := %IX0.5); %QX0.1 := fbMotor2.bRun;这段代码的可维护性优势是碾压性的:如果之后你发现电机启动逻辑还要加一个"前级设备就绪"条件,只需要改FB_Motor功能块内部的一行代码,8台电机的逻辑就全部更新了。这在梯形图时代,你需要在8个电路里重复修改8次。设备越多,ST封装的优势越大。
4.5 任务配置:ST代码在哪个任务里跑,决定了响应速度
封装完功能块后,有一个H5U特有的细节容易忽略——任务配置。在AutoShop工程树里找到"任务配置",默认会有一个主任务(MainTask),扫描周期可以设置,比如2ms、5ms、10ms。ST程序默认都是在主任务里执行的。
如果你有高速响应需求(比如伺服急停、编码器计数),建议把这些逻辑放到一个独立的高速任务里,周期设置为1ms或2ms,然后把普通逻辑放在主任务里。任务配置的原则是:高速任务里只放必要的、短小精悍的代码,不要放FOR循环、字符串处理这类耗时的操作,否则任务会超时,H5U会报警"任务溢出"。
另外一个容易踩的坑是:高速任务和主任务之间共享变量时要考虑扫描周期不一致的问题。同一次扫描中,主任务读到的变量值可能比高速任务晚一个周期,这在某些场合会出现"看起来莫名其妙的延迟",实际就是任务周期错位。简单处理办法是两个任务之间尽量用临时变量传值,避免耦合。
5. 第7步:综合调试,把ST程序跑出工业级的效果
5.1 在线监视:ST代码里的变量实时值怎么看
程序写完了,编译通过只是一小步,真正验证逻辑的是在线调试。H5U的AutoShop支持在线监视ST代码。下载程序后,点击"登录"按钮,PLC进入在线状态,ST编辑器里每一行的变量上都会显示当前的实时值。这是排查逻辑最直接的手段——程序有没有进到某个IF分支,一眼就能看出来。
我调试ST程序时有个固定习惯:先在变量声明区把关键变量加入"监视列表"(AutoShop里叫Watch Window)。比如模式变量nMode、当前状态nState、报警标志bAlarm,在监视列表里可以一次看到所有关键变量的实时变化,不用在密密麻麻的代码里一个个找。
需要特别注意,在线监视时的实时值是PLC的实际运行值,不是仿真值。H5U支持在线修改(在运行状态下修改程序),修改后需要点击"下载"按钮,这个操作会中断程序执行,在产线上操作时必须先确认设备安全状态,最好在设备停机时做在线修改。
5.2 断点调试与变量强制,排查"逻辑不按预期走"的利器
当程序运行结果和预期不一致,又找不到原因时,断点调试是效率最高的手段。AutoShop的ST编辑器支持设置断点,在线状态下程序执行到断点处会暂停,这时你可以查看所有变量的当前值,然后单步执行,逐步观察变量变化。
我举一个真实例子:有一次设备在自动模式下,偶尔会出现气缸误动作,但不是每次都有。梯形图时代我几乎没法查这种偶发问题,只能加辅助继电器慢慢跟。用ST后,我把怀疑的那段IF分支设置断点,然后把异常条件变量强制为TRUE/FALSE反复触发,很快就定位到是一个中间变量在上一段逻辑里被意外覆盖了。
变量强制功能也很有用。比如你想模拟"油温过高"这个故障标志为TRUE的情况,不需要真的去加热油,直接在在线监视窗口把对应变量强制为TRUE,再观察程序的反应。注意强制变量的值后,PLC程序执行到该变量的赋值语句时会被覆盖,所以强制只对"暂时没有赋值逻辑的变量"有效,这是ST调试里一个比较容易误解的点。
5.3 ST程序最常见的四类错误排查
第一类:类型不匹配。编译阶段就会报错,提示Type mismatch或者IN/OUT assignment error。解决办法是看提示信息和出错行,用INT_TO_REAL、REAL_TO_INT、DINT_TO_INT等转换函数处理。
第二类:变量未声明或拼写错误。ST是严格区分大小写的,bStart和bstart是两个不同的变量。如果你声明了bStart,代码里写bstart,编译会报"变量未声明"。这类错误编译期就能拦住,关键是别图省事随手用缩写命名。
第三类:数组越界。运行时会出现程序异常或数据错乱。H5U的ST有严格的数组边界检查,一旦写入arrData[8]而数组定义是ARRAY[0..7],PLC会直接报运行时错误。排查时优先检查所有FOR循环的边界条件。
第四类:逻辑顺序问题。程序是按扫描周期从上到下执行的,不同于C语言的调用顺序。比如你第一行把bRun置为TRUE,第二行又判断IF bStop THEN bRun := FALSE,如果停止按钮已经松开了,那第二行不执行,bRun保持TRUE。这个顺序问题是最隐蔽的,排查方法就是把监视窗口的扫描周期打开,观察每个扫描周期变量的变化顺序。
5.4 一个综合案例:温度报警+平均滤波+电机联动的完整ST程序
最后给一个综合程序框架,把前面学到的所有语法串起来。这个程序完成的功能是:读取4路模拟量温度,做平均滤波后,当平均温度超过阈值且电机处于运行状态时,输出报警灯并停止电机。
VAR // 模拟量原始值 arrAD : ARRAY[0..3] OF INT; // 转换后的温度 arrTemp : ARRAY[0..3] OF REAL; // 平均温度 rAvgTemp : REAL := 0.0; // 电机控制 fbMainMotor : FB_Motor; bOverTemp : BOOL := FALSE; i : INT; END_VAR // 第一步:批量温度转换 FOR i := 0 TO 3 DO arrTemp[i] := INT_TO_REAL(arrAD[i]) * 0.1; END_FOR // 第二步:求平均温度 rAvgTemp := 0.0; FOR i := 0 TO 3 DO rAvgTemp := rAvgTemp + arrTemp[i]; END_FOR rAvgTemp := rAvgTemp / 4.0; // 第三步:超温判断 bOverTemp := rAvgTemp > 85.0; // 第四步:电机联动 fbMainMotor(bStart := %IX0.0, bStop := %IX0.1, bFault := bOverTemp); %QX0.0 := fbMainMotor.bRun; // 第五步:报警灯(超温且电机仍在运行,说明异常) %QX0.1 := bOverTemp AND fbMainMotor.bRun;这个程序麻雀虽小五脏俱全:用了数组、类型转换、FOR循环、功能块实例化、条件判断、输出映射。你把这套结构跑通了,H5U的ST入门就算真正完成了。
我在实际调试这种综合程序时有一条心得:每加一段逻辑就先用在线监视确认这一段的行为,不要全部写完才一次性调试。故障定位的时候,分成"采集-计算-判断-联动"四层来排查,比从头到尾一行行看代码要快一个数量级。
这个系列后续还可以继续往深处走,比如ST和EtherCAT运动控制的结合、ST里如何做凸轮表和电子齿轮的运算、H5U和HMI的配方数据交互,这些都是ST比梯形图有明显优势的领域。如果你把基础7步吃透,这些内容学起来会顺畅很多。