
简介ST语言Structured Text是工业自动化领域广泛使用的PLC编程语言符合IEC 61131-3标准适合需要编写复杂控制逻辑的自动化工程师与电气技术人员。《ST语言编程手册》完整版以PDF电子书形式提供压缩包内为1个PDF文件大小4.22MB体积小巧便于在电脑或移动设备上离线查阅。内容从ST基本原理讲起系统覆盖语法图与语义、字符组与标识符、保护标识符、数字和布尔值、字符串等基础元素并深入讲解ST源文件结构、基本数据类型与值域限制、普通数据类型、基础系统数据类型、用户自定义数据类型、ARRAY数组、枚举、STRUCT结构体、技术目标数据类型、变量声明、变量初始值、赋值与表达式等核心模块整体配有清晰的语法图和示例便于逐步动手验证。目前该手册已有4388人学习下载对刚接触ST的PLC编程人员它能帮助快速搭建语法知识框架对已有经验的开发者也可作为日常查询的浓缩手册在编写复杂数据结构和算法时减少翻查时间提升编程效率。为什么工业现场越来越离不开ST语言做过三五年PLC项目的人大概率都有过这种经历梯形图写了一千行看着屏幕上密密麻麻的触点线圈想找个中间变量得翻半天程序跑到一半出了Bug十几个SET/RST堆在一起谁先执行谁后执行逻辑乱成一锅粥。这个时候转用ST语言Structured Text结构化文本会明显感觉到像从手写账本换成了Excel表格——同样是记录数据、处理逻辑但条理清晰了不止一个量级。ST语言是IEC 61131-3标准中定义的五种PLC编程语言之一也是其中唯一一种纯文本编程语言。它不像梯形图那样依赖图形元素而是用类似Pascal或C语言的语法结构来编写控制逻辑。因为写法接近高级语言ST在做复杂算法、数据处理、流程控制时优势极其明显目前在汽车产线、新能源设备、污水处理、楼宇自控等领域几乎是标配。这篇内容我就把ST语言从基础语法到实战经验完整梳理一遍。如果你是刚接触PLC编程的新手可以把它当入门教材如果你已经在用梯形图做项目想转型ST这篇能帮你少踩很多坑就算你已经写过不少ST程序里面关于调试技巧和代码规范的部分也值得花几分钟扫一遍。1.1 和梯形图相比ST到底强在哪里梯形图的核心优势是直观继电器控制时代延续下来的画法老电气工程师看着就有亲切感。但它的问题也很突出程序量一大逻辑就变得难以追踪。尤其是涉及数学运算、数组处理、循环遍历这类需求时梯形图写起来非常挣扎十个功能块串在一起连线能把屏幕占满。ST语言恰好把这些痛点全部解决了。用文本写逻辑天然支持变量命名、函数封装、条件判断、循环迭代代码可读性和可维护性都大幅提升。举个例子要计算一个数组中所有大于设定值的元素个数梯形图可能要做五六个比较器加计数器ST里一个FOR循环几行就搞定了。这也是为什么现在的运动控制器、视觉系统、机器人控制器普遍支持ST因为它们需要处理的数据量级和复杂逻辑梯形图已经很难胜任。ST的另一个隐性优势是跨平台迁移成本低。IEC 61131-3是国际标准各家PLC厂商都在靠拢Codesys、TwinCAT、Siemens TIA Portal、Mitsubishi GX Works、Omron Sysmac这些主流开发环境都支持ST。这意味着你在一套平台上写的ST技能换个品牌设备依然能用学习投资不会打水漂。1. 编程基础从零开始搭建你的第一个ST程序1.1 程序结构长什么样在动手写代码之前先搞清楚一个ST程序的基本骨架。标准的ST程序由变量声明区和逻辑执行区两部分组成通常长这样PROGRAM Main VAR bStartButton : BOOL : FALSE; // 启动按钮信号 bStopButton : BOOL : FALSE; // 停止按钮信号 bMotorRun : BOOL : FALSE; // 电机运行状态 tRunTime : TIME : T#0S; // 累计运行时间 END_VAR // 主逻辑启保停控制 IF bStartButton AND NOT bStopButton THEN bMotorRun : TRUE; ELSIF bStopButton THEN bMotorRun : FALSE; END_IF; IF bMotorRun THEN tRunTime : tRunTime T#1S; // 每秒累加 END_IF;变量声明区用VAR和END_VAR包裹里面定义这个程序内部使用的变量支持初始值赋值。逻辑执行区从VAR_END之后开始从上到下逐行执行。这里有个新手容易忽略的点ST的程序是循环扫描执行的和C语言的单次运行完全不同。控制器会不停地从头到尾执行这段代码扫描周期通常在几毫秒到几十毫秒之间。理解了这个模型很多时序问题就看得懂了。1.2 注释怎么写代码才能活久见注释在ST里的地位被很多人低估了。梯形图时代大家已经习惯了图上加注释文本框ST时代文字的注释其实更方便也更应该养成习惯。ST支持两种注释方式// 单行注释快捷键通常是CtrlShift空格后按/ (* 多行注释块 适合写一大段说明比如修改记录、逻辑解释 *)我个人的习惯是每个功能块的头部必须有多行注释说明这个功能块的用途、输入输出含义、修改版本记录代码里的关键分支必须加单行注释解释为什么这样写而不是写代码在做什么。说句得罪人的话看别人没注释的ST程序和看一堆密码没什么区别。你在调试现场对着自己三个月前写的代码干瞪眼的时候就会感激当年认真写注释的自己。1.3 ST代码在什么环境下运行ST本身是语言标准但光有语言写不了程序还需要一套运行环境。目前主流的ST开发环境有这么几类CODESYS独立于硬件厂商的IEC 61131-3开发平台很多国产PLC和控制器都内置了CODESYS运行时学习门槛低资料多。Beckhoff TwinCAT基于Windows的实时控制环境ST是其主打语言之一在运动控制和PC-Based控制领域市场占有率很高。Siemens TIA Portal博途平台SCL语言就是ST的西门子实现使用S7系列PLC必然会接触到。Mitsubishi GX Works3三菱的ST支持主要用于iQ-R和iQ-F系列。Omron Sysmac Studio欧姆龙NJ/NX系列ST语法非常标准。如果你是零基础想学ST我的建议是直接下载CODESYS它免费授权、模拟器完善、教程丰富一台普通电脑就能跑起来。用CODESYS打牢ST基础之后再接触其他平台基本就是适应快捷键和操作界面的问题了。2. 数据类型与变量ST语言的基石2.1 核心数据类型全解析ST语言的数据类型体系源自IEC 61131-3标准和C语言的数据类型有相似之处但也有一些自己的特点。理解数据类型是写好ST的基础因为类型不匹配是ST编程中最常见的编译错误来源。先看最常用的几类类型关键字位数取值范围典型用途布尔量BOOL1TRUE/FALSE开关量、逻辑判断整数INT16-32768~32767常规计数、索引双整数DINT32-2^31~2^31-1大范围计算实数REAL32浮点数模拟量处理、PID长实数LREAL64高精度浮点高精度计算时间TIME32毫秒计时定时、延时字符串STRING可变文本报警信息、通信数据这里有一个特别值得注意的点REAL和LREAL的精度问题。REAL是32位浮点精度大约7位有效数字做累积计算时误差会逐渐放大。如果做类似流量累积的长时间积分运算请务必用LREAL否则几天下来累积误差可能大到让你怀疑人生。我在一个水处理项目里就吃过这个亏PID输出积分项用REAL存运行三天后开始漂移换成LREAL之后问题才消失。2.2 变量声明的三种作用域ST变量的作用域分为三个层级理解了这个程序架构才搭得起来局部变量在VAR...END_VAR之间声明只在本程序或功能块内有效。适合存放中间计算值、临时状态用完即走隔离性好。全局变量在全局变量列表中声明整个项目所有POUProgram Organization Unit程序组织单元都能访问。适合存放设备级共享状态比如急停信号、设备模式、生产配方编号等。但全局变量要节制使用全局变量一多程序依赖关系就乱成了蜘蛛网改一处牵全局排查问题的时候想哭。直接寻址变量形如%I0.0.1、%Q0.0.0、%MW100这样的变量直接映射到物理输入输出地址或内存地址。这类变量和硬件强绑定一般只在IO映射层使用。合理的做法是在程序入口处把直接寻址变量复制给有意义的符号变量后续逻辑只用符号变量操作。VAR // 局部变量 nCnt : INT : 0; rSum : LREAL : 0.0; END_VAR2.3 数组与结构体从简单变量到复合数据真实项目里单个变量的使用场景其实有限更多时候你需要处理一组数据或一条记录。ST提供了数组和结构体来应对这些需求。数组声明和使用VAR arrTemperatures : ARRAY[0..9] OF REAL; // 10个温度测量值 arrMatrix : ARRAY[1..3, 1..3] OF INT; // 3x3矩阵 END_VAR // 遍历数组计算平均值 rSum : 0.0; FOR nIdx : 0 TO 9 DO rSum : rSum arrTemperatures[nIdx]; END_FOR; rAverage : rSum / 10;结构体则适合用来描述一条完整的记录比如一个设备的全部属性。TYPE ST_Motor : STRUCT bEnable : BOOL; // 使能信号 nSpeedSet : INT; // 设定转速 nSpeedActual : INT; // 实际转速 rCurrent : REAL; // 运行电流 tRunTime : TIME; // 累计运行时间 END_STRUCT END_TYPE结构体的价值在于把相关数据打包成一个整体传参、复制、归档都非常方便。一个电机就是一个ST_Motor变量整个产线就是ST_Motor的数组程序结构立刻清晰了。3. 核心语法条件判断与循环控制3.1 IF-ELSIF-ELSE条件判断IF语句是所有逻辑控制的基础。ST的IF语法和C语言非常接近只是关键字写法稍有不同IF nMode 0 THEN bOutput : TRUE; ELSIF nMode 1 THEN bOutput : FALSE; ELSE bOutput : bInput; END_IF;这里有个细节值得提醒ST用:做赋值用做比较这两个符号不要搞混。另外ELSIF是一个关键字不是ELSE IF两个词写错编译直接报错。条件判断的嵌套是新手喜欢写但老手尽量回避的东西。三层以上的IF嵌套代码已经很难读了。更好的做法是用早退模式或卫语句方式把特殊情况先处理掉主逻辑保持在平铺状态// 不好的写法层层嵌套 IF bSafe THEN IF nMode 2 THEN IF bReady THEN bStart : TRUE; END_IF; END_IF; END_IF; // 更好的写法前置条件立即判出 IF NOT bSafe THEN RETURN; END_IF; IF nMode 2 THEN RETURN; END_IF; bStart : bReady;3.2 CASE语句处理多分支场景多分支条件判断用CASE代替IF代码会干净很多。比如设备模式选择、故障代码分检、配方编号索引这些场景天生就是CASE的菜CASE nState OF 0: // 空闲状态 bIdle : TRUE; 1: // 运行状态 bRunning : TRUE; rSpeed : 50.0; 2: // 故障状态 bError : TRUE; nErrCode : 5; ELSE // 未定义状态强制复位 nState : 0; END_CASE;CASE语句需要注意的是执行完一个分支之后不会自动跳出到其他地方ST的CASE是结构化分支天然不会“穿透”。另外强烈建议写ELSE分支处理意外值尤其是状态变量万一跑到未知状态还能自动纠偏。3.3 FOR、WHILE、REPEAT三种循环怎么选ST主要有三种循环结构各有用武之地FOR循环已知循环次数首选。数组遍历、批量初始化、固定次数的数学运算用FOR最清晰。WHILE循环满足条件时持续执行适合“等待某个条件成立”的场景。REPEAT循环先执行后判断至少执行一次适合需要先执行动作再检查结果的场景。// FOR遍历清洗数组 FOR nIdx : 0 TO 99 DO arrData[nIdx] : 0.0; END_FOR; // WHILE等待数据就绪 WHILE NOT bDataReady DO // 等待异步数据更新 END_WHILE; // REPEAT至少执行一次读取尝试 REPEAT nRet : ReadData(); UNTIL nRet 0 END_REPEAT;循环要特别注意死循环问题。PLC是循环扫描执行的一个死循环会直接卡死整个控制器的扫描周期导致所有IO和通信全部停摆这是设备废品和事故的高发源头。务必要在循环里加超时限制或者次数上限。// 加了次数上限的WHILE防止死循环 nGuard : 0; WHILE NOT bDataReady AND nGuard 1000 DO nGuard : nGuard 1; END_WHILE;4. 面向过程到面向对象函数与功能块4.1 函数(FUN)和功能块(FB)的本质区别这是ST语言里最容易糊涂的一对概念。函数FUN是纯计算组件同样的输入必然得到同样的输出不保存内部状态。比如计算绝对值、最大值、限幅输入定了输出就定了。函数没有内部记忆。功能块FB则是带“记忆”的组件内部变量会跨扫描周期保存。比如一个定时器功能块你每扫描周期调用它它会记住已经计时多久了。这种“记忆”能力是PLC控制的刚需因为工业控制本身就是有状态的过程。一个功能块的典型写法FUNCTION_BLOCK FB_TimerDelay VAR_INPUT bEnable : BOOL; tDelayTime : TIME : T#5S; END_VAR VAR_OUTPUT bDone : BOOL; nState : INT : 0; END_VAR VAR tElapsed : TIME : T#0S; END_VAR // 内部状态机 CASE nState OF 0: IF bEnable THEN nState : 1; tElapsed : T#0S; END_IF; 1: tElapsed : tElapsed T#100MS; IF tElapsed tDelayTime THEN bDone : TRUE; nState : 2; END_IF; 2: IF NOT bEnable THEN bDone : FALSE; nState : 0; END_IF; END_CASE;功能块最妙的地方是支持实例化。同一个FB_TimerDelay你可以生成十个实例每个实例独立计时互不干扰就像十个工人各有一块表。这种可复用性让ST程序的模块化程度大幅提升。梯形图时代那种“复制粘贴再改地址”的痛苦在ST里彻底消失了。4.2 标准功能块定时器、计数器、触发器几乎所有ST开发环境都会内置一套标准功能块实现IEC 61131-3规定的功能。最常用的三类定时器TON延时导通定时是最常用的输入IN为TRUE后ET变量开始计时到达PT设定值时Q输出TRUE。tonDelay : TON; tonDelay(IN : bStart, PT : T#3S); IF tonDelay.Q THEN // 启动3秒后执行 END_IF;计数器CTU加计数器在CU输入的上升沿计数复位信号R置TRUE时清零输出CV是当前计数值。触发器R_TRIG检测输入信号的上升沿输出Q在检测到从FALSE变成TRUE后的一个扫描周期内为TRUE。这个在按钮检测里太好用了不用自己写脉冲捕捉逻辑。rTrig : R_TRIG; rTrig(CLK : bButton); IF rTrig.Q THEN // 按钮刚被按下 END_IF;4.3 结构化编程实战状态机思维ST语言真正发挥威力的场景是配合状态机思维做流程控制。复杂设备动作用状态机拆解逻辑会变得异常清晰。状态机的基本要素状态、转移条件、动作。比如一个简单的自动上料装置状态0空闲等待启动信号状态1夹取等待夹紧到位状态2搬运等待移动到位状态3松开等待放料完成状态4返回等待回位完成用CASE语句实现这个状态机主逻辑一目了然。调试时想看当前到哪一步了直接监视nState变量即可。这个思维方式是从ST新手进阶到熟手的分水岭。5. 实战案例一个完整的电机控制程序5.1 需求分析与变量规划前面把语法要点都过了一遍现在用一个电机启停控制的完整案例把零碎的知识串起来。这个案例是一个典型的工业场景电机由PLC通过接触器控制带启动按钮、停止按钮、热过载保护、运行反馈信号。变量规划如下变量名称数据类型作用bStartPBBOOL启动按钮常开bStopPBBOOL停止按钮常闭逻辑取反使用bOverloadBOOL热继电器反馈bMotorFeedbackBOOL接触器辅助触点反馈bMotorCmdBOOL控制输出bRunningBOOL电机运行状态5.2 程序编写与逻辑解释PROGRAM PRG_MotorControl VAR bStartPB : BOOL; bStopPB : BOOL; bOverload : BOOL; bMotorFB : BOOL; bMotorCmd : BOOL; bRunning : BOOL; fbSafeDelay : TON; END_VAR // 安全逻辑过载保护强制停机 IF bOverload THEN bMotorCmd : FALSE; END_IF; // 启保停控制逻辑 IF bStartPB AND NOT bStopPB AND NOT bOverload THEN bMotorCmd : TRUE; ELSIF bStopPB OR bOverload THEN bMotorCmd : FALSE; END_IF; // 故障延时确认防止瞬时误报 fbSafeDelay(IN : bOverload, PT : T#500MS); IF fbSafeDelay.Q THEN bRunning : FALSE; ELSE bRunning : bMotorFB; END_IF; // 输出映射 bMotor_Output : bMotorCmd; END_PROGRAM这段程序包含了前面讲过的核心语法条件判断、定时器功能块、输出映射。看这段代码你能清晰地读出一个设备的完整逻辑这就是ST相对梯形图的巨大优势。5.3 程序调试的实战技巧程序写完之后调试才是真正考验人的地方。我调试ST程序时的固定流程供参考先做静态检查。编译通过只是及格线逐行阅读代码看变量命名是否合理、分支条件是否完备、有没有隐藏的RETURN路径提前退出。静态检查能发现大量运行时才暴露的问题。再做在线监视。基本上所有ST开发环境都支持变量在线监视把关键变量的实时值拉出来看跟着扫描周期逐步推演逻辑是否与设计一致。监视的关键是选中会“动”的变量——状态变量、定时器计时值、关键输入信号。最后做强制操作测试。用强制功能模拟输入信号验证各种极端情况下的程序行为按着启动按钮断开反馈会怎样过载信号在运行中突然出现会怎样启动和停止按钮同时按下会怎样这些问题都要在实际运行前测试清楚。6. 调错排查ST编程中的高频疑难杂症6.1 常见编译错误与解决方法编译错误是新手接触ST的第一道坎好在这些问题大多有固定解法。错误类型典型表现解决方法类型不匹配Cannot convert REAL to INT检查赋值两边类型必要时使用REAL_TO_INT转换函数变量未声明Variable xxx not declared检查变量是否在VAR或VAR_INPUT中声明过语法错误Syntax error near IF常见于关键字拼写错误、语句缺分号、END_IF少写了重复定义Duplicate declaration变量名被多次使用检查是否存在重名赋值方向错Assignment expected检查是否把:写成了这些编译错误看着吓人但都是友善的提示。真正难搞的是逻辑错误程序能编译能运行结果就是不对。这类问题通常要靠在线监视逐行排查。6.2 死循环与扫描超时问题PLC扫描超时是ST编程最严重的问题之一直接导致控制器进入停止状态。常见的诱因有两个一个是前面提到的死循环WHILE循环条件永远无法满足控制器卡死在里面另一个是单次扫描耗时过长比如功能块数量过大或者通信指令同步等待。排查扫描超时的思路先用开发环境的监控功能确认是否真的超时然后逐步禁用疑似问题段定位耗时点。禁用时可以用注释大法把怀疑的段落注释掉看CPU负荷是否下降。做完定位之后针对问题优化比如把同步等待改为异步轮询把大数组运算挪到后台任务。6.3 变量初始值和掉电保持问题还有一个比较容易踩的坑是变量初始值。PLC上电之后内存变量要么恢复默认值BOOL是FALSEINT是0要么按照声明里的初始值设定。这里的关键是程序里的初始值只在冷启动时生效热复位控制器在线复位时局部变量可能会保留旧值。掉电保持是另一个话题。有些变量需要断电之后保持现场状态比如当前配方号、累计产量这些变量要设置成保持型RETAIN。但保持型变量有个隐患如果程序升级改了变量布局旧数据可能被错误解读。我的习惯是给所有RETAIN变量加版本号每次程序变更就检查数据兼容性。7. 代码规范让ST程序更好读、好改、好传承7.1 命名规范与注释规范代码规范不是强制要求但它是保护未来自己的最佳手段。我给自己定的规矩变量命名使用匈牙利前缀布尔量用b开头整数用n开头实数用r开头时间用t开头数组用arr开头。比如bStartButton、nSpeedSet、rTemperature。这样一眼扫过去就能分辨类型。注释规范讲究“解释为什么不解释是什么”。如果代码写的是rSum : rSum arrData[nIdx];旁白式的注释比如“累加数组”纯属多余的噪音。有信息量的注释应该是“这里不用INT是因为总量可能超过32767”或者“等待传感器稳定再读数避免抖动误判”。写这种注释才能真正帮助后来人。7.2 模块化与复用写ST不要一把梭把所有逻辑塞进一个程序里。正确的做法是分层底层封装IO操作、通信驱动做成功能块对外只暴露有意义的参数。中间层设备控制逻辑每个设备一个功能块内部做好状态机。顶层工艺流程编排调用中间层功能块实例控制整个系统的运行节奏。这样的架构好处非常多。最直观的是调试效率现场设备不动了先看顶层判断是哪个设备的问题再看对应设备的功能块看它的状态停在哪个节点最后查底层看是传感器坏了还是通信丢了。问题定位基本按分钟计。7.3 代码版本管理PLC编程的版本管理一直是个老大难问题很多项目还是一个文件打天下改动全靠复制备份文件文件名叫“程序V1_最终版_真最终版”。这个习惯真的要改。主流ST开发环境大多支持项目导出为文本格式用Git做版本管理完全可行。我的做法是每个正式项目都建Git仓库每次现场修改都写清楚commit message什么时间、改了什么、为什么改。这样即使一个星期后发现问题也能回退到任一版本对比。这套流程配合前面说的模块化结构让ST项目的长期维护压力大幅下降。8. 经验总结入坑ST这几年学到的东西ST语言的学习曲线其实不陡基本语法一两天就能掌握难的是用它写出好架构的代码。从梯形图转过来的朋友最容易犯的毛病是拿ST当梯形图写满屏的IF嵌套中间变量看完代码还不如看梯形图直观。我自己踩过最大的坑是前期过于迷信ST能解决一切问题忽视了对输入信号的可靠性和时序分析。后来才想明白语言只是工具控制系统的可靠性根本上还是来自对设备工艺的理解和对异常情况的周密防御。ST只是让实现这些防御逻辑变得更顺手了。最后分享一个个人习惯每写完一个功能块我会在注释里补上“这个功能块在什么样的情况下不能使用”。比如“本功能块不支持暂停后恢复”“输入量超过1000时数值可能溢出”。这种逆向说明帮我避免过不少坑也推荐你试试。ST语言值得每个做自动化的人认真学一下。它不会让简单项目变复杂却让复杂项目变得可控。如果你正准备上手找一台旧控制器或者直接用模拟器把本文的电机控制案例敲一遍比看十遍理论都管用。本文还有配套的精品资源点击获取