☰
PLC联锁怎么用WAND/WOR/WXOR把BOOL逻辑改成字级位操作
2026/10/11 1:07:11 网站建设 项目流程

做PLC联锁做了不少年头,我越来越觉得一个问题:很多写ST的人,平时翻来覆去就是IF、FOR、CASE,偶尔用一下算术指令,但WAND、WOR、WXOR这三位“字位操作”老哥,一直被当成生僻指令晾在旁边,只有抄别人程序时才会碰见一次。实际上,要说处理联锁逻辑最趁手的工具,我觉得就是它们仨。今天不打算讲教科书式的定义,就聊聊我是怎么把一堆散了架的BOOL联锁,改写成字级位操作联锁的,以及这么改完之后,程序变短、排查变快、复用还简单的一套真实思路。

这篇内容适合谁看?正在做设备联锁、顺控、安全联锁逻辑,但被大段BOOL条件搞得头大的人;也适合那些刚学ST、想把位操作指令用明白的人。只要你看懂这篇文章,至少能解决三件事:知道WAND、WOR、WXOR分别能在联锁里干什么;知道怎么用“状态字+使能字”把几十条联锁条件压缩成几条ST语句;知道真正现场调试时,这三个指令会踩到什么坑。

1. 为什么联锁逻辑值得用“字”来做

1.1 传统BOOL联锁的四个痛点

先看一个最常见的联锁写法:

IF bPressure_Low AND bLevel_Low AND bEStop THEN bTrip := TRUE; END_IF;

如果只是三五个条件,这么写一点问题都没有。但一套设备动辄十几路联锁信号:出口压力、入口流量、轴承温度、振动、急停、阀门到位、手自动状态……这时候就麻烦了。

第一个痛点是变量爆炸。每个联锁条件都要一个BOOL变量或者一个IO点。程序页翻起来,光是声明变量的区域就占半屏,点位表、注释、安全栅表格,越堆越多。

第二个痛点是改联锁逻辑要改程序。现场经常出现“某台泵振动信号误报,暂时屏蔽两天”这种需求。传统做法是注释掉一行,或者加一个“假标签”按钮。改一次程序重启一次PLC,遇到不能停机的地方就被迫在运行中改逻辑,风险很高。

第三个痛点是调试效率低。在线监控时,你看到十几个BOOL一个一个跳,却说不清到底是谁把最终联锁拉起来的。就算加了一堆中间变量,也只是多了一堆BOOL而已。你可能要同时盯几个变量窗口才能定位。

第四个痛点是复用性差。一套泵组的联锁逻辑,换一台设备、换一组位号,就得复制、粘贴、改位号。如果你改漏一个,那恭喜你,现场会给你上一课。

所以做多了之后你会发现,把这些“同类型、同等级”的联锁信号按位塞进一个WORD里,用“字指令”一起处理,简直是直觉上就该做的事。

1.2 从“一个信号一个变量”换成“一组信号一个字”

用一个比喻解释这个思路:传统BOOL联锁,相当于你去超市买16样东西,每样东西单独拿一个塑料袋拎着;位操作联锁则是把这16样东西全塞进一个带隔层的购物箱,箱子编号,隔仓位号固定,找东西时打开箱子一目了然。

放到程序里:一个WORD有16个位,Bit0到Bit15。你让Bit0代表“出口压力低”,Bit1代表“高位水池液位低”,Bit2代表“P1泵振动”,Bit3代表“P2泵振动”……那么这个WORD就成了一个“设备联锁状态字”,一个变量承载了16个联锁信号。

然后你再准备一个“使能字”,也是16位,每一位对应状态字的同一位。这一位是1,就说明对应联锁条件参与动作;这一位是0,就相当于把这个条件旁路掉。这就是工程上很常用、很多人却没用起来的“使能掩码”机制。

状态字像一块面板,使能字像面板上的一排开关,开关合上的回路才接入联锁。运行时想屏蔽哪个信号,直接在线改使能字的对应位就行,不用改程序,不用停机。

1.3 WAND/WOR/WXOR在联锁里的分工

有了状态字和使能字,接下来就是怎么用三条指令把它们组织成联锁逻辑了。我的习惯分配非常固定:

指令在联锁中的作用工程化理解
WAND按位过滤用掩码筛选出需要参与联锁的有效触发位
WOR按位合并把多个条件、多个状态汇总成一个字,或把一个输出位“拉起来”
WXOR按位比较判断两组状态是否完全一致,常用于双通道反馈校验

它们组合在一起,就是一套“联锁求值流水线”:状态字先经过使能字这个WAND过滤器,再进WOR做汇总判断,输出联锁结果。WXOR则专门负责那些“两路信号必须一致”的场合,比如接触器辅助触点反馈和变频器运行反馈。

记住这个分工,后面看代码就顺了。

2. WAND/WOR/WXOR的原理与易错点

2.1 WAND:用掩码决定谁参与联锁

WAND按位把两个WORD做“与”运算,规则很简单:对应位都是1结果才为1,否则为0。写出来一般是:

wResult := WAND(wStatus, wEnableMask);

这个表达式想表达的逻辑是:wStatus里的某一位是否有效,由wEnableMask的同一位决定。掩码位为1,就放行;掩码位为0,就清零。

举个例子,状态字wStatus = 16#001F,意思是Bit0到Bit4都为1,五个条件全部触发。使能字wEnableMask = 16#001B,二进制是0000 0000 0001 1011,也就是Bit0、Bit1、Bit3、Bit4为1,Bit2为0。那么WAND的结果就是16#001B,Bit2被屏蔽掉,其他四个位保留。

这个操作在工程上非常直观。假设Bit2代表振动信号,而现场振动探头正在检修,随时会误报,那你让使能字的Bit2等于0,振动联锁就被旁路了,其余联锁照常工作。而且这一切可以在运行状态下在线修改,对维护人员极度友好。

我说个容易犯的错:把使能字写反,本来想屏蔽某个位,结果只剩这个位参与联锁。比如你写16#FFFB只清零Bit2,那没问题;但如果你写16#0004,那就等于把其他所有位都屏蔽了,只剩Bit2有效。这类错误在仿真时未必暴露,一旦现场其他条件真实触发,联锁却不动作,那就要命了。所以我在程序里会把使能字做注释,把位号表直接贴在注释里。

WAND还有一个小用途:判断某几个位是否“全部为0”。如果WAND(wStatus, 16#000F) = 0,说明低四位一个都没触发。这个写法在复位逻辑里很常用,后面实战例子会用到。

2.2 WOR:合并条件、拉开输出

WOR按位做“或”运算:对应位只要有任意一个是1,结果就是1。它在这里负责两件事。

第一件事是“汇总”。联锁的本质是“任何有效条件触发,就要动作”,这天然就是一个或逻辑。你可以用WOR把多个字接起来:

wActive := WOR(wAct1, wAct2); wActive := WOR(wActive, wAct3);

如果你用的是支持多操作数的ST平台,写成 wActive := wAct1 OR wAct2 OR wAct3; 也完全等价。但是记住,我们讨论的WAND/WOR/WXOR很多时候是作为系统函数调用的,它接收两个字,返回一个字,这种函数式写法在跨平台移植时更稳妥。

实际用的时候别忽略一个细节:WOR的两拨操作数,往往一个是“状态条件”,另一个是“人为干预”。比如自动模式下,联锁结果字wAct是1,但同时有一个“调试握手解除”信号。你要是把这两个字做WOR,调试状态下联锁也可能被拉下来,这就不对了。所以WOR只合并处于同一逻辑层级、同一权限等级的信号,不同权限等级的要用AND条件去控制。

第二件事是“拉位”。在你构造状态字时,常常需要把一个BOOL条件放进一个字的某个位。很多编译器支持“wStatus.3 := bP1Vib;”这种位元素赋值写法,但如果你的平台不支持,用WOR一样能实现:

IF bP1Vib THEN wStatus := WOR(wStatus, 16#0008); END_IF;

这就是“置位”操作,WOR有一个特点:只有1能把0变成1,0不会把1变成0,所以连续对同一个字做多次WOR,不会误伤其他位。这在拼装状态字时非常安全,因为我用循环扫描时并不知道哪个位已经被置过位。

2.3 WXOR:专门处理“不一致”

WXOR按位做“异或”运算:两个对应位不同,结果位为1;两个位相同,结果为0。这个指令在联锁里的核心场景是“双通道反馈校验”和“状态变化识别”。

什么叫双通道反馈校验?举个例子:电机的接触器辅助触点给一路反馈,变频器的运行继电器又给一路反馈。正常情况下,两个反馈应该是一致的:接触器吸合,变频器也输出运行;接触器断开,变频器也停止。如果两个反馈相反,那系统内部必然有问题,要么触点烧死、要么接线错误、要么继电器卡壳。这时用WXOR把两个反馈字一比较,不一样的位就会变成1,一眼就能找到异常通道。

对应代码是:

wDiff := WXOR(wContactFb, wDriveFb); IF WAND(wDiff, 16#0003) <> 0 THEN // bit0或bit1对应设备反馈不一致,触发报警 END_IF;

这里有个非常隐蔽的坑:WXOR比较的是“整个字16个位都一致”,但你的两个反馈字里,并不是每个位都有实际意义。如果一个字的Bit0对应接触器反馈,另一个字Bit1也存了一些无关状态,WXOR的结果就会把这种“无关差异”也算成不一致。所以要在WXOR之后再用WAND把有效位筛出来,而不是直接判断wDiff是否不等于0。我就会把WXOR和WAND焊死在一条代码里,从不单独判断。

还有一个工程认知要强调:“一致”不等于“都为1”。两路反馈都为0,说明设备确实处于停机状态,这也是一致,WXOR结果位是0,不是报警。很多新手一看到两路都是0就慌了,以为异或只认“一高一低”,其实两路都低是完全合法的停机状态。

在联锁复位里WXOR也很好用:如果要求“检修门关闭且门锁接通”才能复位,两个信号状态一致且都为真,才允许启动;只要有一路没到位,就用WXOR判定为不一致,禁止复位。本质上这是一种比较器。

2.4 宽度、类型和运算符的换算细节

用这三个指令之前,先确认你的操作数宽度。16位和32位是完全两套东西,有的平台提供WAND只能操作WORD,处理32位要换DAND或者DWAND。如果你混着用,轻则编译报错,重则数据被截位,高16位悄悄丢掉,程序看起来运行正常,但某一天某个状态突然消失。

类型尽量用无符号数,也就是WORD、DWORD。如果用INT去存,16#FFFF在INT里是-1,你监控时看到的是一堆负数,很容易产生误解。我的规矩是:凡是位操作相关的变量一律声明为WORD,绝不用INT,更不用REAL。

关于ST原生运算符和函数形式的关系,多说一句。在IEC 61131-3语法里,对字数据用AND、OR、XOR运算符本身是合法的。但在实际工程中,有些PLC的ST编译器对“跨类型、跨长度的运算符重载”支持并不好,用WAND/WOR/WXOR这种函数式指令反而更可靠,也更接近底层指令表。你可以把两者看成同一个逻辑的不同写法,但在代码风格上统一用函数式,不乱交叉,移植时能少挨不少刀。

还有一个细节是优先级。ST里比较运算、算术运算、逻辑运算符的优先级容易被忽略。我见过有人写:

IF WAND(wStatus, wEnableMask) <> 0 AND bReset THEN

这行代码在标准ST里其实有歧义风险。稳妥写法是把WAND结果先赋给一个中间变量wAct,然后再把wAct和其他条件做组合。简单粗暴,但百分之百不会因为优先级踩雷。从可读性上讲,中间变量也是好习惯,因为它能让你在监控软件里直接看到“当前有效联锁位”到底有哪些。

3. 实战:一套三泵联锁改造

3.1 场景还原与原始痛点

假设有这么一个输送系统:三台给水泵,P1、P2、P3,并联向高位水池供水。每台泵有独立接触器和变频器,系统里有出口压力、水池液位、每台泵的振动探头,还有急停按钮。

传统程序大概是这样的:

IF bPressLow AND bTankLow AND bEStop THEN bP1_Trip := TRUE; bP2_Trip := TRUE; bP3_Trip := TRUE; END_IF; IF bP1Vib AND bTankLow THEN bP1_Trip := TRUE; END_IF; IF bP2Vib AND bTankLow THEN bP2_Trip := TRUE; END_IF; IF bP3Vib AND bTankLow THEN bP3_Trip := TRUE; END_IF;

看着还行?那再让你加一个“手动模式屏蔽振动联锁”的条件,加一个“复位按钮”,加一个“低压持续3秒才动作”的延时,这套代码就开始膨胀,注释比代码还长。每次查问题,你都得把这块IF逻辑从头到尾走一遍,最要命的是你很难通过在线监控一眼看出“现在到底是哪个条件让这台泵停了”。

改造目标很简单:把联锁条件按位整理成WORD,用WAND做使能筛选,用WOR做汇总判断,用WXOR做反馈校验。旁边加两张位分配表,程序后期维护人员看懂一次,以后就能自己加条件。

3.2 状态字、使能字的地址规划

先把状态字wStatus的位定义定死,这是整个方案的地基:

位号含义触发电平
Bit0出口压力低1=触发
Bit1高位水池液位低1=触发
Bit2P1振动1=触发
Bit3P2振动1=触发
Bit4P3振动1=触发
Bit5急停按下1=触发
Bit6~Bit15预留

然后定使能字wEnableMask初始值。默认全部参与,就是16#003F,二进制是0000 0000 0011 1111,Bit0到Bit5全为1。以后想屏蔽某一项,在线把对应位改成0即可。

同时定义P1、P2、P3的“泵触发位字”映射,也就是把bit2、bit3、bit4分别翻译成三台泵各自的联锁输出。方便后面每条泵单独取位。

3.3 完整的ST代码

下面是主逻辑,尽量写得规整,方便直接抄:

PROGRAM PumpInterlock VAR // 联锁状态字、使能字 wStatus : WORD; wEnableMask : WORD := 16#003F; wAct : WORD; bTrip : BOOL; // 三台泵的联锁输出 bP1Trip : BOOL; bP2Trip : BOOL; bP3Trip : BOOL; // 双通道反馈校验 wCmdFb : WORD; // 变频器运行反馈,bit0=P1, bit1=P2, bit2=P3 wContactFb : WORD; // 接触器辅助触点反馈 wDiff : WORD; bFbErr : BOOL; bRunReset : BOOL; // 手动复位允许 END_VAR

状态字拼装:如果平台支持位元素访问就直接给位赋值,如果不支持,就用WOR把BOOL位拉起来。

wStatus := 0; IF bPressLow THEN wStatus := WOR(wStatus, 16#0001); END_IF; IF bTankLow THEN wStatus := WOR(wStatus, 16#0002); END_IF; IF bP1Vib THEN wStatus := WOR(wStatus, 16#0004); END_IF; IF bP2Vib THEN wStatus := WOR(wStatus, 16#0008); END_IF; IF bP3Vib THEN wStatus := WOR(wStatus, 16#0010); END_IF; IF bEStop THEN wStatus := WOR(wStatus, 16#0020); END_IF;

联锁求值:

wAct := WAND(wStatus, wEnableMask); IF WOR(wAct, 0) <> 0 THEN bTrip := TRUE; ELSE bTrip := FALSE; END_IF;

三台泵各自取出联锁位:

bP1Trip := (WAND(wAct, 16#0004) <> 0); bP2Trip := (WAND(wAct, 16#0008) <> 0); bP3Trip := (WAND(wAct, 16#0010) <> 0);

输出停机命令:

IF bTrip THEN bP1Run := FALSE; bP2Run := FALSE; bP3Run := FALSE; END_IF; // 也可以让每台泵独立使用 bPxTrip,取决于逻辑结构

双通道反馈校验:

wDiff := WXOR(wCmdFb, wContactFb); bFbErr := (WAND(wDiff, 16#0007) <> 0); // 只检查前三台泵

如果bFbErr为TRUE,可以追加报警、锁定复位、点亮HMI提示,这就是联锁之外的故障诊断层。

3.4 模拟验证的观察要点

有了这套结构,在线验证就变得非常清晰。

先把wEnableMask设为16#003F,然后把wStatus置成0,此时wAct为0,bTrip为FALSE,三台泵都正常允许运行。

接着给wStatus的Bit0置1,模拟出口压力低。wAct = WAND(16#0001, 16#003F) = 16#0001,不等于0,bTrip变成TRUE。但三台泵的单独Trip位都是FALSE,因为触发位是全局低压,不是某一台泵特有的。工程表现就是:三台泵整体联锁停机,直接进复位流程。

再模拟P1振动触发:wStatus的Bit2置1,wAct = 16#0004,bTrip为TRUE,同时bP1Trip为TRUE,另两台泵不受影响。

任何时候你想知道“当前到底哪些联锁位有效”,直接盯监控里的wAct,用十六进制显示,位号表一对照,马上就知道是谁触发的。这比翻几十行BOOL状态快得多。

最后模拟反馈校验:把wCmdFb设为16#0001,wContactFb设为0,wDiff就是16#0001,bFbErr为TRUE,说明P1变频器反馈说在运行,接触器却说没吸合,要么接线有问题,要么接触器触点坏了。

整个验证过程,我基本不动程序,只在线改几个字的值,就能覆盖主要联锁路径。这就是字位操作最实在的价值:把系统状态压缩成“一个值”,人可以直观地“看见”系统状态。

4. 常见问题与排查技巧实录

4.1 位号错位、信号闪烁与扫描时序

最常见的问题是位号错位。不同图纸、不同PLC习惯,Bit0到底是排在十六进制的最右边还是最左边,很容易打架。十六进制16#0001表示最低位Bit0是1,很多人脑子里却是“左边第一位”,一下子就错位了。我的建议是:在代码注释里画一张二进制的位分布图,把“bit0=压力、bit1=液位……”这种对应写成表格贴在那里,谁改谁知道。

第二个问题是信号闪烁。现场触点抖动、模拟量临界点反复穿越、传感器信号瞬间丢失,都会让状态字在几个扫描周期内跳来跳去。如果你直接WAND、WOR、WXOR,触发位存在的时间可能只有一个扫描周期。处理手段是加滤波延时,确认信号维持一定时间后再置入状态字。尤其做联锁,对“瞬时抖动”绝不能手软。

第三个问题藏在扫描时序里。状态字的拼装、使能字的读取、联锁输出,最好在程序里保持固定顺序:先读入IO和中间BOOL,拼装新状态字,再做联锁求值,最后输出。如果在同一个扫描周期里,一段程序更新了状态字,另一段程序又立刻用它做联锁判断,没问题;但如果你把输出关闭逻辑放在状态字拼装之前,就可能发生“本周期读旧状态、下周期才生效”的情况,现场表现就是联锁动作慢了一拍。

WXOR还有个专属时序问题:接触器和变频器动作时间天然有先后差。你发出启动命令的一瞬间,接触器可能3毫秒就吸合反馈,变频器可能50毫秒后才有运行反馈,这期间WXOR必然查出“不一致”。所以双通道反馈校验必须加启动确认时间窗,设备启动后的前几百毫秒不参与校验,等状态稳定后再投入。少了这一步,你的系统会天天误报警。

4.2 联锁无法复位的典型坑

联锁触发后无法复位,是现场最常见的抱怨之一。用字位操作很容易查:看wAct是不是已经归零。如果wAct还是非0,说明某个联锁条件仍然存在,系统当然不允许复位。

但有一种情况是“条件明明消失了,联锁还是不复位”。这时候要检查你用的变量类型和保持属性。状态字如果被声明成保持型变量,断电前是故障态,上电后故障条件虽然清了,状态字却还停留在旧值。解决方法是上电初始化,在程序启动的第一个周期把状态字、使能字、输出字全部归0或者赋一个已知的安全初值。

还有一种很隐蔽的错误:你在联锁复位时错误地修改了使能字。比方说,为了强制复位,有人把wEnableMask清零来旁路所有条件,结果故障清了,忘改回来,使能字一直等于0,联锁永久失效。这是我认为最危险的操作。如果需要临时屏蔽某几个条件,正确做法是在复位完成后把使能字恢复原值,且这个恢复动作要放在复位按钮的边沿里执行,别在持续电平里执行。

另外,触点常开常闭搞反,也会让状态字的某一位恒定是1。排查时不要只看程序里的BOOL名称,要去机柜里对着实际触点状态和IO表核一遍。位操作让人“看得见”状态,但如果采集源本身就反了,字里装的就是错误事实,再漂亮的逻辑也会做出错误判断。

4.3 高效率排查的流程与习惯

最后分享一下我现在的排查流程,基本上是肌肉记忆了。

第一步,看wAct,而不是看一堆BOOL。监控软件里把wAct设为十六进制显示。如果wAct不是0,对照位号表就知道谁还在触发。这是最快的。

第二步,如果wAct是0但联锁还在,就去查bTrip的置位逻辑里是否还有其他条件参与了或运算,比如复位按钮没有接通、双通道校验错误被锁存了。也就是把“联锁源”和“联锁锁存”分开查。

第三步,如果怀疑WXOR误报,分三步走:先停掉校验功能或把wDiff的有效位掩码临时缩小,再看当前设备处于什么状态。比如设备在运行,wContactFb=16#0007、wCmdFb=16#0007,wDiff=0,正常;设备在停止,两个都等于0,也正常。只要两个字眼里的实际位分别成立,WXOR结果才有意义。

第四步,多做小步试验。在线把wStatus强制成16#0000,看整个联锁是否解除;再依次给每一位置1,看对应动作是否准确。这个方法成本最低,也最能树立信心。实际上就是“输入矩阵遍历法”,在做安全联锁验证时极其好使,比对着纸面推演有效得多。

顺手再提一个代码习惯:给状态字里的关键位用常数命名,而不是到处写16#0008这种裸数。我一般用:

CONSTANT BIT_P1VIB : WORD := 16#0008; END_CONSTANT

这样写WAND(wStatus, BIT_P1VIB)这种代码,读起来跟读注释一样清楚。裸数是代码里最差的注释,尤其是十六进制,三周后再看,没人记得那个1是在第几位。

写到最后

实话实说,WAND、WOR、WXOR单个拿出来都很简单,真正难的是形成一种“用字来思考联锁”的习惯。我现在的做法是固定的:信号按位分组装进状态字,使能字负责旁路控制,WAND做过滤,WOR做汇总,WXOR做比较,最后把结果字对应到每个执行机构。这套模式我已经在好几套不同的设备上复用过了,最大的感受就是改逻辑不再提心吊胆,在线监控一眼能看到整个联锁系统的“全景图”。如果你现在还在写那种十几个BOOL变量嵌套的联锁,我真心建议你拿一套小设备先试试这种写法,用顺了之后,你会发现自己回不去了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询