免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

ST语言数组与FOR循环:PLC批量采集的完整实战指南

ST语言数组与FOR循环:PLC批量采集的完整实战指南 做设备调试这些年最烦的一件事就是从一堆仪表、传感器里把数据一个个搬进PLC。以前没有用对方法的时候12路温度变送器就要写12段几乎一模一样的采集代码复制粘贴到手软改个量程还得12处一起改漏一处现场就出幺蛾子。后来把ST语言配合数组和FOR循环用熟练了才真正体会到什么叫批量采集——代码量砍到原来的五分之一不夸张而且逻辑清晰到现场电工都能看懂。这篇就把我这几年用ST数组做FOR循环批量采集的完整经验整理出来包括能直接抄的代码套路、各种平台上的写法差异以及那些会坑得你半夜去现场的细节。先声明一下这里说的ST是IEC 61131-3标准里的结构化文本Structured Text不是算法竞赛里那个ST表。不少朋友搜ST 数组会搜到一堆稀疏表、RMQ的内容那个跟PLC编程完全是两码事。另外文章里涉及的平台包括施耐德SoMachine系列、西门子TIA Portal的SCL、倍福TwinCAT 3这些我实际用过的环境语法风格大同小异我会把差异点都指出来。1. 先把场景说清楚为什么批量采集在PLC里是个真问题很多刚接触PLC编程的朋友觉得采集嘛无非就是读输入寄存器一个通道一行代码的事。早期我也是这么干的但真到了产线级别的设备上批量采集从来不是有没有信号的问题而是几十上百个点怎么组织、怎么维护、怎么不出错的问题。1.1 老式做法一个通道一段代码改起来像噩梦先看一段典型的新手代码这是一个设备上8路压力传感器的采集逻辑// 传统写法逐通道处理 IF bReadCmd THEN bDone : FALSE; rPress1 : INT_TO_REAL(aiRaw1) * 0.1; rPress2 : INT_TO_REAL(aiRaw2) * 0.1; rPress3 : INT_TO_REAL(aiRaw3) * 0.1; rPress4 : INT_TO_REAL(aiRaw4) * 0.1; rPress5 : INT_TO_REAL(aiRaw5) * 0.1; rPress6 : INT_TO_REAL(aiRaw6) * 0.1; rPress7 : INT_TO_REAL(aiRaw7) * 0.1; rPress8 : INT_TO_REAL(aiRaw8) * 0.1; bDone : TRUE; END_IF;这段代码功能是能实现但问题很明显。首先是改一个量程系数得改8处漏一处数据就对不上其次是扩展性极差从8路加到16路又要复制粘贴8段。更要命的是当采集逻辑变复杂的时候比如要加滤波、加报警、加工程值上下限判断这种写法会让程序膨胀到一个扫描周期跑不完。你可能会说把这些做成功能块每个通道调用一次不就行了这种思路比裸写进好一些但依然有一个致命问题通道变量是散的。rPress1到rPress8不是同一个数据结构你要做统计求均值、找最大最小值、要整体上传HMI、要批量写配方就必须一个变量一个变量地操作绕不开。1.2 数组加FOR循环之后写法和人话一样直白用数组重写上面的逻辑声明变量的方式就变了VAR aiRaw : ARRAY[1..8] OF INT; // 8通道原始值 rPress : ARRAY[1..8] OF REAL; // 8通道工程值 i : INT; END_VAR采集逻辑变成FOR i : 1 TO 8 DO rPress[i] : INT_TO_REAL(aiRaw[i]) * 0.1; END_FOR;从8段代码变成3行这就是数组加循环的威力。具体好在哪我总结了几点改系数只改一处把0.1提成全局变量或者常量一行搞定扩展零成本把8改成16数组声明改一下循环上限改一下就完成了可批量操作对数组整体求均值、排序、上传、比较都有现成套路逻辑边界清晰i的边界就是通道数配合调试工具可以直接看数组元素在工程实践中批量采集的难点根本不在读这个动作而在读完之后的数据组织。数组提供了顺序索引的数据容器FOR循环则提供了按索引批量操作的机制这两个配合是ST语言里处理批量数据的基石。2. ST语言里的数组声明、初始化和不同平台的坑数组这个东西在ST里不算难但有几个细节是我见过最多人踩坑的。而且不同PLC品牌的细节差异很大网上教程很少把这些差异讲透我结合自己用过的平台一个个说。2.1 数组声明的基本语法与三种常见写法ST数组声明的标准格式是VAR 数组名 : ARRAY[下界..上界] OF 数据类型; END_VAR注意这里的..是两个点不是省略号这是IEC 61131-3的规范写法。下面是三种最常见的写法// 写法一一维数组下标从1开始这是PLC领域的习惯 VAR aiInput : ARRAY[1..16] OF INT; END_VAR // 写法二下标从0开始熟悉C语言的人可能更习惯 VAR aiInput : ARRAY[0..15] OF INT; END_VAR // 写法三二维数组常用于矩阵、配方表或多组数据 VAR aiMatrix : ARRAY[1..4, 1..10] OF INT; END_VAR我个人的建议是用一个统一的约定要么全部从1开始要么全部从0开始。不同品牌默认习惯不一样比如施耐德的很多示例程序喜欢从1开始西门子的SCL里较常见从0或1开始皆可倍福的TwinCAT里两种都行。最关键的问题是团队协作时一定要统一否则你写的FOR i : 1 TO 8和同事写的FOR i : 0 TO 7指向的数组元素会差一个位置排查起来极其痛苦。我在一个项目里就遇到过混用的情况温度数组用的是1..8压力数组用的是0..7结果做曲线对比时数据整体错位现场查了一个通宵。2.2 数组初始化与动态数组这个不存在的概念数组声明之后默认初始值是数据类型的零值INT就是0REAL就是0.0BOOL就是FALSE。但有的时候我们需要初始化为特定值比如报警上限数组每个通道的上限不一样。这时可以在声明时直接初始化VAR rAlarmHigh : ARRAY[1..4] OF REAL : [80.0, 100.0, 75.0, 90.0]; aiOffset : ARRAY[1..3] OF INT : [0, 2, -1]; END_VAR注意两个细节初始化列表的元素个数必须和数组长度完全一致多了少了都编译不过倍福TwinCAT 3支持一种简写[4(80.0)]表示4个80.0但施耐德和西门子不支持这种语法为了代码可移植性我一般不用还有一个重要概念ST语言里的数组是静态的没有动态数组。也就是说你不能在程序运行过程中改变数组的大小。这个设计是出于PLC实时性和确定性考虑——每个任务的内存分配必须是编译期确定的不能在运行时申请。那些做上位机、Python的人刚转过来最容易在这上面懵有人甚至想在运行时ARR.Append()一个元素这在ST里是不可能的。解决思路有两种一是事先声明一个大数组只使用其中一部分用一个计数器记录有效长度二是用指针配合内存操作一些平台支持但复杂度高且不建议新手用。实践中绝大多数情况用大数组有效长度就足够了。2.3 多维数组与字符串数组的实际用途多维数组最典型的场景是配方管理。比如设备有5种产品配方每种配方有12个参数这时可以用二维数组// 配方数组5个配方每个配方12个参数 VAR rRecipe : ARRAY[1..5, 1..12] OF REAL; iRecipe : INT; // 当前选择的配方号 END_VAR // 加载配方 FOR i : 1 TO 12 DO rParam[i] : rRecipe[iRecipe, i]; END_FOR;这种写法比把配方参数命名为rRecipe1Param1、rRecipe1Param2这种方式优雅一个档次配方切换只需要改iRecipe的值。字符串数组则常用于存储设备名称、报警文本这类批量文本信息。要注意ST里的字符串是固定长度的声明方式VAR sDeviceName : ARRAY[1..8] OF STRING(20); // 8个设备名每个最多20个字符 END_VAR这种数组配合FOR循环做字符串查找、排序都非常方便。热词里有个数组分割并显示包含某一字符的需求其实就可以用字符串数组加FOR循环实现遍历数组元素逐个调用查找函数找到就记录下标然后统一显示。我在5.2节会给出具体代码。3. FOR循环批量采集的完整写法逐行拆一个能跑的案例前面铺垫了这么多现在上一个完整的实战案例。背景是一台12路温度采集设备温度变送器输出4-20mA信号接到模拟量输入模块需要完成以下功能把12路原始值批量转换为温度值量程0-150℃每个通道单独设置温度报警上限采集结果存入数组供HMI读取和趋势记录3.1 硬件与变量定义假设用的是施耐德M241系列PLC配合模拟量扩展模块地址分配如下12路模拟量输入占用%IW1.0到%IW1.11。变量声明如下VAR // 原始量模拟量模块自动刷新到IO映像区 aiRawData : ARRAY[1..12] OF INT; // 工程值转换后的温度 rTemp : ARRAY[1..12] OF REAL; // 报警阈值每个通道独立 rAlarmLimit : ARRAY[1..12] OF REAL : [100.0, 100.0, 120.0, 100.0, 100.0, 150.0, 100.0, 80.0, 100.0, 100.0, 100.0, 100.0]; // 报警标志 bAlarm : ARRAY[1..12] OF BOOL; // 通道数常量 iChCnt : INT : 12; // 循环变量 i : INT; END_VAR这里有个编程习惯问题循环变量的名字我见过有人用i、j、k也有人用nIndex、dwLoop。在ST里i是合法的标识符不与任何关键字冲突所以我个人还是习惯用i简洁。但要注意别在同一个作用域里重复声明。3.2 核心采集逻辑与逐段拆解采集逻辑的核心代码就一段// 批量温度采集与量程转换 FOR i : 1 TO iChCnt DO // 步骤1原始值转换为工程量 rTemp[i] : INT_TO_REAL(aiRawData[i]) * 0.1; // 步骤2报警判断 IF rTemp[i] rAlarmLimit[i] THEN bAlarm[i] : TRUE; ELSE bAlarm[i] : FALSE; END_IF; END_FOR;逐段解释一下这里面的设计逻辑。为什么用INT_TO_REAL(aiRawData[i]) * 0.14-20mA信号经过模拟量模块后通常是0-27648的整数值西门子方式或者0-16383施耐德方式具体取决于模块配置。假设这个模块配置为0-1500对应0-150.0℃那么转换系数就是0.1直接把整数乘0.1得到工程值。所以*0.1这个系数不是拍脑袋写的是根据模块的量程配置算出来的。这里最容易踩的坑是忘记转换数据类型INT乘以REAL在大多数ST编译器里会报错或者做隐式转换我建议一律显式写INT_TO_REAL()这样代码的可读性和可移植性都更好。报警判断为什么要放在循环里而不是另写一段因为报警判断依赖rTemp[i]算出来的结果放在同一个循环体里可以保证算完即判断判断完即存入不浪费一次遍历。如果分开写你会多写一个FOR i : 1 TO 12 DO的循环代码并没有更清晰。当然如果你有专门的报警功能块想要复用那另说。3.3 为什么用FOR而不是WHILE或REPEATST里至少有三种循环结构FOR...END_FOR、WHILE...END_WHILE、REPEAT...UNTIL...END_REPEAT。同样是循环为什么批量采集推荐用FOR我总结了一个选择表格循环类型适用场景优点注意点FOR已知循环次数按固定步长遍历边界清晰代码简短编译期可检查越界风险循环次数固定不适合条件未知的等待WHILE循环次数不确定靠条件判断退出灵活条件满足才进入容易写出死循环PLC看门狗报警REPEAT至少执行一次然后判断条件适合先做一次再决定是否继续的场景使用频率最低可读性一般批量采集的特点是通道数是固定的、已知的天生适合FOR。而且FOR的循环变量会自动递增或递减你不需要在循环体中写i : i 1这种容易出错的手动累加。反观WHILE你可能要这样写i : 1; WHILE i iChCnt DO rTemp[i] : INT_TO_REAL(aiRawData[i]) * 0.1; i : i 1; // 这行要是忘了恭喜你获得一个死循环 END_WHILE;忘了累加在WHILE里是常见低级错误一旦出现PLC进入死循环看门狗超时轻则报错重则CPU停机。用FOR就没有这个问题循环变量由编译器管理。4. 循环体里最容易翻车的五个细节这个章节是纯干货每一项都是我或者同行在项目里真实踩过的坑拿出来给各位当避雷指南。4.1 扫描周期与循环时间这个坑能让你PLC死机很多PLC程序里循环写得很爽一个FOR循环循环几千次在循环体里做字符串操作、文件读写结果程序跑起来整个PLC像老牛拉车运动控制轴直接报跟随误差。根本原因是PLC程序按扫描周期执行通常一个周期只有几毫秒到几十毫秒你的程序必须在这个时间内全部跑完。FOR循环消耗的时间会计入扫描周期。如果循环体内操作很耗时比如大量字符串拼接、浮点运算、通讯指令再加上循环次数很大一个扫描周期就可能超时。超时后不同PLC表现不同有的直接看门狗复位有的进入STOP状态有的只是报警但不解决。控制循环耗时的核心原则循环体内的操作要精简如果逻辑复杂拆出去做成分帧处理每次扫描只处理几个元素大数据量处理别放循环里一次做完比如1000个数组元素的平均值计算可以分多次扫描完成用状态机控制算到第几个了通讯类指令MODBUS读写、TCP发送严禁放在大批量循环里裸奔这些指令本身耗时且需要等待会严重拖垮扫描周期4.2 数组越界一步错就CPU停机FOR循环最常见的致命错误是数组越界。比如数组声明为ARRAY[1..12]循环却写成FOR i : 0 TO 12那么访问arr[0]就是越界访问。在ST编译器里数组越界轻则运行时错误重则直接导致PLC停机进入STOP模式产线跟着停。这是因为ST遵循IEC 61131-3标准运行时环境要保证内存安全不像C语言里越界可能只是读到垃圾值虽然不推荐但至少程序还跑着PLC环境里越界很多时候会直接触发运行时错误。不同平台的越界后果不一倍福TwinCAT 3会报出数组下标越界的错误并停止任务西门子S7-1200/1500在SCL里如果启用了运行时检查也会进入STOP施耐德M241的SoMachine/EcoStruxure要求你必须用正确边界。我在调试时有一个习惯给所有循环上限加一个保险不让裸变量直接控制边界CONST iMaxChannel : INT : 12; END_VAR循环写成FOR i : 1 TO iMaxChannel DO这样即使配置数据被改错也不会超过编译期确定的数组边界。还有一个细节FOR的循环变量类型要与数组下标类型一致。如果数组下标声明为INT循环变量也声明为INT如果声明为DINT倍福里普遍习惯DINT就统一用DINT。混用时编译器可能隐式转换一旦循环次数超过最大整数范围边界就无法保证了。4.3 循环变量i被改动禁改循环计数器的原因在FOR循环体内部绝对不要给循环变量赋值。比如FOR i : 1 TO 10 DO IF 某条件 THEN i : 10; // 想提前退出循环千万别这么干 END_IF; END_FOR;有些从C语言过来的程序员会想用i : 10来提前结束循环但在ST里FOR循环的运行机制是进入循环时记录初始值、结束值和步长每次循环末尾自动将循环变量加上步长然后与结束值比较。在循环体内修改循环变量会导致循环的执行次数完全不可预期轻则逻辑错乱重则死循环。不同编译器对这个情况的处理不一样有的会禁止编译有的只是运行时表现怪异。想提前退出正确做法是用EXIT语句FOR i : 1 TO 10 DO IF 某条件 THEN EXIT; // 立即退出当前循环 END_IF; END_FOR;4.4 数据类型的隐式转换不显式写就等着丢精度在这个案例里INT_TO_REAL(aiRawData[i]) * 0.1这行代码写起来很简单但如果你少了类型转换写成aiRawData[i] * 0.1在一些编译器里会报警告在另一些编译器里会隐式转换。问题在于隐式转换的规则各平台不一致而且隐式转换可能导致精度损失或溢出。比如REAL_TO_INT如果不是显式写的谁知道它怎么舍入ST标准里没规定死有的平台是四舍五入有的平台直接截断。我见过的真实案例一个通道的原始值是16383量程是0-16383对应0-100.0度系数应该是100.0/16383.0 ≈ 0.00610385。有人写成了INT_TO_REAL(aiRaw) * 0.006误差不大但也不小跟标定过的仪表比差了0.5度。后来统一用解析式INT_TO_REAL(aiRaw) * (100.0 / 16383.0)编译期算好精度才对得上。关于数据精度我的建议是所有涉及数据转换的地方显式写出转换函数所有用到的浮点系数尽量用能整除的表达式或者用足够精度的浮点常量避免手写一个似是而非的近似值。4.5 嵌套循环与多任务别把CPU时间烧光ST里允许嵌套循环即循环套循环。二维数组的遍历通常就需要嵌套。但嵌套循环的耗时是乘法关系两层循环体内部如果有复杂操作扫描周期会翻倍甚至翻几倍。还是那个原则循环体的操作要精简到最小单元。如果某个操作不依赖内层循环的变量就把它提到外层如果内层循环的操作只是为了查找一个元素用EXIT找到就跳出不要遍历完整个数组。此外在PLC的多任务体系里批量采集通常放进快速任务或者主任务但循环耗时会影响其他任务的调度。我给一个经验值单个扫描周期内FOR循环累计的执行时间不要超过扫描周期的30%。如果你发现扫描周期明显变长优先检查循环体里是不是塞了不该塞的东西。5. 实战排错两个让我印象深刻的采集故障讲完理论来两个我实际处理过的故障案例。这种排查思路比代码本身更有价值因为代码可以抄思路得自己建立。5.1 案例一施耐德PLC循环左移点灯最后一位始终不亮背景客户现场有一排12个指示灯要求用一个按钮触发让灯从第1个开始依次点亮看起来像流水灯。程序用数组加FOR循环写了移位逻辑。结果现象是灯能从1亮到12但第12个一亮就灭永远停在走不到头的感觉而且循环回来时本应从第1个重新开始实际上又要手动触发一次。排查过程是这样的。先看移位部分的代码// 简化后的示意代码 FOR i : 1 TO 11 DO xLamp[i 1] : xLamp[i]; // 左移 END_FOR; xLamp[1] : xNewTrigger; // 新状态补到第一个这个代码看起来逻辑没问题配合FOR循环从1到11把第i个灯的状态移到第i1个。但是客户的实际诉求是循环移位而不是线性移位。问题出在哪问题一FOR i : 1 TO 11只处理了前11个灯第12个灯在循环里没有被赋值它的状态取决于循环前一周期它自身的状态。这段代码执行完后第12个灯保持了旧状态而第11个灯被赋成了第10个灯的状态。看起来就像最后一位不亮。问题二即使你把循环改成FOR i : 1 TO 12然后赋值xLamp[i1] : xLamp[i]当i12时会越界——数组声明是ARRAY[1..12]xLamp[13]不存在。正确做法是先保存最后一个元素移位后再放回第一个// 循环左移临时保存最左端 xTemp : xLamp[1]; // 左移位 FOR i : 1 TO 11 DO xLamp[i] : xLamp[i 1]; END_FOR; // 最后一个位置补上原来的第一个 xLamp[12] : xTemp;或者用ST支持的ROL循环移位指令在不同平台中指令名有差异西门子用ROL_B、SHL等施耐德用ROL倍福里也可以做移位但用FOR循环的好处是逻辑直白、平台兼容。这个案例的教训是循环边界和数组边界必须严格对齐移位类的逻辑要考虑环的两端衔接额外变量保存被挤出的数据。5.2 案例二批量采集值全部飘零问题出在扫描周期另一个项目8路温度采集来自远程IO站通过MODBUS TCP通讯读取。程序一开始是轮询式读取每个通道在循环里发一条读指令等返回再发下一条。结果现场数据偶尔全部变成0操作工重启一下又好一阵反反复复。排查的时候我先把数据流梳理了一遍。循环里的MODBUS读指令是这样写的示意FOR i : 1 TO 8 DO MODBUS_READ(Addr : iDeviceAddr[i], Data : aiRawData[i]); // 这行指令是异步还是同步是否真的等到了返回 END_FOR;问题在于MODBUS_READ通常是异步通讯过程调用后立即返回数据的读取结果要等通讯完成事件触发才更新不是在调用指令的同一扫描周期内就能拿到。在FOR循环里连续发8条指令相当于一个扫描周期内发了8个通讯请求。而对端的远程IO站处理能力有限缓冲区被塞爆后续请求直接超时或者被丢弃采集结果自然就飘零了。正确的做法是改成分时轮询每个扫描周期只处理一个通道用状态机推进CASE byStep OF 0: // 启动新一轮采集 i : 1; byStep : 1; 1: // 发送第i个通道的读请求 MODBUS_READ(Addr : iDeviceAddr[i], Data : aiRawData[i]); bPending : TRUE; byStep : 2; 2: // 等待返回 IF bResponseReceived THEN rTemp[i] : INT_TO_REAL(aiRawData[i]) * 0.1; i : i 1; IF i 8 THEN byStep : 0; // 全部读完下一轮 ELSE byStep : 1; // 继续下一个通道 END_IF; END_IF; END_CASE;这个案例跟前面讲FOR循环有没有矛盾没有。它说明了FOR循环适合不依赖外部异步事件的纯计算和内部寄存器搬运一旦涉及通讯等待就必须放下循环改用状态机。这是很多从仪表转做PLC的人最容易忽视的点PLC的扫描周期是确定性的但外部通讯是不确定的二者不能简单用循环揉在一起。6. 再进一步批量数据的三个进阶处理思路最后分享一下我目前在项目里常用的几个进阶套路都是在基础采集之上玩出的花活但依然只用数组加FOR循环。6.1 批量滤波一阶惯性滤波器的数组化实现原始采集数据往往带噪声尤其是在变频器附近模拟量信号波动很大。最简单的做法是给每个通道做一个一阶惯性滤波// 滤波系数0~1之间越小滤波越强但响应越慢 rFilterCoef : 0.3; FOR i : 1 TO iChCnt DO rTempFiltered[i] : rTemp[i] * rFilterCoef rTempFiltered[i] * (1.0 - rFilterCoef); END_FOR;这里的原理是一阶惯性环节的递推公式Y(n) X(n) * a Y(n-1) * (1-a)rTempFiltered就是历史滤波值rFilterCoef是权重系数。用数组把每个通道的滤波状态都存起来循环里一行搞定所有通道。系数怎么选如果采样周期是10ms滤波器时间常数T100ms那么a T_sample / (T_sample T) ≈ 0.09。简单说就是采样周期相对于滤波器时间常数越短a就越小滤波越平缓。6.2 批量查找与字符串处理找出包含特定字符的数组元素热词里提到数组分割并显示包含某一字符这个在HMI报警信息和配方管理里很常见。比如一个设备名称数组现在要找出所有包含泵字的设备。用ST写就是VAR sDevices : ARRAY[1..20] OF STRING(30); sMatchList : ARRAY[1..20] OF STRING(30); iMatchCnt : INT : 0; i, j : INT; END_VAR // 遍历所有设备名 FOR i : 1 TO 20 DO // 查找字符串中是否包含泵字返回位置0表示未找到 IF FIND(sDevices[i], 泵) 0 THEN iMatchCnt : iMatchCnt 1; sMatchList[iMatchCnt] : sDevices[i]; END_IF; END_FOR;FIND函数返回子串在字符串中第一次出现的位置找不到返回0。这样就实现了包含某字符则匹配并存入新数组同时记录匹配个数iMatchCntHMI侧只要根据这个长度循环显示即可。不同平台里字符串函数的名称略有差异倍福和施耐德都有FIND西门子SCL里叫FIND或T_CONV配合其他函数使用查一下PLC手册即可。6.3 批量采集数据的缓存与趋势记录循环缓冲区的思路最后一个进阶技巧是做采集数据的环形缓存。PLC内部做不了U盘存大量历史数据但可以用数组加循环做一个滚动缓存把最近N次采集的结果存下来需要时整体取出给HMI画趋势曲线或者做故障追忆。VAR // 缓存数组最多记录最近100次采样的8通道数据 rBuf : ARRAY[1..100, 1..8] OF REAL; iBufIdx : INT : 1; // 当前写入位置 iBufCnt : INT : 0; // 历史记录总数 END_VAR // 每次采集完成后把当前数据写入缓存 FOR j : 1 TO 8 DO rBuf[iBufIdx, j] : rTemp[j]; END_FOR; // 更新写入位置到达末尾则回到开头形成环形覆盖 iBufIdx : iBufIdx 1; IF iBufIdx 100 THEN iBufIdx : 1; END_IF; IF iBufCnt 100 THEN iBufCnt : iBufCnt 1; END_IF;这个做法的好处是不需要动态内存缓存大小在编译期确定运行期间iBufCnt告诉你当前有多少有效数据iBufIdx告诉你下一个数据写在哪。故障追忆时从iBufIdx开始逆序读取就能拿到最近的数据序列了。最后再分享一个小小的习惯。在ST里做批量采集我一开始也经常图省事把循环上限直接写死在代码里比如FOR i : 1 TO 12。后来项目里通道数改了两次改代码的时候漏改了一个12现场数据差了一路查半天才意识到是上限没同步。现在我的做法是所有通道数、数组边界一律用常量或者VAR里的配置变量定义循环里引用变量而不是裸数字。这样改通道配置时只改一处程序逻辑一行不动。这个习惯拯救过我很多次也推荐给你。
返回列表