ST语言除零错误未捕获导致的程序停机异常处理机制

发布于 2026-03-17 13:30:29 · 浏览 4 次 · 评论 0 条

ST语言(Structured Text)是IEC 61131-3标准定义的五大PLC编程语言之一,广泛用于工业自动化控制系统中。其语法接近Pascal,支持变量声明、条件判断、循环、函数调用和结构化表达式,适用于复杂逻辑与数学运算。但正因其表达力强,也隐藏着一类隐蔽却致命的风险:除零错误未捕获导致的程序停机

这类异常不触发常规报警,不写入诊断缓冲区,不进入错误处理分支,而是直接中断当前任务周期,造成主循环“卡死”——表现为:输出冻结、定时器停摆、HMI刷新停滞、通讯心跳超时,最终引发设备急停、产线中断或安全回路动作。更棘手的是,它常在低概率工况下复现(如传感器信号突变、参数配置遗漏、手动调试误输),难以通过常规测试覆盖。

以下为一套完整、可立即落地的异常处理机制,覆盖预防、检测、隔离、恢复四层防线,全部基于标准ST语言实现,无需专用库或厂商扩展指令。


一、理解ST语言中除零错误的本质行为

在绝大多数主流PLC平台(如西门子S7-1500、倍福TwinCAT、罗克韦尔ControlLogix、施耐德Modicon M340/M580)中,ST语言对整数(INTDINT)和实数(REALLREAL)的除法运算,遵循如下默认行为:

  • INT / DINT 类型:执行整数除法。当除数为0时,结果值不确定(UNDEFINED),且不抛出运行时异常;后续若对该结果进行赋值、比较或参与计算,多数平台会将该变量标记为“无效值”,并在后续周期中导致整个任务(Task)被系统挂起。
  • REAL / LREAL 类型:执行浮点除法。IEEE 754标准规定 x / 0.0 应返回 ±INFNaN。但PLC固件通常不启用浮点异常中断,也不自动将 INF/NaN 转为错误状态;若程序后续对 INF> 100.0 判断,或对 NaN= 0.0 比较,结果恒为 FALSE,逻辑悄然失效;若传入函数块(如 MOVESCALE),则可能触发内部校验失败,导致函数块输出锁定或任务跳过。

关键结论:
ST语言本身不提供类似高级语言的 try...catch 机制;除零不是“报错”,而是“埋雷”。

因此,防御必须前置——在除法发生前,强制校验除数有效性。


二、四级防御机制:从编码规范到运行时兜底

防御层级1:编码强制规范(开发阶段)

所有涉及除法的ST代码,必须采用统一模板,禁止裸写 / 运算符。

// ✅ 正确模板:带零值防护的除法封装
FUNCTION_DIV_SAFE : REAL
VAR_INPUT
    pNumerator : REAL;     // 被除数
    pDenominator : REAL;   // 除数
    pDefault : REAL := 0.0; // 除数为零时返回值
    pTolerance : REAL := 1.0E-6; // 浮点零判定阈值
END_VAR
VAR
    bIsZero : BOOL;
END_VAR

bIsZero := ABS(pDenominator) < pTolerance;
IF bIsZero THEN
    FUNCTION_DIV_SAFE := pDefault;
ELSE
    FUNCTION_DIV_SAFE := pNumerator / pDenominator;
END_IF

使用方式:

// 替换原有:SpeedRef := SetPoint / GearRatio;
SpeedRef := DIV_SAFE(SetPoint, GearRatio, 0.0, 1.0E-6);

⚠️ 注意:pTolerance 不可设为 0.0(浮点比较不可靠),典型值 1.0E-6 适用于大多数工艺量程(如转速0–3000 rpm,位置0–10 m)。

对整数运算,同样需封装:

FUNCTION_DIV_INT_SAFE : DINT
VAR_INPUT
    pNumerator : DINT;
    pDenominator : DINT;
    pDefault : DINT := 0;
END_VAR
IF pDenominator = 0 THEN
    FUNCTION_DIV_INT_SAFE := pDefault;
ELSE
    FUNCTION_DIV_INT_SAFE := pNumerator / pDenominator; // PLC整数除法自动截断
END_IF

防御层级2:运行时动态监控(调试与维护阶段)

在主程序循环中嵌入全局除数监控模块,实时捕获“高风险除数”变化趋势:

// 全局变量声明(在全局DB或全局变量表中)
g_DivMonitor : STRUCT
    LastDenominator : REAL;        // 上周期除数
    CurrentDenominator : REAL;     // 当前除数
    IsDenominatorStable : BOOL;    // 连续3周期变化 < 0.1%
    StabilityCounter : UINT;       // 稳定计数器
    ZeroCrossingDetected : BOOL;   // 本次检测到跨零
END_STRUCT

// 主程序中调用(例如在MAIN组织块顶部)
g_DivMonitor.CurrentDenominator := GearRatio; // 示例:监控齿轮比变量
g_DivMonitor.ZeroCrossingDetected := 
    (g_DivMonitor.LastDenominator >= 0.0) AND 
    (g_DivMonitor.CurrentDenominator < 0.0) OR
    (g_DivMonitor.LastDenominator <= 0.0) AND 
    (g_DivMonitor.CurrentDenominator > 0.0);

// 更新稳定性判断(变化率 < 0.1% 视为稳定)
IF ABS(g_DivMonitor.CurrentDenominator - g_DivMonitor.LastDenominator) 
   < (0.001 * MAX(ABS(g_DivMonitor.CurrentDenominator), ABS(g_DivMonitor.LastDenominator), 1.0)) THEN
    g_DivMonitor.StabilityCounter := g_DivMonitor.StabilityCounter + 1;
    IF g_DivMonitor.StabilityCounter > 2 THEN
        g_DivMonitor.IsDenominatorStable := TRUE;
    END_IF;
ELSE
    g_DivMonitor.StabilityCounter := 0;
    g_DivMonitor.IsDenominatorStable := FALSE;
END_IF;

g_DivMonitor.LastDenominator := g_DivMonitor.CurrentDenominator;

此模块不干预逻辑,仅生成诊断信号:

  • g_DivMonitor.ZeroCrossingDetected = TRUE → 触发HMI弹窗:“齿轮比变量跨越零点,请检查传感器接线或参数配置”
  • g_DivMonitor.IsDenominatorStable = FALSE 持续5秒 → 启动慢速采样模式,降低控制频率至1 Hz,避免高频除零冲击

防御层级3:任务级隔离(系统架构阶段)

将高风险计算(如PID参数在线整定、流量补偿、电机滑差计算)部署在独立的低优先级任务中,与主控任务(如轴同步、安全逻辑)物理隔离。

任务名称 优先级 周期 承载内容 异常影响
MAIN_CTRL 高(10) 2 ms 安全I/O扫描、轴位置环、急停链 任一周期失败 → 系统停机
CALC_SAFETY 中(5) 100 ms 温度补偿、负载估算、除法密集型算法 单次失败 → 本任务跳过,MAIN_CTRL 不受影响
DIAG_MONITOR 低(1) 1 s 除数趋势分析、历史极值记录、日志写入 失败无影响

配置方法(以TIA Portal为例):

  1. 在项目树中右键“Tasks” → “Add new task”
  2. 名称填 CALC_SAFETY,类型选 Cyclic,周期设 100 ms
  3. 将所有含 / 运算的FB/FC拖入该任务组织块(OB)
  4. 关键操作:在 CALC_SAFETY OB顶部插入以下代码:
// 任务内看门狗:若单次执行超时,则强制复位本任务上下文
IF ExecutionTime_ms > 80.0 THEN // 预留20%余量
    // 清空所有中间计算变量(如临时数组、累加器)
    FOR i := 0 TO 99 DO
        TempArray[i] := 0.0;
    END_FOR;
    IntegralTerm := 0.0;
    // 记录超时事件到诊断缓冲区
    g_DiagLog[LogIndex].Code := 1024;
    g_DiagLog[LogIndex].Time := T#0s;
    LogIndex := (LogIndex + 1) MOD 100;
END_IF

防御层级4:硬件级兜底(工程交付阶段)

当软件层全部失效时,依赖PLC硬件机制实现“最后屏障”:

  • 启用看门狗定时器(Watchdog Timer)
    所有主流PLC均支持任务级看门狗。以西门子S7-1500为例:

    1. 在设备配置中打开“Properties” → “Cycle/Clock Memory”
    2. 勾选 Enable watchdog for this task
    3. 设置超时时间 ≥ 1.5 × 任务周期(如100 ms任务设为150 ms)
    4. 关键设置:选择 Reaction: Restart task(而非 Stop CPU

    效果:一旦 CALC_SAFETY 因除零卡死,150 ms后系统自动重启该任务,清除所有寄存器状态,主任务 MAIN_CTRL 持续运行。

  • 配置故障安全输入(F-DI)作为除数硬限位
    若除数来自外部模拟量(如压力变送器4–20 mA),可将电流信号同时接入普通AI通道与F-DI通道:

    • 普通AI通道用于计算
    • F-DI通道配置为“电流低于3.5 mA时置位FQ(Fault Quality)标志”
    • 在除法前插入硬约束:
    IF NOT FQ_PressureSensor THEN
        PressureComp := DIV_SAFE(MeasuredPressure, BasePressure, 1.0, 1.0E-3);
    ELSE
        PressureComp := 1.0; // 安全默认值
        // 同时触发F-Alarm:要求人工确认传感器状态
    END_IF

三、真实故障复现与验证方法

为确保机制有效,必须进行三类验证:

  1. 主动注入测试
    在HMI上添加调试按钮,点击后向 GearRatio 变量写入 0.0,观察:

    • DIV_SAFE 是否返回 0.0(非崩溃)
    • g_DivMonitor.ZeroCrossingDetected 是否在下一个周期置位
    • CALC_SAFETY 任务是否在150 ms后自动恢复(用TIA Portal的“Cycle Time Monitor”查看)
  2. 边界值压力测试
    使用脚本连续写入序列:1E-7, 1E-8, 0.0, -1E-8, -1E-7,验证:

    • pTolerance = 1.0E-6 能正确捕获所有 |x| < 1E-6 的“准零”值
    • ABS() 函数对负数零判定无偏差
  3. 长期老化测试
    运行72小时,每10分钟自动记录:

    • g_DivMonitor.CurrentDenominator 极小值
    • DIV_SAFE 调用次数中返回默认值的占比
    • CALC_SAFETY 任务重启次数
      若占比 > 0.1%,说明现场存在持续性传感器漂移,需校准硬件。

四、常见误区与纠正

误区描述 错误示例 正确做法
认为REAL除零会自动报错 IF MyRealVar / 0.0 > 10.0 THEN ... 必须先 IF ABS(0.0) > 1E-6 THEN ...
用整数比较替代浮点容差 IF GearRatio = 0 THEN ... 改为 IF ABS(GearRatio) < 1E-6 THEN ...
在FB内部隐藏除法不暴露 FB内直接写 Output := A / B; 将B作为IN接口,调用方负责预检;或FB内部用DIV_SAFE
依赖HMI显示判断异常 HMI显示“--”就认为正常 HMI必须绑定g_DivMonitor.ZeroCrossingDetected,显示红色告警图标

五、附录:可直接导入的标准化函数块(ST代码)

// 文件名:DIV_SAFE.FB (符合IEC 61131-3标准)
FUNCTION_BLOCK DIV_SAFE
VAR_INPUT
    Numerator : REAL;
    Denominator : REAL;
    Default : REAL := 0.0;
    Tolerance : REAL := 1.0E-6;
END_VAR
VAR_OUTPUT
    Result : REAL;
    IsValid : BOOL; // TRUE=除法成功,FALSE=使用默认值
END_VAR
VAR
    bIsZero : BOOL;
END_VAR

bIsZero := ABS(Denominator) < Tolerance;
IF bIsZero THEN
    Result := Default;
    IsValid := FALSE;
ELSE
    Result := Numerator / Denominator;
    IsValid := TRUE;
END_IF

使用时,在调用处声明实例:

fbDiv1 : DIV_SAFE;
...
fbDiv1(Numerator:=SetPoint, Denominator:=GearRatio);
SpeedRef := fbDiv1.Result;
IF NOT fbDiv1.IsValid THEN
    // 触发降级模式:切换至开环控制
    ControlMode := OPEN_LOOP;
END_IF

评论 (0)

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

扫一扫,手机查看

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