资讯动态

ST语言编程手册:从语法图到避坑实践全解析

发布时间:2026/10/15 21:38:54 来源:尧图企业网站定制
简介《ST语言编程手册》是一份系统介绍STStructured Text语言编程知识的参考文档面向工业自动化、PLC控制及机器人编程领域的技术人员与学习者。手册从ST语言的基本原理讲起详细拆解语法图、字符组、标识符规则、预留标识符、数字与布尔值等基础元素并进一步阐述数据类型、变量声明、运算符及表达式写法覆盖顺序、选择、循环三类控制结构以及函数、数组、字符串等常见编程要素。同时对手册中赋值与表达式的语法图、基础与派生数据类型的变量赋值方式也有专门说明帮助读者快速掌握这门高级文本编程语言的核心用法适合作为日常查阅与系统学习的工具书。资源为单个doc文档压缩包大小5.63MB目前已有233人学习下载适合需要夯实ST语言语法基础或编写PLC程序时查阅参考的开发者和工程师。1. ST语言编程手册一本把语法图讲透的语言规范不是速查表ST语言编程手册这份文档核心价值不在罗列语法关键词而在用语法图把ST结构化文本的完整规则讲清楚——从字符组、标识符到赋值、表达式、控制语句再到FC/FB、程序组织和调试手段。适合两类人一类是刚接触IEC 61131-3 ST、想系统建立语言体系的新手照着手册能少走很多弯路另一类是已经在用梯形图或功能块、遇到ST编译报错和变量初始化问题需要查根因的工程师。我拆这份手册时最明显的感觉是它比一般教程多了语义细节能直接回答“为什么这样写不行”而不是只告诉你“应该这样写”。2. 先把语法图读懂ST语言的词法与表达式基石2.1 语法图怎么读序列、选项、迭代、替代手册开头用大量篇幅讲语法图。我第一次扫过时觉得啰嗦后来真照着语法图写 ST 代码才发现这是整本手册的主心骨。语法图是从左到右读的图中的块分两类圆形或椭圆代表终结符也就是语言自带的基本元素比如关键字、数字、标识符本身矩形代表复合单元需要继续展开另一张语法图。结构上无非四种序列块按顺序出现选项可以跳过迭代一个或多个重复替代多选一。用一段最简单的赋值语句来对照理解。假设要写speed : 100;对应的语法图大致是先是一个标识符speed然后冒号加等号接着一个表达式100最后分号结束。这里的“标识符”和“表达式”都是复合单元可以继续展开。手册里给出这种分层的意义在于当你想写复杂表达式时能预判哪些位置可以塞进一个更大的语法单元哪些位置只能放终结符。下面是我按手册语法图拆出来的一个完整赋值声明块VAR speed : INT; // 声明一个整型变量 END_VAR speed : 100; // 赋值语句这段代码里VAR...END_VAR是变量声明的固定外壳赋值行则严格遵循语法图里的“标识符 : 表达式 ;”序列。写的时候要注意冒号等号之间不能加空格或换行这是语法图里终结符的固定写法。手册里特别强调格式化规则虽然宽松但像:这种组合符号必须连在一起否则输入源文本时会触发编译错误。2.2 标识符与数字命名规则和进制写法标识符规则是新手踩坑重灾区。手册明确标识符由字母、数字、单独的下划线任意组成首字符必须是字母或下划线字母大小写不区分所以Anna和AnNa是同一个名字连续两条下划线是不允许的下划线后面必须跟字母或数字。无效例子很有代表性4ter首字符是数字*#AB含非法特殊字符RR__20连续下划线S value里空格是特殊字符。这些规则我后来在控制器里都实测过报错信息虽然不一样但基本都能对应到手册的描述。数字部分也有好东西。整数支持多种进制前缀VAR a : INT; // 十进制写法直接用 END_VAR a : 2#1010; // 二进制等于10 a : 8#17; // 八进制等于15 a : 16#F; // 十六进制等于15这里2#、8#、16#是进制前缀数字里可以用下划线做视觉分隔比如60_000.15编译时下划线会被忽略。手册提醒数字里不能出现逗号和空格也不能超过类型允许的最大最小值。另一个反直觉的点是负号只能在SINT、INT、DINT等有符号整数类型前出现如果你给LINT或BYTE写负号在某些平台上会直接报错。我当时在模拟量处理里写-1赋给一个WORD变量编译没过查手册才明白是类型符号限制。2.3 运算符优先级与表达式从算术到逻辑手册把表达式分成算术表达式、关系表达式、逻辑表达式和位串表达式并明确给出了运算符优先级表。优先级从上到下大致是括号、函数调用、一元负号、乘除取模、加减、关系比较、逻辑与、逻辑或、赋值。这个优先级顺序和大多数高级语言一致但ST里有个容易忽略的点赋值运算符:优先级最低所以a : b c * 2;的结果是先算c*2再加b。常见做法是写复合条件时不要依赖优先级直接加括号。比如IF (temp 100) AND (pressure 0.5) THEN state : OVER_LIMIT; ELSE state : NORMAL; END_IF;这段代码里两个关系表达式都加了括号AND逻辑运算的结果作为IF的条件。手册的表达式解释顺序是先求子表达式再按优先级结合。如果你不写括号AND优先级高于OR可能导致条件和你想象的不一样。我一般会强制自己给每个关系比较加括号减少后期调试时的心态爆炸。3. 变量与数据类型声明语法、用户定义类型和初始化3.1 基本数据类型与用户定义类型从INT到STRUCT手册里把数据类型分成基本类型和用户定义类型。基本类型包括BOOL、BYTE、WORD、DWORD、SINT、INT、DINT、REAL、LREAL、STRING等。这里值得注意的不是名字而是取值范围限制。比如INT是 16 位有符号整数范围 -32768 到 32767DINT是 32 位有符号整数。手册专门列出了数值范围表我建议把它打印出来贴在工位旁边因为运动控制里一个REAL转INT的粗暴截断就可能让轴位置跳变。用户定义类型是手册的重头戏包括数组、枚举、结构体。数组的声明语法是TYPE AxisData : ARRAY[1..8] OF REAL; MotorState : (STOPPED, RUNNING, FAULT); DriveInfo : STRUCT enable : BOOL; speedSet : REAL; torqueLimit : REAL; END_STRUCT; END_TYPE这里定义了三种类型AxisData是一个 8 元素实数数组下标从 1 到 8MotorState是枚举三个值DriveInfo是结构体包含布尔、两个实数。手册里特别强调枚举在比较时用的是整型位置值所以一旦在枚举中间插入新值原有枚举的顺序会变保存的参数可能出现错位。这是标准隐患后面避坑章我再细说。3.2 变量声明的语法与存储区单元变量、局部变量、保持性手册把变量按作用域和存储范围分了好几类单元变量、全局设备变量、本地变量、静态变量、临时变量。这些分类不只是概念直接影响初始化和断电保持行为。单元变量还有“保持”和“不保持”之分。保持变量在断电后能恢复上一次的值不保持变量在启动时会重新初始化。声明语法如下VAR_GLOBAL RETAIN gErrorCount : DINT; // 保持的全局变量断电后不丢 END_VAR VAR_GLOBAL gTempValue : REAL; // 不保持的全局变量上电清零或恢复默认 END_VAR VAR localBuf : ARRAY[1..10] OF BYTE; // 本地变量每次调用重新分配 END_VARRETAIN关键字必须放在VAR_GLOBAL后面这是某运动平台的标准写法。手册还区分了静态变量和临时变量静态变量用VAR RETAIN声明在功能块实例里能跨调用保持临时变量每次进入 POU 都重新初始化。这一点在我做多轴同步时坑过自己把累积误差放在普通局部变量里函数块一执行就被清零累积逻辑完全失效后来改成静态变量才正常。3.3 初始化时机变量下载后为什么不是全清零手册里讲“变量初始化的时间”这一节非常实用。不保持的全局变量在每次暖启动或冷启动时都会初始化保持变量只在第一次下载且版本 ID 变化时才执行一次初始化。这意味着你修改了一个保持变量的初始值并重新下载程序如果没有正确匹配版本 ID设备可能继续使用旧的保持值。验证方法很简单用仿真或离线模式建一个示波器式的观察表PROGRAM InitCheck VAR vInitCounter : INT : 100; // 初始值100 vRunValue : INT; END_VAR vRunValue : vInitCounter 1; END_PROGRAM如果你连续两次下载这个程序第二次下载后vInitCounter的实测值可能是 100也可能是上一次运行改过的值取决于它是否被声明为保持变量、以及版本 ID 是否变化。手册原文的结论是下载后全局变量的初始化不是无条件发生的。实际操作时我一般会在下载后强制复位一次 CPU或者用平台提供的“下载并重新初始化”选项避免玄学一样的现场问题。4. 控制语句与程序组织IF、CASE、循环、FC与FB的调用边界4.1 分支与循环IF/CASE/FOR/WHILE/REPEAT的写法ST 的控制语句和 Pascal 系语言很像但有几个边界要注意。IF 语句支持ELSIF连续判断CASE 语句在做多分支时比 IF 更清晰。手册里 CASE 的写法比较严格分支值必须是常量不能是变量表达式。CASE mode OF 0: speed : 0.0; 1: speed : 100.0; 2, 3: speed : 200.0; // 多个值共用一个分支 4..7: speed : 400.0; // 连续范围 ELSE speed : 50.0; END_CASE;CASE 选择后面必须用OF分支值可以是单值、逗号分隔的多值或范围ELSE处理未列出的情况。我比较喜欢用 CASE 管理设备状态机因为编译器会检查分支是否重复比一堆 IF 嵌套安全得多。循环语句有FOR、WHILE、REPEAT三种外加EXIT跳出。手册里特别讲了 FOR 语句的处理规则循环变量从初值递增或递减到终值步长默认 1但循环变量在循环体内部不能被修改。这个限制很多教程都没提实际写的时候如果想在循环里改下标得用另一个临时变量否则行为不可预期。FOR i : 1 TO 10 BY 2 DO sum : sum values[i]; END_FOR;BY 2指定步长如果省略则默认为 1。这里i会在每次迭代后自动加 2直到超过 10。需要指出的是FOR循环的上下限在进入循环时只计算一次所以循环体里修改values数组长度不会影响迭代次数。手册的语义这一点写得很清楚避免了我后来在动态数组场景下的误解。4.2 FC与FB功能与功能块的参数传递区别这是我拆手册时最有收获的部分。功能 FC 没有内部状态同样的输入永远得到同样的输出功能块 FB 有内部实例状态输出不仅取决于输入还取决于历史状态。手册里用一整节讲参数转移包括输入参数、输入输出参数、输出参数三种还专门画了“参数访问时间”图——输入参数在调用开始时被读取输出参数在调用结束时被写入。一个典型的功能块声明FUNCTION_BLOCK FB_MotorCtrl VAR_INPUT runCmd : BOOL; speedRef : REAL; END_VAR VAR_OUTPUT running : BOOL; actSpeed : REAL; END_VAR VAR_IN_OUT posCount : DINT; // 输入输出参数外部变量直接绑定 END_VAR VAR internalTimer : TON; // 内部实例默认初始化 END_VAR调用时输入参数可以按名称传值也可以按位置传值。我习惯用名称传递可读性好也避免位置写错FB_MotorCtrl_1(runCmd : opStart, speedRef : refValue, posCount : axisPos);这里FB_MotorCtrl_1是功能块实例axisPos是外部 DINT 变量通过VAR_IN_OUT绑定后功能块内部对posCount的修改会直接作用于axisPos。手册里的关键在于VAR_IN_OUT不像输入参数那样在调用开始时才拷贝它本质上是引用传递调用前后外部变量始终同步。这一点如果没看明白很容易出现“为什么输出没变化”的困惑。4.3 从源代码到运行POU的组织与表达式调用手册的第三章专门讲 ST 在控制器里的集成方式源文件分INTERFACE和IMPLEMENTATION两部分INTERFACE放对外可见的类型和变量声明IMPLEMENTATION放具体代码。程序组织单元 POU 包括 FC、FB、程序 PROGRAM 三类。程序可以调用 FC 和 FBFC 和 FB 内部又可以互相调用但不能递归调用手册原文明确禁止。PROGRAM Main VAR axisReady : BOOL; END_VAR axisReady : FB_MotorCtrl_1.running AND (axisPos 0); END_PROGRAM这里演示了在程序里访问 FB 输出参数的方式直接用实例名加点号加输出名。注意FB_MotorCtrl_1.running是输出参数的符号访问必须在实例声明之后使用。手册还提到表达式也可以作为独立单元存在用于纯计算型逻辑不在循环任务里占 POU 栈空间。5. 避坑ST编程中我踩过的五个常见错误5.1 连续下划线和保留字看起来合法编译却报错现象定义了一个变量叫error__code编译直接报“无效标识符”。一开始我以为是编译器版本问题反复检查没看出来。原因手册标识符规则写得明白不允许连续两条下划线。解决把变量名改成error_code重新编译立即通过。从那以后我写命名规范时直接把“禁止连续下划线”写进团队约定并且避免用struct、enum、command这类前缀开头因为手册提示这些可能和系统命令冲突。5.2 整数隐式转换DINT到INT的溢出是运行期才炸现象某个轴位置反馈从传感器读进来是DINT直接赋给INT变量运行几十秒后数值突然跳成负的。原因手册里隐式数据类型转换一节写得很清楚从宽类型到窄类型不会自动截断或报错而是按二进制位截取导致溢出后的值完全不可预期。解决先做范围判断再用显式转换INT_TO_DINT或DINT_TO_INT包裹。比如IF rawPos 32767 THEN iPos : 32767; ELSIF rawPos -32768 THEN iPos : -32768; ELSE iPos : DINT_TO_INT(rawPos); END_IF;显式转换函数名在手册附录里列了一整套记住一个规则源类型在前目标类型在前缀里中间用_TO_连接。5.3 保持变量的初始化时机下载重启后没按预期复位现象给一个RETAIN变量改了初始值下载后变量实测值还是上一次运行的旧值现场报警。原因保持变量在断电时保存重新下载时只有当版本 ID 改变才会重新初始化。如果只是改了初始值而版本 ID 没更新旧值会继续存活。解决平台里重新编译全下载或者在下载设置里勾选“初始化保持变量”。手册里的原话是保持变量的初始化发生在“版本 ID 不匹配”时所以我在每次发布版本时都会手动改一个版本常量确保保持变量能被可靠初始化。5.4 在FB外部访问FB输出参数顺序控制里读到的值不对现象在程序里直接读FB_1.running第一次扫描读到的是上一次调用的残留值导致状态判断错乱。原因FB 输出参数只有在 FB 执行结束后才更新程序代码如果先访问输出、后调用 FB读到的就是旧值。手册中“参数访问时间”明确说明了这一点。解决把访问代码放在调用语句之后或者把输出先暂存到程序级变量里保证同一个扫描周期内读到的是当次计算结果。5.5 断点不生效没进debug模式白忙一场现象在 ST 代码里设了断点运行到那一行就是不暂停怀疑编译器优化掉了。原因手册的调试章节里要求先设置 debug 模式、定义 debug 任务组再激活断点。很多平台默认不在调试状态断点只是摆设。解决调试前先打开调试任务组设置把目标 POU 所在的任务加进去激活断点然后单步运行。断点还有调用路径条件项目大时可以用调用路径过滤避免每次都被其他任务打断。6. 把这本手册用成调试工具交叉引用、代码属性与符号浏览器组合验证6.1 交叉引用表快速找出变量被谁改过手册里的参考数据功能包含交叉对照表能列出每个变量出现在哪些 POU、哪些行、是读取还是写入。排查未初始化变量时我通常会按变量名生成交叉引用重点看是否有多个 POU 同时写同一个保持变量。如果两个任务周期不同比如一个 1ms 一个 10ms 都在写同一个REAL那这个变量就是典型的数据竞争点。解决方式是加互斥逻辑或拆分变量。6.2 代码属性估算POU占用与任务负载代码属性视图能显示 POU 的字节大小和局部数据栈占用。当系统提示内存溢出或任务超时时我会用代码属性做一个快速排序找出体积异常大的 POU然后检查是否重复实例化了大型结构体或数组。手册给出的本地数据栈存储要求让我意识到一个包含大数组的 FB 实例会占不少栈空间所以全局数组尽量放在单元变量里而不是塞进 FB 实例。6.3 一套组合验证流程从符号浏览器到断点调用路径我现在调试 ST 程序的习惯是先在符号浏览器里确认符号类型、地址和保持属性再打开交叉引用表看变量流向然后用断点调用路径限定只在指定 POU 的指定调用链上暂停。比如处理“位置跳变”问题我会在位置计算 POU 入口设断点调用路径设为Main → AxisCtrl → PositionCalc这样只有经过完整调用链到达该断点时才会触发暂停过滤掉其他任务里的噪声。手册里这套组合工具的价值说得直白点就是让你不用靠TRACE和到处塞临时变量来猜问题。我经历过一次轴误动作排障花了三个小时用MOVE指令和手动标记翻变量后来用交叉引用五分钟定位到是一个背景任务里的旧 FB 实例在改同一段共享数据。从那以后我每次拿到新程序都会强制走一遍“交叉引用 代码属性 符号浏览器”的静态检查流程再上真机调试。希望这份 ST 语言编程手册里的方法也能帮你在下次调试时少走这些弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑