☰
PLC联锁逻辑中的位操作:WAND/WOR/WXOR高效实现与排错
2026/10/11 1:13:47 网站建设 项目流程

1. 位操作与联锁:先把概念对齐

1.1 为什么联锁逻辑要用“字”而不是一个个位

接触现场的人应该都有同感:联锁逻辑写到最后,最难收拾的不是功能有多复杂,而是“点太多、太散”。一台设备几十个安全联锁点,急停、安全门、气压、前级设备运行状态、后级设备无堵料,每个都是独立的BOOL量。新手上手时最喜欢写一串“IF ... AND ... AND ...”,设备多的时候,这个IF能写几十行,后面人接手看代码的时候,光是数括号都能数晕。

我调试项目的时候有个习惯:把所有联锁相关的输入信号先归组到一个“状态字”里,每个bit对应一个联锁条件,然后用字级运算一次性判断。这样最大的好处是代码短、变量少、排查快。你打开监控表,一个WORD就能同时看到十几个联锁点的实时状态,不需要再挨个看BOOL点。一次WAND下来等于瞬间完成了16路条件的组合判断,这种效率是散写的IF逻辑完全比不了的。

联锁逻辑和普通控制逻辑还有一个本质区别:普通逻辑关心“做什么”,而联锁逻辑关心的是“不允许做什么”。普通逻辑里的辅助继电器可以随意置位复位,但联锁条件一旦不满足,输出必须立刻切断,而且还要能锁存、要能复位、要能记录故障。这就决定了用字位操作比写一堆零散的IF要可靠得多,也好维护得多。

1.2 WAND/WOR/WXOR三条指令到底做了什么

先把这个概念固定下来:WAND就是字与(Word AND),WOR就是字或(Word OR),WXOR就是字异或(Word eXclusive OR)。它们都是“逐位运算”,把两个16位的WORD或者32位的DWORD放在一起,按bit位逐一做逻辑运算,结果出来还是一个字。

我自己的理解方式是用生活化的类比:WAND像一个筛网,同一个位子上两个数都是1,结果才放过1,有一个是0就挡住;WOR像两路开关并联,只要任何一路是通的,结果就是1,只有两路都断开才是0;WXOR像一杆天平,两个位不一样,结果就是1,一样了反而输出0。

这三条指令单独看都很简单,但联锁应用里真正值钱的地方是“批量处理位”。你用一条WAND就能同时判断16个开关量的状态,这在项目点数多的时候效率提升非常明显。更重要的是,字级运算的逻辑过程和最终结果可以用一个16进制数直接监控,比盯十几个BOOL变量的变化要直观得多。

1.3 联锁逻辑设计的关键思维:故障安全

聊联锁之前,必须先讲一个原则:故障安全。绝大多数联锁应用里,我们把“条件满足”定成1,比如“急停已释放=1”“安全门已关闭=1”“气压正常=1”。联锁通过意味着这些位全部为1。为什么约定成“正常为1”?因为一旦通信断线、模块掉站、信号线松脱,很多输入会自然变成0,联锁自然不允许启动,设备处在安全状态。反过来,如果设计成“故障为1”,那线断了、模块掉了反而会被误判成正常,这是很危险的事。

我见过有人写联锁时图省事,把“门开信号”这类常开点直接当成允许条件,结果门开传感器掉电变成0,系统反而认为安全门关了,这在后期维护阶段相当吓人。所以在定状态字每一位的极性之前,一定要先拿笔写下:这一位是“正常时为1”还是“故障时为1”。后面做WAND掩码判断时,也必须始终记住这个约定,否则很容易把联锁逻辑写反。

2. 三条指令的进阶用法拆解

2.1 WAND:掩码筛选,把一个状态字“压”成一组条件

WAND最典型的用法就是“取位”。外部送入一个状态字StatusWord,你只关心第0、2、5这三路状态,其他位一概不理,那就构造一个掩码“0000 0000 0010 0101”,也就是16#0025,把它和StatusWord做WAND。结果里其他位全部清零,只留下你关心的三路,值对不对一目了然。

联锁应用里我更常用的是“掩码相等判断”。假设联锁条件分布在状态字的低4位,条件是它们必须全部为1,代码可以这样写:

IF (StatusWord AND 16#000F) = 16#000F THEN InterlockOK := TRUE; ELSE InterlockOK := FALSE; END_IF;

这段代码的含义是:先取出低4位的值,再判断它是不是等于全1的掩码。只有低4位全部为1,联锁才通过。这种写法比我刚入行时写的“IF (bit0 AND bit1 AND bit2 AND bit3)”精炼很多,而且后续增加或者减少联锁点,只需要改掩码,IF判断结构完全不用动。

这里要提醒一个细节:部分ST编译器不允许直接在IF表达式里用AND对字变量做按位运算,或者说这样写容易和BOOL逻辑搞混。我一般先声明一个临时字变量,先做运算再比较,这样兼容性好,在线监控时还能直接看到计算出来的中间值:

TempWord := WAND(StatusWord, 16#000F); IF TempWord = 16#000F THEN InterlockOK := TRUE; END_IF;

关于WAND这个助记符,这里说明一下:在一些平台里它确实是函数形式,用来对两个字做与运算;而遵循标准IEC 61131-3的ST环境里,直接用AND运算符即可。两种写法本质一样,具体用哪个看控制器手册。

2.2 WOR:旁路合并,临时解除某一位联锁

设备调试和维护阶段,经常需要临时旁路某个联锁点。比如安全门开关坏了,备件还没到,现场又急需让设备运转起来。这种场景用WOR就很顺手。

思路是把旁路开关做成一个字,某一位为1,代表旁路掉对应的那一个联锁条件。然后把旁路字和状态字做WOR,这一位不管原状态是0还是1,结果都变成1。再和掩码做后面的判断,联锁自然“通过”了。

// 旁路字屏蔽对应的条件位 ConditionAfterBypass := WAND(WOR(StatusWord, BypassWord), 16#000F); IF ConditionAfterBypass = 16#000F THEN InterlockOK := TRUE; END_IF;

举个实际例子:低4位里第2位是冷却风机运行状态,风机故障时这一位会变成0。检修时需要临时启动,那就把BypassWord的第2位置1。这样即使StatusWord里这一位是0,经过WOR之后也变成1,最终联锁判断通过。

但必须强调,旁路在正式生产中是双刃剑。我见过不止一次,调试人员旁路了一个点之后忘记取消,结果运行了几天,安全联锁形同虚设。所以旁路功能一定要有严格限制:要么只在手动调试模式可用,要么旁路后必须产生报警并记录,要么设置超时自动解除。我自己做项目的固定做法是:旁路字单独放一个DB块,每次联锁输出真正动作之前,程序检查“旁路激活字”,只要非零就写入报警记录,并且把当前旁路状态同步到上位机画面,让操作员永远能看到“现在有几个联锁点正在被旁路”。

2.3 WXOR:状态比对,捕捉命令和反馈的不一致

WXOR在联锁里有一个特别经典的用法:检查“输出命令”和“设备反馈”是否一致。比如给某台电机的接触器发出运行命令,命令字的某一位是1,正常情况下设备反馈字对应的位也应该变成1。如果命令是1、反馈是0,或者命令是0、反馈是1,说明设备没有按照指令动作,这往往意味着接触器卡死、断路器跳闸、线缆断掉等故障。

Mismatch := WXOR(CommandWord, FeedbackWord); IF Mismatch <> 0 THEN // 存在不一致,延时后报联锁故障 END_IF;

我第一次用这个思路是在一组多台输送机上。每台输送机对应一个字的一个bit,命令和反馈一一对应。程序定时做XOR比对,只要有不一致,就说明有某台输送机要么没启动、要么没停下,马上进入联锁停机流程。对一台几十台设备的系统来说,这种批量比对比逐个写“IF命令和反馈不相同THEN”要高效得多。

不过实际现场有个坑必须注意:命令发出后,反馈信号是有延迟的。刚发出命令的几百毫秒内,接触器还没吸合,反馈自然还是0,这时候XOR结果为1,但这不是真实故障。所以要做延时判断,等反馈稳定后再采样比对。我通常的做法是:命令变化后启动一个定时器,延时几百毫秒到一秒不等,延时结束后再取反馈值做XOR。设置延时时间时还要考虑接触器动作时间、传感器响应时间以及IO扫描周期。

2.4 三条指令组合使用的一个完整小例子

这三条指令单独用已经很顺手,但真正解决联锁问题时,往往需要组合起来。我写一个完整的组合逻辑:状态字低4位是必须满足的条件,第5位是禁止启动条件,外部还有一个旁路字可以临时旁路第2位。

// 1. 禁止位检查:bit5为1时直接禁止输出,不做后续判断 IF WAND(StatusWord, 16#0020) <> 0 THEN RunEnable := FALSE; RETURN; END_IF; // 2. 旁路处理后,判断必须位是否全为1 TempWord := WAND(WOR(StatusWord, BypassWord), 16#000F); IF TempWord = 16#000F THEN RunEnable := TRUE; ELSE RunEnable := FALSE; END_IF;

这段逻辑里,每行我都留了注释,实际项目里别人接手也好懂。组合使用最大的价值是:如果不做掩码,你需要把“必须位”和“禁止位”拆开写好几段IF;现在一条WAND扫掉了禁止位,一条WOR加WAND解决了旁路和必须位判断。现场排查时,只需要看TempWord的值,马上就知道到底哪一个条件没满足,不用拿着万用表满柜子跑。

3. ST 代码实操:从模板到完整联锁功能块

3.1 先搭一个可复用的联锁判断模板

写联锁逻辑,我建议不要每个设备都复制粘贴一大段IF,而是做成FB(功能块)复用。联锁FB的核心职能就三件事:接收状态字、接收掩码和旁路字、输出联锁结果和故障位。

下面是一个不带锁存的简单模板:

FUNCTION_BLOCK FB_InterlockBasic VAR_INPUT CondWord : WORD; // 状态字 MaskRequired : WORD; // 必须为1的位 MaskForbidden : WORD; // 必须为0的位 BypassWord : WORD; // 旁路位 END_VAR VAR_OUTPUT InterlockOK : BOOL; // 是否通过 FaultCode : WORD; // 故障位 END_VAR VAR ReadyWord : WORD; END_VAR // 禁止位检查:任一禁止位为1,立即失败并返回 IF (CondWord AND MaskForbidden) <> 0 THEN InterlockOK := FALSE; FaultCode := CondWord AND MaskForbidden; RETURN; END_IF; // 必须位检查:加上旁路后必须全为1 ReadyWord := (CondWord OR BypassWord) AND MaskRequired; IF ReadyWord = MaskRequired THEN InterlockOK := TRUE; FaultCode := 0; ELSE InterlockOK := FALSE; FaultCode := ReadyWord XOR MaskRequired; END_IF;

这个模板里有个小技巧:在“必须位不满足”时,我用“ReadyWord XOR MaskRequired”生成故障字。哪个bit不满足,对应位就是1。这样在线监控FaultCode时,一眼就能看出是第几个联锁点没有满足,排查速度会快很多。

3.2 带旁路、故障锁存的联锁功能块

上面的模板没有锁存,适合做“启动条件判断”这类不需要记忆的场景。但真正的安全联锁通常要求“故障保持”,也就是联锁动作之后,就算故障条件消失,也必须手动复位后才能重新启动。这个特性很关键,我专门把它单独做成一个带锁存的FB。

FUNCTION_BLOCK FB_InterlockLatch VAR_INPUT RunRequest : BOOL; // 启动请求 CondWord : WORD; MaskRequired : WORD; MaskForbidden : WORD; BypassWord : WORD; ResetCmd : BOOL; // 复位命令 END_VAR VAR_OUTPUT RunEnable : BOOL; // 最终允许输出 Tripped : BOOL; // 是否处于联锁动作状态 TripCode : WORD; END_VAR VAR ReadyWord : WORD; LatchTripped : BOOL; END_VAR // 计算当前联锁是否满足 ReadyWord := (CondWord OR BypassWord) AND MaskRequired; // 禁止位和必须位综合判断,任一不满足就置锁存 IF ((CondWord AND MaskForbidden) <> 0) OR (ReadyWord <> MaskRequired) THEN LatchTripped := TRUE; TripCode := (CondWord AND MaskForbidden) OR (ReadyWord XOR MaskRequired); END_IF; // 复位:只有当前条件已经满足时,复位才有效 IF ResetCmd AND (ReadyWord = MaskRequired) AND ((CondWord AND MaskForbidden) = 0) THEN LatchTripped := FALSE; TripCode := 0; END_IF; Tripped := LatchTripped; RunEnable := RunRequest AND NOT LatchTripped;

这里有两个细节必须单独讲。

复位条件加上了“当前条件已经满足”这个判断,防止现场人员故障还没排除就反复按复位,导致设备刚脱离故障又重新允许启动。很多安全规范都明确要求复位必须在联锁条件满足时才能生效,这个逻辑一定不能省。

锁存变量LatchTripped我这里放在FB内部的静态变量里,而不是外部的普通BOOL线圈。这样每个设备实例化一个FB,锁存状态互不干扰,也不会被程序其他逻辑或者上位机随便写入。我之前见过有人把锁存写成全局中间继电器,结果触摸屏上不小心写了一地址,误操作一下锁存就没了,非常被动。

3.3 地址、DB 与字节序问题

字级位操作最折腾人的坑,就是“看着对,算出来不对”。这里必须谈谈地址映射和字节序。

先说地址。很多控制器的数字量输入在硬件上按字节排列,一个DI模块的16个点对应一个字的高低字节。编程时可以直接把这个字赋给CondWord。但如果信号来自远程IO或者第三方协议,比如Modbus TCP,位顺序会因为寄存器字节序不同而乱掉。Modbus寄存器默认大端,部分控制器的内部字节序却是小端,两边一对接,bit0到bit7的内容经常是反的。

我的处理习惯是:所有来自通信的联锁状态字,先统一做一次字节交换规整。平台有字节交换指令就用指令,没有就手动来:

// 字节交换规整示例 CorrectWord := ((RawWord SHL 8) OR (RawWord SHR 8)) AND 16#FFFF;

这样定义一条项目规范:内部统一使用CPU原生字节序,所有通信数据先规整再进联锁逻辑。有了这个规矩,后面能省掉大量莫名其妙的排查时间。

另外,状态字、掩码、旁路字最好都放到掉电保持区或者单独DB块。现场最怕的是PLC断电重启,旁路字清零,设备突然恢复所有联锁,操作员一脸懵。更怕的是旁路字存储在普通变量区,PLC一重启原来的旁路设置全丢了,但现场人员以为旁路还生效,结果设备启动不了。这些状态最好放入保持区,并且上电时在HMI上做一次确认。

4. 实战案例:模拟包装线启动联锁的完整实现

4.1 工艺背景和联锁清单

为了让这些指令真正落到地上,我虚构了一条小型包装线的输送段来做演示。设备组成包括:入口传送带A、主传送带B、出口传送带C,外加一台气缸推料机构。启动主传送带B之前,需要满足这些联锁条件:

bit位含义正常状态
bit0急停已释放1
bit1安全门已关闭1
bit2气压正常1
bit3入口传送带A正在运行1
bit4出口传送带C正在运行1
bit5推料气缸处于后限位1
bit6料位检测无堵料1
bit7变频器无故障1

另外还有一个禁止条件:bit8代表“紧急联动停止”信号。这个信号为1时,就算其他条件都满足,也绝对不允许启动。

4.2 IO规划与变量映射

定义状态字和掩码如下:

VAR StatusWord : WORD; // 来自DI和通信,按位对应上表 MaskRequired : WORD := 16#00FF; // 低8位必须全为1 MaskForbidden : WORD := 16#0100; // bit8禁止为1 BypassWord : WORD; // 调试用旁路字 RunEnable : BOOL; FaultCode : WORD; END_VAR

这里刻意把必须位全部放在低8位,禁止位单独占一个高bit,也就是bit8。这样规划的好处是:MaskRequired用16#00FF一眼就能看懂,不需要换算二进制;禁止位单独一个高bit,也不容易和必须位混淆。以后联锁点增加,比如加了bit9、bit10,只需要把MaskRequired改成16#03FF,程序逻辑一行都不用动。

4.3 联锁 ST 代码与逐段讲解

沿用前面FB的设计思路,实战代码可以写成这样:

// 第一步:禁止位优先检查 // 紧急联动停止一旦激活,无条件禁止启动,并且不再进入后续判断 IF (StatusWord AND MaskForbidden) <> 0 THEN RunEnable := FALSE; FaultCode := 16#0100; // 这里可以考虑增加动作延时,避免瞬时干扰脉冲导致误动作 ELSE // 第二步:必须位判断,带旁路处理 IF ((StatusWord OR BypassWord) AND MaskRequired) = MaskRequired THEN RunEnable := TRUE; FaultCode := 0; ELSE RunEnable := FALSE; FaultCode := ((StatusWord OR BypassWord) AND MaskRequired) XOR MaskRequired; END_IF; END_IF;

逐段说明一下关键点。

禁止位检查放在最前面,因为安全联锁里“绝对禁止条件”的优先级最高。如果先做必须位判断再做禁止位判断,就存在一个很小的窗口:禁止位有故障时,必须位通过后RunEnable会短暂变成TRUE,这个瞬间可能导致危险输出。程序里用IF-ELSE结构保证两个判断互斥,能最大限度压缩这种窗口。

必须位判断里用了“OR BypassWord”,旁路位为1的bit在判断中等同于强制满足。但我之前强调过,旁路必须受控。实际工程中我在逻辑外还加了一道互锁:BypassWord只有在手动调试模式下才可写为非零值,正常自动模式下来自HMI的旁路请求不会被接受。

FaultCode的生成只用了XOR,哪位不满足,哪位就是1。在线监控表里看到FaultCode的值,用十六进制转二进制一换算,缺的是哪个点立刻清楚。我在现场靠这个技巧省掉了大量来回跑柜子查线的时间。

4.4 测试方法和安全注意点

写完了必须测,而且要有套路地测。我测试联锁项目时有个强迫症习惯:把边界情况列成用例,而不是随便点点看。

第一轮测“全0状态”。把所有联锁输入全部置0,此时RunEnable必须为FALSE,FaultCode必须非零。全0状态往往能暴露初始化时序问题:如果某些输入在上电瞬间还没被刷新,状态字是0,而程序却判断联锁通过了,那说明代码有问题,必须修。

第二轮测“逐位置1”。从bit0开始,每次只把一位置1,确认RunEnable始终保持FALSE。这轮测试专门验证掩码是否写错。如果只置bit1(16#0002)时RunEnable变成TRUE,那说明MaskRequired不是低8位,肯定是某个错误值。

第三轮测“全1状态”。把所有必须位都置1、禁止位置0,此时RunEnable必须变成TRUE。最后加测旁路字:把某一位旁路后,即使该位原始状态是0,RunEnable也应该为TRUE,同时确认旁路位不能旁路禁止位。

这些测试看着笨,但现场真能拦住大事。我碰到过掩码少算一位导致“全部正常时反而不给启动”的案例,没有全状态测试的话,这种问题排查起来非常折磨人。

安全注意点我再重复一遍:旁路功能必须受控;联锁故障需要手动复位;禁止位优先级必须高于必须位。这三条是所有联锁项目不能破的底线。

5. 常见问题与排错实录

5.1 WAND结果全为0:掩码和对齐的坑

最常被问的问题就是:状态字明明有值,但WAND结果一直为0。这种问题基本可以顺着两个方向查。

第一个方向是掩码写错。确认掩码的十六进制每一位都对得上。低8位必须为1是16#00FF,不是16#FF00,也不是16#0FF0。十六进制和二进制换算时把位序搞反是新手最容易犯的错。我建议定义掩码变量时,旁边用注释把二进制写出来,比如“// 16#00FF = 0000 0000 1111 1111,对应bit0~bit7”,减少误读。

第二个方向是字节序错位。通信数据到了CPU内部后,bit0~bit7可能不在预期位置。以Modbus TCP进来的数据为例,要确认寄存器高低字节有没有交换,必要时先做字节规整。排查方法就是同时监控原始状态字和规整后的值,对比一下就能定位问题。

5.2 联锁误动作:旁路未屏蔽、故障锁存逻辑缺陷

误动作比不动作难查,因为设备已经处于错误运行状态,危险性更高。我见过的典型原因有这么几类。

旁路位误置。调试时在HMI上勾了一个旁路,试完忘记取消,之后设备在安全条件不满足时也能启动。排查半天最后发现旁路字里一个bit一直是1。解决思路就是前面强调的:旁路必须和手动模式互锁,进入自动模式前强制检查旁路字,非零就禁止自动启动并报警。

锁存逻辑缺陷。故障发生后LatchTripped被置为TRUE,然后故障消失。如果锁存变量写在外部普通BOOL区,上位机或者别的逻辑可能把它覆盖,锁存就丢了。所以锁存变量必须放在FB内部静态区,并且不可以被外部强制写入。

还有一个坑是“复位按钮粘住”。如果复位信号一直为1,并且条件刚好满足,锁存会在每个扫描周期都被清除,操作员根本没意识的时候设备就开始恢复了。复位信号我会做上升沿触发,确保只有“按下”动作生效,而不是电平持续有效。

5.3 在线调试的小工具和操作习惯

字级位操作在线调试,我离不开三样东西:监控表、强制功能、以及一个用来显示位状态的小工具。

监控表是基本功。把StatusWord、BypassWord、ReadyWord、FaultCode放在同一页监控表,全部按十六进制显示。四行数据一眼看过去,就能判断联锁卡在哪一步:ReadyWord是0说明状态字和旁路字有问题;ReadyWord值不等于掩码全1,就用FaultCode反推具体是哪个位不满足。

小工具的做法是:在画面或者监控表里把FaultCode拆成多个BOOL位,单独显示。拆法很直接,拿16#0001这个掩码逐位和FaultCode做WAND,结果送给一个BOOL数组。画面组态时把数组拉出来,每个联锁点就是一个单独的指示灯,维护人员不用会算十六进制也能定位故障。这套做法在车间现场特别好用,极大降低了对维护人员的技术门槛。

强制功能要非常谨慎。调试时我可以强制状态字的某一位来模拟故障,但强制之前必须记录原值,测试结束后必须解除强制。我看到过太多因为强制定时器或者IO字忘记解除而“灵异”的案例了。我给自己立过一条规矩:联锁相关字强制持续时间不超过一个调试班次,交班前必须清强制并签字确认。

5.4 不同PLC平台上位操作的实现差异

最后聊一个移植代码时最容易忽略的点:不同控制器的ST环境,位操作指令写法差异挺大。

某类欧系平台的标准ST里,没有专门的WAND/WOR/WXOR助记符,直接使用AND、OR、XOR运算符。这是IEC 61131-3的标准风格,可移植性最好。

某类日系平台保留了WAND、WOR、WXOR这种助记符,用起来更像函数调用,比如“WAND(输入1, 输入2, 输出)”。从日系平台往欧系平台迁移代码时,第一件事就是把助记符改成标准运算符,还要仔细核对函数参数的顺序,因为不同平台的函数参数位置可能正好相反。

还有一部分基于成熟内核的控制器平台,标准和助记符可以混着写。我的原则是:优先写标准ST风格,这样代码放到哪里都能看懂、都能编译。实在遇到平台不支持运算符,才退回函数式写法,并用注释标注清楚。

另外,字长也要留意。有的平台WORD是16位,有的支持DWORD,也就是32位。状态点超过16个时,就要改用DWORD级别的位操作,掩码从16#FFFF变成16#FFFFFFFF。我见过有人在DWORD掩码里只写了16#FFFF,结果只覆盖低16位,高16位完全不参与比较,联锁判断错得悄无声息。这个坑在点数多的项目里特别值得警惕。

最后说一个我自己的实操习惯:写完联锁逻辑后,我会准备一张纸,手工推导两到三组极端状态下的十六进制计算,比如“StatusWord=16#FFFF,MaskRequired=16#00FF,BypassWord=16#0002”,推完再拿到PLC在线监控里对一遍。这个小动作花不了几分钟,但能最大程度避免低级错误上线。联锁这东西,宁可多测几步,也不能让现场设备在不安全的状态下“顺利”转起来。

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

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

立即咨询