资讯动态

ST语言编程手册:从语法到调试完整指南

发布时间:2026/10/15 9:51:15 来源:尧图企业网站定制
简介ST语言编程手册是一份面向工业自动化、机器人控制及数据采集处理场景的系统性编程参考适合PLC工程师、自动化项目开发人员以及正在学习IEC 61131-3结构化文本的进阶读者。手册从ST语言的基本原理与语法图出发细致讲解字符组、标识符规则、预留标识符、数字与布尔值、字符串等基础元素并完整覆盖数据类型、变量声明、赋值与表达式、运算符、控制结构、函数、数组、字符串等关键主题还包含用户定义的数据类型派生数组、枚举、结构体、技术目标数据类型、轴属性继承等较为深入的内容能帮助读者在设备控制程序编写中理解语法细节、变量作用域与数据类型匹配方法。全手册整理为1个doc文档压缩包大小5.63MB内含完整章节结构目录清晰适合按需检索与反复查阅。目前已有234人学习下载对希望系统掌握ST语言并用于实际控制器编程的开发者来说是一份可作案头参考的实用手册。1. ST语言编程手册一份值得逐页精读的语法原卷做运动控制或者设备开发的工程师几乎没有不被ST语言绊过几回的名字起好了被系统提示预留标识符、REAL转INT转出了意外结果、功能块的输出读到的是上一周期的旧值。ST语言编程手册这份文档把语法图、标识符规则、数据类型、变量声明、表达式、控制语句一直讲到功能块和程序调试按完整目录拆开属于适合放在手边当“语法底稿”的资料。网上能翻到的ST资料多半是零散截图和碎片经验这份的完整目录本身就很值钱。想用这份手册当参考底稿的话在文档站搜“ST语言编程手册”就能下到Word原版适合正在用ST写底层逻辑、做轴控制或维护老程序的工程师看完了能把黑匣子里的语言规则一个个对上号。2. 语法基座从语法图到标识符先看清ST语言的名字边界2.1 用语法图读语法四个基本结构的含义这份手册和其他编程文档最不一样的地方是它用语法图而不是大段文字来定义规则。语法图要从左往右读结构上有四个基本套路序列是块一个接一个出现选项是可以跳过某些块迭代表示一个或多个语句重复替代就是在几条分支里选一条。理解了这四个词任何一条语法规则拿到手都能拆开。数字写法就是个典型例子。手册里整数规则允许在数字中间插下划线当视觉分隔所以60_000.15是合法的而10 20 30因为有空格就是非法。这类规则用文字描述很容易忽略用语法图一眼就能看出下划线是可选项、空格根本不在ST字符组里。提示读这类文档时先花十分钟把“语法图中的块”这一节读掉后面每个语法图都会变成流程图而不是天书。很多人跳着看结果卡在“规则的意义”上其实那一节就是在解释语义和限制的标注位置。2.2 标识符的规则与无效命名表标识符是变量、功能、数据类型的名字。核心规则首字符必须是字母或下划线后面可以跟字母、数字或下划线连续两个下划线不允许大小写不敏感编辑器和运行环境视为同一个名字。有效标识符无效标识符无效原因value4ter首字符不能是数字Pos_Axis*#AB不允许特殊字符下划线除外mode1RR__20连续两个下划线TempValueS value不允许空格补充一个容易忽略的点Array虽然在词法上是有效标识符但它属于预留标识符只能按预定义用途使用不能拿来命名变量。手册里特别提到尽量别用下划线、struct、enum或command开头的名字因为技术包下载时这些前缀已经被系统命令、参数和数据类型占用了看着合法下装时才报错这种问题排查起来最浪费时间。我一般会在团队命名规范里直接禁掉这些前缀从源头避免。2.3 预留标识符与保护标识符哪些词你不能碰预留标识符是语言本身预先定义用途的名字大小写不敏感。保护标识符是其中必须避开的一类全表在手册附录里。常见的IF、FOR、WHILE这类控制词肯定不能用ARRAY、STRUCT、ENUM这些类型关键字也是。容易被忽略的是那些系统功能块和标准功能的名字。你起一个一模一样的变量名编译不一定报错但调用时解析到系统功能上行为完全不可控这种坑一旦踩进去查错成本非常高。判断一个名字能不能用除了避开保护词表我还有一条经验凡是手册“预留词语”附录里出现过的一律不加修饰地避开。想要含义接近的命名就在前面加自己的工程前缀比如HmiStart、AxisCmd既避免冲突又方便交叉对照表检索。2.4 数据类型分层基本、派生和技术目标ST的数据类型不是扁平的一张表手册把它分成了三层。第一层是基本数据类型语法直接内置的整数有SINT、INT、DINT浮点有REAL、LREAL再加BOOL和字符串。每个类型都有值范围限制写程序时最容易翻车的是REAL转INT这类跨精度操作这个放到后面转换章节细说。第二层是用户定义的数据类型基于基本类型组合出来。ARRAY就是数组枚举本质上是带名字的整数集合STRUCT是结构体可以嵌套。这里值得注意两点第一枚举的底层是整数但手册建议赋值和比较都用枚举符号不要走整数中间值第二STRUCT结构体可以整体赋值一个结构体变量直接等于另一个结构体变量不需要逐字段拷这一点和C语言很不一样很多从C转过来的工程师不知道。第三层是技术目标数据类型比如轴的引用、测量输入这些硬件抽象。它们有属性继承关系子类型会继承父类型的轴属性。这类数据类型一般不是自己声明的而是来自系统库写程序时只需要实例化它们来绑定具体硬件。下面这段是标准的类型声明写法TYPE AxisState : (Standstill, Homing, Running, Error); // 枚举类型 Point3D : STRUCT // 结构体类型 x : REAL; y : REAL; z : REAL; END_STRUCT; DataBuffer : ARRAY[0..99] OF INT; // 数组类型 END_TYPETYPE到END_TYPE是一次完整的数据类型定义块。AxisState是枚举后续变量声明里可以用AxisState.Standstill或直接Standstill引用枚举值使用带类型前缀的写法可读性和编译检查都更可控。Point3D里声明了三个REAL字段字段之间用分号分隔。DataBuffer定义了一个索引从0到99的整数数组注意ARRAY下标边界是可以自定义的从0开始、从1开始都合法关键是要和后续FOR循环的上下边界保持一致。基础类型之间不是随便互转的手册给出了值范围限制表以INT和DINT为例INT的范围是-32768到32767DINT的范围更大把一个DINT的变量塞进INT变量可能溢出编译期不报错运行时才出问题所以跨类型赋值前先查范围表。另外手册还区分了“普通数据类型”和“基础系统数据类型”后者是运行系统直接用的类型比如时间、轴引用附带系统属性和初始化规则。这个区分直接影响第3章讲的变量初始化时机。3. 变量生命周期声明、初始化和存储区域的落地选择变量声明不仅仅是“写个名字”在ST里声明位置决定了变量的生命周期和存储位置进而决定它什么时候被初始化、掉电后还在不在。这一章把声明分区、保持属性和初始化时机一次说清。3.1 声明分区VAR、VAR_INPUT、VAR_OUTPUT与VAR_IN_OUT的用途在功能块和程序里变量声明按关键字分区。VAR是内部变量只在本POU内可见VAR_INPUT是输入参数调用方传值进来FB内部可以读取VAR_OUTPUT是输出参数FB执行完把结果写出去调用方在调用后读取VAR_IN_OUT最特殊它不拷贝数据而是直接把调用方的变量引用传进FBFB内部对它赋值等于直接改调用方的变量。声明分区数据方向典型用途VAR内部私有中间变量、计数器、临时状态VAR_INPUT调用方→POU使能、目标值、模式选择VAR_OUTPUTPOU→调用方完成标志、实际位置、报警码VAR_IN_OUT双向引用轴引用、大数据结构、共享缓冲区VAR_IN_OUT不产生数据拷贝适合传大结构体或轴这样的技术目标代价是FB内改它外面调用变量跟着变这个副作用要心里有数。手册在“参数转移原则”里特别强调IN_OUT是引用转移而不是值转移这一点和输入参数有本质区别。3.2 保持型、非保持型与存储区域声明变量时关键字里可以带RETAIN属性。VAR_GLOBAL RETAIN声明的全局变量是保持型的掉电后值还在不带RETAIN的全局变量属于非保持型每次重新上电会重新初始化。非保持变量存普通全局区保持变量存掉电保持区本地变量和临时变量在本地数据栈上。存储区域这块手册按Kernel版本来描述不同版本对本地数据栈的需求不一样。我一般会重点看两个数字保持区大小和本地数据栈大小。临时变量在任务每次调用时占用栈空间递归深度大或者FB嵌套层数多的时候栈溢出表现为程序随机跑飞和逻辑错误完全不同查的时候容易误判成算法问题。3.3 初始化时机的差异变量的初值在哪写、什么时候生效ST有一套完整的时序这块是调试时最容易产生预期差的地方。变量类型初始化时机保持型全局变量上电/复位时仅当版本ID变化或首次下载时初始化非保持全局变量每次启动冷启动或暖启动时初始化本地临时变量任务每次调用时分配初值不确定静态变量首次调用时初始化后续调用保留功能块实例实例化的POU首次执行前初始化技术目标系统变量由系统周期刷新不按用户程序初始化这里最反直觉的是保持型变量。手册描述得很清楚保持变量在掉电后保留旧值重新上电时如果程序版本ID没有变化就不会重新执行初始化。也就是说你在声明处写了新初值下载程序时如果没有触发版本变更设备里跑的还是上一次的旧数据。这个坑我在现场踩过后面第5章避坑部分详述。3.4 声明和初始化的完整示例VAR_GLOBAL RETAIN g_machineMode : INT : 3; // 保持型掉电保留 g_machineName : STRING : CNC-01; // 字符串初值 END_VAR VAR counter : DINT : 0; // 普通内部变量 flag : BOOL : FALSE; // 布尔初值 lastPos : REAL : 0.0; // 位置缓存 END_VAR这个例子里有两个细节值得注意。第一字符串用单引号包裹ST里字符串不是用双引号。第二声明处的:就是初值counter写成DINT : 0之后每次非保持初始化都会回到0如果整型声明不加初值默认按0处理还是按未定义处理取决于运行系统版本手册推荐的做法是显式写初值避免依赖默认行为。功能块实例的初始化是另一个时机实例所在POU首次执行前完成初始化之后调用不再重置内部VAR这也是FB能保存状态的原因。4. 赋值、表达式与数据类型转换计算顺序的确定性这一章讲表达式。熟练的ST工程师很少翻书但有两个点值得回看赋值的完整形态和运算符优先级表。这两个地方恰好是不同类型程序员翻车率最高的位置。4.1 赋值形态不只是等号右边ST的赋值用冒号等号这条对C转过来的工程师是第一个要改的手癖。先看一段完整的赋值示例position : 100.5; // REAL变量赋值 mode : AxisState.Running; // 枚举赋值用符号 point : pointB; // STRUCT整体赋值 flagWord.2 : TRUE; // 对WORD类型按位赋值 sensorName : Sensor_01; // 字符串赋值position那一行是基础类型赋值没什么可说的。mode那一行用的是枚举符号不要写成mode : 2这种整数写法虽然部分实现能通过编译但后续在符号浏览器和Watch表中看到的就是数字排查困难。point这一行是STRUCT整体赋值手册里是允许的等于把pointB的所有字段一次性拷给point前提是两边类型完全一致。flagWord.2这种按位访问也属于赋值的一部分位访问的前提是变量本身是位串类型比如WORD、DWORD索引从0开始。字符串可以用变量整体赋值ST对字符串的操作比C语言省心一点不需要strcpy。4.2 算术、关系、逻辑与位串表达式表达式按结果类型分四类。算术表达式的结果是数值关系表达式的结果是BOOL逻辑表达式处理BOOL值位串表达式按位处理位串。手册里在“运算对象”一节强调表达式不一定是单个值可以是常量、变量、函数调用的组合。比如SQRT((x2-x1)(x2-x1) (y2-y1)(y2-y1))这个表达式里函数调用、乘法、加法、括号同时出现求值顺序完全由优先级和括号决定。关系表达式最容易被忽视的是连续比较的问题。数学上写0 x 100很自然但ST里这么写不一定得到预期结果因为是左结合运算符0 x的BOOL结果会继续和100做比较类型上乱掉。正确写法是拆成x 0 AND x 100两条。4.3 运算符优先级表运算符优先级是ST手册里最值得复印一张贴在显示器旁边的表格。优先级高→低运算符1括号、函数调用2一元负号 -3NOT4* / MOD5 -6比较 7 8AND 、XOR9OR这里最反直觉的是比较运算符低于加减、高于等号判断。也就是说a b c会被解释成(a b) c这没问题但a b 1会被解释成a (b 1)也不会出问题。真正容易翻车的是AND、XOR、OR这些逻辑运算符比比较运算符低表达式a b AND c d实际是(a b) AND (c d)绝大多数情况符合预期可一旦混入等号比如a b AND c d按优先级则是(a b) AND (c d)如果按C语言的思维惯性去读就会写错。我建议任何混用场景都加括号。优先级表是帮你读懂别人代码的不是鼓励你写模糊表达式。编译器不会因为你多写了括号报警反而会因为模糊表达式出运行时问题。同行评审时看到无括号混合表达式直接标注“请拆开”这个习惯能省很多调试时间。4.4 隐式与显式类型转换基础类型之间的转换分隐式和显式两种。隐式转换只在拓宽方向上发生比如SINT到INT、INT到DINT、DINT到REAL这种赋值不会丢数据。反向操作必须显式转换比如把REAL赋给INT直接写是不行的必须套转换函数。i : INT; r : REAL; l : DINT; i : 100; r : i; // 隐式转换INT - REAL安全 l : REAL_TO_DINT(r); // 显式转换REAL - DINTREAL_TO_DINT的舍入方式需要特别注意很多ST实现是向零取整而不是四舍五入。也就是说1.9变成1-0.5变成0这和C语言的强制转换行为一致但和数学直觉不一致。工程上要做四舍五入先加0.5再转或者使用标准库提供的取整函数。手册里还有“补充的转换”一节覆盖两个方向上更复杂的转换组合比如字符串与数值互转。这类转换有专门的系统功能不要自己用加减乘除拼。我一般会整理一个自己项目的转换清单把常用的LREAL_TO_INT、REAL_TO_DINT、INT_TO_STRING写成统一封装的函数转换逻辑只写一遍后续所有人调用同一个入口避免各自实现导致舍入策略不统一。5. 控制语句、FC/FB与避坑从IF到功能块调用的5个高频问题这一章是手册里使用频率最高、也是现场问题最多的一块控制语句的执行语义、FC与FB的组织差异最后落到5个高频踩坑记录。5.1 控制语句的完整语义IF、CASE、FOR、WHILE、REPEAT五类语句每类都有边界条件。先看一段综合示例IF mode AxisState.Running THEN speed : 1000; ELSIF mode AxisState.Homing THEN speed : 100; ELSE speed : 0; END_IF; CASE step OF 0: state : 10; 1, 2: state : 20; 3..5: state : 30; ELSE state : 0; END_CASE; FOR i : 0 TO 99 BY 2 DO data[i] : 0; END_FOR; WHILE busy DO idx : idx 1; END_WHILE; REPEAT idx : idx 1; UNTIL data[idx] 0 END_REPEAT;IF语句用ELSIF做多分支注意拼写是ELSIF不是ELSEIF这是新手最常见的编译报错之一。CASE的分支项支持单值、用逗号分隔的列表、用两个点表示的区间step缺省后的ELSE分支建议一定写防止未覆盖的值让状态机直接失控。FOR的BY子句表示步长缺省是1边界是包含的FOR i : 0 TO 99会执行100次不要在循环体里改i这是铁的纪律。WHILE是先判断后执行REPEAT是先执行后判断所以REPEAT至少执行一次这个差异在边界初始化场景里很关键。5.2 EXIT、RETURN、GOTO与WAITFORCONDITION的边界EXIT用在循环体内立即跳出当前循环多层循环时只跳出一层。RETURN用在功能或功能块内部直接返回调用方后续语句不再执行。GOTO和标签配合使用手册里有专门一节我建议把它当作最后手段。GOTO最大的问题是会让程序结构的可读性断掉交叉对照表和调用路径都很难跟踪维护老程序时看到GOTO先画控制流图再看逻辑。WAITFORCONDITION是面向运动控制同步的等待语句它让当前任务等待一个条件满足再继续适合轴到位后再执行下一步的场景。但要注意条件长时间不满足会让任务挂在那里配合超时监控使用。我在实际项目里给所有WAITFORCONDITION都加了一个计时器做超时保护条件不满足就跳错误分支避免设备卡在中间状态。5.3 FC与FB组织一个POU的边界FC是无状态的功能输入参数参与计算返回一个值没有内部记忆。FB是功能块有实例实例内部VAR可以保持状态。两者在声明部分上的差异、调用方式上的差异直接影响程序结构。比较项FC功能FB功能块状态保持无执行完即释放有实例内部VAR跨调用保持调用方式直接按函数名调用必须先声明实例再按实例调用输出函数名本身作为返回值通过VAR_OUTPUT输出适用场景计算距离、转换、校验轴控制、计时器、状态机FC的例子已经在前一章出现过SQRT计算距离那一例就是FC。FB调用必须实例化实例放在声明区FB_FeedControl_1 : FB_FeedControl; // 实例声明 FB_FeedControl_1(enable : TRUE, targetPos : 200.0); // 实例调用 done : FB_FeedControl_1.done; actualPos : FB_FeedControl_1.actualPos;FB调用时输入参数用名字赋值不按位置传递这样调用可读性强漏参数时编译器也能给出明确提示。输出参数在调用结束后读取不要在调用前读那个时刻读到的是上一次调用的残留值这个点就是下面第5条坑的来源。5.4 避坑排查5条高频踩坑记录第一条枚举变量被整数污染现象设备偶尔出现无法解释的状态跳变Watch表里枚举值变成数字符号浏览器里显示无效值。原因某段旧程序里用整数给枚举赋值编译器没有拦截运行时的枚举范围检查默认关闭。解决把赋值全部改成枚举符号写完代码后在编译设置里打开强类型检查枚举和整数之间不再隐式互转。第二条保持变量下载后还是旧值现象程序下载后第一次运行用的还是上一版遗留的旧参数没有按新初值执行。原因保持变量只在版本ID变化时才初始化普通下载没有触发版本变更。解决设备调试阶段尽量用非保持变量必须使用保持变量时在初始化逻辑里维护一个自己的版本号或手工复位标志上电后用软件层强制重写一次初值。第三条FOR循环越界现象数组第0到99个元素循环里处理到一半程序跑飞排查发现访问了data[100]。原因循环边界写错加BY步长后边界判断不是在最后一步刚好吻合或者循环体里从数据表反查计算下标算出了越界值。解决FOR循环边界写成常量或显式变量循环体里不修改循环变量需要动态变化的下标用WHILE并加上下限保护。第四条REAL转INT舍入方向不如预期现象位置值1.9转换后变成1期望四舍五入得到2偏差导致加工精度偶发异常。原因REAL_TO_DINT按向零取整处理不是四舍五入负数时更明显-0.5取整变0。解决显式转换前按正负做偏移正数加0.5、负数减0.5再转或直接调用标准库里封装好的四舍五入函数。第五条FB输出参数读到上一次的值现象状态机里上一个周期刚把FB的done置TRUE这个周期开头读done还是旧值导致逻辑漏掉一次触发。原因FB输出参数只在FB被调用时更新调用前的读取一定命中上一周期值。解决输出参数读取统一放在FB调用语句之后调用顺序固定下来不跨任务分散读取FB输出。6. 调试习惯断点、watch表和程序运行方式的组合技巧6.1 断点要配调用路径调试模式不是直接按运行就开始。我会先进入调试模式、定义调试任务组、在目标POU里设断点然后激活。重点在于断点可以绑定调用路径只在某个任务或某条调用链到达时才停下。比如轴控制FB在多个任务里都被调用我只想在插补任务里停下来看行为就单独定义这个调用路径避免在后台任务里被无谓打断。手册里“断点调用路径/任务选择参数”那一节写得非常细值得对照操作一遍。6.2 watch表按初始化时机分组我习惯开两个watch表一个只放保持变量一个放非保持变量。上电瞬间先看保持表确认哪些值还在、哪些该初始化的没初始化再切到非保持表对照。这个习惯能快速定位“旧值残留”还是“未初始化”问题。watch表里还可以直接修改变量值来做扰动测试比改程序下载快得多。6.3 程序运行看调用栈死循环或数组越界时程序运行窗口会显示当前代码位置和调用路径。这个信息比断点更快因为它直接告诉你CPU现在是停在哪一行、从哪条路径进来的。配合符号浏览器里的交叉对照表可以倒推这个变量最后被哪个POU写过。之前处理过一台设备“位置漂移”问题先怀疑算法、再怀疑轴参数、最后用watch表盯住FB输出发现是另一个任务在FB调用前读了输出参数。从那以后我每次调试都强制走一遍“断点绑定调用路径、watch分组、程序运行定位”三板斧直接把变量生命周期和调用时机两个维度压进调试习惯。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑