ST怎么写三选一逻辑:Output := A OR B OR C; 或使用 CASE 语句

发布于 2026-03-14 23:50:16 · 浏览 3 次 · 评论 0 条

在电气自动化编程中,实现“三选一”逻辑(即从三个输入信号 A、B、C 中任一为真时,使输出 Output 为真)看似简单,但实际需结合控制需求、可读性、可维护性、诊断能力及标准规范综合判断。ST(Structured Text,结构化文本)是IEC 61131-3标准定义的高级编程语言,广泛用于PLC(可编程逻辑控制器)开发。它支持布尔运算、条件分支、循环和结构化数据操作。本文将手把手说明如何用ST编写真正可靠、可验证、符合工业实践的三选一逻辑,涵盖三种主流写法:布尔表达式直写、IF-ELSE链、CASE语句,并逐项对比其适用场景、隐患与优化技巧。


一、明确“三选一”的真实含义:先定义需求,再写代码

工业现场中,“三选一”常被误解为纯布尔或运算。但实际需求往往隐含以下约束:

  • 互斥性要求?
    若A、B、C代表三台泵的启动请求,系统可能要求“同一时刻仅允许一台运行”(即互斥),此时 Output := A OR B OR C 仅表示“有任一请求”,但未阻止多台同时运行——这不是真正的“三选一”,而是“三选多”。

  • 优先级要求?
    若A是手动急停信号,B是自动运行信号,C是远程启停信号,需明确:A有效时是否必须屏蔽B/C?此时逻辑不再是简单OR,而需嵌入优先级判定。

  • 诊断与复位要求?
    当Output为TRUE时,操作员需快速知道“是哪个输入触发的”。若仅用 A OR B OR C,无法追溯源头,故障排查耗时翻倍。

  • 信号有效性验证?
    工业现场信号易受干扰。A、B、C是否已做滤波(如上升沿检测)、去抖(debounce)、状态确认(如反馈回读)?未预处理的原始信号直接参与逻辑,会导致误动作。

因此,第一步永远不是写代码,而是用文字锁定需求。例如:

“当本地按钮A、触摸屏按钮B、远程DCS指令C中任意一个发出‘有效启动脉冲’(上升沿且持续≥50ms),且无急停信号E=FALSE、无故障信号F=FALSE,则置位输出Q_Start。输出为TRUE期间,记录并保持触发源(A/B/C)。复位由停止按钮G或故障信号F上升沿触发。”

这个描述已包含:边沿检测、时间滤波、安全条件、源记录、复位机制——远超 OR 表达式能力。


二、写法一:布尔表达式直写 —— 何时可用?如何加固?

最简写法:

Output := A OR B OR C;

适用场景

  • 纯状态汇总(如“报警汇总灯”:任一子系统报警则总灯亮);
  • 三信号物理上不可能同时为真(如三路独立安全光幕,每路只对应一个门区);
  • 项目处于原型验证阶段,且后续会重构。

严禁场景

  • 需要区分触发源;
  • A/B/C存在竞争或时序重叠风险;
  • 信号未经滤波(如按钮未消抖,导致Output频繁抖动)。

🔧 加固措施(必须添加)

  1. 信号预处理:为每个输入增加50ms去抖(以CODESYS为例):
    
    // 声明FB实例(需在VAR_GLOBAL或FB内部声明)
    fbDebounce_A(IN := A, PT := T#50ms);
    fbDebounce_B(IN := B, PT := T#50ms);
    fbDebounce_C(IN := C, PT := T#50ms);

// 使用滤波后信号
Output := fbDebounce_A.Q OR fbDebounce_B.Q OR fbDebounce_C.Q;


2. **增加安全使能条件**(防止误启):
```pascal
Output := (fbDebounce_A.Q OR fbDebounce_B.Q OR fbDebounce_C.Q) 
          AND NOT EmergencyStop 
          AND NOT SystemFault;

⚠️ 注意:OR 运算符在ST中从左到右短路求值(IEC 61131-3 §3.4.2),即若 fbDebounce_A.Q = TRUE,则B、C不再计算。这可提升效率,但若B/C含副作用(如调用函数块),结果不可预测——故禁止在OR右侧放置有副作用的表达式


三、写法二:IF-ELSE链 —— 清晰表达优先级与源记录

当需要定义执行顺序或记录触发源时,IF-ELSE链最直接:

// 声明源记录变量(类型为枚举或整数)
TYPE tTriggerSource :
(
    NONE : 0,
    BUTTON_A : 1,
    BUTTON_B : 2,
    BUTTON_C : 3
);
END_TYPE

VAR
    TriggerSource : tTriggerSource := NONE;
    Output : BOOL := FALSE;
END_VAR

// 主逻辑
IF fbDebounce_A.Q AND NOT EmergencyStop AND NOT SystemFault THEN
    Output := TRUE;
    TriggerSource := BUTTON_A;
ELSIF fbDebounce_B.Q AND NOT EmergencyStop AND NOT SystemFault THEN
    Output := TRUE;
    TriggerSource := BUTTON_B;
ELSIF fbDebounce_C.Q AND NOT EmergencyStop AND NOT SystemFault THEN
    Output := TRUE;
    TriggerSource := BUTTON_C;
ELSE
    Output := FALSE;
    // 可选:保持TriggerSource不变,或清零
END_IF;

优势

  • 优先级一目了然(A > B > C);
  • TriggerSource 变量可直接用于HMI显示或历史记录;
  • 每个分支可独立添加安全条件(如B还需校验权限等级)。

⚠️ 关键细节

  • ELSIF 而非 ELSE IF:ST中必须连写,否则语法错误;
  • 所有分支必须覆盖 Output 的赋值,避免锁存旧值;
  • 安全条件(NOT EmergencyStop)重复出现,易出错——应提取为中间变量:
    SafeToStart : BOOL := NOT EmergencyStop AND NOT SystemFault;
    ...
    IF fbDebounce_A.Q AND SafeToStart THEN
      ...

四、写法三:CASE语句 —— 结构化、易扩展、适合多状态

当“三选一”未来可能扩展为“五选一”或需映射到设备ID时,CASE是首选:

// 假设A/B/C对应设备编号1/2/3,输入为UINT
VAR
    RequestSource : UINT := 0; // 外部传入:1=A, 2=B, 3=C
    Output : BOOL := FALSE;
    ActiveDeviceID : UINT := 0;
END_VAR

// 将离散信号转换为源编码(需在主循环前执行)
IF fbDebounce_A.Q THEN RequestSource := 1; END_IF;
IF fbDebounce_B.Q THEN RequestSource := 2; END_IF;
IF fbDebounce_C.Q THEN RequestSource := 3; END_IF;

// 主CASE逻辑
CASE RequestSource OF
    1: 
        IF NOT EmergencyStop AND NOT SystemFault THEN
            Output := TRUE;
            ActiveDeviceID := 1;
        END_IF;
    2: 
        IF NOT EmergencyStop AND NOT SystemFault THEN
            Output := TRUE;
            ActiveDeviceID := 2;
        END_IF;
    3: 
        IF NOT EmergencyStop AND NOT SystemFault THEN
            Output := TRUE;
            ActiveDeviceID := 3;
        END_IF;
    ELSE
        Output := FALSE;
        ActiveDeviceID := 0;
END_CASE;

优势

  • 新增选项只需增加一个 X: 分支,不破坏原有结构;
  • ActiveDeviceID 可直接驱动设备选择器或通信地址;
  • 符合模块化设计思想,便于单元测试(对每个数字输入单独验证)。

⚠️ 陷阱规避

  • 避免漏写 ELSE:若 RequestSource 因干扰变为0或4,无ELSE将导致 Output 锁存旧值——必须显式置FALSE;
  • 禁止在CASE内修改判别变量CASE RequestSource OF ... RequestSource := 0; ... END_CASE; 是未定义行为;
  • 数值型判别需确保范围可控:若 RequestSource 来自模拟量AD转换,必须加限幅:
    RequestSource := LIMIT(1, 3, RequestSource); // 限定1~3

五、终极方案:封装为可复用函数块(FB)

将逻辑抽象为FB,实现一次开发、多处调用:

// FB名称:FB_ThreeChoiceStarter
FUNCTION_BLOCK FB_ThreeChoiceStarter
VAR_INPUT
    Start_A : BOOL;
    Start_B : BOOL;
    Start_C : BOOL;
    Enable : BOOL := TRUE;     // 总使能
    Reset : BOOL := FALSE;     // 复位边沿
END_VAR
VAR_OUTPUT
    Output : BOOL;
    SourceID : BYTE; // 1=A, 2=B, 3=C, 0=None
END_VAR
VAR
    fbDeb_A, fbDeb_B, fbDeb_C : R_TRIG; // 上升沿检测
    Deb_A, Deb_B, Deb_C : BOOL;
    LastOutput : BOOL;
END_VAR

// 去抖与边沿检测
fbDeb_A(CLK := Start_A);
fbDeb_B(CLK := Start_B);
fbDeb_C(CLK := Start_C);
Deb_A := fbDeb_A.Q;
Deb_B := fbDeb_B.Q;
Deb_C := fbDeb_C.Q;

// 核心逻辑(带优先级与复位)
IF Reset THEN
    Output := FALSE;
    SourceID := 0;
ELSIF Enable THEN
    IF Deb_A THEN
        Output := TRUE;
        SourceID := 1;
    ELSIF Deb_B THEN
        Output := TRUE;
        SourceID := 2;
    ELSIF Deb_C THEN
        Output := TRUE;
        SourceID := 3;
    END_IF;
ELSE
    Output := FALSE;
    SourceID := 0;
END_IF;

调用示例:

fbStarter1(Start_A := LocalBtn, Start_B := HMI_Btn, Start_C := DCS_Cmd, 
           Enable := NOT E_Stop, Reset := StopBtn);
MotorRun := fbStarter1.Output;
LED_Source := fbStarter1.SourceID;

价值

  • 每个实例独立维护状态(LastOutput, SourceID);
  • 输入/输出接口标准化,HMI、SCADA、其他FB均可统一接入;
  • 修改内部逻辑不影响调用端——符合IEC 61131-3封装原则。

六、调试与验证:3步实操检查清单

写完代码后,必须通过以下验证:

  1. 信号时序仿真
    在PLC仿真环境(如Codesys Simulator)中,手动设置A/B/C的上升沿时间差为1ms、10ms、100ms,观察 Output 是否在首个有效信号到达后1个扫描周期内置位,且 SourceID 始终正确。

  2. 安全条件穿透测试
    强制 EmergencyStop := TRUE,再触发A/B/C——Output 必须保持FALSE,且 SourceID 不更新。

  3. 复位可靠性测试
    使 Output := TRUE,然后给 Reset := TRUE 一个单周期脉冲——Output 必须在下一周期变为FALSE,SourceID 归零。

若任一测试失败,立即回溯:检查变量声明域(是否误用GLOBAL)、边沿检测时钟源(是否与主任务同步)、复位逻辑位置(是否在CASE/IF之后执行)。


七、避坑指南:90%工程师踩过的5个错误

错误现象 根因 修正方案
Output 偶发不动作 信号未去抖,PLC扫描周期内电平不稳定 必用 R_TRIGTON 滤波,禁用原始BOOL
SourceID 显示错误 多个输入同时有效,IF-ELSE优先级未覆盖全部组合 改用CASE,或明确声明“仅首个有效信号生效”
代码编译报错 Syntax error near 'OR' OR 前后缺少空格,或使用中文字符 检查所有空格为ASCII 32,OR 前后各一个空格
下载后PLC报运行时错误 CASERequestSource 为INT但分支用UINT字面量 统一类型:CASE RequestSource OF 1: ... END_CASE;(1自动转为INT)
HMI显示源ID延迟1秒 SourceID 未在每次扫描都赋值,依赖锁存 确保每个分支(含ELSE)均对 SourceID 赋值

八、工程建议:按项目阶段选择写法

  • 样机/快速验证:用布尔表达式 Output := A OR B OR C,但必须同步添加滤波和使能;
  • 正式投产项目:强制使用IF-ELSE链,优先级写死,源变量命名清晰(如 g_bStartSource_A);
  • 平台化/长期运维项目:封装为FB,接口增加 DiagnosticText : STRING 输出当前状态描述,供HMI直接显示。

记住:自动化代码不是写给自己看的,而是写给三年后的维护工程师、写给审计人员、写给安全评估机构的。每一行ST代码,都要经得起问:“为什么这样写?不这样写的后果是什么?”

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文