ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

S7-200指针寻址批量统计程序实战:平均值最大最小值

S7-200指针寻址批量统计程序实战:平均值最大最小值 做西门子200S7-200项目时我经常要处理一类需求从一批测量值里算出平均值、最大值、最小值然后送到触摸屏或上位机显示。数据量有时候十个有时候二十个最烦的是数据起始地址还可能随着配方切换变化——直接寻址累加的程序写一次改一次烦不胜烦。后来改用指针重写统计逻辑配合FOR循环做批量扫描一份子程序通吃各种场景。这篇文章把完整程序、注释思路和调试过程里踩过的坑都整理出来给同样在S7-200上做数据统计的同行参考。重点不是说要写出炫技的代码而是把批量数据统计这个高频需求做成真正能复用、能交付、能维护的程序模块。1. 为什么S7-200程序里要用指针从直接寻址的痛点说起1.1 直接寻址做统计的三种典型困境先看最常见的做法。假设有10个测点数据存放在VW100、VW102、VW104一直到VW118要求平均值很多工程师的直觉是LD SM0.0 MOVW VW100, VW120 // 累加和初值 I VW102, VW120 I VW104, VW120 I VW106, VW120 I VW108, VW120 // 后面略去10次累加数据少的时候这没问题但项目一扩张问题全来了。第一类问题是数据量变化。今天10个测点明天工艺调整变成20个那就得在网络里再补10行累加指令稍不注意漏掉一个平均值就错了。如果项目周期紧、现场改频繁这种补网络的操作非常容易出低级错误。第二类问题是起始地址变化。很多设备有多套配方配方A的数据放在VW100开始配方B的数据放在VW200开始。直接寻址时你得把整段累加和比较逻辑全部替换成新地址等于把程序重写一遍。更麻烦的是如果数据个数不同还要同步增加或者删除指令改完还要重新调试。第三类问题是最隐蔽的你只想统计某一段数据。比如数据区有50个值但只关心第10个到第30个之间的平均值和极值。直接寻址的做法要么复制几份逻辑要么写出一堆判断跳转程序可读性直接崩掉。本质上这三种困境的根源都指向同一个事实数据处理的范围是动态的而直接寻址把范围写死了。1.2 指针方案解决什么指针间接寻址的思路完全不一样。它不去点名每个数据地址而是把当前处理到哪个地址装在一个变量里程序每处理完一个数据就让这个变量指向下一个地址。数据个数和起始地址都变成变量循环体只有一份。还是那10个测点用指针写出来大概是这样的逻辑初始化指针指向VW100循环10次每次取指针指向的字做累加和比较然后指针加2字节指向下一个字。如果换成20个测点只改数据个数一个变量如果换起始地址只改指针初值。程序本体一行不用动。很多新手觉得指针难主要是被指针间接寻址取地址这些术语吓住了。实际上在S7-200里指针的用法非常固定就三件事取地址、取内容、改地址。把这仨练熟批量数据处理就打开了新窗口。当然我也要说一句公道话如果只是偶尔统计三五个固定的数据没必要上指针直接寻址反而清爽。指针的价值在于数据规模可变、数据处理逻辑可复用这是选择边界。2. 动手前的准备S7-200指针寻址原理与内存区规划2.1 指针到底是个什么东西用个生活化的类比你查资料不是直接背下文件全部内容而是记住文件在哪个柜子哪个抽屉哪一层然后按这个线索去取。PLC里VW100里放的是数据本身而指针变量里放的是数据存在哪个地址这个线索。在S7-200里构造指针分三步取地址MOVD VB100, VD0把VB100的地址值放进VD0。注意符号表示取地址MOVD是双字传送因为S7-200的地址编号是32位的。取内容MOVW *VD0, VW30星号*表示取指针指向的地址里的内容这里就是取出VW100的值放到VW30。改地址D 2, VD0把VD0里的地址值加2。因为S7-200的字节编址方式是每8位一个地址编号一个字VW占2个字节所以处理完一个字后指针要加2才能指向下一个VW。这里最容易绕晕的是MOVD VB100, VD0里的VB100是一个32位地址值而MOVW *VD0, VW30里*VD0代表的是VD0指向的那个字的内容。一个是门牌号一个是门牌号对应的房间里放的东西千万别混。2.2 指针能指向哪些区域S7-200的指针可以指向V、I、Q、M区但是不能做位寻址。也就是说你可以取VW、AIW、IW这类字数据但没法直接用指针操作某一个位。实际项目里V区用得最多因为V区是掉电保持的数据存储区适合放批量测量值和处理结果。对模拟量采集这个常见场景指针还可以直接指向AIW区域比如用AIW0作为起始地址就能连续扫描一组模拟量输入通道。这在AIW通道数量较多、又想动态决定扫描范围时特别有用后文测试部分我会补一个这种思路的扩展。2.3 本次程序的地址规划表代码能正常跑一半功劳在地址规划。我把程序用到的地址全部列出来后面调试时也照这个表去监控地址用途说明VD0数据指针指向当前待处理的数据字VW4数据个数N入参调用前写入范围1~100VD1032位累加和所有数据求和必须用双字VD1432位平均值商高字为VW14低字为VW16VW40平均值输出16位整数平均值VW42余数输出平均值的小数余数部分VW20最大值输出统计期间的最大值VW22最小值输出统计期间的最小值VW24FOR循环当前索引循环指令内部使用VW26FOR起始值固定为1VW28FOR结束值固定为N-1VW30当前数据缓冲每轮循环取出的数据VD32临时双字N转换、除法除数VD34商乘N中间量计算余数用VD36余数中间量低字为VW38M0.0数据个数错误标志N1时置位M0.1统计完成标志统计完成后置位这里特别强调两个设计决策。第一累加和必须是32位双字。16位整数的范围是-32768到32767就算100个数据每个平均只有5000累加和就到500000了16位根本放不下。用ITD把每个数据先符号扩展成32位再累加是防止溢出的关键。第二输出区、中间变量区、数据区三者相互隔离。指针区放在VD0中间变量集中在VW24~VW38数据区从VB100开始保证指针在循环过程中不会把自己存数据的区域也覆盖进去这个问题很多第一次写指针的人会栽后面坑点部分详细说。2.4 数据结构约定数据区从VB100开始按照字VW连续存放每个数据占2个字节。VW100存第1个数据VW102存第2个数据以此类推。最多支持100个数据也就是从VB100到VB298的区域。数据来源可以是触摸屏写入、上位机Modbus下发也可以是数据块预置。本文测试阶段用数据块直接填值方便验证。3. 完整程序清单STL语句表与逐行注释以下程序我全部用STL语句表编写。原因很简单S7-200的STL和LAD可以互相转换但指针加循环的逻辑在STL里表达最紧凑逐行注释也最直观。Micro/WIN里新建子程序把代码粘进STL编辑器即可。Network 1 // 复位标志位保证本次统计从干净状态开始 LD SM0.0 R M0.0, 1 R M0.1, 1 Network 2 // 指针初始化指向数据区首地址VB100 LD SM0.0 MOVD VB100, VD0 Network 3 // 数据个数检查N1时置错误标志并跳转到结束 LDW VW4, 1 S M0.0, 1 JMP 9 Network 4 // 取首个数据作为最大值、最小值和累加和的初值 LD SM0.0 MOVW *VD0, VW30 // 当前数据 指针指向的字 MOVW VW30, VW20 // 最大值初始化为首元素 MOVW VW30, VW22 // 最小值初始化为首元素 ITD VW30, VD32 // 带符号扩展转双字防止累加溢出 MOVD VD32, VD10 // 总和 首元素 D 2, VD0 // 指针跳过已处理的首元素指向第二个数据 Network 5 // 计算循环次数FINALN-1INIT1 LD SM0.0 MOVW VW4, VW28 // 装载N -I 1, VW28 // N-1剩余待处理数据个数 MOVW 1, VW26 // FOR起始值 Network 6 // 循环处理剩余数据累加、比大小、指针步进2字节 FOR VW24, VW26, VW28 MOVW *VD0, VW30 // 取出当前数据 LDW VW30, VW20 // 当前值 ≥ 当前最大值 MOVW VW30, VW20 // 则更新最大值 LDW VW30, VW22 // 当前值 ≤ 当前最小值 MOVW VW30, VW22 // 则更新最小值 ITD VW30, VD32 // 当前值转双字 D VD32, VD10 // 累加到总和 D 2, VD0 // 指针移动2字节指向下一字 NEXT Network 7 // 计算平均值双字除法商 总和 / N LD SM0.0 ITD VW4, VD32 // N转双字 MOVD VD10, VD14 // 被除数 总和 /D VD32, VD14 // 商存入VD14低16位VW16可直接读取 Network 8 // 计算余数余数 总和 - 商×N LD SM0.0 MOVD VD14, VD34 // 商 *D VD32, VD34 // 商×N MOVD VD10, VD36 // 总和 -D VD34, VD36 // 总和 - 商×N 余数 MOVW VW16, VW40 // 平均值整数部分输出 MOVW VW38, VW42 // 余数输出 S M0.1, 1 // 统计完成标志 Network 9 // N1时的跳转目标子程序返回 LBL 93.1 第一个数据为什么要单独处理主程序刚进入时我们不知道这批数据是什么范围所以最大值和最小值不能盲目设0或设一个大数否则统计结果会错。我的做法是先把第一个数据取出来同时作为最大值、最小值和累加和的初值。这样无论数据是正数还是负数、数值多大初值天然就在正确范围内。循环从第2个数据开始处理所以Network 4末尾指针已经加上2指向了VW102。循环次数也相应设置为N-1避免多算一次。3.2 更新最大值和最小值的比较逻辑比较部分用的是两个带条件的传送指令LDW VW30, VW20 // 当前值 ≥ 当前最大值 MOVW VW30, VW20 // 更新最大值这里用的是而不是意思就是遇到相等的情况也允许更新。在实际效果上二者没有差别因为值相等时更新与否结果一样。我习惯用和写逻辑上更完整看着也舒服。最小值同理LDW VW30, VW22 MOVW VW30, VW22这两个比较在STL里是独立逻辑行前一个比较的结果不会影响后一个因为每条LDW都会重新开始一个新的能流逻辑。这是S7-200语句表和传统继电器梯形图不一样的地方写的时候不用担心上面的MOVW会不会把下面的比较打断。3.3 余数为什么不用取余指令而是用减法S7-200数学指令里有除法/D可以直接得到商但很多版本里要拿到余数最稳的还是用总和减掉商乘个数这个办法。数学原理很简单余数 总和 - 商 × N代码里对应三步MOVD VD14, VD34 // 取出商 *D VD32, VD34 // 商乘以N得到整数部分对应的总和 MOVD VD10, VD36 -D VD34, VD36 // 原总和减去整数倍总和剩下的就是余数为什么不用专门的取余指令一方面S7-200指令集里取余运算MOD在部分固件版本和Micro/WIN版本里表现不一致不如加减乘除直观另一方面这个公式本身很好理解现场维护的人一看到总和-商×N就明白在算什么比查手册回忆取余指令的格式要快得多。3.4 高低字对应关系必须看明白网络7和网络8里出现了VW16和VW38这两个地址我专门解释一下因为这是新手最容易误读的地方。VD14是一个32位双字由四个字节构成VB14、VB15、VB16、VB17。其中VB14和VB15组成高16位字VW14VB16和VB17组成低16位字VW16。PLC的32位除法结果VD14是完整数据但如果平均值不超过16位能表示的范围那么有效的整数部分就在低字VW16里。所以我写MOVW VW16, VW40把平均值输出到VW40。VW38同理是VD36余数中间量的低字因为余数最大也不会超过N-116位足够。简单记在S7-200里想要32位数据的低16位就取VD地址里数字2的那个VW地址。VD14的低字就是VW16VD36的低字就是VW38。4. 程序怎么跑起来调用方式、测试方法与结果验证4.1 主程序调用子程序统计逻辑放在子程序SBR_0里主程序负责初始化参数和触发调用。下面给出一个可以直接用的最小主程序Network 1 // 首次扫描初始化数据个数实际项目里由触摸屏或上位机写入 LD SM0.1 MOVW 5, VW4 // 测试阶段先写死5个数据 R M0.0, 1 R M0.1, 1 Network 2 // 用I0.0上升沿启动统计 LD I0.0 EU CALL SBR_0触发方式根据现场需求调整可以用按钮、触摸屏位、定时中断或者上位机Modbus写线圈。重点是VW4这个数据个数必须在调用前准备好不然子程序统计的是错误的数据范围。数据区我建议在数据块Data Block里预置测试阶段最方便。Micro/WIN的数据块写法很简单VW100 100 VW102 200 VW104 150 VW106 180 VW108 170这表示VW100里存100VW102里存200依次类推。4.2 第一组验证测试整数平均编译下载以后我在状态表里添加这几项监控VW4、VW40、VW42、VW20、VW22、M0.0、M0.1。把PLC切到RUN给I0.0一个上升沿观察结果项目期望值实际值平均值VW40160160余数VW4200最大值VW20200200最小值VW22100100完成标志M0.111手算验证100200150180170800800除以5等于160整除余数0最大值200最小值100。完全一致。4.3 第二组验证测试有余数的场景把数据改成100、101、102、103、105它们的和是511除以5商102余1。期望结果平均值102、余数1、最大值105、最小值100。实测结果也符合。这一步主要验证余数计算逻辑正确以及VW42确实能输出非零余数。4.4 边界条件测试边界测试不能省。我把N设置为1只保留VW100200。此时程序应该输出平均值200、最大值200、最小值200、余数0并且完成标志置位。再把N设置为0此时M0.0应该变成1程序跳过统计逻辑直接返回VW40维持上一次的值。N1时Network 5算出的FINAL是0FOR指令会检查当前索引大于结束值直接跳过循环体所以程序不会出错。N0时Network 3的比较指令会触发跳转。这两个防御逻辑虽然简单但真遇到上位机发了个0或者触摸屏没设置好能避免CPU进入异常或者统计出荒唐结果。4.5 模拟量AIW数据的扩展用法S7-200的指针可以指向模拟量输入区。如果现场有一批AIW通道要统计把Network 2的指针初始化改成MOVD AIW0, VD0数据个数N改成需要扫描的AIW通道数即可。注意AIW地址也是字对齐的每次指针步进仍然是2字节。这个扩展让同一个子程序既能处理V区内存数据也能处理模拟量实时值一个模块两种用途。4.6 如果要平均值带一位小数怎么办整数平均值丢掉小数位在某些项目里不够用比如温度要求显示到0.1度。常见做法是把总和放大10倍再除*D 10, VD10 // 总和放大10倍再除以N这个时候商就代表平均值乘以10的数值比如平均160.5度商就是1605。触摸屏显示时除以10加小数点即可。需要确认这个操作是否安全100个数据每个最大约32000原始总和约3200000放大10倍约32000000远小于S7-200双字的上限约21亿所以不会溢出。但如果数据量更大或者数值接近32767放大100倍就要重新核算了。5. 实操中最容易翻车的几个坑5.1 指针越界没有人替你踩刹车S7-200对指针访问V区没有自动的边界保护。如果N设置成200而数据区实际只有VB100到VB298循环前50次没问题指针到VB400之后就会越过V存储区的末尾轻则读回一个没有任何意义的数据重则导致V区写入冲突、CPU停机报错。我的建议是双保险。第一调用侧对N做限幅触摸屏和上位机写入VW4时通过组态脚本限制在1~100第二程序在检查N1的同时也可以加一条LDW VW4, 100的判断大于100时同样跳转并置错误标志。我在上面的代码里只保留N1的判断是为了让主逻辑更清晰实际工程项目里我会加上上限判断。5.2 字对齐陷阱指针偏移必须是偶数VW地址要求偶数字节开头这是S7-200字访问的硬性隐含规则。如果你不小心把指针做了D 1, VD0指针指向了奇数地址再去执行MOVW *VD0, VW30读出来的数据是错位的甚至可能触发访问异常。表现上就是统计数据忽大忽小非常难排查。要保持字对齐遍历VW数据就必须以2为步进。我的代码里两次指针移动用的都是D 2绝不是写错了。如果你的数据是双字VD连续的那步长就要改成4。这个规则记牢能避免一大类莫名其妙的灵异现象。5.3 指针区与数据区重叠最隐蔽的故障我曾经犯过这个错为了省地址把数据区放在VB0到VB30然后指针存在VD0。程序第一遍跑起来正常第二次触发统计时数据已经被上一次循环写坏了。为什么因为指针变量本身就在数据区内循环过程中MOVW *VD0, VW30取到的数据可能正是指针自己或者别的中间变量改来改去整个数据区就乱了。解决方法就是地址规划的早期阶段把区域彻底分开。我给出的表格里变量区和输出区全部安排在VB0~VB43数据区从VB100开始中间留了足够缓冲就是防止这种交叉污染。调试遇到数据一统计就变样时先检查地址分配表看看指针、中间变量和数据区有没有重叠。5.4 FOR循环结构里别塞跳转指令S7-200规定FOR和NEXT之间的循环体不能包含跳转指令JMP也不能用CRET条件返回从循环中间跳出。我见过有人想在某个条件下提前结束循环在循环体内加跳转结果编译报错或者运行行为怪异。如果确实需要提前退出通常的替代方案是用一个标志位在循环体内判断标志后把所有后续操作空转掉。但对于我们这种固定次数的数据统计循环次数本来是确定好的完全不需要提前退出按部就班跑完才是正确的。5.5 状态表监控时别把VW14当成平均值程序跑完有人会在状态表里加VW14来监控平均值结果看到一个大数跟触摸屏显示不一致以为程序写错了。其实VW14是VD14的高字平均值整数部分在低字VW16里。32位运算的结果读取低字这个坑不仅在平均值这里有很多用到双字运算的地方都会遇到。我在代码里单独增加了MOVW VW16, VW40这一步专门把平均值低字复制到VW40就是为了让现场调试的人一眼能看到真正的平均值不用每次都纠结高低字。类似地余数我也复制到VW42。输出地址清晰能省掉大量解释工作。5.6 累加溢出16位加法是新人重灾区如果不用ITD转换直接用一串I做16位累加前几个数据没感觉数据一多累加和一旦超过32767符号位翻转数值瞬间变成负数平均值、最大值、最小值全跟着崩。这个问题在现场调试里非常常见因为测试数据量少的时候看不到溢出一上真实数据就暴露。解决办法就是我代码里的标准处理三件套取数据后先ITD转32位再用D累加最后用/D做32位除法。这套流程对数据为正或负都适用符号扩展后负数也能正确累加。5.7 注释要能帮半年后的自己这个程序的顺利运行有很大一部分功劳在于一开始就把注释写得足够完整。我的注释习惯是每条指令行后面跟上这句在做什么、为什么这么做比如D 2, VD0 // 指针移动2字节指向下一字既是说操作也在提醒读者字长是2字节。写注释最忌讳的是把MOVW VW30, VW20写成把VW30送到VW20这是用原文复述等于没注释。真正有用的注释应该写出意图为什么传、在什么条件下传、传完之后对业务结果有什么影响。给PLC程序写注释本质上是给未来的自己和其他维护工程师写说明手册。结尾最后分享一个实用的扩展思路当前子程序的数据区首地址VB100是写死的如果你有多个数据源要统计可以把这个地址作为子程序的入口参数传入把子程序升级成一个带形参的通用函数调用时指定起始地址和数据个数即可。我当时把几个工位的批次数据都交给同一个统计子程序处理程序体积没变只是每次调用传入不同的地址参数和个数参数项目维护起来轻松很多。这个改造逻辑其实不难核心就是把MOVD VB100, VD0换成一个从形参读地址的指令。建议你理解代码之后先在这个版本上跑通再往灵活性方向改。用指针的好处就在这——程序结构一旦确立后续的扩展基本都在参数上做文章而不是在代码里堆重复逻辑。
返回列表