多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ST语言数组+FOR循环:PLC批量采集不再重复写代码

ST语言数组+FOR循环:PLC批量采集不再重复写代码 做PLC编程的兄弟都知道用ST语言结构化文本写批量采集最烦的就是一遍一遍复制粘贴。16路模拟量你能写16段赋值32路温度写32段要是赶上几百个点光代码量就能让人崩溃。我这几年在项目现场攒下来的经验是——遇到这种场景直接上数组和FOR循环把采集逻辑压成几行循环体既省事又不容易错。这篇文章就把我实际用ST数组FOR循环做批量采集的完整思路、程序模板和踩坑记录整理出来给正在被重复代码折磨的工程师一个能直接抄的作业。1. 批量采集的痛点代码重复到怀疑人生数组FOR循环怎么破1.1 真实场景还原16路AI信号的采集代码长什么样假设你手头有一个16通道的模拟量输入模块需要把每一路的电流信号转成工程量比如温度、压力、液位。传统的写法很朴素aiEng[1] : INT_TO_REAL(aiRaw[1]) * 0.01; aiEng[2] : INT_TO_REAL(aiRaw[2]) * 0.01; aiEng[3] : INT_TO_REAL(aiRaw[3]) * 0.01; ... aiEng[16] : INT_TO_REAL(aiRaw[16]) * 0.01;看起来没什么大不了但是如果通道数变成32、64甚至128呢现场调试的时候还经常要加量程上下限、滤波系数、报警阈值每一路都是一样的公式只是下标不一样。复制粘贴16次之后下一个维护这段程序的人几乎肯定会在第9路和第13路之间改错一个数字。更麻烦的是IO映射。很多PLC的系统库并没有把通道直接暴露成AI_Module.ch1这种连续编号而是给你一堆分离变量比如%IW0.0、%IW0.2、%IW0.4。此时连复制粘贴都不好使你得手动一个一个对应。1.2 重复代码的代价错位、遗漏、改不动重复代码最阴险的问题不是费手指而是三个字改不动。第一是错位。复制16行代码把第8行的ch8改成ch9的时候眼睛一花改成了ch8程序编译照样通过但第9路采集到的永远是第8路的数据。这种逻辑错误在仿真里很难发现只有跑现场对点表的时候才会暴露而那时候排查成本已经非常高了。第二是遗漏。新增加了一路测点随手在末尾加上一段赋值往往忘了中间还有一路没处理。于是从中间开始后面的通道全部错位。要是工艺上恰好有一路是连锁保护信号后果不堪设想。第三是改不动。量程变了或者把0.01的系数改成0.02你得在16个地方逐一修改。漏改一处那一路数据就带着旧系数进入后续逻辑。我在一个项目里就见过类似问题最终查出来的原因令人哭笑不得量程变更时漏改了一个通道。1.3 数组FOR循环怎么把代码量降下来把上面那段重复代码换成数组和FOR循环效果是这样的FOR i : 1 TO 16 DO aiRaw[i] : AI_Module.ch[i]; aiEng[i] : INT_TO_REAL(aiRaw[i]) * 0.01; END_FOR;16路映射加转换两行循环体搞定。增加通道的时候只需要改循环上限或者用常量定义量程系数变化只要改循环体里一行。程序的可读性和可维护性完全是两个级别。这里有一个前提IO模块的通道地址必须能用数组下标索引。如果是分离的变量可以用一个额外的索引数组做映射核心逻辑不变。这也是我为什么要在本文里强调ST语言里数组和FOR循环不是锦上添花而是批量采集场景的基本功。2. ST语言数组声明与初始化下标习惯、内存开销和二维存储方案2.1 声明语法在VAR区里定义一个数组ST语言里数组声明很直接在变量区写VAR aiRaw : ARRAY[1..16] OF INT; aiEng : ARRAY[1..16] OF REAL; END_VARARRAY[下界..上界] OF 类型就是基本格式。下界和上界是任意整数可以是0、1甚至是ARRAY[-5..5]这种负下标虽然没什么人用。这和C语言从0开始、并且下标必须非负的习惯很不一样很多从C转过来的工程师第一次写ST数组会先入为主地写ARRAY[0..15]这个习惯没有对错但会直接影响FOR循环的边界写法。2.2 下标从1开始还是从0开始对应通道号更省心我的习惯是下标从1开始原因很简单现场通道号是从1数到16的。如果数组下标也是1到16那么通道号和下标天然一致写aiRaw[9]就能直接联想到第9通道。如果用0到15每次都要做减1的换算调试的时候非常容易晕。不过要注意有些ST方言的系统函数库内部返回的是以0为基准的位索引比如读取某个WORD变量的第0号位时你可以把位0到位15映射到数组的1到16。这样做的时候循环边界要仔细想清楚这是后面排错章节会专门提到的坑。2.3 初始化有几种姿势声明赋初值、循环清零、按需填充ST数组初始化有三种常用方式。第一种是声明时直接给初值VAR scale : ARRAY[1..4] OF REAL : [10.0, 10.0, 50.0, 100.0]; END_VAR这种方式适合固定表比如每个通道对应不同的量程上限。多维数组初始化要用嵌套括号例如ARRAY[1..2, 1..3] OF REAL : [[1,2,3],[4,5,6]]。第二种是运行时统一清零。很多工程师忘记在批量采集前把数组清一遍导致残留上次的数据。最稳妥的做法是在程序启动时用FOR循环清一次FOR i : 1 TO 16 DO aiEng[i] : 0.0; aiRaw[i] : 0; END_FOR;部分PLC系统库提供了FILL之类的指令批量填充数组效率更高但可读性一般我更喜欢清晰明了的FOR循环。第三种是按需填充也就是在采集循环里一边读一边写这也是本文实战部分的核心模式。2.4 二维数组怎么用来存历史数据批量采集还有一个典型需求把每一帧数据存到历史缓冲里供趋势显示和分析。这里用二维数组非常自然。比如16路信号每100ms采集一次保存最近10帧VAR history : ARRAY[1..10, 1..16] OF REAL; frameIndex : INT; END_VAR按照惯例第一个下标是帧号行第二个下标是通道号列。在采集循环里FOR i : 1 TO 16 DO history[frameIndex, i] : aiEng[i]; END_FOR;frameIndex每100ms加1超过10就自动回绕到1。这就是一个简单环形缓冲后面扩展章节还会细讲。2.5 内存开销与容量规划ST的数组是静态分配内存的不支持动态数组。所以定义数组前必须想清楚最大容量。算一下账一个REAL占4字节16路就是64字节存10帧640字节。对比现代PLC动辄几兆的用户内存这点开销不算什么。但如果是上千个测点、每天存几万条历史记录就要认真规划。另外在线监控时大数组的查看和上传都很慢会影响调试效率。我的经验是宁可定义得保守一点也别拍脑袋开一个ARRAY[1..10000] OF REAL。先算清需要多少个数据点再额外加20%余量就够了。以后真的不够用了再改也不迟。3. FOR循环的语法细节闭区间、步长、嵌套以及和WHILE/REPEAT的取舍3.1 FOR语句的精确语义闭区间和BY步长ST语言里FOR循环的标准写法是FOR i : 1 TO 16 BY 1 DO // 循环体 END_FOR;BY 1可以省略默认步长就是1。这个语句等价于C语言里的for (i 1; i 16; i)注意这里是闭区间它执行的是16次i1到i16循环结束后i的值是17。而C语言的for (i 0; i 16; i)是半开区间执行16次i0到i15循环结束后i的值是16。两个语言的边界习惯不同混用是最容易踩的坑。我画了一张对照表场景ST语言C语言数组1..16遍历FOR i : 1 TO 16 DOfor (i 1; i 16; i)数组0..15遍历FOR i : 0 TO 15 DOfor (i 0; i 16; i)循环结束后i的值17163.2 步长不只是1隔点采样、逆序处理批量采集偶尔会遇到隔点采样。比如只处理偶数通道可以写FOR i : 2 TO 16 BY 2 DO aiEng[i] : INT_TO_REAL(aiRaw[i]) * 0.01; END_FOR;.BY 2让变量按2递增同样可以BY 3、BY 5。逆序处理的时候可以用DOWNTO关键字FOR i : 16 DOWNTO 1 DO // 从第16路往回处理 END_FOR;这里的边界同样是闭区间。有一点必须注意步长不能为0。有些运行时如果遇到步长为0会直接进入死循环虽然ST标准要求步长必须是正负常量但不同厂商的处理并不完全一致不要拿这个去试探控制系统。3.3 嵌套FOR循环二维数组遍历时别把行和列写反二维数组的遍历几乎必然用到嵌套FOR。以历史缓冲里把当前采集值写入某一行为例FOR row : 1 TO 10 DO FOR col : 1 TO 16 DO history[row, col] : aiEng[col]; END_FOR; END_FOR;这里外层变量是行frameIndex内层变量是列通道号。循环顺序是先固定第一行把16列全填完再换第二行。对于存储场景如果要写整个历史矩阵只要对应清楚行和列先外后内完全可行。实际项目中常常只需要把某一帧写入固定一行不涉及整矩阵遍历。但如果你要执行矩阵求和、找极值嵌套循环就非常常用。唯一需要注意的是索引顺序大部分人写错都是因为把[row, col]写成了[col, row]在语法上合法逻辑上完全错乱。3.4 WHILE和REPEAT什么时候比FOR更好说了这么多FOR的好话也要承认它不是万能的。批量采集通道数固定用FOR最合适但如果循环次数取决于外部状态比如等待某个外设就绪、读取通讯应答就应该用WHILE或REPEAT。WHILE是先判断后执行可能一次都不执行WHILE ready FALSE DO // 轮询外设状态 END_WHILE;REPEAT是后判断至少执行一次REPEAT // 读取状态 UNTIL ready TRUE END_REPEAT;这两种循环都要格外小心死循环。工业现场只要死循环一次CPU扫描周期就卡死轻则看门狗复位重则整个设备停机。我的建议是凡是写这些等待型循环里面必须加超时计数器比如最多等100个周期就强制退出。批量采集中不需要这种模式绕来绕去明确通道数一行FOR足够。4. 实战拆解16路模拟量信号批量采集的标准程序模板4.1 程序前的变量定义好现在进入正题。以最常见的16路模拟量采集为例逐步搭建一个标准模板。首先定义常量和变量PROGRAM BatchAI VAR CONSTANT AI_COUNT : INT : 16; // 通道总数 END_VAR VAR i : INT; aiRaw : ARRAY[1..AI_COUNT] OF INT; // 原始值 aiEng : ARRAY[1..AI_COUNT] OF REAL; // 工程量值 enabled : ARRAY[1..AI_COUNT] OF BOOL; // 通道使能 scaleHigh : ARRAY[1..AI_COUNT] OF REAL; // 量程上限 scaleLow : ARRAY[1..AI_COUNT] OF REAL; // 量程下限 rawMin : REAL : 0.0; // 原始值下限 rawMax : REAL : 27648.0; // 原始值上限 END_VARAI_COUNT用常量定义后期扩展通道数只需要改这一处。enabled数组用来屏蔽不需要的通道scaleHigh和scaleLow是每路独立的量程上下限。4.2 量程转换公式是怎么放进循环的标准电流信号的原始值和工程量是线性关系。西门子系统中4-20mA对应的原始值范围是0到27648有的仪表系统用0到65535根据硬件手册来定。线性换算公式Eng Raw * (scaleHigh - scaleLow) / rawMax scaleLow放到循环里就是FOR i : 1 TO AI_COUNT DO IF enabled[i] THEN aiEng[i] : (INT_TO_REAL(aiRaw[i]) * (scaleHigh[i] - scaleLow[i]) / rawMax) scaleLow[i]; ELSE aiEng[i] : 0.0; END_IF; END_FOR;这里的INT_TO_REAL是把整型原始值转成浮点数必须显式做。很多初学者会直接写aiRaw[i] / rawMax因为ST语言通常允许整型和实型混写但一旦混写就可能出现隐式转换丢失精度的问题。我的原则是在循环体里凡是涉及浮点运算的地方所有参与运算的整型都显式转成REAL看着啰嗦但排错省心。4.3 批量读取数字量输入把16位DI塞进一个数组模拟量转换完毕再来看数字量。16路DI点在PLC里常常打包成一个WORD16位每一位代表一个通道状态。我们可以在循环里逐位提取VAR diWord : WORD; diBits : ARRAY[1..16] OF BOOL; END_VAR批量提取的写法FOR i : 0 TO 15 DO diBits[i 1] : SHL(diWord, i) AND 16#8000; // 或者按位与 END_FOR;更直白的方法是用SHL和比较FOR i : 0 TO 15 DO diBits[i 1] : (SHL(diWord, i) AND 16#8000) 0; END_FOR;逻辑不再展开核心思想就是把连续分布的状态位映射成数组元素让后面统一处理。注意我的数组下标是1到16而位编号是0到15所以表达式里有一个1的偏移。这种边界细节正是容易出错的地方。4.4 增加采集状态和通道使能批量采集不能光读数值还要考虑通道状态。比如断线、超量程、模块通讯故障这些信息都应该被采集到。可以增加一个状态数组VAR aiStatus : ARRAY[1..AI_COUNT] OF BYTE; END_VAR约定这个状态字的bit0表示模块通讯正常bit1表示通道断线bit2表示超出量程。在循环体里通过外设状态读入然后按位判断。原本要每个通道单独写的报警逻辑现在统一在循环里处理FOR i : 1 TO AI_COUNT DO aiStatus[i] : GetChannelStatus(AI_Module, i); IF enabled[i] AND (aiStatus[i] AND 16#01) 0 THEN aiEng[i] : ConvertRawToEng(aiRaw[i], scaleLow[i], scaleHigh[i], rawMax); ELSE aiStatus[i] : aiStatus[i] OR 16#80; // 标记通道不可用 END_IF; END_FOR;这样写的好处是报警状态、数据转换、通道屏蔽全在一个循环里完成程序结构非常紧凑。4.5 完整程序模板典型OB块内把上面的片段整理成一个可在主程序或循环中断OB中直接调用的完整模板PROGRAM BatchAI VAR CONSTANT AI_COUNT : INT : 16; END_VAR VAR i : INT; aiRaw : ARRAY[1..AI_COUNT] OF INT; aiEng : ARRAY[1..AI_COUNT] OF REAL; enabled : ARRAY[1..AI_COUNT] OF BOOL; scaleHigh : ARRAY[1..AI_COUNT] OF REAL; scaleLow : ARRAY[1..AI_COUNT] OF REAL; rawMax : REAL : 27648.0; END_VAR BEGIN // 1. 从IO模块读取原始值假设系统函数支持数组映射 FOR i : 1 TO AI_COUNT DO aiRaw[i] : ReadAnalogInput(AI_Module, i); // 自定义读取函数 END_FOR; // 2. 量程转换与使能屏蔽 FOR i : 1 TO AI_COUNT DO IF enabled[i] THEN aiEng[i] : (INT_TO_REAL(aiRaw[i]) * (scaleHigh[i] - scaleLow[i]) / rawMax) scaleLow[i]; ELSE aiEng[i] : 0.0; END_IF; END_FOR; // 3. 可选的滤波、报警、趋势记录 // ... END_PROGRAM这已经是一个能跑通全部流程的骨架。实际项目中ReadAnalogInput可以是读取硬件影子内存变量的封装也可以直接赋值系统IO变量。4.6 放到哪个循环任务里执行循环中断OB vs 主OB批量采集中一个很多人忽略的问题这段程序放在哪个任务里如果放在主OB比如每扫描周期执行一次采样的时间间隔是不均匀的因为主OB的执行时间会随机器的负载波动。对于温度、压力这种慢变量无所谓但如果是轴转速、流量等需要恒定采样间隔的信号波动会直接影响控制和趋势准确性。正确做法是放到固定周期的循环中断OB里比如OB32定时100ms一次。批量采集循环就在固定间隔内执行采样周期稳定后续滤波和微分处理才有意义。我的习惯是凡是涉及批量采集和数组写入历史缓冲的逻辑一律放在循环中断OB中同时把数据发布通过中间变量留给主OB去使用。5. 同步性与性能批量采集会不会读到一半的数据5.1 模拟量输入模块的更新机制批量采集看起来只是在循环里逐个通道赋值但现场数据往往不是那么同步的。绝大多数模拟量输入模块的多个通道并不是在同一瞬间完成转换的。模块内部扫描通道A/D、更新寄存器、刷新背板总线这个过程是串行轮询的。也就是说PLC在一个循环周期内连续读取16个通道很可能第1通道已经拿到的是新转换值第16通道拿到的还是上一个转换周期的旧值。对一般过程控制来说这种半拍延迟没什么影响。但如果要做相位分析、动态补偿或者设备保护连锁通道间的时间一致性就会成为一个隐患。5.2 什么时候必须用快照/SYNC功能好在不少PLC的IO模块都提供了同步读取或一致性数据机制。以西门子为例部分AI模块可以通过硬件组态启用同步更新让模块在收到PLC一个触发信号后把所有通道的转换值同时锁存到影子寄存器然后PLC再从影子寄存器批量读出。这种做法的本质是先触发快照再用FOR循环读取保证所有通道的值来自同一瞬时。实现起来并不复杂通常是先调用一个模块级功能块发送SYNC指令等一个周期确认信号然后在同一个循环中断里读取全部通道。我建议凡是采集数据要用于设备联锁、振动分析、差值运算的高速回路优先启用同步读取普通过程量直接循环读也能接受。5.3 循环体本身的开销几千个通道也不怕从指令开销角度讲FOR循环比很多人想象的要便宜。PLC里一条FOR循环的迭代开销取决于控制器的指令集有的控制器对FOR有硬件级优化有的则是软件逐条执行。就算一个周期执行上千次迭代每条迭代有几次索引加法、比较、跳转整体计算量通常远小于通讯或浮点运算。实际工程中真正会影响扫描周期的是循环体内调用了重型功能块比如PID、FFT或通讯收发。批量采集循环体内尽量放简单赋值和初等运算别把PID调用塞进FOR循环。如果确实需要对每路都做PID也建议先在循环里把数据采集完再在循环外对独立通道分别调用PID块。5.4 防止循环中修改循环变量还有一个容易踩的隐性问题在循环体内修改循环控制变量i。ST标准严格来说没有完全禁止这种操作但不同厂商实现差别很大。有的控制器允许结果循环次数变得不可预测有的直接报错还有的勉强能运行但编译器和在线监控给出的循环次数已经失真。我见过一种低级但致命的问题有人在循环里用i : i 1来做隔点处理结果循环因为变量被改动而提前结束采集程序在设备运行一段时间后忽然漏掉后半段通道。排查了很久才定位到是循环变量被改。记住一句话循环变量在循环体内只能读不能写。要实现隔点采样用BY 2不要手动改i。6. 排错笔记五个ST数组与FOR循环的高发错误和排查方法6.1 错误一循环结束后的索引值超出数组边界这个坑几乎所有ST新手都要踩一次。先看代码FOR i : 1 TO 16 DO aiEng[i] : ... END_FOR; // 这里 i 等于 17 result : aiEng[i]; // 越界ST的FOR闭区间导致循环结束后变量i不是16而是17。此时如果接着用i访问数组访问的是第17个元素而数组只有16个元素。有的控制器会抛出越界故障有的则不会直接读到相邻内存的随机值这才是最阴险的。排查方法很简单只要循环之后还需要引用循环变量的最终值就不要让它指向数组。要么在循环结束前用另一个变量保存最后一次用到的索引要么循环结束后立刻给i赋一个安全值比如i : 0。6.2 错误二数组下界和循环边界不一致声明ARRAY[1..16]循环却写FOR i : 0 TO 15这是从C语言习惯带过来的错误。结果是第一路没有采集到最后一路越界。如果是在线调试可以看到第0个元素的值永远是0或者程序报地址越界。排查这种错误需要一个硬手段断点监控。把循环停下来逐个看i的当前值和数组元素内容。如果循环里读出来的数据整体偏移了一位基本就是边界写错了。我的统一规则是声明了1..N循环就写1 TO N声明了0..N-1循环就写0 TO N-1。不要混用两套边界体系。6.3 错误三多维数组的行列颠倒二维数组访问history[row, col]和history[col, row]在语法上都合法但语义完全相反。行对应帧号列对应通道号一旦写反历史数据记录会错乱而且很难通过单步调试发现因为编译不报错数值看起来也合理。我的排查建议是在写嵌套循环时先在纸上把行和列的名称写清楚再在循环里加一个固定的测试值。比如强制给history[3, 5] : 999.0然后在监控表里查看第3行第5列是不是999。如果不是就说明行列混乱了。6.4 错误四类型转换丢精度循环里的类型混用非常普遍。比如aiEng[i] : INT_TO_REAL(aiRaw[i]) * 0.01; -- 正确 aiEng[i] : aiRaw[i] * 0.01; -- 部分编译器隐式转换可能正确 aiEng[i] : aiRaw[i] / 27648 * 100; -- 错误整型除法先执行第三种写法里aiRaw[i] / 27648如果按整型除法执行两个INT相除得到0再乘100还是0。这就是为什么我坚持在循环体里所有整型参与浮点运算时都显式转换。排查方法是看监控值如果工程量输出全部是0而原始值明显非零十有八九是整型除法问题。修改为INT_TO_REAL(aiRaw[i]) / 27648.0 * 100即可。6.5 错误五厂商方言差异ST语言作为IEC 61131-3标准的一部分落地到不同厂商时总有细节差异。我整理了一个常见差异表项目CODESYS / 倍福西门子SCL施耐德/M241数组默认下界从0开始可自定义可自定义FOR循环边界闭区间闭区间闭区间BY步长支持支持支持循环变量类型支持INT/DINT支持INT/DINT支持INT/DINT数组初始化声明时列表初始化支持列表初始化部分版本仅列表初始化从CODESYS项目迁移到SCL最危险的就是数组下界的默认值变化。CODESYS习惯用ARRAY[0..15]SCL习惯用ARRAY[1..16]迁移时所有循环边界都要重新检查。我做过一次跨平台移植至少有一半的bug出在边界不统一上。7. 批量采集的复用与扩展常量、功能块、结构体数组和趋势存储7.1 用CONSTANT定义通道数改一处全局生效前期把通道数写死成16很容易后期扩展就麻烦。所以从第一版程序开始就建议定义VAR CONSTANT AI_COUNT : INT : 16; END_VAR所有数组声明和FOR循环上限都引用这个常量。等现场要加一个8通道模块只需要把AI_COUNT改成24重启程序所有数组自动扩容循环边界自动匹配。连代码都不用改这就是常量的好处。7.2 封装成功能块输入输出接口、实例化多个设备批量采集逻辑如果只写在一个OB里复用性差。更专业的做法是封装成功能块FB。比如FUNCTION_BLOCK FB_BatchAI VAR_INPUT Enable : BOOL; END_VAR VAR_OUTPUT Values : ARRAY[1..16] OF REAL; Status : ARRAY[1..16] OF BYTE; END_VAR VAR i : INT; END_VAR使用的时候实例化多个FB分别对应不同的IO模块或设备主程序里只需要维护各自的实例接口。这种抽象让程序结构性变得很好新增一套设备不需要改采集算法只是新增一个FB实例并接线而已。7.3 结构体数组把传感器信息打包如果采集对象不是简单的数值而是带有通道号、原始值、工程量、报警状态、量程上限的完整传感器信息可以用结构体数组TYPE SensorInfo : STRUCT Channel : INT; Raw : INT; Eng : REAL; Alarm : BOOL; ScaleHigh : REAL; ScaleLow : REAL; END_STRUCT END_TYPE声明变量VAR sensors : ARRAY[1..16] OF SensorInfo; END_VAR采集循环里FOR i : 1 TO AI_COUNT DO sensors[i].Raw : ReadAnalogInput(AI_Module, i); sensors[i].Eng : ConvertRawToEng(sensors[i].Raw, sensors[i].ScaleLow, sensors[i].ScaleHigh); sensors[i].Alarm : (sensors[i].Eng sensors[i].ScaleHigh); END_FOR;结构体数组让批量两个字更有价值一次循环所有属性全部更新后面的逻辑可以直接用sensors[5].Eng不再需要维护一堆平行数组的索引关系。项目越做越大这种写法的优势越明显。7.4 趋势存储与环形缓冲区把历史数据保存成环形缓冲是批量采集的常见扩展需求。核心逻辑很简单用取模运算让索引在范围内回绕。frameIndex : frameIndex 1; IF frameIndex 10 THEN frameIndex : 1; END_IF; FOR i : 1 TO AI_COUNT DO history[frameIndex, i] : aiEng[i]; END_FOR;这样history始终保持最近10帧数据每次写入都覆盖最旧的一帧。对于上位机要读取历史趋势的场景PLC端只需要维护这个缓冲上位机用一条通信指令把整块二维数组读上去即可远比一帧一帧手动拼接高效。7.5 批量采集后的数据上送打包成DB减少通讯负担批量采集完成后数据通常要送给上位机或触摸屏。不要把16个REAL变量一个个传给上位机而是打包到一个结构体或数据块里上位机一次读取整个块。比如VAR_GLOBAL AI_DATA : SensorInfoArray; // 结构体数组类型 END_VAR上位机通过OPC UA或者Modbus TCP读取这个块一条报文就能拿到所有通道的数值和状态。通讯点数量减少画面刷新也不会闪烁整个系统反应都变快了。在我做过的项目里128路温度采集、每100ms一帧全部逻辑包括三块AI模块的通道映射、量程转换、断线报警、环形缓冲存储总共不到80行代码。核心就是数组加FOR循环再套一个结构体管理。你把这套东西吃透了批量采集这个技能点基本就算彻底毕业了。
返回列表