在电气自动化系统中,PLC(可编程逻辑控制器)的输出点常以字节(8位)、字(16位)或双字(32位)为单位进行批量读写。但实际工程中,往往只需修改其中某一位(如仅置位 Q0.3、仅复位 M4.7、仅翻转 DB1.DBX5.2),而非整字操作——否则易引发“误写其他位”的严重风险:例如用 MOV_W #16#FF00, QW0 会无差别覆盖 Q0.0–Q0.7 和 Q1.0–Q1.7 共16个点,导致未被关注的输出意外动作。
ST(Structured Text,结构化文本)语言作为IEC 61131-3标准的核心高级语言,原生支持位级布尔运算。其关键优势在于:所有AND/OR/XOR操作均按位执行,且结果可直接赋值给位地址。掌握ST位操作掩码技术,是实现安全、精准、可维护输出控制的底层能力。
一、理解位掩码(Bitmask)的本质
位掩码是一个整数,其二进制表示中仅目标位为1(或0),其余位均为0(或1)。它不直接代表物理状态,而是作为“选择器”与当前值进行逻辑运算,从而隔离并修改特定位。
例如,要操作字节中的第3位(从0开始计数,即 bit3,对应 QB0.3):
- 置位掩码(Set Mask):
16#08→ 二进制0000 1000→ 仅 bit3 为1 - 复位掩码(Reset Mask):
16#F7→ 二进制1111 0111→ 仅 bit3 为0,其余为1 - 翻转掩码(Toggle Mask):
16#08→ 同置位掩码(XOR特性决定:1 XOR 1 = 0,0 XOR 1 = 1)
掩码不是魔法数字,而是可计算的:对第n位(n从0起),
- 置位/翻转掩码 = $2^n$(十进制)或
16#+ 十六进制表示 - 复位掩码 =
NOT (2^n),即对置位掩码取反
| 位号 n | $2^n$(十进制) | 二进制(8位) | 十六进制 | 常用场景 |
|---|---|---|---|---|
| 0 | 1 | 0000 0001 |
16#01 |
控制 Q0.0 |
| 3 | 8 | 0000 1000 |
16#08 |
控制 Q0.3 |
| 7 | 128 | 1000 0000 |
16#80 |
控制 QB0.7 |
| 12 | 4096 | ——(需16位) | 16#1000 |
控制 QW0.12(= Q1.4) |
注:QW0 表示 Q0.0–Q1.7 共16位;QW0.12 即第12位(0起),对应物理点 Q1.4(因 Q0.0–Q0.7 = 8位,Q1.0–Q1.7 = 后8位,故第12位 = Q1.4)。
二、三大核心操作的ST代码模板(零错误可复制)
以下所有代码均基于标准ST语法(TIA Portal V18 / CoDeSys / Unity Pro通用),变量声明与调用方式保持一致。假设目标为 QB0 的 bit3(即 Q0.3)。
1. 置位(Set):将某位强制为 TRUE,其余位保持不变
原理:当前值 OR 置位掩码 → 仅目标位变为1,其他位不变(因 x OR 0 = x)
// 方式1:直接对QB0操作(推荐用于输出字节)
QB0 := QB0 OR 16#08;
// 方式2:使用中间变量提升可读性(大型项目必备)
VAR
setMask_0_3 : BYTE := 16#08; // 显式声明掩码,便于维护
END_VAR
QB0 := QB0 OR setMask_0_3;
// 方式3:对位地址直接赋值(部分PLC支持,但非所有平台兼容)
Q0.3 := TRUE; // ✅ 简洁,但无法用于DB块内位地址(如 DB1.DBX2.3)
2. 复位(Reset):将某位强制为 FALSE,其余位保持不变
原理:当前值 AND 复位掩码 → 仅目标位变为0,其他位不变(因 x AND 1 = x,x AND 0 = 0)
// 复位掩码 = NOT(16#08) = 16#F7(8位下)
QB0 := QB0 AND 16#F7;
// 或使用标准函数避免手算(更安全)
QB0 := QB0 AND (NOT 16#08); // 编译器自动计算为 16#F7
// 推荐:封装为函数块(FB)提高复用性
FUNCTION_BLOCK FB_ResetBit
VAR_INPUT
inByte : BYTE;
bitNo : INT; // 0~7
END_VAR
VAR_OUTPUT
outByte : BYTE;
END_VAR
outByte := inByte AND (NOT SHL(1, bitNo)); // SHL(1,3)=8 → NOT=247=16#F7
3. 翻转(Toggle):将某位取反,其余位保持不变
原理:当前值 XOR 翻转掩码 → 目标位:0→1,1→0;其他位:x XOR 0 = x
QB0 := QB0 XOR 16#08;
// 安全写法:先读再写(避免多任务并发冲突)
VAR
tempQB0 : BYTE;
END_VAR
tempQB0 := QB0;
QB0 := tempQB0 XOR 16#08;
⚠️ 关键提醒:
QB0是输出映像区地址,直接读写即操作PLC周期末的输出刷新值;- 所有
OR/AND/XOR运算均为按位整数运算,非布尔逻辑;- 掩码必须与操作数位宽一致(对
BYTE用BYTE型掩码,对WORD用WORD型掩码)。
三、进阶实战:跨字节/字/双字的位操作统一方法
当目标位超出单字节范围(如控制 QW0.15 或 QD0.27),需扩展掩码位宽并确保类型匹配。
场景:置位 QW0 的 bit15(即最高位,对应 Q1.7)
- QW0 是16位无符号整数(
WORD) - bit15 掩码 = $2^{15} = 32768$ =
16#8000 - 必须使用
WORD类型掩码,否则高位被截断
// ✅ 正确:显式声明 WORD 类型
VAR
setMask_QW0_15 : WORD := 16#8000;
END_VAR
QW0 := QW0 OR setMask_QW0_15;
// ❌ 错误:用 BYTE 掩码(16#80)会导致只影响低8位
QW0 := QW0 OR 16#80; // 实际等效于 QW0 := QW0 OR 16#0080 → 影响 bit7,非 bit15
场景:复位 DB1 中 DBX2.5(DB1数据块第2字节的bit5)
DB1.DBX2.5是位地址,不可直接参与AND运算- 必须先读字节,运算后回写
VAR
dbByte : BYTE;
resetMask : BYTE := 16#DF; // NOT(16#20) → bit5=0,其余=1
END_VAR
dbByte := DB1.DBB2; // 读取第2字节
DB1.DBB2 := dbByte AND resetMask; // 写回(仅bit5复位)
场景:原子化操作(避免读-改-写时被中断覆盖)
在高速循环或中断服务程序中,若多个任务同时修改同一字节,可能因“读-改-写”非原子性导致丢失更新。解决方案:
- 使用PLC内置原子指令(如 TIA Portal 的
SET_BIT/RST_BIT系统函数); - 或采用
XOR配合状态标志实现无锁切换(适用于简单开关):
// 用一个BOOL变量触发翻转,确保每次只执行一次
IF toggleRequest THEN
QB0 := QB0 XOR 16#08;
toggleRequest := FALSE; // 清求
END_IF
四、常见陷阱与规避策略
| 错误现象 | 根本原因 | 修复方案 |
|---|---|---|
QB0 := QB0 OR 8; 不生效 |
8 是整数(INT),非BYTE类型,某些PLC隐式转换失败 |
显式写为 16#08 或 BYTE#8 |
| 复位后其他位变0 | 误用 QB0 := 16#F7;(全量赋值) |
必须用 AND,禁用 := 直接赋值 |
| 操作 QW0.12 时控制了 Q0.4 | 位号理解错误:QW0.12 = 第12位 = Q1.4(非Q0.4) | 牢记:字节号 = bitNo DIV 8,位号 = bitNo MOD 8 |
掩码 16#8000 在BYTE变量中溢出 |
类型不匹配导致高位丢弃 | 声明为 WORD 或 UDINT,勿用 BYTE |
| 在FC中修改全局输出字节未生效 | FC默认参数传递为值传递(copy-in/copy-out) | 改用 IN_OUT 参数,或直接访问全局地址 |
五、工业现场最佳实践清单
-
掩码常量集中管理:在全局常量数据块(如
GVL_Masks)中定义所有常用掩码,例如:GVL_Masks.SET_Q0_3 : BYTE := 16#08; GVL_Masks.RST_Q0_3 : BYTE := 16#F7; GVL_Masks.TOG_Q0_3 : BYTE := 16#08; -
禁止硬编码掩码:永远不要在逻辑中写
QB0 := QB0 OR 16#08;,而应写QB0 := QB0 OR GVL_Masks.SET_Q0_3;—— 便于后期搜索替换、版本对比、HMI同步。 -
位操作优先于线圈指令:在复杂条件分支中,用
QB0 := QB0 OR ...替代多个Q0.3 := condition1 OR condition2;—— 避免重复赋值冲突,且执行效率更高。 -
调试时用HEX显示:在监控表中将
QB0设为十六进制格式,实时观察16#00→16#08→16#0F变化,比布尔数组更直观定位哪一位被修改。 -
安全复位优先:对安全相关输出(如急停阀、抱闸),复位操作必须独立于置位逻辑,并添加超时验证:
IF safetyResetReq AND (QW0 AND 16#0008 <> 0) THEN QW0 := QW0 AND 16#FFF7; // 复位bit3 safetyResetTimer(IN := TRUE, PT := T#100ms); END_IF
六、完整可运行示例:LED状态机控制(8路LED分组闪烁)
需求:8个LED(Q0.0–Q0.7)按4种模式循环:全灭→奇数亮→偶数亮→全亮,每2秒切换,且支持手动单点强制。
PROGRAM PLC_PRG
VAR
modeTimer : TON;
currentMode : INT := 0; // 0=off, 1=odd, 2=even, 3=all
forceMask : BYTE := 16#00; // 手动强制掩码(bit0=Q0.0强制...)
forceValue : BYTE := 16#00; // 对应强制值
END_VAR
// 模式定时切换
modeTimer(IN := TRUE, PT := T#2S);
IF modeTimer.Q THEN
currentMode := (currentMode + 1) MOD 4;
modeTimer(IN := FALSE);
END_IF
// 计算基础模式值
CASE currentMode OF
0: baseValue := 16#00; // 全灭
1: baseValue := 16#55; // 10101010 → 奇数位(0,2,4,6)
2: baseValue := 16#AA; // 01010101 → 偶数位(1,3,5,7)
3: baseValue := 16#FF; // 全亮
END_CASE
// 应用手动强制(高优先级):forceMask中为1的位,取forceValue对应值;为0的位取baseValue
QB0 := (baseValue AND (NOT forceMask)) OR (forceValue AND forceMask);
// 示例:强制Q0.0亮、Q0.1灭 → forceMask=16#03, forceValue=16#01 → QB0.0=1, QB0.1=0, 其余按模式
此例展示:
AND (NOT mask)提取“非强制位”的原始值;AND mask提取“强制位”的指定值;OR合并二者,实现无损叠加;- 完全避免
IF...THEN...ELSE分支,性能稳定。
七、为什么不用MOVE指令?
新手常试图用 MOVE 指令配合位拆分实现,例如:
// ❌ 危险且低效
MOVE(IN := QB0, OUT := tempWord); // 转WORD
// 修改tempWord某位...
MOVE(IN := tempWord, OUT := QB0);
问题在于:
- 多次MOVE增加扫描周期;
- 位拆分需额外移位/掩码运算,代码膨胀;
MOVE无法保证字节对齐,不同PLC解释可能不一致;- 最致命:
MOVE是整字赋值,一旦中间变量被其他任务修改,输出即被污染。
而 QB0 := QB0 OR 16#08 是一条原子指令(在多数PLC中编译为单条CPU指令),无中间状态,无竞态风险。
八、总结:位掩码是电气自动化的“位级手术刀”
它不依赖特定硬件指令,不增加扫描时间,不引入新变量,仅通过三个基础布尔运算(AND/OR/XOR)和两个确定性掩码(置位/复位),即可实现:
- 单点独立控制(不影响邻居);
- 多点组合控制(如
QB0 := QB0 OR 16#30;同时置位 Q0.4 和 Q0.5); - 状态安全叠加(模式+强制);
- 故障快速定位(HEX监控一眼可见);
真正掌握它,意味着你已越过“开关量控制”的表层,进入“位级资源精确调度”的工程深水区。

暂无评论,快来抢沙发吧!