直接进入正题。我见过不少刚开始搞PLC编程的朋友,梯形图里画线圈、触点、自锁互锁一套一套的,逻辑控制玩得飞起,结果一到现场设备要跟仪表、变频器、上位机交换数据就卡住了。不是不会写程序,是不知道面对一堆寄存器里的数字到底该用什么指令去挪、去算、去比。PLC的数据处理指令,说白了就是干这个的。
这篇文章我从实际干活的角度,把传送、比较、算术、逻辑、移位、转换这几类最常用的数据处理指令掰开讲清楚,什么时候用哪个、参数怎么填、现场容易在哪里翻车,都会提到。适合刚从继电器思维转过来的电气工程师,也适合做设备调试、跟HMI和上位机打交道的人。看完不敢说让你变成算法大师,但至少面对一份需要处理数据的控制程序,你不会再一头雾水。
1. 只会“通断”是走不远的:现代PLC的数据处理职责早就变味了
很多教材喜欢把PLC定义为“可编程逻辑控制器”,强调的是逻辑。但实际去了现场你会发现,现代PLC早就不只是把输入信号搬到输出线圈那么简单的角色了。它的本质是一台工业级的嵌入式计算机,CPU、内存、寄存器、通信接口一应俱全。之所以叫“逻辑控制器”,那是因为历史习惯叫法,真到了做项目的时候,数据处理占的比重往往比想象中大得多。
1.1 数据到底从哪里来,又要到哪里去
你想想一台稍微像样点的自动化设备里,数据量一点都不少。称重仪表实时往PLC里灌重量值,扫码枪把一长串字符结果发过来,变频器回报当前运行频率和电流,温度传感器模块把模拟量转成数字量塞进数据寄存器,HMI屏幕要读写各种配方参数,上位机还要从PLC里取产量数据做报表。这些场景没有一个是靠X触点和Y线圈能搞定的,全部要落到寄存器上做数据的搬运和运算。
我曾经接手过一条半自动包装线,原来的程序很简单,就是气缸到位、压紧、松开、计数几个逻辑动作。后来客户非要加一个在线称重分选需求,称重仪表的数据得进PLC,跟设定值比较,再决定推杆动作。程序里面用于逻辑通断的部分没增加多少,反而全是读数据、比较大小、做累计、写入显示的程序段。这个项目让我彻底意识到一件事:PLC的数据处理指令,才是真正支撑设备“智能化”的地基。
1.2 逻辑控制与数据控制的边界在哪里
简单说,逻辑控制解决的是“什么时候做什么动作”,数据控制解决的是“做了之后这个值怎么处理”。两者不是割裂的,很多时候是同一个程序里配合使用。比如气缸推进这件小事,需要判断限位信号是否为ON,这是逻辑;但气缸推到位之后要判断推力是否超过阈值,推力值从变送器读进来,跟设定值做比较,这就是数据处理。
所以初学PLC的朋友不要有误区,觉得逻辑控制简单就先学,数据处理难就放后面。数据处理指令本身一点都不难,难的是理解数据在整个系统里的流转路径。你只要想清楚一件事——数据从哪个地址来、经过什么运算、最终写到哪个地址去——指令的选择就水到渠成。反过来说,如果数据流转路线没搞清,即使你把指令手册背得再熟,到现场还是不知道怎么搭这个框架。
2. 搬数据与做比较:MOV、CMP这类指令才是使用频率最高的
如果统计一下实际项目里所有程序指令的出现次数,传送指令和比较指令绝对排在最前面。原因很简单,任何数据处理的起点都是“把数据从一个地方搬到另一个地方”,而所有决策动作的前提都是“现实数值与目标数值的比较结果”。
2.1 MOV指令没你想的那么浅,位长和地址类型都要盯紧
很多人觉得MOV就是把一个值从源传送到目标,简单得很。但实际用的时候有几个细节很容易被忽略。
第一个是位长问题。大多数PLC的MOV处理的是16位数据,也就是一个字。如果数据超过了16位能表示的范围,就得用32位传送指令,通常叫DMOV。曾经有人在程序里把一个32位的累积流量数据用MOV去传送,结果高16位全丢了,显示出来的数值一会儿对一会儿错,排查了好久才发现是传送指令用错了位长。
第二个是地址类型。MOV的源地址可以是立即数、数据寄存器、偶发是特殊功能寄存器,目标地址一般就是数据寄存器。但要注意,有些PLC对特殊功能寄存器是只读的,你往里面传数值未必写不进去,就算写进去也可能被系统强制刷新覆盖,程序执行结果跟你想的完全不一样。这个在调试送电的时候特别容易踩坑,最好在编程手册里把寄存器的读写属性先看明白。
第三个是执行条件的配合。MOV放在条件为ON的扫描周期里执行,意味着它会持续不断地传送数据。如果你只希望某个时刻传送一次,那么条件必须是一个脉冲信号,否则数据会被不断覆盖。不少初学者在这个问题上摔过跟头,写了一个用常闭触点控制的传送,结果目标数据不停跟着源数据变动,整个程序逻辑都乱了。
我用一个实际场景来说吧。设备上有两个配方,A配方和B配方,选哪个配方就把对应的一组参数搬进当前工作寄存器。这个场景就用两个常开的按钮输入分别触发两次MOV,把不同配方区的数据一次性覆盖到工作区。这个用法简单,但胜在清晰,后面工艺参数修改都不用动逻辑,只改数据区就行。
2.2 CMP比较指令的三个标志位,别只把结果当一个开关用
比较指令CMP的功能是把两个数值做比较,结果通过标志位反映出来。通常有三类结果:大于、等于、小于。不同PLC标志位表示方式不一样,有的用三个标志位分别对应三种结果,有的用一个寄存器里的二进制位表示。
我在这里想提醒的是,比较指令虽然叫“比较”,但它在程序里的用途远远超过“超限报警”这么简单。在实际项目中,比较结果完全可以作为动作切换的依据。比如说称重分拣,称出来的重量大于上限推杆推入A料箱,等于或小于下限推入B料箱,这本质上就是做了两次比较之后输出两路不同的动作。如果把比较结果单纯当成一个报警开关,那数据处理就浪费了一半的能力。
使用比较指令的时候还要注意数值格式的一致性。有些PLC比较器比较的是无符号数,有些支持有符号数,如果数据源里混入了BCD码格式的数值,比较结果很容易出错。我的习惯是在比较之前先做一次明确的数据转换,确保两个操作数的格式完全一致,才进行比较。代码上看多了一行转换指令,但调试的时候能为你省下大把时间。
2.3 传送与比较的搭配逻辑:排查问题的第一步永远是确认数据进来了没有
程序里数据处理出了问题,我总会告诉新手,第一件事不是改指令,而是确认数据源头是否正确。把这个原则落实成具体操作,就是用监控模式去察看在线的寄存器数值。按下启动按钮,那个对应的数据寄存器是否从0变成了1?仪表发送过来的值是否已经出现在接收寄存器里?如果数据根本没进PLC,或者进到了你意想不到的地址,后面一切比较、运算、输出都没有意义。
传送指令在这里起到了“探针”的作用。我会在调试期间临时加一些MOV指令,把关键中间值搬到一个专门用于监控的地址区域,通过HMI或者编程软件的在线监视看一眼数值。确认无误后再把临时的探针删掉。这个习惯帮我快速定位过很多问题,其效率远高于对着程序一行行猜。
3. 加减乘除不只是算数:算术运算指令里的数据类型与溢出陷阱
算术运算指令的助记符大家都很熟悉,ADD是加法、SUB是减法、MUL是乘法、DIV是除法。但真正在现场调试的时候,出问题最多的恰恰是这些看起来人畜无害的“算数”。问题根因就两个:数据类型不匹配、运算结果溢出。
3.1 16位运算的边界问题,比你想的更容易触发
我们先来算一笔账。16位有符号整数的范围是-32768到32767,无符号的是0到65535。在工业现场,一个比较大的计数数值、一个累积流量,稍微运行一阵子就可能突破这个范围。一旦运算结果溢出,程序不一定会报警,但寄存器的值会突然变成一个莫名其妙的负数或者很小的正数,跟现实完全对不上。
解决方式是使用32位运算指令,或者在做乘法和加法之前,先把16位数据扩展成32位再参与运算。千万别以为只有做乘除才需要32位,哪怕只是一个简单的加法,两个数都是两万多,加起来就可能突破32767了。现场的实际值如果接近临界范围,宁可提前换成32位运算,也不要等出了故障再去猜。
我记得有个项目是做板材计数,一个班的产量累计在十几万片,一开始程序用16位加法累加,运行到半上午数据就乱了,显示成负值还继续加。后来改成32位累加,问题立刻消失。这个案例听起来特别基础,但在现场就是经常发生,原因就是写程序的时候没把量程算进去。
3.2 除法的商和余数,别把余数当垃圾丢了
DIV除法指令执行完之后,不仅仅产生商,还有一个余数。大多数PLC把商保存在目标寄存器里,把余数放在紧挨着的下一个寄存器中。有人只关心商而忽略余数,结果在后续处理里需要用到余数来分析和计算的时候,发现那个位置的数据早就被别的指令覆盖了。
余数在一些场景下是有实际意义的,比如做分度计算、位置换算、圆周分割。举个例子,一个转盘上均匀分布12个工位,你有一个绝对角度值,想知道当前停在第几个工位,就可以用角度除以360再除以每个工位角度,商是转过的圈数,余数决定工位号。这种算法虽然也可以用其他方式实现,但用DIV拿余数是很自然的一条路。
所以我建议写除法运算时,如果不确定后续逻辑会不会用上余数,干脆把商和余数的寄存器都预留出来,贴上注释说明。即使当前用不上,也给后续维护留一条后路。
3.3 浮点数运算在PLC里的正确打开方式
传统的整数运算在需要高精度的场合远远不够用,比如温度值25.6摄氏度、压力值1.25MPa、实物重量12.345千克。现代PLC普遍支持浮点数运算,但在PLC里浮点数和上位机程序里的概念没有本质区别,都遵守类似IEEE754的存储格式。
实操中有两个常见的坑。第一个是整数和浮点数的混用,很多PLC的运算指令并不自动做隐式类型转换,你必须先通过转换指令把整数转成浮点数,再参与浮点运算,否则轻则精度丢失,重则直接报错。第二个是浮点数在显示的时候要注意精度格式,比如在HMI上明明PLC里存的是25.60000,显示出来却变成了25.59999,这不是数据错了,而是浮点数二进制存储固有的精度现象。如果你在意这个,处理方法一般是在HMI显示端做位数截取,或者在PLC端把浮点数放大为整数后再显示。
浮点运算对我个人来说用得最多的场景是PID控制的前置处理,比如把模拟量输入换算成工程单位,或者把工程单位再反算成输出占空比。这些公式不复杂,但类型转换搞错了,整个控制环就废了。所以我养成了一个习惯,凡是写浮点运算程序,第一行永远是类型转换指令,先把输入量统一成浮点格式,再往下算。
3.4 逻辑运算不只是“与或非”,它还能干位掩码的活儿
逻辑运算指令比如AND、OR、XOR在PLC里往往被看作非常初级的东西,因为继电器逻辑电路里就有与或非。但在数据处理这个语境下,逻辑运算的用途是把一个数据里的某几位截取出来,或者把某几位单独赋值为0或1,这就是业内常说的按位处理。
举个例子,一个状态寄存器里第0位表示运行状态,第1位表示故障状态,第2位表示手动模式。你想单独提取故障状态,就可以用寄存器和一个常数做与运算,这个常数第1位是1其他位是0,这样就可以把第1位的值单独“抠”出来。这种技巧在跟变频器、伺服驱动器、通信模块打交道的时候几乎天天用。
用生活化的类比,逻辑运算里的与运算就像一套筛子,你设置好哪一位要透过筛子,哪一位被挡住,结果里就只保留你关心的那几位。这个思路比照着位的定义一条条写IF判断要高效得多。
4. 移位、循环与转换指令:现场高级玩法的底层支撑
如果说前面传送、比较、算术是基础款,那么移位指令、循环指令、数据转换指令就是拉开程序水平差距的分水岭。很多人在书本上见过这些指令,但实际项目里用不上,就没认真学。真实情况恰恰相反,越是自动化程度高的设备,越离不开这类指令。
4.1 移位指令解决流水线工位跟踪,比一堆中间继电器优雅太多
在流水线式的设备上,产品沿传送带依次经过各个工位,每个工位有不同的加工动作。难点在于如何知道传送带当前哪些位置上有产品,每个产品应该在哪个工位做哪种动作。传统的做法是用一组中间继电器去模拟位置状态,但产品多了、工位多了,这种继电器做法会变成一场灾难。
移位指令天生就是解决这个问题的。简单讲,你用一个16位的数据寄存器表示传送带上的16个位置,寄存器某一位是1就代表对应位置有产品。每当传送带前进一个固定间距,就把这个寄存器整体向左或向右移动一位,同时在寄存器入口处写入新产品的有无状态。这样整个传送带上的产品分布状态就被一个寄存器完整刻画出来了。
这个场景我做过多回,印象最深的是一条装盒线,产品经过封箱机之后要分流到三条包装支线。我用一个字的移位寄存器跟踪进料位置,用三个比较指令去判断不同位置上的产品应该去哪条支线。程序结构非常清爽,现场维护的人也很容易理解。如果用传统继电器方案,十几个继电器加几十条逻辑才能实现同样效果。
移位指令的使用关键在于“移动一位”与“物理位置变化一个节距”必须严格对应。所以在编写这类程序时,移位指令通常不是持续执行,而是在检测到传送带编码器转过一定角度时,由脉冲信号触发执行一次。如果触发条件设计得不好,位置信息就会逐渐错位,堆出来的货比预料的多一个或者少一个,排查起来特别痛苦。
4.2 循环移位指令做旋转工位逻辑,省心省力
循环移位指令跟普通移位类似,唯一区别是把从一端挤出去的那一位补到另一端,形成一个循环。这个名字听上去很抽象,但应用场景特别明确——圆盘式多工位设备。
假设一个8工位的旋转工作台,每个工位依次经历上料、检测、加工、下料等工艺。工件的实际位置是随着转盘旋转而复用的,下一个周期的工件又会回到第一个工位。这个时候如果用普通移位指令,数据总有一天会被移到寄存器外面丢失掉。循环移位指令则完美适配这个逻辑,转一圈、位循环一圈,状态刚好对得上。
我见过有人用一长串的加减计算和条件判断来实现旋转工位跟踪,程序写了三屏之多,调试的时候自己都绕晕了。而用循环移位指令,加上每转一个工位触发一次移位,几行代码就能把事情说清楚。数据处理的精髓,往往是选对了指令,代码自然就变短了。
4.3 BIN/BCD转换指令:跟仪表和显示设备打交道绕不开的格式障碍
许多传感器仪表为了方便读数,输出的是BCD码格式的数据。所谓BCD码,就是把一个十进制数的每一位分别用一组二进制去表示,它是一种“四位的二进制数对应一位十进制数”的编码。学过的人清楚,用起来其实很别扭,因为PLC内部的算术运算并不支持BCD码,你必须先转换成普通的二进制数(BIN格式),做完运算后再转回BCD格式送给显示设备。
这个转换指令在跟称重仪表、温度控制器、计数器模块通信时几乎避不开。我第一次接触的时候吃过亏,从仪表读回来的重量值直接送到HMI显示,结果屏幕上显示出一串奇怪的十六进制数字,完全不是正常的重量。后来才弄明白,仪表默认输出的数据格式是BCD,而HMI按二进制格式去解读了。
处理这种问题的经验是:先搞清楚通信协议的格式定义,明确各个寄存器输出的到底是BIN还是BCD,再决定是否需要加转换指令。不要不加判断地直接搬数据,格式错了,数值再对也是白搭。
另外,上位机或HMI在读写PLC数据时,同样存在格式一致性问题,你在PLC里辛辛苦苦转换好的数值,到了上位机如果没有按约定的格式解析,还是会乱。所以数据格式这件事,需要整个系统从上到下采取统一约定,不能只盯着一处。
5. 一个真实项目里的数据流:从称重仪表到分流推杆,数据经历了什么
理论讲了不少,拿个实际案例把数据流的完整链路串起来讲,效果会好很多。我之前做过一条产品分选线,客户要求根据产品重量分成三个档位,然后由不同的推杆把产品推入对应料箱。这个项目的核心就是数据处理,没有复杂的动作逻辑,但每一步数据变换都必须正确。
5.1 需求拆解与数据链路设计
先理清需求。产品经过称重台,仪表把重量值发给PLC,PLC将这个值与上下限比较,比较结果决定推杆的动作。此外,还需要统计每个档位的产品数量,并在HMI上实时显示当前重量和累计数量。
我把数据链路画出来是这样的:
- 称重仪表通过通信将重量值存入PLC指定寄存器(BCD格式)。
- 通过转换指令把BCD格式转换成BIN格式,放入中间寄存器。
- 将BIN格式重量值乘以一个倍率系数,得到以克为单位的实际重量。
- 实际重量与两个设定值做比较,比较结果驱动三个档位的推杆输出。
- 每次比较完成,对应档位的计数值加1。
- HMI从PLC读取重量、计数值、设定值,用于显示和参数修改。
这里面每一步指令的选择都很明确,而且我特别重视格式转换这一步。因为仪表输出的BCD码如果不做处理,加法、比较都无从谈起,所以转换指令放在数据链路最前端,一定不能漏。
5.2 核心程序怎么写才能避免重复累加
分档计数这个环节,比较容易犯的错误是:只要推杆还在动作期间,计数指令的扫描条件一直为ON,计数就不断重复增加,一次合格品被记录成十几次。解决方式有几种,我习惯用比较结果从0变1的上升沿脉冲去触发累加。
用指令表的写法思路大致是这样:
- 把重量比较结果存储在一个辅助寄存器里。
- 如果这一档的比较结果刚变为ON,且前一周期该结果还是OFF,则触发一次加法指令,给计数值加1。
- 这样无论推杆动作持续多长,一个产品只对应一次累加。
当时程序里最难调的就是这个脉冲触发的部分。因为在梯形图里写上升沿脉冲,不同品牌的PLC触点写法不同,但思路是一致的:需要“当前周期状态”和“上一周期状态”两个位一起判断。现在很多PLC自带上升沿触点,直接用就行。
5.3 边界情况和信号抖动怎么处理
工业现场没有理想数据。称重产品经过秤台时,数值会跳动,特别是传送带还在运行的情况下,产品还没完全停稳,重量值已经开始往上冒了。如果程序不加任何处理,可能取到一个偏大或偏小的瞬时值,分选结果就会出错。
我当时加了一个简单的稳定判断:当重量值连续若干次采样变化幅度小于一个阈值时,才认定重量稳定,允许进入下一步比较。这个实现用到了比较指令和计数器,逻辑上不复杂,关键是阈值和时间窗口的选择需要根据现场动态调。这个经验后来在很多项目里都复用上了,我个人觉得比直接买贵的仪表值当“稳定”要靠谱得多,因为现场振动、电磁干扰都会让瞬时值变得不靠谱。
另外,档位设定值、倍率系数这些参数,我全部放在了HMI可写的寄存器区,而不是写死在程序里。这样客户需要调整分选范围时,直接在触摸屏上改就行,不用打开电脑改程序下装。
5.4 项目做完之后的几句实在话
这个项目本身不复杂,但它是典型的数据处理完整闭环。仪表数据进来、格式转换、运算比较、结果输出、统计累加、显示交互,一条线全部打通。做完之后,我感觉最能提高调试效率的,就是在动手写程序之前先把数据流向图画清楚。
当时我在纸上把每个数据的源地址、目标地址、数据类型、转换步骤都标出来,然后才开始写逻辑。实际操作中这个准备工作花了大半个小时,但后面的编码和调试非常顺利。对比以前摸着石头过河的状态,这个习惯帮我省下的时间远超过那半小时。
6. 数据被“吞了”?从三类真实故障看数据处理指令的调试基本功
无论指令学得多熟,到了现场总会遇到程序结果跟预期不一致的情况。我总结遇到最多的故障大致有三种,每个背后都有一套固定的排查链路,分享出来希望对大家有实际帮助。
6.1 监控有值但程序不动作,先查执行条件再查数据声明
遇到过这样一个情况:数据明明已经在寄存器里了,但程序就是不执行相应的动作。程序里面有一行ADD指令,负责把一个增加的数值累加到总量寄存器里,可不管怎么加减操作,总量纹丝不动。
我的排查思路是:先在线监视程序,看ADD指令前面的条件触点是ON还是OFF。结果发现条件触点在绝大部分时间都是OFF,只在按下按钮的那一瞬间是ON。但ADD指令本身不是脉冲执行,而按钮按下的时间又短,条件ON的时间只有一个扫描周期的一部分,却执行了一次或多次,理论上不该完全没变化。
继续深挖,原来问题出在这个条件触点被其他地方的一个双线圈冲突影响,某个分支程序在某个时刻把同一个输出位强制写成了OFF,覆盖了按钮输入的ON信号。这种双线圈问题靠眼睛盯很难发现,用程序的交叉引用功能查找这个位在哪些地方出现,很快就找到问题位置了。
这给我一个很重要的启发:遇到数据处理结果不对时,不要急着怀疑指令参数,优先检查这个指令执行的条件是否稳定成立。很多时候不是“算法不对”,而是“该执行的时候没执行”。
6.2 数值乱跳甚至出现负数,检查数据格式和转换步骤
另一个常见故障是,从模拟量输入模块读回来的温度值,在HMI上显示的时候一会儿是25.3,一会儿又变成-12.7,特别不稳定。如果模拟量模块和PLC之间的数据类型、数值范围没弄对,很容易出现这种奇怪的显示。
排查链路是这样:先用编程软件在线监视模拟量通道的原始数值寄存器,看是一个什么样的数据范围。如果原始数值在0到4000之间变动,基本说明模拟量模块的采集本身没问题。接着检查PLC里做的量程换算公式,看看是把0到4000的原始值换算成工程单位,还是漏了某个系数。
我当时发现那个温控模块的原始数据是0到4000对应-50到200摄氏度,而程序里的换算公式却按0到10000的量程去做比例计算,算出来的结果当然完全不对,浮点部分还会因为符号转换问题变成负值。把量程系数改过来后,显示立刻恢复正常。
这里我特别强调一下,不同厂家的模拟量模块,其数字量范围差异很大,有的0到4000,有的0到20000,有的带符号,有的无符号。拿到新模块的第一件事就是去手册里查模拟量数值范围和对应的工程单位,不要凭经验猜,猜错的概率比想象中大得多。
6.3 数据能进但读出来不对,通信字节顺序和地址偏移是重灾区
跟外部设备通过通信交换数据时,还有一种看似“没毛病”的故障:数据明明已经从设备发过来了,PLC寄存器里也能看到一组数据,但数字组合起来就是不对。最典型的就是字节顺序搞反了。
很多智能仪表在通信时默认发送的是高字节在前、低字节在后,但PLC内部的寄存器是低字节在前、高字节在后。如果不做字节交换处理,你读到的数据看起来就是一组对调过的乱码,比如实际的0x1234读出来变成了0x3412。
排查这个问题的方法很直白:通信调试窗口里看十六进制的原始报文,同时对比PLC寄存器里接收到的数据。把两者摆在一起,如果高字节和低字节位置对不上,那就是字节序问题,加一个字节交换指令即可。这类问题如果你没预料到,排查起来会觉得非常玄学,但知道了原因之后,一分钟就能解决。
还有一个地址偏移的问题,有些设备说明书写第1个数据地址是40001,但在PLC实际映射里却是40010,中间隔了9个空地址。很多人在这个偏移量上翻过车,数据读回来总是在某个区段错位。遇到这种情况,我一般会从设备文档上把寄存器地址表完整抄下来,跟PLC通信配置里的地址逐个核对一遍,确认没有偏移误差再进行通信调试。
6.4 调试工具与习惯,比记住所有指令更值钱
说到最后,我觉得调试数据处理程序最值钱的工具不是某条高级指令,而是在线监视功能的使用熟练度。一个合格的工程师应该做到:看到程序里任何一个关键数据寄存器,能立刻在监控窗口里把它找出来,能看懂数值的单位和含义。这项能力需要平时多加练习,没有什么捷径。
我自己的习惯是,在每一段数据处理逻辑旁边都加上详细注释,数据源是什么,目标是什么,单位是什么,转换公式是什么。现场出故障时,照着注释一步步查,效率非常高。如果程序里全是裸奔的数字没有注释,过三个月再看就完全不认识这些寄存器是干什么用的了,更别说调试。
还在做PLC这行的朋友,如果手头有旧项目的程序,不妨把里面所有用过数据处理指令的段翻出来复盘一下。看看当初的注释全不全,想想现在会用什么不同的思路实现。技术这件事,就是要靠一个个实际项目的积累和反刍才真正变成自己的本事。