资讯详情

资讯详情

ST语言编程手册:语法图、数据类型与类型转换避坑

简介《ST语言编程手册》以Word文档形式系统梳理结构化文本ST的语法与工程应用面向工业自动化、机器人控制、数据采集与处理领域的初学者及需要查漏补缺的编程人员帮助其从语法规则过渡到可实际落地的控制程序编写。压缩包内仅含1个doc文件约5.63MB正文按原理、基本元素、数据类型、变量声明、赋值与表达式、控制结构、函数、数组和字符串等章节展开并配有语法图、标识符规则、数据类型取值范围、用户自定义类型与派生类型等细节便于按目录检索和对照练习。目前已有235人学习适合作为日常查阅手册或系统自学材料读者可从中掌握ST源文件结构、注释与语句组织方式理解枚举、结构、数组等复杂类型的声明与赋值并借助运算符、分支循环和函数封装建立较完整的编程思路。1. ST 语言编程手册不是教程它是一本按语法图排出来的规则字典多数人翻 ST 语言编程手册都是被一个编译错误逼过去的一段 IF 嵌套写完下载下去轴不动或者一个 REAL 赋给 INT小数点后面的数悄无声息地没了程序不报错设备就是不对。这时候再去读手册才会发现它压根不是按“从入门到精通”组织的而是按字符组、标识符、字面量、数据类型、变量声明、表达式、控制语句、功能与功能块、源文件结构、调试手段一层层排下来的规则字典。这份 word 文档的价值不在通读而在于它把“这个写法到底合不合法、这个变量到底什么时候被初始化、这个转换到底丢不丢精度”这三类问题一次说清楚。它适合已经能写几段 ST、但一遇到类型不匹配或者变量莫名被清零就想找依据的人纯新手拿它当第一本书会卡在语法图那一节拿它当查错依据刚刚好。2. 语法图不是装饰把第一章当查询表用手册开篇就摆语法图很多人扫一眼当成学术装饰跳过去结果后面每一节都在用同一套记号。语法图真正回答的是一个很朴素的问题这行代码在语言层面合不合法。它不负责告诉你业务对不对只负责告诉你语法树能不能建起来。把它当成一张查得到的表比从头到尾读一遍有用得多——尤其是你手上有一段从别人项目里抄来的声明编译不过又看不出哪里不对的时候。2.1 四种块结构决定你能不能在心里跑一遍语法语法图从左往右读方块只有四类组合方式认熟了就能把任何一段声明翻译成“必选什么、可选什么、能重复几次、在哪分叉”。结构图形特征在 ST 里的典型落点序列方块依次排列无分支赋值语句目标 : 表达式选项主线旁挂一条可跳过的支路变量声明里的初值、RETAIN 之类的限定符迭代带回环箭头标识符的后续字符、数组下标列表替代主线分叉成两条或多条数字字面量的十进制 / 2# / 8# / 16# 写法读图时先分清方块类型圆形或椭圆形是终端也就是字符组、预留词这类不需要再展开的基本元素矩形是复合单元说明它背后还有另一张图。这个区分很实用——当你看到一张图里嵌了一个矩形就知道“这里还有一层规则”不用硬猜。落到代码上最常被忽略的是“选项”这一类。变量声明里RETAIN、AT定位、:初值全是可跳过的支路跳过之后走的是默认路径。默认路径是什么直接决定你下载之后看到的是 0 还是上次的值。VAR RETAIN nTotal : DINT : 0; // 带初值但它在保持区下载策略另说 rSpeed : REAL : 1.5; END_VAR VAR i : INT; // 无初值按类型默认值走 bFlag : BOOL; END_VAR这段声明的逻辑不复杂坑在两条支路的组合RETAIN决定存储介质: 初值决定初始化时写入什么两者互相独立。带初值的保持变量在“不初始化下载”的情况下依然是旧值这不是 bug是两条规则叠加的结果。参数上唯一要记的是初值表达式的类型必须能赋给左边声明的类型写nTotal : DINT : 1.5这种不同平台的容忍度不一样别赌。2.2 标识符与预留词命名踩坑最多的地方标识符规则本身很短短到很多人不看然后在联调阶段付出代价。规则具体要求反例首字符字母或下划线4ter后续字符字母、数字、下划线S value含空格连续下划线不允许RR__20大小写不区分Anna与AnNa是同一个——预留词不能拿来命名变量或类型ARRAY、IF、CASE前缀尽量避免以下划线、struct、enum、command 开头_MyVarARRAY这个例子最能说明问题它字符合法、首字符合规、没有连续下划线作为标识符完全成立但它是预留词所以依然不能用。规则表和预留词表要一起看只看前一张会漏。// 合法首字符字母或单下划线后续用单下划线分隔 VAR nAxisCount : INT; _bEnable : BOOL; // 首字符下划线后面必须跟字母或数字 rPos_1 : REAL; END_VAR // 编译期就会拦下的写法记个印象 // 4ter - 首字符是数字 // RR__20 - 连续两个下划线 // S value - 含空格 // ARRAY - 预留词不能当变量名下划线开头这条我一般建议直接禁掉。它在语法上合法但在装技术包的时候容易出事技术包里的命令词、参数名、数据类型也可能带这类前缀两边一撞报错出现在下载阶段而不是编译阶段改起来要顺着引用一个个摸。命名规范里写死“首字符用字母、禁止双下划线”比事后排查便宜得多。2.3 数字字面量、布尔值与字符串编译期就会拦下的那批错误数字这块的错误基本都是硬错误编译器直接拦反而是最好处理的。字面量合法写法非法写法与原因整数0、1、-1743、60_000123,456逗号、36.小数点二进制2#1111写成1111会被当成十进制八进制8#17同上十六进制16#F同上浮点0.0、1.3、-0.2、60_000.15中间夹空格布尔TRUE/FALSE拿来和整数直接比大小字符串Axis_1用双引号VAR nDec : INT : 15; nBin : INT : 2#1111; // 同一个值 15 nOct : INT : 8#17; // 15 nHex : INT : 16#F; // 15 rBig : REAL : 60_000.15; // 下划线只做视觉分隔不参与数值 sName : STRING : Axis_1; // 字符串用单引号 bOn : BOOL : TRUE; END_VAR四种进制写法编译完没有任何区别差别只在调试器里看变量时的显示形式下划线是纯视觉分隔不改变数值也别指望用它绕开长度限制。真正会被忽略的是符号位手册里明确写了负号只能出现在有符号类型之前把-1塞进一个无符号类型有些平台直接报错有些平台给你回绕成一个大正数后者在 watch 表里看就是一个莫名其妙的巨大值——这类现象十有八九先回去查声明不用怀疑算法。把这三节当成索引编译报错先定位到字符组、标识符、字面量运行时的怪值再去翻数据类型和变量作用域。顺着这个顺序查比从第一章读起快得多。3. 数据类型与变量声明范围、派生和初始化时机ST 的类型系统不算复杂但“声明在哪”和“初始化为多少”这两件事是运行时问题的高发区。手册把基本类型、派生类型、变量声明、初始化时机分成四节其实是一条线类型决定取值范围声明位置决定生命周期存储域决定掉电之后还在不在初始化时机决定你下载完看到的是 0 还是旧值。这条线上的任何一环没搞清楚调试就会变成猜测。3.1 基本类型与取值范围SINT 里塞 200 会怎样类型位宽取值范围常见误用BOOL1TRUE / FALSE和位串类型混着做位操作SINT8-128 ~ 127当计数器用跑过 127 之后读数跳负INT16-32768 ~ 32767存毫秒计时几十秒就见底DINT32约 ±21 亿存累计脉冲勉强够USINT80 ~ 255做减法结果为负直接回绕UINT160 ~ 65535同上UDINT320 ~ 约 42 亿同上REAL32单精度约 7 位有效数字做累加求和误差越滚越大LREAL64双精度大量运算时扫描周期变长CHAR / STRING——字符 / 字符串用做内容比较实际比的是引用SINT做计数器是最典型的省空间操作跑到 127 加一之后回绕成负数watch 表里看就是一个负值不报任何异常曲线掉到负轴才发现。这类问题的排查成本全在“发现”上一旦怀疑到类型看一眼声明就解决了。反过来如果一开始就把计数类变量统一声明成DINT代价只是多几个字节省下的是半夜排查的时间。浮点这边的问题更隐蔽。REAL只有约 7 位有效数字做位置累加时每次加一个很小的增量加到一定量级之后增量直接被吞掉位置就停住不动了。这种“程序在跑但值不变”的现象第一反应往往是逻辑写错实际是精度耗尽。累加类的量要么用LREAL要么改成绝对量计算别在REAL上做长时间累加。3.2 ARRAY、STRUCT 与枚举派生类型的声明骨架派生类型的语法结构很固定一张声明块能把三种都覆盖。TYPE E_State : (IDLE, HOMING, RUNNING, FAULT); // 枚举底层按整数存 ST_AxisData : STRUCT nState : E_State; rPos : REAL; rVel : REAL; bReady : BOOL; END_STRUCT; A_JointPos : ARRAY[1..6] OF REAL; // 一维数组下标从 1 起 END_TYPE这段声明里枚举的底层是按整数存储的成员顺序就是 0、1、2、3。危险点在于“顺序会变”后来有人往中间插了一个新状态序号整体后移此时如果旧程序里的常量值、HMI 上配的状态号、历史记录里的值还按老顺序理解设备行为就会错位而且编译阶段完全看不出来。稳妥做法是给枚举成员显式指定值或者干脆别把枚举的整数值往外部暴露。ARRAY的下标上下界都要写死不要指望运行时越界检查——很多平台在数组越界时不一定报错可能读到相邻内存值看起来“有”但毫无意义。声明成[1..6]之后循环里就老老实实写FOR i : 1 TO 6别混用 0 基写法。STRUCT的赋值要注意整体赋值和成员赋值是两回事。整体赋值在多数平台上是逐成员拷贝成员多的时候会占扫描周期成员赋值更可控。如果结构里含数组整体拷贝的开销还要再乘。3.3 变量声明的位置决定生命周期这一段是手册里信息密度最高的部分之一也是最值得做成表贴在工位上的。声明区作用域存储特性典型用途VARPOU 内FC 中为临时FB 中为静态中间量、状态VAR_INPUTPOU 内只读调用时传入命令、设定值VAR_OUTPUTPOU 内可写调用后可读状态、反馈VAR_IN_OUT双向读写传引用大结构、数组VAR_TEMP仅本次调用有效每次调用重新初始化循环变量、临时计算VAR_STAT跨周期保持掉电不保持内部状态机VAR_GLOBAL全局分保持与非保持跨 POU 共享最容易混淆的是VAR在 FC 和 FB 里的语义差异同一个关键字在 FC 里调用结束就失效在 FB 里却跨周期保持。很多人从 FC 迁到 FB 时把一堆中间变量留在VAR段以为是临时的结果这些值跨周期残留上一轮的计算结果污染下一轮现象是“偶发算错”复现全看时序。VAR_GLOBAL RETAIN nTotalCycles : DINT; // 掉电保持 END_VAR VAR_GLOBAL rCurrentTemp : REAL; // 非保持重新上电回初值 END_VAR保持与非保持的差别只在存储介质上和“有没有写初值”是两码事。带初值的保持变量在“不初始化下载”时依然是旧值不带初值的非保持变量上电后是类型默认值。手册里专门用一节讲初始化时机包括保留全局变量、非保留全局变量、本地变量、静态变量、FB 实例、技术目标系统变量各自的初始化时刻——这些时刻不是同一个点下载动作、启动类型、是否保持都会影响结果。联调阶段“我明明改了初值”的困惑九成出在这里。提示改完初值下载前先确认这个变量在不在保持区以及下载选项里初始化是怎么勾的。这两步花十秒能省掉半小时的自我怀疑。4. 表达式到功能块优先级、类型转换和 FC/FB 选型语法写对只是第一步算错和用错封装才是真正拖时间的地方。表达式这块的错误有个共同特征不报错、结果偏、排查时盯着逻辑看半天最后发现是少了一对括号或者一次隐式转换。功能与功能块这块的错误则是结构性的用 FC 的地方用了 FB或者反过来程序能跑但状态残留或者实例冲突。4.1 运算符优先级一对括号省三天优先级运算符说明高()括号唯一能强制改写顺序的手段↓函数调用先算参数再调用↓**幂↓负号、NOT单目运算符↓*/MOD乘除取模↓-加减↓关系比较↓相等比较↓AND逻辑与↓XOR逻辑异或低OR逻辑或// 想算 (a b) * c少写括号就变成 a b * c rRes1 : rA rB * rC; // 先乘后加 rRes2 : (rA rB) * rC; // 括号改写优先级 // 逻辑表达式里 AND 结合得比 OR 紧 bOK : bA AND bB OR bC; // 等价于 (bA AND bB) OR bC优先级表不用背但要知道“逻辑与比逻辑或紧”这一条因为它和自然语言的直觉相反。写条件判断时只要出现两种以上逻辑运算符就加括号读代码的人不用回头查表改代码的人也不会改错结合顺序。求值顺序是另一个隐性坑。表达式一般从左往右求值如果中间调用了带副作用的函数比如函数内部改了全局量顺序就影响结果。这种写法本身就该避免函数尽量做成纯函数同样的输入给同样的输出不碰外部状态。真需要副作用就老老实实拆成几条独立语句别塞在一个表达式里。4.2 赋值与类型转换隐式转换的边界在哪赋值的左边是变量右边是表达式类型不一致时就会发生转换。手册把转换分成隐式和显式两类隐式的那部分是最需要盯的。转换方向是否安全说明INT → DINT安全位宽变大不丢信息DINT → INT危险超出范围会截断整型 → REAL基本安全数值很大时有精度损失REAL → INT危险小数部分被丢弃不是四舍五入位串 ↔ 整型看平台字节序处理不一致无符号 → 有符号危险高位被解释成符号位VAR nInt : INT; nDint : DINT; rReal : REAL; END_VAR nInt : 300; nDint : nInt; // 隐式INT - DINT安全 rReal : nInt; // 隐式整型转浮点 nInt : rReal; // 隐式浮点转整型小数被丢掉不报错 nInt : REAL_TO_INT(rReal); // 显式意图明确排查时好搜显式转换函数的命名规则一般是“源类型_TO_目标类型”具体函数名各平台有差异但套路一致。两种写法编译结果可能完全一样差别在可维护性代码里出现REAL_TO_INT任何人一眼就知道这里有一次有损转换可以重点关注写成隐式这段转换在代码里是隐形的review 的时候直接滑过去。REAL转INT是截断不是四舍五入2.9得到2-2.9得到-2。需要四舍五入就自己加 0.5 再用平台提供的取整函数别指望隐式转换帮你做。这类偏差在位置控制里会累积单次看不出来跑一天就是可见的偏移。4.3 控制语句选型IF、CASE、FOR、WHILE、REPEAT 各自的边界语句什么时候用退出方式注意点IF分支少、条件不规整——嵌套超过三层就考虑拆成 FBCASE状态机、离散枚举——必须有 ELSE 兜底FOR次数确定的循环计数到界 / EXIT循环变量别在循环体里改WHILE次数不定、先判断后执行条件不成立 / EXIT条件变量要在循环体内更新REPEAT至少执行一次UNTIL 条件成立 / EXIT同上注意死循环EXIT提前跳出循环——只跳出一层RETURN提前结束 POU——FB 里提前返回会跳过后面的输出赋值CASE nState OF 0: // IDLE bReady : TRUE; 10: // HOMING IF bHomeDone THEN nState : 20; END_IF; 20: // RUNNING FOR i : 1 TO 6 DO rJoint[i] : rJoint[i] rDelta[i]; IF rJoint[i] rLimit THEN nState : 90; // 越限跳故障 EXIT; // 只跳出 FOR不会跳出 CASE END_IF; END_FOR; ELSE nState : 90; // 兜底防止状态值错位后整段逻辑静默不执行 END_CASE;EXIT只跳一层这件事第一次遇到基本都会判断错。嵌套两层循环时内层EXIT之后外层继续跑现象是“明明已经退出了还在动”。要么用标志位加外层判断要么把内层抽成一个 FC用RETURN直接结束。CASE的ELSE不是可选装饰。状态值可能来自 HMI 写入、旧版本下载残留、枚举顺序变更任何一个来源给出一个不在分支里的整数没有ELSE就是整段逻辑跳过设备停在那儿没有任何报错。这种“静默不执行”比崩溃难查十倍。4.4 FC 与 FB什么时候要实例什么时候只要结果维度FC功能FB功能块是否有实例无有每个调用点一个实例内部变量调用结束失效静态变量跨周期保持输出能否保持不能能调用方式直接写名字实例名加参数列表典型用途数学计算、单位换算轴控制、状态机、通信FUNCTION FC_Scale : REAL VAR_INPUT rRaw : REAL; rGain : REAL; rOffset : REAL; END_VAR FC_Scale : rRaw * rGain rOffset; END_FUNCTION FUNCTION_BLOCK FB_Axis VAR_INPUT bEnable : BOOL; END_VAR VAR_OUTPUT bReady : BOOL; END_VAR VAR nState : INT; // 静态跨周期保持 END_VAR // 状态机逻辑写在这里 END_FUNCTION_BLOCK判断标准很直接这段逻辑有没有“上一次调用留下的状态”。有就必须用 FB因为只有 FB 的实例能跨周期保存变量没有用 FC 更轻不占实例内存调用开销也小。反过来把带状态的逻辑写成 FC靠全局变量存状态程序能跑但同一个 FC 被两个地方调用时状态就串了而且串得毫无规律。参数传递方向也要留意VAR_INPUT在块内只读VAR_OUTPUT由块内写、块外读VAR_IN_OUT双向。FB 的输出参数在块外访问有时机要求——调用之后才有值调用之前读到的是上一周期的残留。第一次调用时读到的是什么取决于实例的初始化规则不能想当然。5. 避坑清单五类翻车现场和它们的排查路径这些坑的共同点是编译能过下载能跑问题在运行时才暴露而且现象和原因隔得比较远。每一条我都按“看到什么、为什么、怎么改”写照着对一遍能省掉不少猜测。5.1 五条踩坑记录第一条改了初值下载后还是旧值。现象是声明里写nCount : INT : 5编译下载watch 表里显示 0 或者上次运行的值。原因有两个层面变量在保持区下载时默认不覆盖或者下载选项里的初始化方式没选对平台按“保持”策略处理了。解决办法是先确认声明区是保持还是非保持再确认下载时的初始化选项最后才怀疑初值表达式本身。如果这个变量本来就需要一个确定的起点别依赖初值在启动逻辑里显式赋值这样行为是代码写的不是选项决定的。第二条浮点转整型结果总比预期小 1。现象是rReal等于 2.9赋给INT之后得到 2日志里看就是差一。原因是隐式转换走的是截断直接砍掉小数部分不是四舍五入。解决办法是明确转换意图需要四舍五入就加 0.5 再取整需要向下取整就保持现状但写显式函数让别人看得懂。关键是别让隐式转换散落在业务代码里改的时候找不到。第三条CASE 少写 ELSE状态机停住不动。现象是设备停在某个状态没有任何报警手动切状态也没反应。原因是当前状态值不在任何分支里——可能是 HMI 写了个越界值也可能是枚举中间插了成员导致旧值含义变了。没有ELSE整段CASE直接跳过程序继续跑但这个 POU 什么也没做。解决办法是CASE必写ELSE兜底分支里置故障位同时把原始状态值记下来下次再出现就能直接看到是哪个值惹的祸。第四条双下划线命名编译过了装技术包失败。现象是项目自己编译一切正常装技术包或者做集成的时候报标识符冲突。原因是命名里有连续下划线或者以下划线开头撞上了技术包内部的命令词和类型名。这类冲突暴露得晚改起来要顺着引用一个个改。解决办法是在命名规范层面直接禁掉首字符用字母禁止连续下划线加一条静态检查比事后排查划算。第五条直接访问 I/O 和过程映像混用读数慢一拍。现象是程序里读某个输入点值和实际状态对不上差一个扫描周期。原因是过程映像在循环任务开始时统一刷新直接地址访问则是即时读两者混在一个 POU 里读到的就不是同一个时刻的值。解决办法是先明确这个点用哪种访问方式同一个信号只走一条路径对时序敏感的逻辑统一用过程映像需要即时性的单独隔离出来并且写清楚为什么。5.2 排查顺序从交叉对照表到断点工具不少顺序比工具重要。顺序本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →