首页 / 资讯中心 / 文章详情

ST联锁编程进阶:用WAND/WOR/WXOR位运算精简多条件联锁逻辑

ST联锁编程进阶:用WAND/WOR/WXOR位运算精简多条件联锁逻辑 ★ FEATURED ARTICLE
写ST联锁写了五六年我越来越觉得能在关键时候把代码从一团乱麻里捞出来的往往不是那些花哨的算法而是最容易被忽略的字逻辑运算WAND、WOR、WXOR。大多数工程师遇到多条件联锁第一反应就是A AND B AND C一路串下去条件一旦超过十个整面墙都是布尔表达式看着头大改起来更头大。而IEC 61131-3体系里WAND、WOR、WXOR能对WORD、DWORD、LWORD按位做与、或、异或十来个联锁条件一次就能折叠进一个32位联锁字。后面无论做总联锁判定、首出报警锁存还是给HMI上报状态都清爽得多。这篇文章不讲手册里已有的指令定义只讲我在实际项目里怎么用这三个指令搭联锁以及踩过的坑适合已经会写ST、想往位级思维再走一步的同行。1. 为什么联锁代码越写越“肿”字级运算的价值所在1.1 先看一个让代码膨胀的经典写法假设要做一个设备启动允许判断条件包括急停未触发、门锁关到位、液压站压力正常、润滑油流量正常、温度不超限、变频器无故障、接触器无反馈异常、模式选择在自动位。教科书式写法是这样的IF b_EMO_Not AND b_DoorLocked AND b_HydPress_OK AND b_LubFlow_OK AND b_Temp_OK AND b_Drive_OK AND b_KMD_OK AND b_Mode_Auto THEN b_RunPermit : TRUE; ELSE b_RunPermit : FALSE; END_IF;这段代码本身没有错但在真实项目里信号不会叫这么短的名字。它们可能散落在各个功能块里写成 fb_SafeMon.bEMO_Closed、fb_Station2.Status.DoorLocked、fb_Hydraulic.bPressureOK 这种长名一行轻轻松松超出屏幕。更要命的是调试阶段设备起不来想知道到底是哪个条件不满足只能一条一条在监控表里看或者临时加一堆输出变量非常浪费时间。条件数量翻一倍呢16个条件就是16个AND串在一起加上换行缩进整个IF判断得滚两屏。而且每增加一个条件都要小心翼翼地把新变量插到对应的位置漏掉一个括号或者写错一个名字编译通过了但逻辑却不对。这样的代码说它是“联锁”不如说它是一堵早晚要倒的墙。1.2 联锁的本质一组布尔条件的组合判定把联锁想成“多个开关串联”就很容易理解位级运算的价值。8个条件相当于8个开关串联任何一个断开灯就不亮。而8个开关的状态本质上就是8个比特1代表闭合0代表断开。它们可以拼成一个8位二进制数。所以“所有条件都满足”这句话翻译成位运算就是“这8个比特全都是1”。反过来说如果我们手里有一个“模板”模板里想检查哪一位哪一位就是1不想管的位是0这个模板就是掩码。把条件数和掩码一按位与得到的结果如果等于掩码说明模板里要求的位全都是1结果不等于掩码说明至少有一位不满足。这个逻辑干净利落而且不管条件数是8个、16个还是32个代码都只有一行。很多同行第一次听到这个思路时都会产生一个疑问这不是把简单问题复杂化了吗单个条件用布尔变量多直白干嘛非要塞进一个整数里我的回答是当你只有两三个条件时确实没必要。但现场设备的联锁条件动辄十几个、二十几个还要做首出报警、历史记录、HMI显示这时候把所有条件装进一个32位字整套逻辑的复杂度反而降下来了。1.3 WAND/WOR/WXOR到底在做什么这三个指令我在联锁里分工挺明确先列个表方便理解指令运算规则我在联锁里的用途WAND对应位都为1结果才为1过滤出需要检查的位判断总允许WOR对应位只要有一个为1结果就为1多个状态字合并或把派生条件合入WXOR对应位相同为0、不同为1找出条件字与掩码的差异做首出锁存举个例子。条件字是二进制 0011 0101掩码是 0000 1111那么 WAND 结果是 0000 0101不等于掩码 0000 1111说明低四位里至少有一位不满足。再用 WXOR 算一下0011 0101 XOR 0000 1111 0011 1010非零位出现在bit0、bit2、bit3这三个就是当前不满足的条件。你看一条WAND找出“有没有问题”一条WXOR找出“问题在哪几个位”这就是位级联锁的骨架。2. 联锁字的数据模型每一位代表一个条件2.1 位分配先把“字典”定下来用位级联锁之前第一件事不是写代码而是画一张位分配表。这个环节偷懒后面维护就是灾难。我在模拟项目X里用的联锁字是32位的UDINT位分配大致是这个样子位号变量名含义允许条件1bit0EMO_OK急停未触发急停回路常闭点闭合bit1DOOR_OK门锁关到位门锁开关闭合bit2HYD_OK液压站压力正常压力开关闭合bit3LUB_OK润滑油流量正常流量开关闭合bit4TEMP_OK温度不超限温度检测正常bit5DRV_OK变频器无故障变频器故障输出为0bit6KMD_OK接触器无反馈异常反馈与指令一致bit7MODE_OK模式处于自动模式选择在自动位bit8-bit30RSV备用0bit31IO_OKIO通讯正常从站在线且数据有效这张表既是程序注释的一部分也是后面HMI“位状态表”页面的字段来源。我习惯把位表存在代码文件头部每位都用带名字的常量或者直接在程序里写注释引用坚决不用魔法数字。位分配还有一个原则把最关键的、必须最后兜底的条件放在最高位比如IO_OK这样即使某个位分配错了最高位的总闸还在。2.2 置位约定把“允许”统一映射为1这里有一个很多新手容易翻车的地方IO信号本身的极性和联锁条件里的“允许”含义不一定同相。急停开关现场接的是常闭点正常时输入为TRUE代表急停没有按下所以允许启动。但如果安全栅信号是反逻辑同一个条件可能就要取反才能放进联锁字。我的做法是专门写一个联锁信号采集段把所有条件统一预处理成“1表示允许、0表示禁止”的BOOL再放进联锁字。代码长这样// 联锁字清零后逐位写入确保每个位都有确定值 stILock : UDINT#0; IF b_EMO_Closed THEN stILock.0 : TRUE; // 急停未触发 END_IF; IF b_DoorLocked THEN stILock.1 : TRUE; // 门锁关到位 END_IF; IF b_HydPress_OK THEN stILock.2 : TRUE; // 液压正常 END_IF; // 其余位同理这里有一个很关键的习惯每次先整体清零再逐位置位。只置位不复位的写法容易留旧值比如某一位在故障时被置了0故障恢复后忘了把它置回1联锁字就一直带着错误的0设备明明条件都满足了却不允许启动。清零后再写每个位每一轮扫描都有确定值不会残留上一周期状态。2.3 用SHL生成掩码少写一点十六进制掩码是“我想检查哪些位”的模板。最容易出错的地方就是手写十六进制0xFF是低8位0xFFFF是低16位写错一位数字就全乱了。更稳妥的办法是用移位来生成// 掩码需要检查bit0-bit7即低8位 MASK_ILOCK : SHL(UDINT#1, 8) - UDINT#1;SHL(UDINT#1, 8) 是把1左移8位变成0x100减1后得到0xFF正好是低8位全1。需要检查bit0、bit2、bit7这种间隔位时可以分别生成再或起来MASK_PART : SHL(UDINT#1, 0) OR SHL(UDINT#1, 2) OR SHL(UDINT#1, 7);这样掩码的含义一眼就能看出来比0x1285这种数字清楚得多。掩码一旦定义好我在整个程序里就不会再手改它要改条件就回去改位表再重新生成掩码。这个规矩看着琐碎但在排查“为什么某个条件没生效”的时候能帮你省掉大量猜谜时间。3. 三段核心逻辑总联锁判定、首出锁存、状态合并3.1 总联锁判定一行WAND替代一整排AND条件字和掩码都准备好了总允许的判断就变成了一行比较// 总允许需要检查的联锁位全部为1 IF (stILock AND MASK_ILOCK) MASK_ILOCK THEN b_RunPermit : TRUE; ELSE b_RunPermit : FALSE; END_IF;有些IDE里AND默认就是位运算有些IDE里需要写成 WAND(stILock, MASK_ILOCK) 这种函数式调用语法有差异查一下帮助文档再写。关键是逻辑结果等于掩码说明全部满足结果不等于掩码说明至少有一位不满足。如果只想在真正需要时才计算还可以先判断是否使能IF b_Enable AND ((stILock AND MASK_ILOCK) MASK_ILOCK) THEN b_RunPermit : TRUE; ELSE b_RunPermit : FALSE; END_IF;这比一长串AND表达式好在哪里一是好读一眼看出判断对象是“这个联锁字”二是好扩展新增条件无非改MASK和置位段不碰这一行判定逻辑三是好调试打开监控表看 stILock 这一行二进制所有条件状态一览无余。3.2 首出锁存WXOR找出“第一个翻车的位”首出报警就是在多个联锁条件同时不满足时准确报出最先触发的那一个。很多设备安全规范里是明确要求这个功能的。位级做法非常优雅当总允许从TRUE变成FALSE的那个扫描周期用WXOR比较条件字和掩码凡不满足的位异或结果会是1锁存下来再去定位最低非零位即可。真正要做得稳还得解决两件事一是“不满足的位可能同时有好几个”但首出只需要业务上优先级最高的那一个我一般取最低非零位二是要用沿检测捕获“总允许下降沿”这个时刻点。实现代码如下// 下降沿总允许从TRUE翻到FALSE的那一拍 b_FallEdge : b_RunPermit_Prev AND NOT b_RunPermit; IF b_FallEdge THEN // 不满足的位异或后为1 nErrMask : MASK_ILOCK XOR stILock; // 找最低非零位作为首出位 nFirstBit : -1; FOR i : 0 TO 31 DO IF (nErrMask AND SHL(UDINT#1, i)) 0 THEN nFirstBit : i; EXIT; END_IF; END_FOR; bLockOut : TRUE; END_IF; b_RunPermit_Prev : b_RunPermit;代码里 nErrMask : MASK_ILOCK XOR stILock; 就是在做WXOR有些IDE直接写成 WXOR(MASK_ILOCK, stILock) 也一样。注意我用 b_RunPermit_Prev 记录了上一周期的总允许状态b_RunPermit_Prev AND NOT b_RunPermit 就是下降沿。沿检测本身是时序逻辑靠的是变量自己记住上一拍不是靠某条指令算出来的。还有一个容易踩的坑如果你在一个程序周期里同时更新 stILock 和做总允许判定那么下降沿检测触发时stILock 已经是本周期的新值异或算出的“不满足位”可能包含刚发生变化的多个位。我习惯先把上一周期的 stILock 保存成 stILock_D1等到检测到下降沿时用 stILock_D1 和掩码做异或这样锁存的是“导致允许消失的那一拍”的真实条件字。3.3 用WOR合并多个状态字上报HMI设备通常有很多子系统每个子系统都有自己的状态字HMI如果挨个读几十个BOOL会很费通讯资源。用WOR可以把状态字按位或成一个汇总字一次上报stSysStatus : WOR(fbDriveUnit.stDiag, fbPumpUnit.stDiag); stSysStatus : WOR(stSysStatus, fbValveUnit.stDiag);注意位分配不要重叠。子系统A的状态位用了bit0~bit7子系统B就必须从bit8开始定义否则或在一起会互相干扰。这跟前面联锁字的位表是一套管理方法本质上都是“位号总表”。3.4 一个完整的联锁单元示例把上面几段串成一个完整的模拟项目X联锁单元放在一个程序任务里// 联锁信号采集 stILock : UDINT#0; IF b_EMO_Closed THEN stILock.0 : TRUE; END_IF; IF b_DoorLocked THEN stILock.1 : TRUE; END_IF; IF b_HydPress_OK THEN stILock.2 : TRUE; END_IF; IF b_LubFlow_OK THEN stILock.3 : TRUE; END_IF; IF b_Temp_OK THEN stILock.4 : TRUE; END_IF; IF b_Drive_OK THEN stILock.5 : TRUE; END_IF; IF b_KMD_OK THEN stILock.6 : TRUE; END_IF; IF b_ModeAuto THEN stILock.7 : TRUE; END_IF; // 总允许 MASK_ILOCK : SHL(UDINT#1, 8) - UDINT#1; // 低8位 b_RunPermit : (stILock AND MASK_ILOCK) MASK_ILOCK; // 首出锁存 b_FallEdge : b_RunPermit_Prev AND NOT b_RunPermit; IF b_FallEdge THEN nErrMask : MASK_ILOCK XOR stILock; FOR i : 0 TO 7 DO IF (nErrMask AND SHL(UDINT#1, i)) 0 THEN nFirstBit : i; EXIT; END_IF; END_FOR; END_IF; b_RunPermit_Prev : b_RunPermit;输入输出对应关系如下表联锁解锁后直接看 nFirstBit 就能定位到具体原因不需要逐个点监控首出位对应条件处置方向0急停触发或回路断线检查急停回路1门锁未到位检查门锁与气动机构2液压压力不足检查油泵与压力开关3润滑油流量不足检查油位与流量开关4温度超限检查散热与测温回路5变频器故障查看变频器故障码6接触器反馈异常检查接触器辅点与线圈7非自动模式切换模式选择开关这个表直接贴在HMI画面旁边操作人员查找原因的时候非常方便还能避免“现场只会按复位不知道故障根源”的情况。4. 踩过的坑位宽、扫描时序、掩码和断线4.1 位宽混用DWORD与WORD的“隐形截断”联锁字用UDINT掩码也用UDINT但如果操作数里混进一个WORD麻烦就来了。有一次我把从IO模块读来的WORD直接传给WAND那个WORD的状态字最高位恰好是1结果高位被解释成符号位按位与之后整个结果都不对现场设备偶尔启动不了查了半天才发现是类型转换的锅。经验就一条所有参与位运算的变量统一声明为UDINT或者DWORD禁止WORD和UDINT混用。从IO读来的WORD先显式转换成UDINT再参与位运算。如果选了64位的LINT要确认IDE支持的是LAND而不是WAND别硬套32位的写法。这类问题编译阶段往往不报错报错也只是一句“类型不匹配”真跑起来才暴露所以一开始就要把类型管住。4.2 扫描周期中间态联锁字为什么会闪跳联锁字在一个扫描周期里被多次写入时如果IO信号之间没有同步可能在某次采样时读到“又没门锁、又有急停”的中间状态总允许瞬间翻转一下。这个问题在仿真器里很难复现真机却会偶尔出现表现为设备明明没故障却闪停一下。我的对策是先把IO统一做一次快照把所有输入考入中间变量联锁程序只读中间变量不直接引用IO地址。这样同一个扫描周期内所有联锁位看到的是同一份快照不会因为IO刷新顺序不同而分裂。对需要抗抖动的信号再加一个“连续N个周期保持才生效”的滤波器避免电磁干扰引起误触发。这套做法会让程序多一层变量但换来的是联锁的确定性值得。4.3 掩码写错与逐位强制测试掩码0xFF写成0xF或者bit号错位一位这类错误最隐蔽因为总允许照样输出TRUE设备也能正常启动只是某个条件从来没参与过联锁。等到真正危险发生才发现那一脚刹车是空的。我后来定了一条死规矩任何新联锁逻辑上线前必须做一轮逐位强制测试。方法不复杂从bit0开始把条件字里的某一个位在程序里强制置0确认总允许下拉、首出位显示正确然后恢复接着测下一位。这样一轮下来掩码写没写错、位号对没对上当场就能暴露。测试过程最好做成一个临时测试段用HMI或调试面板上的开关来触发别用在线强制的硬办法不然一个不小心就把整个CPU的内存改乱了。4.4 从站通讯断线联锁字怎么处理如果联锁条件有很大一部分来自远程IO从站那从站断线时输入字会清零、保持旧值甚至出现全1具体行为取决于从站配置。无论哪种都可能导致联锁误判。我的做法很简单在联锁字最高位专门放一个“IO通讯正常”位代码在联锁采集段里加一句IF bIO_CommOK THEN stILock.31 : TRUE; END_IF;MASK里也把bit31加进去。这样只要从站一断线总允许必然变成FALSE设备不会在“数据已经不可信”的情况下继续动作。等通讯恢复条件字重新建立后再由其他条件决定是否允许启动。这套办法虽然朴实但拦住过好几次莫名其妙的启动事故。5. 维护心得把联锁逻辑封装成一个可复用FB5.1 FB接口设计让外部只连“条件”和“复位”前面这一套逻辑每次项目都重写一遍太不划算了。我把它封装成一个功能块FB_ILock接口大致是这样FUNCTION_BLOCK FB_ILock VAR_INPUT bEnable : BOOL; arCond : ARRAY[0..31] OF BOOL; // 每个元素对应一个联锁条件 bReset : BOOL; // 复位首出锁存 END_VAR VAR_OUTPUT bPermit : BOOL; // 总允许 bFailActive : BOOL; // 已锁存首出 nFailBit : INT; // 首出位号 0..31 END_VAR VAR stCondWord : UDINT; stMaskWord : UDINT; stErrMask : UDINT; bPrevPermit : BOOL; END_VARFB内部做的事情很固定数组转联锁字、掩码判定总允许、WXOR锁存首出、复位处理。调用方只需要把32个条件BOOL数组连过来剩下的细节都藏在FB里。打开实例监控里面 stCondWord、stErrMask、nFailBit 一目了然维护起来比散落在主程序里的几十行IF舒服得多。用FB还有一个额外的好处以后想加“首出优先级排序”只需要在VAR_INPUT里加一个优先级数组内部排序逻辑改一次所有实例跟着升级。比起让每个设备程序复制粘贴同一段联锁代码这个方式的长期维护成本低太多了。5.2 位级方案的边界哪些场景不适合硬套不是所有联锁都适合位级硬套。条件很少比如只有两三个直接写AND组合反而更直白没必要为了用位运算而位运算。条件里如果包含大量模拟量比较也建议先把比较结果转成BOOL再置位到联锁字不要在FB里塞模拟量计算否则FB的通用性和可读性都会被拖累。还有一种情况最需要警惕条件之间有复杂的或逻辑比如“两台泵任意一台可用”“A联锁与B联锁互为备用”。这种不能用简单的一个位来表示得先用传统布尔表达式把派生条件算好再作为一位输入FB。换句话说FB接收的一定是最终化简后的“允许/禁止”布尔值不是还没规整的原始信号。这一点我在给团队新人培训时反复强调过因为它决定了FB的适用范围。5.3 在线调试技巧联锁字可视化与故障注入在线监控一个UDINT联锁字直接看数字实在太累尤其32位全亮的时候一串十六进制根本看不出谁是谁。我通常把联锁字复制到一个BOOL数组在HMI或上位机做一个位状态表每一行显示位号、条件名、当前状态。对模拟量类条件显示的是“比较结果满足/不满足”不是原始数值这样排障时一眼就能找到是哪一位拉了后腿。另一个很实用的套路是故障注入测试。我在联锁采集段前面预留了一个测试开关某个测试变量为TRUE时强制把指定位置0效果等同于现场把对应信号断开专门用来验证首出锁存逻辑是否正确。这个功能在项目验收、改造验证时特别香比拿着螺丝刀去短接端子安全得多。最后再分享一个小经验首出位不一定是数值最小的位HMI显示时最好用条件名映射表不要只显示一个数字位号否则操作工根本不知道“bit3”是哪个条件。我现在的习惯是每个项目的联锁字位表都放在程序区开头的大段注释里谁接手都能在五分钟内看懂。这几个指令本身并不复杂真正值钱的是把它放进什么样的数据结构里。
阅读完成 · 觉得有帮助?
咨询建站