首页 / 资讯中心 / 文章详情

PLC结构化文本ST编程:数学运算的类型陷阱与工程实践

PLC结构化文本ST编程:数学运算的类型陷阱与工程实践 ★ FEATURED ARTICLE
1. 为什么ST编程里的数学运算值得单独拿出来聊做过几年工控的人都有体会PLC编程上手不难难的是把程序写得稳、写得快、写得让后来的人能看懂。尤其是一碰数学运算很多人的代码就开始“放飞自我”了——变量命名随便、运算顺序靠猜、数据溢出看命最后设备一抖整个产线跟着遭殃。我最早接触ST结构化文本语言是在某物流分拣项目上。当时要算传送带上包裹的加速度和间距用的还是梯形图加功能块硬凑公式拆得七零八落调试一整天眼睛都快瞎了。后来切到ST一句a : (v2 - v1) / t;就能表达清楚那一刻我才意识到数学运算这件事在ST里是真的能写出“程序的样子”的。那这篇就围绕ST编程中的数学运算把设计思路、核心细节、实操过程和问题排查全部捋一遍。适合那些刚接触IEC 61131-3标准、想在PLC里把算术逻辑写得干净利落的开发者也适合正在做运动控制、温度PID、流量换算这类需要大量计算的项目的朋友。本文涉及的代码示例基于CodeSys风格的ST语法也兼容大多数主流PLC环境。实际使用前请对照你手头控制器的数据类型和指令集做微调。2. 数学运算在ST里的定位与整体设计思路2.1 数学运算不是“算出来就行”而是“怎么算才对”很多PLC程序里数学运算看似简单实则坑最多。主要体现在四个方面数据类型匹配整数除法和浮点除法结果完全不一样判断错一个类型偏差就是数量级的。运算溢出16位整数相乘直接爆上限程序不报错但数值就错得离谱。扫描周期与计算时机同一个变量在一个扫描周期内多次赋值到底取哪个值取决于你代码的顺序。可读性与可维护性公式堆成一座山三个月后自己回来看都头疼更不说交接给别的工程师。所以ST里的数学运算本质上是一个“工程化设计”问题。数学公式只是骨架类型体系、执行顺序、边界处理、命名规范这些才是血肉。2.2 为什么选ST而不是梯形图来做复杂运算梯形图擅长表达逻辑控制比如互锁、顺序启动、急停回路这些用触点线圈非常直观。但一旦涉及连续量的计算——比如流量累计、位置插补、温度曲线拟合——梯形图就暴露了两个短板功能块连线纷繁复杂一个公式要拆成五六个块中间变量多到怀疑人生。参数修改极不方便改一个系数要翻好几个功能块的引脚。ST则完全相反。它允许你像写高级语言一样用IF、FOR、WHILE、CASE组织流程用:直接赋值表达式还能把公式封装成带输入输出的函数块。同一个PID运算梯形图可能要占三屏ST只要一个FB代码量缩到原来的三分之一。2.3 运算封装与代码组织的思考方式我自己在项目里通常会把数学运算分成三个层级基础运算层就是加减乘除、取余、绝对值、取整这些操作作为最底层的计算原子。功能封装层把常见的公式如流量补偿、速度换算、坐标变换封装成独立的函数或功能块输入输出明确。策略决策层在程序主循环中调用功能封装层根据传感器状态、模式选择、操作指令来触发不同计算路径。这种分层思路的好处是底层如果出了数据类型问题只改一处全局生效上层如果改了工艺逻辑不用满屏找公式。打个比方就像中式厨房基础运算层是“刀工”功能层是“配菜”决策层是“炒菜的顺序”。刀工练好了配菜标准化了炒菜才能稳定高效。3. 核心细节拆解数据类型、运算符优先级与隐式转换3.1 ST里有哪些跟数学运算强相关的数据类型ST语言继承了IEC 61131-3的类型体系。我们在做数学运算时最常用到的有类型位宽范围典型用途BYTE8位0~255小计数、状态字INT16位-32768~32767常规整数计算DINT32位-2147483648~2147483647大累计量、位置计数REAL32位浮点约±3.4e38温度、速度、压力等模拟量LREAL64位浮点约±1.7e308高精度轨迹计算、科学计算其中最容易踩坑的是INT和REAL之间的混用。比如VAR iTemp : INT : 120; rResult : REAL; END_VAR rResult : iTemp / 10;这段代码在ST里运行iTemp / 10是先执行整数除法得到12余数丢弃然后才转换成REAL赋值给rResult。你期望得到12.0没问题但如果换成iTemp : 125你期望12.5结果却是12.0。rResult : iTemp / 10.0; // 这才是浮点除法结果是12.5这类细节真的是只有踩过坑才知道。3.2 运算符优先级表别以为ST和C语言完全一样ST的运算符优先级与C/C、Pascal有相似之处但并非完全一致。实际工作时我建议把复杂公式拆开用小括号把优先级写死既让自己安心也让后来的人少猜。ST运算符优先级从高到低大概是括号( )函数调用如ABS(x),SQRT(x)一元运算-负号、NOT逻辑非指数**注意ST的指数运算符是**不是^这一点和很多高级语言不一样乘除取模* / MOD加减 -比较 逻辑与AND逻辑或XOR、OR一个容易出错的点ST里既是比较运算符又是赋值目标但在表达式中用是比较。例如IF rValue 0 THEN // 比较判断 END_IF而赋值始终用:。这个初学者经常记混写得多了就成了肌肉记忆。3.3 隐式转换方便的代价是“你猜不到”很多ST编译器允许部分数值类型自动转换——比如INT赋值给REAL一般不会报错。但反过来REAL赋值给INT不同厂家有不同行为有的直接截断小数有的四舍五入有的干脆编译报错要求显式转换。为了保证程序的可移植性和预测性我个人的习惯是所有跨类型赋值一律写显式转换函数INT_TO_REAL、REAL_TO_DINT、DINT_TO_INT。所有除法操作至少有一个操作数是REAL否则就把分子或分母显式转换。所有累计量默认用LREAL或DINT绝不用小范围的INT做累加。rAvg : INT_TO_REAL(iSum) / INT_TO_REAL(iCount);即使两端的临时转换看起来多余但换来的是代码的可读性和可移植性非常划算。4. 实操过程一个计算功能块从设计到落地4.1 场景传送带线速度与产量累计这里我拿一个实际做过的模拟项目X来演示。需求是一条产线上有个编码器每转一圈发2000个脉冲辊筒直径是125mm我们要实时计算当前线速度m/min并在换班时间段内累计总产量米数。输入信号编码器脉冲计数iPulseCount增量式每100ms读取一次。 输出信号线速度rSpeedm/min、累计长度rTotalLengthm。先把已知条件列成公式辊筒转一圈的周长C PI * dd为直径单位要转成米。每圈脉冲数N 2000那么每个脉冲对应的长度L_p C / N。100ms内计到的脉冲数记为deltaP那么线速度V L_p * deltaP / 0.1单位m/s。若要m/min再乘以60。4.2 功能块代码我建了一个名为FB_ConveyorCalc的功能块FUNCTION_BLOCK FB_ConveyorCalc VAR_INPUT iDeltaPulse : DINT; // 本次扫描周期内的脉冲增量 rWheelDiameter : REAL; // 辊筒直径单位 mm iPulsesPerRev : DINT; // 每转脉冲数 END_VAR VAR_OUTPUT rSpeedMeterPerMin : REAL; // 线速度 m/min rLengthMeter : LREAL; // 累计长度 m END_VAR VAR rCircumference : REAL; rPulseLength : REAL; bInit : BOOL : FALSE; END_VAR在功能块内部我先初始化常量再执行计算// 首次扫描时计算脉冲当量 IF NOT bInit THEN rCircumference : 3.1415926 * (rWheelDiameter / 1000.0); // 直径 mm - m rPulseLength : rCircumference / INT_TO_REAL(iPulsesPerRev); bInit : TRUE; END_IF // 速度计算脉冲增量 * 脉冲当量 / 扫描周期 rSpeedMeterPerMin : (INT_TO_REAL(iDeltaPulse) * rPulseLength) / 0.1 * 60.0; // 累计长度每扫描周期累加一次 rLengthMeter : rLengthMeter (INT_TO_REAL(iDeltaPulse) * rPulseLength);这段功能块的核心逻辑就是把脉冲数换算成物理量再按时间维度换算速度。代码只有十来行但背后涉及了类型匹配、圆周率精度、单位统一这几个点任何一个疏漏都会让结果失真。4.3 参数计算的详细过程来手动算一遍加深理解。辊筒直径125mm周长C 3.1415926 * 0.125 0.392699075 m每脉冲对应的长度L_p 0.392699075 / 2000 0.0001963495 m假设某个100ms周期内编码器计数增量为500个脉冲实际前进距离 500 * 0.0001963495 0.0981748 m 线速度 0.0981748 / 0.1 0.981748 m/s 换算为 m/min 0.981748 * 60 58.9049 m/min这个结果如果直接丢到HMI上显示保留一位小数就是58.9 m/min和实际用测速仪量出来的基本一致。4.4 现场几个关键调整扫描周期不是精确的100ms怎么办PLC扫描周期会因为通信任务、中断程序的加入而抖动。严谨的做法是在程序里用一个自由运行定时器测出真实的周期时间或者利用系统提供的周期变量如T#100MS对应的真实时间。否则速度显示会一路乱跳。编码器反向时怎么办脉冲增量可能是负的此时线速度应该显示0或反向取决于工艺。至少要在功能块里判断一下IF iDeltaPulse 0 THEN rSpeedMeterPerMin : 0.0; END_IF累计溢出LREAL虽然范围极大但长期运行仍然要注意数值精度。比如一年运行300天每天产出几千米累计到百万米级别后小数的精度会被稀释建议在交接班时清零一次或者采用分段累计再合并。5. 高级数学函数与实用技巧5.1 常用数学函数清单ST标准库提供了不少数学函数很多新手不知道还在用泰勒展开自己算正弦实际上直接调用就行ABS(x)绝对值SQRT(x)平方根SIN(x)/COS(x)/TAN(x)三角函数注意输入是弧度而非角度ASIN(x)/ACOS(x)/ATAN(x)反三角函数LN(x)自然对数LOG(x)常用对数EXP(x)指数函数EXPT(x, y)x的y次方我经常看到有人问角度转弧度怎么做ST里没有直接的RAD()函数需要自己乘系数rRad : rDeg * 3.1415926 / 180.0;如果想直接封装可以这样写一个函数FUNCTION F_DegToRad : REAL VAR_INPUT rDeg : REAL; END_VAR F_DegToRad : rDeg * 3.1415926 / 180.0;5.2 避免“算得太勤”扫描周期内的计算次数控制一些数学计算比较昂贵比如SQRT、ATAN这类函数在低端PLC上可能耗时几十微秒到上百微秒。如果每个扫描周期都算一遍而且调用了很多次CPU负载就会升高。我的做法是温度、压力等慢变量每100ms甚至500ms计算一次就够了。位置环、速度环必须每个周期计算但尽量调用标准库函数避免自写复杂公式。需要高频率做三角运算的场合如电子凸轮建议查表插值把预计算好的正弦表放在内存里运行时按索引取。比如电子凸轮里的角度转位置可以提前生成一个数组把0~360度分成36000份每0.01度一个位置值。这样运行时只做数组索引和线性插值速度非常快三角函数完全不用实时算。5.3 定点数模式某些PLC里REAL比想象中“虚”这里额外说一个细节点有些运动控制器为了确定性推荐使用定点数比如32位整数表示千分之一毫米。这时候数学运算的思路就变了加减法直接做没问题。乘除法要注意缩放因子。例如用 Q24.8 格式24位整数部分8位小数部分两个数相乘后结果要右移8位才是真实值。溢出检查要更频繁因为定点数的范围往往比浮点小。我自己做一个位置插补算法时就吃过定点数的亏。当时的控制器是某品牌的专用运动控制器浮点运算会被编译器转成软件模拟速度慢得一塌糊涂后来全部改成整数定点计算速度提升了近十倍。关键是要在代码里写清楚缩放系数否则很容易出现神秘的大数误差。6. 常见问题与排查技巧实录6.1 计算结果为0或者巨大负数这个我在调试时遇到过很多次尤其是刚接触ST的人。典型原因一整型除法rResult : iA / iB; // 两个INT相除如果iA iB结果直接为0因为整数除法取整。解决方案把其中一个操作数转成REAL。典型原因二变量初始化问题PLC里VAR如果没有初值尤其在功能块里有的系统默认初始化为0有的保持上次值。如果某个变量从来没被赋值就直接参与除法分子为0结果自然是0。排查方法在关键运算之前临时写一段测试代码给所有输入变量赋固定值观察输出。6.2 模拟量转换之后数值跳变模拟量输入0~10V或4~20mA转换到工程量公式是rEng INT_TO_REAL(iRaw) / 27648.0 * (rScaleMax - rScaleMin) rScaleMin;但很多初学者把缩放比例弄反了或者把iRaw当成了REAL直接运算导致跳变。另一个容易被忽略的地方是模拟量模块的原始值在断线时可能直接跳到满量程或负满量程如果不加量程判断程序会把这个值当成真实值计算然后执行错误动作。所以我一般在功能块里加一道限幅逻辑IF iRaw 2000 THEN // 小于量程下限的10%左右认为断线或信号故障 rEng : 0.0; ELSE rEng : ...; END_IF6.3 数据溢出而不报错16位INT最大32767两万乘两就爆了。但很多PLC在溢出时并不会停机只会让变量变回负值程序继续跑。例如VAR iVal : INT : 30000; END_VAR iVal : iVal 4000;在不少编译器中iVal会变成 -28536。而某些编译器会抛出异常但异常处理没写好程序照样静默运行。预防措施能选DINT绝不选INT尤其是在累加、累乘场景。对输入值做上限判断。关键结果做范围检查超出预期就输出报警。IF ABS(rResult) 10000.0 THEN rResult : 0.0; bError : TRUE; END_IF6.4 运算时间太长导致看门狗报警如果你在ST里写了很大的循环比如FOR i : 1 TO 10000去算矩阵或者调用了高开销函数在一个扫描周期内无法完成PLC会触发看门狗或任务超时错误。解决思路把重计算拆到多个扫描周期用状态机逐步执行。或者把数据分发到后台任务与实时任务分离。优先用PLC自带数学库函数别自己写复杂的数值算法。我在一个视觉定位项目中需要做坐标变换矩阵求逆直接在PLC里全周期算了一次结果看门狗爆了。后来改成先把矩阵参数存下来在后台任务里算前台只读结果问题立刻解决。6.5 常见问题速查表现象可能原因处理办法结果恒为0整型除法、变量未初始化转换REAL、给变量初值结果偶尔跳变扫描周期抖动、模拟量断线用真实周期时间、加信号判断结果溢出成负数INT越界、REAL精度不足换DINT/LREAL、加阈值检查看门狗报警计算量过大、死循环拆分任务、后台计算、精简算法三角函数结果错角度/弧度未转换乘PI/180转换角度不同PLC结果不一致隐式转换规则不同都写成显式转换7. 个人经验与扩展思路玩ST几年下来我最大的体会是数学运算本身不难难的是你愿不愿意为“算得对、算得稳、算得可维护”多花一点心思。很多现场问题追根溯源都是很小的地方——类型选错了、单位搞混了、扫描周期被忽略了。这些东西写代码的时候多用点心后面就能少熬几个夜。如果要做扩展我个人觉得两个方向最值得琢磨一是把常用的运算功能块沉淀成自己的库比如单位换算、滤波、量程变换、PID计算换项目时直接拖过来用省时省力二是把数学运算和状态机结合起来让复杂的计算流程按步骤执行既防看门狗超时又能方便地在HMI上显示计算进度。最后分享一个小技巧每次写完一个ST数学运算功能块我都会在程序里加一段“自检触发”——给输入一组已知数据对比输出是否和预期一致比如用125mm直径 2000脉冲 500增量计算输出必须接近58.9m/min。这个自检逻辑平时不调用但在现场调试怀疑算法有问题时一键就能验证比拿万用表测半天靠谱得多。
阅读完成 · 觉得有帮助?
咨询建站