资讯动态

ST结构化文本编程入门:从梯形图思维到ST思维的实战指南

发布时间:2026/10/18 11:15:37 来源:尧图企业网站定制
1. 为什么ST语法值得花时间啃透如果你是从梯形图LD或者功能块图FBD转过来的工控人第一次看到STStructured Text结构化文本代码时大概率会有两种反应要么觉得“这不就是 Pascal 吗能有多难”要么觉得“一堆分号、冒号、END_IF看着就头大”。我当初属于后者。但真正在项目里被逼着用ST写了几个复杂逻辑之后我的态度彻底变了——ST不是“另一种画梯形图的方式”它是一门真正能让你把控制逻辑当程序来写的语言。ST是IEC 61131-3标准里定义的五种编程语言之一和梯形图、功能块图、顺序功能图、指令表并列。它的定位很明确处理那些用图形化语言表达起来极其别扭、甚至根本画不出来的逻辑。比如复杂的数学运算、数组和结构体的批量处理、字符串操作、状态机、循环遍历、条件分支嵌套——这些用梯形图去搭要么搭到怀疑人生要么根本没法维护。ST的语法接近Pascal结构清晰可读性强写出来的代码逻辑密度高一个几十行的ST程序块换成梯形图可能要画好几屏。这篇文章适合谁看如果你是刚接触ST的电气工程师、自动化从业者、设备调试人员或者是从其他编程语言转过来做工业控制的人那这篇内容就是给你写的。我会从语法基础讲到实操细节从常见坑讲到排查技巧尽量把我在实际项目里踩过的、见过的、总结出来的东西都倒出来。不搞教科书式的罗列而是按照“实际写代码时会遇到什么”这个顺序来组织。需要提前说明的是不同品牌的PLC比如西门子、三菱、欧姆龙、倍福、汇川等对ST的支持程度和语法细节会有差异但IEC 61131-3标准定义的核心语法是通用的。我下面讲的内容以标准语法为主涉及品牌差异的地方会专门指出来。你手里用的是哪个平台的软件对照着看就行。2. ST语法的核心骨架与设计逻辑2.1 为什么ST长成这个样子ST的语法设计借鉴了Pascal这不是偶然的。Pascal本身就是为教学和工程计算设计的语言结构严谨、关键字明确、可读性好。IEC 61131-3标准在制定时需要一种能覆盖从简单逻辑到复杂算法的文本语言Pascal的语法风格正好符合这个需求。ST的几个核心设计决策值得理解分号作为语句结束符。每条执行语句以分号结尾这是从Pascal和C语言继承来的习惯。分号的作用是告诉编译器“这句话说完了”。很多人从梯形图转过来最不习惯的就是这个分号经常漏写。漏了分号编译直接报错而且报错位置往往在下一行容易让人找错方向。关键字不区分大小写。IF、if、If在ST里是一样的。但变量名通常是区分大小写的这一点要注意。我个人的习惯是关键字全部大写变量名用驼峰或下划线命名这样一眼就能区分。强类型。ST是强类型语言每个变量都有明确的数据类型不同类型之间不能随便赋值。这个设计的好处是能在编译阶段就发现很多逻辑错误坏处是写代码时要做类型转换稍微麻烦一点。但相信我这个麻烦是值得的它帮你避免的运行时错误远比转换的麻烦多。结构化的控制流。ST提供了完整的条件分支IF、CASE、循环FOR、WHILE、REPEAT、跳转RETURN、EXIT等控制结构。这意味着你可以用ST实现任何算法逻辑不受图形化语言的限制。2.2 ST在IEC 61131-3里的位置IEC 61131-3定义了五种编程语言它们不是互相替代的关系而是各有适用场景语言全称最适合的场景典型用户LD梯形图离散逻辑、继电器替代电气工程师FBD功能块图信号流、过程控制过程工程师SFC顺序功能图状态机、流程控制系统工程师IL指令表底层优化、紧凑代码资深程序员ST结构化文本复杂算法、数据处理程序员型工程师实际项目中很少只用一种语言。常见的组合是用SFC搭主流程框架用LD写简单的联锁逻辑用ST写复杂的计算和数据处理。ST的优势在于当逻辑复杂到一定程度图形化语言表达起来会变得极其臃肿而ST可以用更少的代码量、更清晰的逻辑结构来实现。2.3 一个最小的ST程序长什么样先看一个最简单的例子感受一下ST的基本结构FUNCTION_BLOCK FB_MotorControl VAR_INPUT Start : BOOL; Stop : BOOL; Fault : BOOL; END_VAR VAR_OUTPUT Running : BOOL; END_VAR VAR State : INT; END_VAR IF Fault THEN State : 0; Running : FALSE; ELSIF Start AND NOT Stop THEN State : 1; Running : TRUE; ELSIF Stop THEN State : 0; Running : FALSE; END_IF; END_FUNCTION_BLOCK这段代码定义了一个功能块有两个输入启动、停止、一个故障输入、一个运行输出。逻辑很简单有故障就停按启动就运行按停止就停。但麻雀虽小五脏俱全变量声明区、条件分支、赋值语句都有了。注意几个细节VAR_INPUT、VAR_OUTPUT、VAR是变量声明区分别对应输入变量、输出变量和内部变量。END_VAR结束声明。IF...THEN...ELSIF...ELSE...END_IF是条件分支结构。:是赋值运算符不是。这些是ST最基本的语法元素。3. 变量、数据类型与声明规则3.1 变量声明的完整体系ST的变量声明区是整个程序的基础。声明区写得好不好直接决定了代码的可读性和可维护性。IEC 61131-3定义了多种变量声明关键字每种对应不同的作用域和用途声明关键字作用域用途说明VAR局部功能块/程序内部使用的变量VAR_INPUT输入从外部传入的变量VAR_OUTPUT输出向外部输出的变量VAR_IN_OUT输入输出既可读又可写的引用传递变量VAR_GLOBAL全局整个项目可访问的变量VAR_TEMP临时每次调用时重新初始化的临时变量VAR_STAT静态保持值的静态变量功能块中VAR_EXTERNAL外部引用外部全局变量实际写代码时最常用的是VAR、VAR_INPUT、VAR_OUTPUT和VAR_IN_OUT。VAR_IN_OUT特别值得说一下它是引用传递传进来的是变量的地址而不是值。这意味着在功能块内部修改VAR_IN_OUT变量会直接改变外部变量的值。这个特性在写通用功能块时非常有用比如写一个数组排序功能块把数组作为VAR_IN_OUT传进去排序后外部数组就直接被修改了。3.2 数据类型全解析ST的数据类型分为基本类型和派生类型两大类。基本类型是标准定义的派生类型是用户自定义的。基本数据类型类型位数范围说明BOOL1TRUE/FALSE布尔量BYTE80~255字节WORD160~65535字DWORD320~4294967295双字SINT8-128~127短整型INT16-32768~32767整型DINT32-2^31~2^31-1双整型USINT80~255无符号短整型UINT160~65535无符号整型UDINT320~2^32-1无符号双整型REAL32浮点数单精度浮点LREAL64浮点数双精度浮点TIME--时间类型STRING--字符串选类型的时候有个原则够用就好不要一味求大。比如一个计数器最大只到1000用INT就够了没必要用DINT。类型选大了浪费内存在某些资源紧张的PLC上可能影响性能。但反过来如果计算过程中可能溢出就要选大一号的类型。比如两个INT相乘结果可能超过INT范围就要先把操作数转成DINT再运算。派生数据类型派生类型包括数组ARRAY、结构体STRUCT、枚举ENUM等。这些类型是ST真正强大的地方。数组的声明方式VAR SensorValues : ARRAY[1..10] OF REAL; Matrix : ARRAY[1..3, 1..3] OF INT; END_VAR结构体的声明方式TYPE MotorData : STRUCT Speed : REAL; Current : REAL; Temperature : REAL; Status : INT; END_STRUCT END_TYPE枚举的声明方式TYPE MachineState : ( Idle : 0, Running : 1, Fault : 2, Maintenance : 3 ); END_TYPE枚举类型在状态机编程中特别好用用有意义的名称代替数字代码可读性直接上一个台阶。3.3 变量初始化的那些坑ST允许在声明变量时直接赋初值VAR Counter : INT : 0; Setpoint : REAL : 100.0; Name : STRING : Motor1; END_VAR但这里有几个坑要注意坑一功能块中的VAR变量初始化时机。在功能块中VAR声明的变量在每次功能块被调用时不会重新初始化它们保持上一次的值。只有VAR_TEMP声明的变量才会在每次调用时重新初始化。这个区别很关键如果你写了一个功能块里面有个计数器变量你以为每次调用都从0开始但实际上它保持上次的值逻辑就全乱了。坑二VAR_INPUT的初始化。有些平台允许给VAR_INPUT变量设默认值有些平台不允许。即使允许也要注意如果外部调用时没有连接这个输入它才会用默认值如果连接了默认值就被覆盖了。不要依赖输入变量的默认值来做逻辑判断。坑三字符串初始化的长度。STRING类型在ST中通常有默认长度限制比如80个字符如果赋的字符串超过这个长度会被截断。需要更长的字符串时要声明为STRING[255]这样的形式。实操心得我习惯在声明变量时全部显式初始化即使是VAR变量。这样做的好处是代码意图清晰别人看你的代码时不用去猜这个变量的初始状态是什么。而且万一平台对初始化的处理有差异显式初始化能避免很多意外。4. 运算符、表达式与类型转换4.1 运算符优先级与常见陷阱ST的运算符和大多数编程语言类似但有几个地方容易出错。先看完整的运算符表优先级运算符说明1最高()括号2函数调用如ABS(x)3**幂运算4NOT, -取负逻辑非、算术取负5*, /, MOD乘、除、取模6, -加、减7, , , 比较8, 等于、不等于9AND, 逻辑与10XOR逻辑异或11最低OR逻辑或最容易踩的坑是逻辑运算符和比较运算符的优先级。看这个例子IF A B AND C D THEN ... END_IF这个写法是对的因为比较运算符的优先级高于逻辑运算符AND所以先算A B和C D再算AND。但如果你写成IF A AND B C THEN ... END_IF这就出问题了。因为的优先级高于AND所以先算B C再算A AND (B C)。如果你的本意是(A AND B) C那就完全错了。这种错误编译器不会报错但逻辑是错的排查起来很痛苦。避坑技巧在混合使用逻辑运算符和比较运算符时永远加括号。不要依赖优先级括号是最可靠的。IF (A B) AND (C D) THEN这样写虽然多敲几个字符但逻辑一目了然也不会出错。4.2 类型转换的正确姿势ST是强类型语言不同类型之间不能直接赋值。比如你不能把一个INT变量直接赋给REAL变量必须显式转换VAR i : INT : 100; r : REAL; END_VAR r : INT_TO_REAL(i); (* 正确显式转换 *) (* r : i; *) (* 错误编译不通过 *)ST提供了一套标准类型转换函数命名规则是源类型_TO_目标类型转换函数说明INT_TO_REAL整型转浮点REAL_TO_INT浮点转整型截断小数部分INT_TO_DINT整型转双整型BOOL_TO_INT布尔转整型TRUE1, FALSE0INT_TO_BOOL整型转布尔0FALSE, 非0TRUEWORD_TO_INT字转整型位模式不变REAL_TO_STRING浮点转字符串这里有个重要的区别数值转换和位模式转换是两回事。INT_TO_REAL是数值转换100变成100.0。而WORD_TO_INT是位模式重新解释同样的16个位用WORD解释是无符号数用INT解释是有符号数。搞混了这两种转换数据就全乱了。浮点转整型时要注意截断还是四舍五入。标准规定REAL_TO_INT是截断直接去掉小数部分不是四舍五入。如果需要四舍五入要自己写i : REAL_TO_INT(r 0.5); (* 正数的四舍五入 *)但注意负数的情况-3.7加0.5变成-3.2截断后是-3而四舍五入应该是-4。所以更严谨的写法是IF r 0.0 THEN i : REAL_TO_INT(r 0.5); ELSE i : REAL_TO_INT(r - 0.5); END_IF;4.3 位运算与逻辑运算的区别初学者容易混淆位运算和逻辑运算。AND、OR、XOR、NOT在ST中既可以用于布尔量也可以用于整数按位运算。但用于布尔量和用于整数时行为是不同的VAR b1, b2, b3 : BOOL; w1, w2, w3 : WORD; END_VAR (* 布尔逻辑运算 *) b3 : b1 AND b2; (* TRUE AND FALSE FALSE *) (* 位运算 *) w1 : 16#FF00; w2 : 16#0FF0; w3 : w1 AND w2; (* 结果是 16#0F00 *)用于布尔量时AND就是逻辑与用于整数时AND是逐位与。编译器根据操作数类型自动判断不需要你指定。但你要清楚自己在做什么运算。另外ST中和AND是等价的是AND的简写形式。有些平台的键盘上更好打所以用的人也不少。但为了代码一致性建议统一用AND。5. 控制结构条件、循环与跳转5.1 IF语句的完整用法与嵌套技巧IF语句是ST中最常用的控制结构没有之一。基本形式IF condition THEN (* 条件为真时执行 *) END_IF;带ELSE的形式IF condition THEN (* 条件为真时执行 *) ELSE (* 条件为假时执行 *) END_IF;带ELSIF的多分支形式IF condition1 THEN (* 条件1为真时执行 *) ELSIF condition2 THEN (* 条件2为真时执行 *) ELSIF condition3 THEN (* 条件3为真时执行 *) ELSE (* 所有条件都为假时执行 *) END_IF;写IF语句有几个经验值得分享条件表达式的顺序很重要。ELSIF是按顺序判断的一旦某个条件为真后面的就不再判断了。所以应该把最可能为真的条件放在前面把最不可能为真的放在后面。这样能减少平均判断次数提高执行效率。虽然对于现代PLC来说这点性能差异微乎其微但在高频扫描的场合还是有意义的。避免过深的嵌套。IF里面套IF套个三四层代码就变得很难读了。我见过有人套了七层IF最后自己都理不清逻辑。遇到需要深层嵌套的情况可以考虑用CASE语句替代或者把内层逻辑抽成单独的功能块。条件表达式不要太复杂。一个IF条件里塞五六个AND、OR读起来很费劲。如果条件确实复杂可以先算到一个布尔变量里再用这个变量做判断(* 不推荐条件太复杂 *) IF (A 10) AND (B 20) AND (C TRUE) AND (NOT D) AND (E 5) THEN ... END_IF; (* 推荐拆成中间变量 *) Condition1 : (A 10) AND (B 20); Condition2 : (C TRUE) AND (NOT D) AND (E 5); IF Condition1 AND Condition2 THEN ... END_IF;5.2 CASE语句状态机的最佳搭档CASE语句是ST中实现状态机的利器。基本形式CASE State OF 0: (* 状态0的处理 *) 1: (* 状态1的处理 *) 2: (* 状态2的处理 *) 3, 4, 5: (* 状态3、4、5共用处理 *) 6..10: (* 状态6到10共用处理 *) ELSE (* 其他状态的处理 *) END_CASE;CASE语句的几个关键点CASE的选择表达式必须是整数类型INT、DINT、UINT等不能是REAL。所以用CASE做状态机时状态变量要声明为整数类型配合枚举使用效果最好。CASE的分支可以合并。用逗号分隔多个值或者用..表示范围。这个特性在写状态机时特别有用比如多个状态的处理逻辑相同就可以合并到一个分支里。ELSE分支不是必须的但强烈建议加上。ELSE分支处理未预期的状态值相当于一个安全网。如果状态变量因为干扰或错误变成了一个未定义的值没有ELSE分支的话程序就什么都不做可能造成危险。加上ELSE分支可以在这里做错误处理比如报警、复位到安全状态等。CASE语句在底层通常编译成跳转表所以它的执行效率比一连串的ELSIF高。当分支超过三四个时用CASE比用ELSIF更合适。5.3 FOR循环批量处理的利器FOR循环用于已知循环次数的场景FOR i : 1 TO 10 BY 1 DO (* 循环体 *) END_FOR;BY指定步长默认为1可以省略。步长可以是负数实现倒序循环FOR i : 10 TO 1 BY -1 DO (* 倒序循环 *) END_FOR;FOR循环在ST中主要用于数组遍历、批量初始化、数据查找等场景。比如求一个数组的平均值Sum : 0.0; FOR i : 1 TO 10 DO Sum : Sum SensorValues[i]; END_FOR; Average : Sum / 10.0;注意事项FOR循环的循环变量在循环结束后会保持最后的值。如果循环正常结束i的值是TO后面的值加步长。比如FOR i : 1 TO 10循环结束后i是11。这个细节在循环结束后还要用i的时候要注意。5.4 WHILE和REPEAT条件循环WHILE循环在条件为真时执行WHILE condition DO (* 循环体 *) END_WHILE;REPEAT循环先执行一次循环体再判断条件REPEAT (* 循环体 *) UNTIL condition END_REPEAT;WHILE和REPEAT的区别在于WHILE可能一次都不执行如果初始条件为假REPEAT至少执行一次。选择哪个取决于你的逻辑需求。在PLC编程中WHILE和REPEAT要慎用。因为PLC是循环扫描的如果WHILE的条件一直为真就会陷入死循环看门狗超时PLC报故障停机。所以用WHILE和REPEAT时一定要确保循环条件能在有限次迭代后变为假或者在循环体内有EXIT语句。5.5 EXIT、RETURN和CONTINUEEXIT用于提前退出循环FOR i : 1 TO 100 DO IF SensorValues[i] Threshold THEN FoundIndex : i; EXIT; (* 找到后立即退出循环 *) END_IF; END_FOR;RETURN用于提前退出整个功能块或函数IF Fault THEN RETURN; (* 有故障直接返回不执行后面的逻辑 *) END_IF;CONTINUE用于跳过本次循环的剩余部分直接进入下一次迭代。但要注意不是所有平台都支持CONTINUE用之前查一下你用的平台的文档。6. 函数与功能块ST的模块化基石6.1 FUNCTION和FUNCTION_BLOCK的区别ST中可调用的程序单元主要有两种FUNCTION函数和FUNCTION_BLOCK功能块。它们的核心区别在于是否有内部状态。FUNCTION没有内部状态相同的输入永远得到相同的输出。它就像数学函数一样y f(x)给定x就得到y不记忆任何东西。FUNCTION的典型例子是数学运算求绝对值、三角函数、类型转换、字符串处理等。FUNCTION_BLOCK有内部状态它可以记住上一次调用的信息。功能块内部可以有静态变量这些变量在多次调用之间保持值。功能块的典型例子是PID控制器、定时器、计数器、状态机等。看一个FUNCTION的例子FUNCTION F_Clamp : REAL VAR_INPUT Value : REAL; Min : REAL; Max : REAL; END_VAR IF Value Min THEN F_Clamp : Min; ELSIF Value Max THEN F_Clamp : Max; ELSE F_Clamp : Value; END_IF; END_FUNCTION这个函数把输入值限制在Min和Max之间。注意函数的返回值是通过函数名赋值的F_Clamp : ...就是设置返回值。再看一个FUNCTION_BLOCK的例子FUNCTION_BLOCK FB_Counter VAR_INPUT CountUp : BOOL; Reset : BOOL; END_VAR VAR_OUTPUT Count : INT; END_VAR VAR LastCountUp : BOOL; END_VAR IF Reset THEN Count : 0; ELSIF CountUp AND NOT LastCountUp THEN Count : Count 1; END_IF; LastCountUp : CountUp; END_FUNCTION_BLOCK这个功能块实现了一个上升沿计数器。LastCountUp是内部变量它记住了上一次CountUp的状态用来检测上升沿。这就是功能块有状态的表现。6.2 功能块的调用与实例化功能块在使用前必须先实例化VAR MyCounter : FB_Counter; StartButton : BOOL; ResetButton : BOOL; CountValue : INT; END_VAR MyCounter(CountUp : StartButton, Reset : ResetButton); CountValue : MyCounter.Count;实例化的意思是给功能块分配内存空间创建一个具体的“实例”。同一个功能块可以实例化多次每个实例有自己独立的内部状态。比如你可以创建两个计数器实例一个计产品数量一个计故障次数它们互不干扰。调用功能块时输入参数用:赋值输出参数通过实例名.输出名读取。这种调用方式叫“命名参数调用”参数顺序可以任意。也可以按位置调用MyCounter(StartButton, ResetButton);但按位置调用可读性差参数多了容易搞错顺序不推荐。6.3 写通用功能块的几个经验写功能块时有几个设计决策会影响它的通用性和可维护性输入输出尽量用标准类型。不要用自定义的结构体作为输入输出除非确实需要。标准类型BOOL、INT、REAL等通用性更好别人调用你的功能块时不用去了解你的自定义类型。内部变量用VAR而不是VAR_STAT。除非你明确需要变量在功能块调用之间保持值否则用VAR。VAR_STAT是静态变量它的值在功能块多次调用之间保持不变但这也意味着如果你不小心可能会引入难以排查的状态依赖。功能块不要太大。一个功能块只做一件事做精做透。如果一个功能块超过200行ST代码就要考虑拆分了。大功能块难测试、难维护、难复用。给功能块写注释。在功能块头部用注释说明它的功能、输入输出含义、使用注意事项。ST的注释用(* *)或//。我习惯用(* *)写块注释用//写行注释。7. 常见问题与排查技巧实录7.1 编译错误速查表错误现象可能原因解决方法报错“缺少分号”上一行末尾漏了分号检查报错行的上一行报错“类型不匹配”不同类型直接赋值加类型转换函数报错“未声明的变量”变量没在VAR区声明在VAR区添加声明报错“重复声明”同一变量名声明了两次删除重复声明报错“END_IF缺失”IF没有对应的END_IF检查IF/END_IF配对报错“CASE表达式类型错误”CASE用了REAL类型改用整数类型报错“功能块未实例化”直接调用功能块名先声明实例变量7.2 运行时逻辑错误的排查思路编译通过不代表逻辑正确。运行时逻辑错误往往更隐蔽排查起来更费劲。我总结了几种常见的逻辑错误类型和排查方法状态保持问题。功能块内部变量保持了上次的值导致逻辑不符合预期。排查方法在功能块入口处监控所有内部变量的值看是否符合预期。如果发现某个变量应该是初始值但实际不是检查它是不是声明在VAR区而不是VAR_TEMP区。上升沿/下降沿检测失效。用LastState变量检测边沿时如果LastState的更新位置不对边沿检测就会失效。正确的做法是在功能块末尾统一更新LastState而不是在IF分支里面更新。数组越界。ST的数组索引如果超出声明范围有些平台会报错有些平台会静默读取错误的内存。排查方法在访问数组前加范围检查或者用LOWER_BOUND和UPPER_BOUND函数获取数组边界。浮点数比较。浮点数不能用直接比较因为浮点运算有精度误差。0.1 0.2不一定等于0.3。比较浮点数时要用容差(* 不推荐 *) IF r 0.3 THEN ... (* 推荐 *) IF ABS(r - 0.3) 0.0001 THEN ...7.3 性能优化的几个实用技巧ST代码的执行效率在大多数场合不是瓶颈但在高频扫描或复杂算法的场景下优化还是有意义的减少不必要的类型转换。类型转换有开销能在声明时就选对类型就不要在运行时转换。循环内避免重复计算。把循环内不变的表达式提到循环外(* 不推荐 *) FOR i : 1 TO 100 DO Result[i] : Input[i] * 2.0 * PI * Frequency; END_FOR; (* 推荐 *) Factor : 2.0 * PI * Frequency; FOR i : 1 TO 100 DO Result[i] : Input[i] * Factor; END_FOR;用CASE代替多层ELSIF。当分支超过四个时CASE的跳转表实现比ELSIF的逐个判断要快。合理使用EARLY RETURN。在功能块开头检查错误条件如果不满足就RETURN避免执行后面的无用逻辑。8. 从梯形图思维切换到ST思维8.1 思维方式的根本转变从梯形图转到ST最大的障碍不是语法而是思维方式。梯形图是“电流从左边流到右边”的思维是并行的、图形化的。ST是“指令从上到下依次执行”的思维是串行的、文本化的。这个差异导致几个常见的思维误区误区一以为所有语句同时执行。在梯形图中同一扫描周期内所有梯级是并行计算的实际上PLC是串行扫描但梯形图的语义是并行的。在ST中语句严格按顺序执行前面的语句会影响后面的语句。所以ST中变量的赋值顺序很重要。误区二用梯形图的思路写ST。有些人写ST就是把梯形图的逻辑“翻译”成文本结果写出来的代码又长又难读。ST的优势在于可以用更抽象的思维来表达逻辑不要被梯形图的思维限制住。误区三忽视扫描周期的影响。ST代码在一个扫描周期内执行一次如果逻辑需要多个扫描周期才能完成就要用状态机来实现不能在一个周期内用循环等待。8.2 什么场景该用ST什么场景不该用ST不是万能的有些场景用梯形图更合适场景推荐语言理由简单联锁逻辑LD图形化直观电工容易看懂复杂数学运算ST文本表达更清晰状态机ST CASE代码紧凑逻辑清晰数组批量处理ST循环结构天然适合安全逻辑LD/FBD图形化便于审查和验证通信协议处理ST需要位操作和字符串处理PID控制FBD STFBD搭框架ST写算法设备启停控制LD维护人员容易理解我的经验是能用图形化语言清晰表达的逻辑就用图形化语言图形化语言表达起来别扭的逻辑就用ST。不要为了用ST而用ST也不要因为不熟悉ST就回避它。8.3 一个完整的ST项目结构示例最后看一个完整的项目结构示例把前面讲的东西串起来(* 主程序 *) PROGRAM MainProgram VAR (* 功能块实例 *) Motor1 : FB_MotorControl; Counter1 : FB_Counter; (* 输入变量映射 *) StartBtn : BOOL; StopBtn : BOOL; FaultSignal : BOOL; (* 输出变量映射 *) MotorRun : BOOL; ProductCount : INT; (* 内部变量 *) InitDone : BOOL : FALSE; END_VAR (* 初始化 *) IF NOT InitDone THEN ProductCount : 0; InitDone : TRUE; END_IF; (* 电机控制 *) Motor1(Start : StartBtn, Stop : StopBtn, Fault : FaultSignal); MotorRun : Motor1.Running; (* 计数 *) Counter1(CountUp : MotorRun, Reset : FALSE); ProductCount : Counter1.Count; END_PROGRAM这个结构展示了ST程序的典型组织方式变量声明区、初始化逻辑、功能块调用、输出赋值。实际项目中主程序通常很简洁复杂的逻辑都封装在功能块里。写ST代码这些年我最大的体会是语法只是门槛思维才是关键。ST的语法规则一两天就能学会但要用ST写出清晰、可维护、高效的代码需要大量的实践和反思。每次写完一个功能块回头看看能不能更简洁、更清晰、更通用这个习惯比学任何语法特性都有用。

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

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

免费获取报价 →
↑