
1. 这三个指令到底在干什么——先厘清底层逻辑做PLC编程的人尤其是从梯形图转过来学结构化文本ST的一开始看到WAND、WOR、WXOR这三个指令多少会有点懵。梯形图里做联锁无非就是常开常闭触点串并联逻辑肉眼可见到了ST里习惯写法变成了IF语句加布尔变量一行行写下来也还行。那为什么还要单独拎出WAND、WOR、WXOR这三个家伙先说结论这三个指令不是用来替代普通逻辑判断的它们解决的是“一个周期内同时处理多个位的组合”这个场景。注意关键词是“同时”和“多个位”。普通ST逻辑写起来是顺序执行的哪怕你用AND、OR连接多个条件PLC扫描到这一行时本质上是按顺序把前一个结果再和后一个条件做运算。而WAND、WOR、WXOR这类“字操作”指令就像是一把梭子直接把一整组16位或者32位数据一次算完。举个不那么严谨但好理解的类比普通逻辑是一条条排队过安检位操作指令是直接开闸放人过闸机。从底层看WAND是字与、WOR是字或、WXOR是字异或它们按位独立运算互不干扰。比如WAND(IN1:16#F0F0, IN2:16#0FF0)结果就是16#00F0。每一位的运算规则跟单个布尔量的AND/OR/XOR完全一致但因为它们是按字或双字并行处理的所以在数据打包传输、状态集中映射、多重联锁条件汇总这些场景里效率上的优势非常明显。我做过一个实际项目一条产线上有32个安全门状态如果用IF逐个判断代码能写一大堆但用WAND一次校验16个门的状态位两行代码就能完成等同的效果而且逻辑更清晰。还有一个容易忽视的点WAND、WOR、WXOR不会发生“短路求值”现象。普通逻辑表达式里如果前面已经能确定结果后边的表达式就不会被扫描到这在某些需要同时检查状态和复位信号的联锁逻辑里会埋坑。而位操作指令不存在这个机制它是完完整整把每个位都算一遍。正是这个特性让它在联锁应用中变得异常可靠。一句话总结如果你面对的联锁逻辑只是三五条件、设备数量少IF够了但如果信号成组出现、状态需要集中校验、互锁条件多到肉眼数不过来就该请WAND/WOR/WXOR上场了。2. 从信号组合到联锁控制——先看最基础的落地写法2.1 用WOR合并互不排斥的“启动源”很多设备有多个启动来源本地按钮、中控远程启动、自动程序启动。这些信号之间不是互斥关系只要任何一路有效设备就允许启动。这种场景就是WOR的典型应用。以16位字变量为例假设定义了一个WORD类型的变量每一位代表一个启动允许信号VAR StartSources : WORD; // 第0位:本地按钮, 第1位:中控远程, 第2位:自动程序 StartAllow : BOOL; END_VAR // 把三个启动源对应的位合并到同一个字变量里 StartSources.0 : LocalButton; StartSources.1 : RemoteCommand; StartSources.2 : AutoProgram; // 用WOR合并只要有一个位为1结果就不为0 StartAllow : WOR(IN1 : StartSources, IN2 : 16#FFFF) 16#0000;这段代码的思路是先做字合并再做非零判断。实际上直接用WAND配合掩码可能更常见。注意这里的IN2用16#FFFF意思是所有位都参与判断——如果你只关心低三位掩码就应该写成16#0007。掩码这个概念后面会反复用到请先记住一个原则永远不要假设别人会把高位清零主动用掩码限定关注范围。2.2 用WAND实现“全部满足才放行”的许可链联锁里最经典的就是“许可链”冷却泵运行、润滑油压力正常、门关闭、急停未触发全部满足才允许主轴启动。用梯形图写是四个触点串联用ST的普通逻辑写是IF冷却泵 AND 润滑压力 AND 门关 AND 急停未触发 THEN。但如果这些条件是以“字变量中的不同位”存在的WAND就来了。假设有一个状态字StatusWord第0位冷却泵运行反馈第1位润滑压力正常第2位安全门关闭第3位急停未触发注意这里设计为急停未触发时该位为1我们要判断“低4位全部为1”代码就极简// 掩码为低4位有效全部置1才通过 IsReady : (WAND(IN1 : StatusWord, IN2 : 16#000F) 16#000F);相比IF语句逐个判断这段代码一目了然——你一眼就能看出“哪几位参与联锁”。修改联锁条件时不用去数代码行直接改掩码即可。比如想增加“第4位液压压力正常”掩码从16#000F改成16#001F完事。实际工程中被问到最多的一个问题是为什么用等于判断而不是大于0因为WAND的结果是位组合只有“全部为1”才能让联锁放行。如果写成大于0那只要有一位满足就会通过等于把“与”逻辑变成了“或”逻辑。这是低级错误但在现场我确实见过有人这样写排查了半天发现是运算符选错。2.3 用WXOR揪出“不该同时为1”的互锁位互锁场景里A和B两个状态不允许同时有效。比如正转接触器和反转接触器如果同时吸合主回路短路设备损坏是板上钉钉的事。ST里常规写法是IF Forward AND Reverse THEN Alarm : TRUE; END_IF;但这样只能报警没法在代码层面确保两者不可能同时为1因为外部反馈可能异常。如果用WXOR逻辑就变成了// 只取第0位和第1位参与互检 PermitRun : (WXOR(IN1 : StatusWord, IN2 : 16#0003) 16#0003);这里需要先解释一下为什么WXOR的结果等于掩码就意味着两个互锁位状态相反假设正转位在第0位反转位在第1位正常情况下两个值只能是0和1或1和0。异或运算的特性是“相同为0相异为1”所以如果两个位状态相反WXOR结果为1如果相同要么同时为0要么同时为1结果都为0。等于16#0003就是等于掩码即“低两位都是1”也就是说第0位和第1位确实不同一个为1一个为0。这个场景里WXOR的价值在于它把“互斥校验”变成了一次运算而且覆盖面是可以任意扩展的——如果你想校验3个位中只能有一个为1比如三选一的模式选择WXOR不能直接搞定但加上一个额外的或判断就成。后面我们会展开说。3. 联锁实战一套带手动/自动切换和状态反馈的电机控制块前面讲的都是指令的拆解热身这一节我完整过一遍实战代码场景是某输送设备上的电机控制。这个项目我做过类似版本下面写的是简化后可以直接复用的骨架逻辑做了脱敏但结构完整。3.1 控制需求拆解电机本身不复杂复杂的是联锁条件混杂了自动、手动和故障旁路。需求如下自动模式下启动条件是上游设备运行反馈、下游设备准备好、安全门闭合、无急停复位请求。手动模式下只要安全门闭合、无急停请求点动按钮直接启动。运行中如果触发“堵料故障”必须立即停机并且除非故障复位信号给到否则不允许再启动。状态输出把运行状态、故障状态、模式状态打包成一个WORD送到HMI显示和上位机。这套需求里自动启动条件之间有严格的“全部满足”关系适合WAND手动模式和自动模式之间是“或”的关系适合WOR堵料故障与复位信号之间是“该位为1就停机”的关系用WXOR检验状态一致性也适用。3.2 变量与数据类型规划VAR // 输入映射来自现场IO或上位机通信 bSafetyDoor : BOOL; // 安全门闭合反馈 bEStopOK : BOOL; // 急停未触发1正常 bUpstreamRun : BOOL; // 上游设备运行反馈 bDownstreamRdy : BOOL; // 下游设备准备好 bManualMode : BOOL; // 手动模式激活 bAutoMode : BOOL; // 自动模式激活 bManualStart : BOOL; // 手动点动按钮 bAutoStart : BOOL; // 自动启动指令 bBlockageFault : BOOL; // 堵料故障 bFaultReset : BOOL; // 故障复位信号 // 联锁状态映射字 wPermitStatus : WORD; // 位映射见下方注释 // 0: 安全门闭合 // 1: 急停未触发 // 2: 上游运行 // 3: 下游准备 // 4: 手动模式 // 5: 自动模式 // 6: 堵料故障 // 7: 故障复位请求 // 8: 手动点动 // 9: 自动启动指令 wAutoPermit : WORD; // 自动允许计算结果 wManualPermit : WORD; // 手动允许计算结果 wModeEnable : WORD; // 模式允许合并结果 bStartSignal : BOOL; // 最终启动指令 bRunFeedback : BOOL; // 电机运行反馈 bFaultLatch : BOOL; // 故障锁存 END_VAR把布尔量映射到字变量的位这一步很关键。不是简单赋值就完事你要强制自己设计好位图。这个位图设计得清晰后续WAND/WOR/WXOR的掩码才写得清爽。比如我在这套程序里约定低4位是自动启动条件第4和第5位是模式标志这样的位布局是为了让掩码能分片使用。3.3 状态字组装与自动联锁计算先把各位映射到wPermitStatuswPermitStatus.0 : bSafetyDoor; wPermitStatus.1 : bEStopOK; wPermitStatus.2 : bUpstreamRun; wPermitStatus.3 : bDownstreamRdy; wPermitStatus.4 : bManualMode; wPermitStatus.5 : bAutoMode; wPermitStatus.6 : bBlockageFault; wPermitStatus.7 : bFaultReset; wPermitStatus.8 : bManualStart; wPermitStatus.9 : bAutoStart;自动联锁启动条件要求0、1、2、3位全部为1且6位堵料故障为0。自动模式下还要求第5位为1。先算自动允许// 自动联锁安全门急停上游下游全部就绪 wAutoPermit : WAND(IN1 : wPermitStatus, IN2 : 16#000F); bAutoPermitOK : (wAutoPermit 16#000F);再算故障锁定状态。这里用WXOR做“故障与复位一致性检测”// 第6位故障第7位复位。正常情况下故障为1时复位必须为0 // 如果两者同时为1即故障没消失却给了复位说明复位请求不可信 wFaultCheck : WXOR(IN1 : wPermitStatus, IN2 : 16#00C0); bFaultValid : (wFaultCheck 16#00C0);这一段的含义要仔细理解当故障位1且复位位0时异或结果低两位都是1等于掩码16#00C0故障有效当故障位0且复位位1时异或结果也是16#00C0但这种情况属于“无故障却给复位”不应该允许启动所以还要额外判断故障位确实为1。实际项目中这个逻辑通常搭配锁存器使用下面的步骤会补完整。好到了模式合并。自动允许和手动条件之间用WOR合并不是指直接把两个允许位的字相加而是说最终启动条件等于“自动允许且自动模式”或“手动允许且手动模式”。这里有一个常见的易错点WOR只能做“多路来源满足其一”的合并但它不能帮你区分模式。所以模式本身要作为与条件先参与运算// 自动模式有效 自动模式标志位为1 与 自动联锁通过 wAutoModeResult : WAND(IN1 : wPermitStatus, IN2 : 16#0020); bAutoModeOK : (wAutoModeResult 16#0020) AND bAutoPermitOK; // 手动模式有两点不同不需要上下游反馈但依然需要安全门、急停有效 wManualBase : WAND(IN1 : wPermitStatus, IN2 : 16#0003); bManualBaseOK : (wManualBase 16#0003) AND bManualMode; // 手动点动按钮接在手动允许基础上 bManualStartOK : bManualBaseOK AND bManualStart; // 故障情况下无论手动自动都不允许启动 bFaultLatch : bFaultValid OR (bFaultLatch AND NOT bFaultResetPulse); bFaultLatch : bFaultLatch AND NOT bFaultReset; // 最终启动信号 bStartSignal : (bAutoModeOK AND bAutoStart) OR bManualStartOK; bStartSignal : bStartSignal AND NOT bFaultLatch;实际操作中我建议先把每个中间结果独立命名并做输出方便调试时监控。不要在FB里只留一个最终结果否则现场调试时看不到中间是哪一环不满足会被人追着问。3.4 为什么这样写比纯BOOL逻辑更稳这套写法最大的优势在于状态位映射后HMI和上位机拿到的就是打包好的字上位机做显示和诊断时不需要把几十个布尔量一个一个交换通信。我只发了两个WORD变量就覆盖了所有联锁状态通信负载降低非常明显。另一个隐性好处是可追溯性。用梯形图写联锁别人看程序要看半天用ST加位操作设计每个掩码像协议文档一样清晰。定版之后维护人员拿到代码先看位图注释再对照掩码基本不需要反复追问设计意图。我参与的一个项目还直接把位图低成本映射成了HMI上的状态列表联锁不满足时哪位不满足一眼可见省了很多售后的沟通成本。从执行性能角度一个扫描周期内同时组装状态字、执行掩码校验、锁定故障状态指令条数远少于同功能的多层IF嵌套程序扫描周期也能压缩。在设备节拍快的产线上这个优势会被放大。4. 用WXOR做双通道一致性校验和安全联锁4.1 双传感器信号打架了怎么办在安全联锁和关键过程控制中经常会出现两个传感器检测同一个物理量比如安全光幕的两路输出或两个接近开关检测同一个到位位置。正常情况下两路信号逻辑值应该一致一旦不一致要么其中一路坏了要么现场接线松动此时设备继续运行会有风险。这种一致性校验用普通写法会有个细微但致命的问题——分步判断导致瞬间误判。比如IF Ch1 Ch2 THEN StatusOK : TRUE; ELSE StatusOK : FALSE; END_IF;这段代码逻辑上没错但如果Ch1和Ch2是外部输入在PLC扫描周期内可能Ch1先被系统更新、Ch2还没来得及更新导致一个扫描周期内两个值“看起来”不一致输出抖动一次。WXOR并不能直接消除硬件采样延迟但你可以把两路信号打包进同一个字然后在同一个周期内完成一致性判断减少因为程序扫描顺序造成的误判窗口。实际上更常见的做法是把硬件输入直接映射到字的两个位上然后用WXOR一次性得出结论。4.2 冗余互检代码模板下面是一个双通道校验的FB骨架我在项目中反复用过结构的通用性相当好FUNCTION_BLOCK FB_DualChannelCheck VAR_INPUT bCh1 : BOOL; // 通道1 bCh2 : BOOL; // 通道2 bEnable : BOOL; // 校验使能 END_VAR VAR_OUTPUT bChkOK : BOOL; // 校验通过 bChkFail : BOOL; // 校验失败 END_VAR VAR wChState : WORD; wChXor : WORD; END_VAR主体逻辑wChState.0 : bCh1; wChState.1 : bCh2; // 低两位做异或一致时结果0不一致时结果1 wChXor : WXOR(IN1 : wChState, IN2 : 16#0003); bChkFail : (wChXor 16#0003) AND bEnable; bChkOK : NOT bChkFail;这里解释一下关键细节当bCh10, bCh20时异或结果0bCh11, bCh21时异或结果也是0。只有一真一假时异或结果的低两位是1即16#0003。所以判断等于16#0003就是判定“不一致”。但注意这里没有处理“两个位同时为0”和“两个位同时为1”两种情况的差异也就是说这个模式只查“不一样”不查“是不是都对”。如果需要双通道都为1才算OK比如双接近开关都到位才允许动作前面得先串联一个WAND校验。实际项目里往往两种校验都要先WAND确认“都到位”再用WXOR确认“没有状态冲突”两道关下来通道故障才能被准确捕捉。4.3 一致性校验的时序与复位设计双通道校验最大的坑在故障恢复。如果一路传感器瞬断了一下又恢复系统可能直接触发停机复位按钮都救不回来。机械抖动、电磁干扰引起的误跳闸比真正的传感器损坏更频繁。我在某个控制柜就吃过这个亏光幕接线端子松了产线每两天跳一次闸查了很久最后发现是端子虚接导致通道信号时有时无。要给检测逻辑加上稳定时间窗口。简单做法是检测到不一致后先不立即触发故障而是启动一个延时计时器比如50ms到200ms。如果这个时间窗口内信号恢复正常不触发故障否则才置位故障锁存。这跟硬件安全继电器的“短时不一致忽略”机制是一个思路。不必追求无延时响应联锁应用里抗抖动的价值大于极速响应。差不多是这样一个骨架// 一致性检测结果送入定时器 bMismatchPulse : (wChXor 16#0003) AND bEnable; IF bMismatchPulse THEN tonMismatch(IN : TRUE, PT : T#100MS); ELSE tonMismatch(IN : FALSE, PT : T#100MS); END_IF; IF tonMismatch.Q THEN bChkFail : TRUE; END_IF;这100ms不会影响正常工况下的响应速度但足以滤掉大部分现场干扰脉冲。注意定时器要保证每次不一致消除后能够复位否则一旦故障锁存只能断电重启维护体验很差。5. 常见问题与排查技巧实录5.1 位串长度不一致导致的编译错误这是新手最容易遇到的把WORD和INT混在一起做WAND或者把WORD和DWORD放一块。ST的理念是强类型两个输入的数据类型不一致直接编译报错。处理方式很机械先统一所有参与位运算的变量类型该用WORD就用WORD该用DWORD就用DWORD。不要觉得类型转换麻烦就跳过。我的习惯是规范所有状态字统一为WORD除非位超过16个才用DWORD。程序里不要一会儿WORD一会儿DWORD混着传早晚会出问题。5.2 掩码写错导致联锁“形同虚设”举一个实际例子有位同事在做启动许可判断时掩码写成16#000E我一直以为他想要检查第1、2、3位结果他本意是检查0、1、2、3位。第0位“安全门闭合”根本没参与联锁。这种情况靠看代码很难发现必须靠功能测试。我推荐的习惯是每个掩码旁边写注释列出涉及的位号与含义。比如// 掩码16#000F检查0-3位安全门急停上游下游另外在调试监控表里单独显示每个掩码参与联锁后的结果一比对就知道是否生效。5.3 使用WAND与直接使用BOOL的差异要分清WAND等指令是按字运算输出是字要提取某个结果位得用比较指令或再与特定掩码比较。如果你只关心某一位比如“第3位是否满足”写wResult.3这种方式是允许的但还不如直接WAND再比较简洁。反过来说如果你要的只是单一逻辑量的与运算那就老老实实写BOOL与别硬塞进WAND里反而增加阅读负担。工具没有高低用对地方才有价值。5.4 扫描周期带来的“数据快照”问题ST程序按顺序扫描wPermitStatus的位在不同时刻被赋值意味着你在程序下方用WAND读取这个字时它只代表当前周期某一瞬间的映射结果。如果在同一个扫描周期内输入映射还没完成就执行了联锁校验拿到的是上一周期的老状态。这在绝大多数场景下影响不大但如果你处理的是非常快速的边沿信号或者要求严格同步的逻辑建议把输入映射放在程序最前面或者放到独立的FB里确保联锁计算时所有状态位已经处于最新一致状态。我的经验做法是输入物理IO映射区独立成段放在周期任务的最前面联锁逻辑放在其后并保证位状态组装和联锁校验在同一个程序组织块内按固定顺序执行。这样做虽然不能消除IO采样延迟但可以让程序内部的逻辑判断顺序稳定可控。5.5 故障排查实录联锁不释放哪一位卡住了有一个印象很深的调试场面。设备运行中突然停机操作屏显示“自动联锁不满足”。用诊断软件连上PLC监控wPermitStatus的值发现是16#000B——对照位图一看第0位安全门闭合状态为0其余几个条件都是1。也就是说门信号丢了。到现场一查门磁开关没问题但接线端子处被油污影响接触不良信号时通时断。监控程序时看到的现象却是联锁一会通一会断。加了100ms的滤波延时后问题消失。从那之后凡是外部的安全类输入进联锁逻辑前我都建议做去抖处理时间不必太长50ms足够应对大部分接触不良。排查时有个实用技巧在HMI上开辟一块“联锁状态”页面把wPermitStatus各位的状态直接映射过去显示成勾选/未勾选列表。联锁不满足时操作工不用开柜门接电脑查程序看页面就知道哪一路没到位。这个改造在客户现场好评度极高也大大减少了我自己的售后骚扰电话。5.6 两个容易忽略的实战细节一个是PLC系统手册里关于字运算指令对ENO能流输出的定义。部分品牌PLC在检测到操作数非规范化或数据类型不匹配时会把ENO置为FALSE。如果你的联锁结果刚好依赖于ENO会出现程序“莫名不输出”的情况。建议结论性判断还是基于运算结果比较不依赖ENO。另一个是程序备份与版本管理。改过掩码和位图一定要在程序注释和设计文档里同步更新。曾遇到过同事现场紧急改了掩码但没有同步图纸下一次改版时按旧图恢复程序又给改回去设备再次异常停机。联锁逻辑不是写给自己看的是写给人机协同的安全屏障文档同步跟代码正确同等重要。最后分享一个实际操作中的小体会用位操作写联锁最大的收益不是代码变短而是逻辑结构变“透明”。每个联锁条件清晰对应到一个位、一个掩码出问题时顺着位图查状态往往几分钟就能定位。这种思路也改变了我的程序设计习惯先定义状态字位图再写逻辑而不是先写一堆BOOL变量再回头整理。新项目拿到手我第一件事永远是把所有开关量输入列出、画好位映射表再谈控制方案。养成这个习惯之后即使不用WAND/WOR/WXOR你的ST项目也会比大多数人写得清爽。