PLC联锁逻辑优化:ST位操作指令WAND/WOR/WXOR实战指南

发布时间:2026/10/11 1:08:43
PLC联锁逻辑优化:ST位操作指令WAND/WOR/WXOR实战指南
做PLC和上位机联调这些年ST里最容易被低估的一组指令就是WAND、WOR、WXOR这几个位操作。很多同事写联锁逻辑习惯一条条写IF和AND逻辑一多整个FB页面就密得没法看查个问题还得从头捋到尾。实际上把布尔条件抽象成位再用一个WORD当“条件快照”配合WAND/WOR/WXOR做掩码判断联锁代码能写得又短又清楚排查的时候看一眼HEX就明白是哪个条件卡住了。这篇文章就围绕ST位操作在联锁场景里的用法从底层字节逻辑到工程落地的坑逐个讲透。1. 位操作指令的基础理解先搞清楚WAND/WOR/WXOR在算什么1.1 位运算的底层逻辑很多人学ST到布尔运算就停了觉得AND、OR、XOR已经够用再往上不过是函数名多几个字母。其实WAND、WOR、WXOR并不是什么新运算它们就是把AND、OR、XOR从单像素变成了按像素批处理。举一个最直白的例子。假设有两个16位WORD值wA : 16#00FF; wB : 16#0F0F;布尔AND只能判断“条件A为真且条件B为真”但WAND会把wA的每一位去和wB的对应位做AND然后输出一个新的16位值wR : WAND(wA, wB); // 结果为 16#000F结果值每一位都代表“这一位上的两个原始位是否同时为1”。这种“整字批处理”特性非常契合联锁判断一台设备的启动条件往往有一二十个布尔量直接逐条写判断会非常啰嗦而把它们打包成一个WORD后一次WAND就能把“哪些条件满足、哪些不满足”完整算出来。用生活类比解释就是布尔运算是你在逐个检查10盏灯是否都亮着而位运算是一次性拍一张整排灯的照片再从照片里数有几盏亮、几盏暗。1.2 型号命名里的门道不同PLC平台对这三个函数的命名有些差异。比如IEC 61131-3标准里常用的是AND、OR、XOR但不少日系和欧系平台在ST里提供的是WAND、WOR、WXOR。这个字母W代表“Word”意思是操作对象是16位WORD类型。也有平台提供DAND/DOR/DXOR操作对象是32位DWORD。如果编码器、变频器返回的是双字状态或者联锁条件数超过16个就要注意使用DWORD版本。我遇到过一个项目就是取了16个DI硬点之后后来甲方追加了两路急停条件硬塞进原WORD里放不下程序里改了一堆变量类型非常折腾。后来再有类似需求我都直接列DWORD预留空间。需要注意WAND、WOR、WXOR的位长度必须一致。ST里的隐式转换不一定总是可靠两个不同长度的变量想直接位操作有些平台会编译报错有些则会出现高位被丢弃的情况。我的习惯是统一用DWORD或者显式强转避免编译器差异挖坑。1.3 三种指令的分工边界WAND是“交集筛选”多用于判断多个条件是否同时满足或者从一组状态里筛出哪些有效。WOR是“并集聚合”多用于多个来源的报警或故障合并成一个总字方便上层画面统一显示或统一判断。WXOR是“差异检测”多用于状态切换检测、互斥校验、或判断两个快照之间是否有位翻转。最容易被忽略的是WXOR。它的逻辑是两个位不同则输出1相同则输出0。用在联锁里可以用来检测“前后两条扫描周期之间某个状态字是否有变化”变化的位置就是1。这个能力配合上升沿触发逻辑可以在不增加大量比较指令的情况下实现多状态同时变化检测。2. 联锁信息的组织方式从布尔散兵到整字快照2.1 联锁的本质是条件汇合联锁逻辑说穿了就是“多个条件同时满足才允许动作或者任一危险条件出现就切断动作”。常规写法是一堆AND和OR的堆叠页面上还能看但一旦出现旁路、强制、复位、权限分层条件数量膨胀后就非常痛苦。把条件组织成位图可以有效缓解这类问题。所谓“位映射”就是给每一个布尔条件分配一个固定比特位这样所有条件就拼成了一个WORD或者DWORD。联锁判断从“比较各种布尔表达式”变成“比对整数字”。举个例子一台设备控制FB里假设把Bit0定义为急停回路正常Bit1定义为液压站压力正常Bit2定义为前后门关闭到位Bit3定义为变频器无故障Bit4定义为润滑系统正常。那么整个联锁状态就可以用一个WORD变量表示每个布尔条件的变化直接反映在对应的位上。对比一下两种做法。常规写法的启动条件判断IF bEStopOk AND bPressureOk AND bDoorCloseOk AND bVfdNoFault AND bLubeOk THEN bRunAllowed : TRUE; END_IF;如果还要知道“具体哪个条件没满足”还得再逐条写IF NOT bEStopOk THEN bBlockReason : 1; END_IF; IF NOT bPressureOk THEN bBlockReason : 2; END_IF;位操作写法只需要把条件打包一次后面所有判断都通过掩码运算完成性能和可读性都更好。2.2 把条件打包成WORD两种常见的组织手法把散落的BOOL变量变成一个WORD常见做法有两种。第一种是在FB内部把布尔输出逐个赋值给WORD的指定Bit。例如wCond : 16#0000; IF bEStopOk THEN wCond : wCond OR 16#0001; END_IF; IF bPressureOk THEN wCond : wCond OR 16#0002; END_IF; IF bDoorCloseOk THEN wCond : wCond OR 16#0004; END_IF;这样写的好处是清晰但代码量不低。第二种做法是直接通过地址映射把连续的Bool输入映射到同一字节或字上这依赖平台支持的变量映射方式比如直接寻址声明wCond AT %QW50 : WORD;或者用AT指令将FB输入引脚重叠到一个WORD上。这种方式效率最高但可移植性差而且一旦条件顺序变化地址表管理起来费神。我个人的经验是新建项目用第一种老项目改造如果原地址表已经稳定可以用第二种。还有一种常见需求是从BOOL数组生成WORD。比如从现场采集的16路DI信号已经在数组里存好了可以把数组循环移位拼出一个字wCond : 16#0000; FOR i : 0 TO 15 DO IF arrDi[i] THEN wCond : wCond OR (WORD_TO_DINT(1) * 2 ** i); // 请按平台函数风格自行替换 END_IF; END_FOR;这里有几个注意点一是位偏移和地址顺序一定要和图纸一致DI卡件的通道顺序不一定等于模块端子的物理顺序二是大端和小端问题很多控制器内部字节序和外部通讯字节序不一致需要确认。2.3 位掩码设计联锁代码能不能看懂全看这张表位掩码的定义是“关心哪些位”。比如要判断“液压压力、门到位、变频器无故障、润滑正常”这4个条件全部满足其他位不管那么掩码就是wMask : 16#0016; // Bit1, Bit2, Bit3, Bit4这个16进制值不是随手写的拆开看Bit0 急停未复位 不参与判断 Bit1 液压压力正常 需要判断 16#0002 Bit2 前后门关到位 需要判断 16#0004 Bit3 变频器无故障 需要判断 16#0008 Bit4 润滑系统正常 需要判断 16#00104个要判断的位加起来就是 24816 30即16#001E。我之前写错了掩码导致调试了半天后来才养成了“先写位分配表再写程序”的好习惯。实际项目中位分配表应尽量维护在一个常量结构里。建议把所有掩码都声明成常量并且把二进制注释写清楚。比如CONSTANT cMask_StartPermissive : WORD : 16#001E; // 二进制 0000 0000 0001 1110 END_CONSTANT不要直接拿16#001E去硬编码比较那样换了个人看程序根本不知道这个数字什么意思。更规范一点的做法是给每个位定义有名字的常量CONSTANT cBit_EStop : WORD : 16#0001; cBit_Pressure : WORD : 16#0002; cBit_Door : WORD : 16#0004; cBit_Vfd : WORD : 16#0008; cBit_Lube : WORD : 16#0010; END_CONSTANT需要组合掩码时用这些常量叠加wMask_Start : cBit_Pressure OR cBit_Door OR cBit_Vfd OR cBit_Lube;2.4 字节序和扫描周期对位判断的影响位操作本质上是对内存中的整数做运算因此不是所有按位排列的都是布尔量。通讯协议里常见这种坑设备返回一个状态字文档里说Bit0是运行、Bit1是故障、Bit2是远程但如果你按低字节在前解析出来看到的HEX值是反的。我的做法是收到一个新协议的状态字后先写一段临时测试逻辑把Word拆成16个布尔量逐一核对文档里的位定义和现场实际状态。确认无误后再开始写联锁。这个测试步骤虽然简单但能避免后期把整条联锁逻辑建立在错误的字节序之上。扫描周期的问题则更容易被忽视。一个WORD的多个位如果来自不同的IO更新时刻位之间的“快照一致性”就得不到保证。多数控制系统在IO刷新时会整体更新通道数据但如果其中一些位来自通讯从站另一些来自本机IO它们的刷新时刻是不一样的在一个周期里读到的状态字可能同时包含旧周期值和新周期值。解决办法是在OB1或者任务开头统一采集一次状态将需要联锁判断的原始值锁存到内部变量后续逻辑全部基于锁存后的快照判断。这一招能很大程度上避免“偶发性联锁误动作”的排查难度。3. 联锁应用实战三个高频场景拆解3.1 启动允许联锁WAND判断“全部满足”这是WAND用得最多的场景。设备启动前要确认急停未拍、安全门关好、油压正常、无报警等一长串条件。传统的写法是长串IF嵌套或AND连写问题在于后期维护困难。假设调试时甲方说“我想看一眼是哪个条件没满足”传统写法要么额外写一坨赋值语句把每个NOT条件映射到提示变量要么干脆让人去监控十几个BOOL标签逐个找。位操作写法可以一次性给出答案。先看核心判断代码wCond : WORD; // 条件快照 wMask_StartPerm : WORD; // 启动允许掩码 wResult : WORD; // WAND结果 bStartPerm : BOOL; // 启动允许输出 // 假设 wCond 已经打包好了条件位 wResult : WAND(wCond, wMask_StartPerm); if wResult wMask_StartPerm THEN bStartPerm : TRUE; ELSE bStartPerm : FALSE; END_IF;关键点是条件成立的标准是WAND结果等于掩码本身而不是不等于0。如果只判断不等于0那只能说明至少有某个条件满足了无法确定全部满足。这个细节是新手最容易踩的坑。那到底是谁没满足呢可以通过取反掩码来快速定位wMissing : WAND(wCond, wMask_StartPerm) XOR wMask_StartPerm;wMissing中为1的位就是应当满足但实际没满足的条件位。上位机画面上可以直接根据这个word去解析是哪几个条件缺失调试时极大降低了沟通成本。这里再补充一个小细节联锁通过了之后输出bStartPerm只是允许启动不是直接启动。在不带安全PLC的普通设备控制里很多工程师会把联锁输出直接串进输出线圈但这在逻辑上混淆了“允许”和“执行”。建议联锁功能块只负责输出允许位启动命令和允许位在最终输出逻辑里做AND这样每个模块职责清晰图纸审查也方便。3.2 故障字聚合用WOR把分散的报警合并成一个字一个车间级系统里各工位、各站都有独立报警字。做集中监控时如果去逐个“OR”这些报警布尔量画面上要多出成百上千个报警标签。用WOR把多个16位报警字合并就能快速得到系统级总报警字。假设有两个子站的报警字wAlarm_StationA : WORD; // 子站A报警字 wAlarm_StationB : WORD; // 子站B报警字 wAlarm_Total : WORD; // 汇总报警字合并逻辑wAlarm_Total : WOR(wAlarm_StationA, wAlarm_StationB);这个操作看似简单到有点“不值一提”但它解决了一个实际问题报警优先级过滤。如果报警字里的位权重不同你可以先做一次掩码筛选再对需要关注的高优先级位做WOR。比如wHighA : WAND(wAlarm_StationA, cMask_HighPriority); wHighB : WAND(wAlarm_StationB, cMask_HighPriority); wHighTotal : WOR(wHighA, wHighB);这样总报警字里只有高优先级位会被置位用于触发声光报警低优先级位只做状态记录。在很多项目里这个做法能让报警系统的逻辑层级简化一大截。还有一个实际用途是故障字请求复位。很多设备模块支持“故障字清零”的需求某个站有多个报警条件复位时希望把所有报警条件一次性清除。可以先把“复位命令”和“各报警字”做WAND再把结果写回各个子站报警字。不过这种操作涉及输出变量的写保护策略需要谨慎建议只在特定模式下使用。3.3 状态切换检测与互锁WXOR的妙用WXOR在联锁中的价值主要体现在两种场景。第一种是“检测变化”第二种是“互斥校验”。检测变化最常见的应用是从站设备的状态字通过通讯周期性更新希望捕获“某一时刻哪些位跳变”。假设上一周期的快照是wOld本周期是wNewwDiff : WXOR(wOld, wNew);wDiff中为1的位就是发生变化的位置。如果只关心特定几个位是否变化再和掩码做一次WANDwChange : WAND(wDiff, cMask_CareAbout);这个用法在很多智能设备的“心跳”检测、模式切换、运行/停止边界识别里很好使。传统做法是对每个位单独做上升沿变量16个位可能要写16组edge检测用WXOR一行就完成了。第二种应用是互斥校验。典型的场景是设备有前进/后退、正转/反转两组主令两者不允许同时有效。如果把状态位放在一个WORD里那么以下判断就很直接wExclusive : WXOR(wStateAndCmd, cMask_Forward OR cMask_Reverse);当系统中恰好只有指定的一个方向命令时XOR的结果满足掩码的异或期望值。不过要小心XOR在这种场景下的含义它可检测“两个方向命令是否不同”如果同时给了两个命令结果不会符合预期。互斥校验更常用的写法其实是先合并掩码再统计位数。但不是所有ST平台都自带统计位数的功能所以也可以用变通方案wCfg : WOR(wCmdForward, wCmdReverse); if wCfg 16#0006 THEN // Bit1和Bit2同时为1说明两个方向同时给了命令系统处于非法状态 END_IF;如果希望更通用可以使用WXOR配合预设的正确模板。比如设定“允许的模式”是0x0001、0x0010、0x0100三选一把当前模式字和每个合法值做WXOR若某个异或结果为0则说明当前模式合法。这个思路在模式互锁里很实用。4. 进坑与复盘工程落地中的具体经验4.1 常见问题速查表现象可能原因排查方式WAND结果总是等于0位掩码用错或者条件打包时根本没映射到相应位监控wCond的十六进制值对照位表核对明明所有条件满足联锁不通过掩码把不需要判断的位也算进去了导致结果不等于掩码重新展开掩码二进制核对每个参与位通讯来的状态字看起来是乱的大小端字节序与协议不一致编写临时拆位逻辑逐个位对照偶发性误动作多个位来自不同刷新时刻快照不一致在任务开头统一锁存状态字WXOR检测变化时频繁误触发快照更新周期与逻辑扫描周期不匹配快照只在固定时间点更新保持节奏一致数组拼字后位顺序错误数组索引方向反了用固定测试值验证拼字逻辑实际调试中最痛苦的往往不是逻辑本身而是“逻辑可能没毛病数据不知道从哪来的”。所以位操作联锁程序在调试阶段强烈建议给每个中间WORD变量加上HMI监控画面用十六进制显示再配上位表说明。这样现场联调时技术人员不用去翻代码看画面就能把问题报给你。4.2 联锁功能块的结构设计建议在写联锁功能块时我一般会分成三个区域输入采集区、位运算区、输出输出区。输入采集区负责把分散的BOOL条件整理为WORD快照同时做一次锁存。位运算区用WAND/WOR/WXOR搭出判断逻辑输出允许位或报警字。输出区负责将结果安全输出包括置位、复位、故障字上报等。这样分区的意义在于设备逻辑变更时绝大多数情况只需要修改输入采集区的位映射表位运算区和输出区可以保持稳定。尤其是多个设备共用同一个标准联锁块时这种结构化设计能显著降低后期维护成本。另外联锁功能块尽量别把WAND结果直接又当作下一个FB的输入形成过长的组合逻辑链。ST虽然支持嵌套表达式但工程上只要出现三层以上的位运算嵌套可读性就会骤降。如果你发现自己的表达式里有三个以上的WAND请拆成中间变量。4.3 安全联锁的边界位操作不是安全认证的替代品这点一定要单独讲。位操作再方便也只是功能逻辑层面上的联锁手段。真正涉及人身安全的保护回路例如急停回路、光栅、安全门应当使用安全继电器或安全PLC硬接线实现ST里的WAND/WOR/WXOR不能替代安全回路也不能作为安全完整性等级要求的核心逻辑。很多工程师问我“我能不能把急停信号也放到这个WORD里参与联锁判断”功能上当然可以但理念上不建议。急停信号应当走硬接线安全回路PLC里即便参与逻辑也只是做状态显示和记录。如果把安全相关信号和普通联锁位混在一起未来安全认证或整改时会有很大麻烦。4.4 我比较推荐的编码习惯一是给每一个位操作加注释时不要只写“WAND判断”要写出这个掩码对应哪些条件、判断通过代表什么意义。二是常量命名不要用16#F0F0这种可读性差的直接量而是定义成描述性的常量。三是位分配表建议从0到15固定顺序不要随意插队否则变更维护时容易头脑发热。四是在版本更新记录里标注位定义的变化。某个月我调整过一个旧项目的Bit3含义过了两个月后在另一个设备上套用了旧版逻辑最后排查时才发现原来该设备的Bit3定义不同。这类问题靠记忆一定会翻车必须写进注释或者文档。5. 一点额外的收尾心得写了这么多位操作和联锁的内容我自己的体会是判断一个工程师对ST的理解深度很多时候不是看他能不能写出复杂算法而是看他如何组织条件数据。位操作并不玄学核心价值在于它逼着你先梳理信息结构再落代码。WAND、WOR、WXOR三个函数本身五分钟就能学会难的是理解“位”背后的工程语义——哪些条件应该放到同一个字里、哪些位优先级更高、哪些数据必须在同一时刻采样。把这些想透再上板联调你的联锁逻辑会比你现在的版本稳定得多。如果你正在用ST写设备控制建议下一个项目里挑一个启动允许联锁把散落的条件位打包成一个WORD配一张位表用WAND重写一遍。跑一个班次之后再回头看大概率你就回不到以前那种长串IF嵌套的写法了。