ST语言浮点数NaN或Infinity值未过滤导致的逻辑判断失效

发布于 2026-03-17 18:17:59 · 浏览 3 次 · 评论 0 条

在电气自动化系统中,ST(Structured Text)语言是IEC 61131-3标准定义的高级文本编程语言,广泛用于PLC逻辑开发。它语法接近Pascal,支持变量声明、条件判断、循环、函数调用等结构化特性,适合实现复杂控制算法。然而,其对浮点数(REAL 类型)的处理存在一个隐蔽但高危的陷阱:当参与比较运算的浮点数为 NaN(Not a Number)或 Infinity(正/负无穷)时,ST语言标准未强制要求运行时进行自动过滤或异常拦截,所有关系运算符(=, <>, <, <=, >, >=)均返回 FALSE。这一行为看似“安全”,实则导致逻辑判断彻底失效——本该触发的安全停机不动作,本该跳转的工艺分支被跳过,本该报警的异常状态静默消失。


一、问题本质:ST语言中NaN与Infinity的布尔语义是“全假”

IEC 61131-3 第3版标准第8.3.2节明确规定:对于 REAL 类型的操作数,若任一操作数为 NaN,则所有关系运算的结果均为 FALSE。同样,+INF-INF 在比较中也遵循特殊规则:

  • +INF > x 对任意有限 x 返回 TRUE
  • -INF < x 对任意有限 x 返回 TRUE
  • +INF = +INF 返回 FALSE(注意:这与IEEE 754标准一致,但极易被误认为应为 TRUE);
  • NaN = NaN 返回 FALSE
  • NaN > 0, NaN < 0, NaN <> NaN 全部返回 FALSE

这意味着:任何包含 NaNInfinityIF 判断条件,只要该值参与了关系运算,整个条件表达式恒为 FALSE,无论原逻辑意图如何

例如,一段典型的安全联锁代码:

IF (MotorSpeed > 0.0) AND (MotorTemp < 120.0) THEN
    RunMotor := TRUE;
ELSE
    RunMotor := FALSE;
    TriggerAlarm := TRUE;
END_IF;

若传感器故障导致 MotorSpeed 变为 NaN(如模拟量输入模块通信中断后返回无效浮点值),则 (MotorSpeed > 0.0) 计算结果为 FALSE,整个 IF 条件为 FALSE,程序进入 ELSE 分支——表面看“触发了报警”,但真实风险在于:RunMotor 被强制置 FALSE 是结果,而非原因;而更危险的是,某些场景下开发者会将 ELSE 仅用于日志记录,不执行停机动作。更隐蔽的是如下写法:

IF MotorSpeed > MaxSafeSpeed THEN
    EmergencyStop();
END_IF;

一旦 MotorSpeedNaNMotorSpeed > MaxSafeSpeed 永远为 FALSEEmergencyStop() 永不执行——系统在“无声中失控”。


二、NaN与Infinity的常见来源(非理论,全部来自现场)

以下来源均在实际工程项目中复现并造成停机事故:

  1. 除零运算

    SpeedRatio := ActualSpeed / SetpointSpeed; // 当SetpointSpeed=0.0时,结果为±INF或NaN(取决于ActualSpeed符号)
  2. 浮点计算溢出

    LargeValue := 1E30 * 1E10; // 超出REAL范围(通常为±3.4E38),结果为+INF
  3. 模数转换(ADC)故障值透传
    某些PLC模拟量模块在通道断线时,固件直接向用户寄存器写入 0x7FC00000(IEEE 754单精度NaN编码),而ST程序未做校验即读取为 REAL 变量。

  4. 外部数据接口污染
    OPC UA客户端从上位机接收 REAL 值时,若上位机使用JSON传输,null 或字符串 "NaN" 被错误解析为浮点 NaN;或Modbus TCP响应中,故障寄存器被填充为 0xFFFF,按浮点解释即为 NaN

  5. 数学函数返回值未检查

    SqrtResult := SQRT(Operand); // Operand < 0.0 → 返回NaN(ST标准未规定SQRT负数行为,多数厂商实现为NaN)
    LogResult := LN(Input);       // Input ≤ 0.0 → 返回NaN或-INF
  6. 初始化未赋值变量
    VAR 区域声明的 REAL 变量在PLC首次上电时,内存内容为随机值,其中约1/256概率为NaN编码(因IEEE 754 NaN有大量有效bit模式)。


三、检测:三步定位NaN/Infinity(无需额外硬件)

步骤1:编写通用检测函数块(FB)

创建函数块 FB_IsValidReal,输入 IN : REAL,输出 IsValid : BOOL

FUNCTION_BLOCK FB_IsValidReal
VAR_INPUT
    IN : REAL;
END_VAR
VAR_OUTPUT
    IsValid : BOOL;
END_VAR
VAR
    u32Bits : DWORD; // 用于提取REAL的32位二进制表示
    expBits : WORD;  // 指数字段(bits 30..23)
    fracBits : DWORD; // 尾数字段(bits 22..0)
END_VAR

// 将REAL按位转换为DWORD(需PLC支持字节序一致的类型转换,如TIA Portal中用REAL_TO_DWORD)
u32Bits := REAL_TO_DWORD(IN);

// 提取指数:掩码 0x7F800000 → 右移23位得8位指数
expBits := WORD((u32Bits AND 16#7F800000#) / 16#800000#);

// 提取尾数:掩码 0x007FFFFF
fracBits := u32Bits AND 16#007FFFFF#;

// IEEE 754规则:指数全1(255)且尾数非零 → NaN;指数全1且尾数为零 → INF
IsValid := NOT ((expBits = 16#FF#) AND (fracBits <> 0)) 
           AND NOT ((expBits = 16#FF#) AND (fracBits = 0));

✅ 说明:该函数严格依据IEEE 754单精度定义。expBits = 255(十六进制FF)且 fracBits ≠ 0NaNexpBits = 255fracBits = 0±INF。其余情况均为有效数。

步骤2:在关键变量读取后立即调用检测

// 读取物理输入
RawSpeed := IW100; // 假设为整型原始值
MotorSpeed := INT_TO_REAL(RawSpeed) * 0.1; // 转换为工程量

// 立即检测
fbValidSpeed(IN := MotorSpeed);
IF NOT fbValidSpeed.IsValid THEN
    MotorSpeed := 0.0; // 安全默认值
    FaultCode := 16#0001; // 设置故障码
END_IF;

步骤3:全局监控(适用于TIA Portal/CoDeSys等支持诊断缓冲区的平台)

启用PLC运行时诊断,在 System Configuration → Diagnostics 中勾选:

  • Enable floating-point exception trapping(若支持)
  • Log invalid floating-point operations

当发生 NaN/INF 运算时,诊断缓冲区将记录事件ID 0x8012(浮点无效操作)及触发地址,可导出CSV快速定位问题代码行。


四、防御:五层过滤策略(从硬件到逻辑)

层级 措施 实施要点 防御效果
L1:硬件层 选用带“断线检测”和“超限标记”功能的模拟量模块 如西门子SM1231 AI 8x13bit,配置通道参数时启用 Breakwire detection,模块自动将断线通道置为特定状态字,而非无效浮点值 阻断90%以上NaN源头
L2:驱动层 在设备驱动或IO访问函数中嵌入有效性检查 所有 READ_AI 函数在返回前执行 FB_IsValidReal;无效值返回预设安全值(如0.0)并置位 IO_Fault 标志 隔离硬件异常,业务逻辑无感
L3:数据层 为所有 REAL 变量定义“受控别名” 使用 TYPE 定义:TYPE T_SafeReal : STRUCT Value : REAL; Valid : BOOL; END_STRUCT;所有运算前检查 .Valid 字段 强制契约:无效值不可参与计算
L4:逻辑层 关键判断前插入显式有效性守卫 pascal IF NOT fbValidSpeed.IsValid THEN<br> RunMotor := FALSE;<br> RETURN;<br>END_IF; 最小侵入式修复,适用于遗留代码
L5:测试层 单元测试注入边界值 使用PLC仿真器(如S7-PLCSIM Advanced)在测试用例中强制将变量设为 NaN/INF,验证 IF 分支覆盖完整性 验证防御措施真实生效

强烈推荐组合使用L2+L4:驱动层拦截硬件异常,逻辑层守卫核心判断,兼顾鲁棒性与可维护性。


五、重构示例:从脆弱到健壮

原始脆弱代码(某输送带速度联锁):

// 主程序循环
IF (BeltSpeed > 0.0) AND (BeltLoad < 150.0) AND (NoJams) THEN
    DriveEnable := TRUE;
ELSIF (BeltSpeed = 0.0) AND (BeltLoad = 0.0) THEN
    DriveEnable := FALSE;
ELSE
    DriveEnable := FALSE;
    BeltFault := TRUE;
END_IF;

重构后健壮代码:

// 1. 驱动层封装(在独立FB中)
fbBeltSpeed(IN := IW200, OUT => BeltSpeedRaw);
fbBeltLoad(IN := IW202, OUT => BeltLoadRaw);

// 2. 数据层转换(带有效性)
fbValidSpeed(IN := BeltSpeedRaw); 
fbValidLoad(IN := BeltLoadRaw);

BeltSpeed.Value := BeltSpeedRaw;
BeltSpeed.Valid := fbValidSpeed.IsValid;
BeltLoad.Value := BeltLoadRaw;
BeltLoad.Valid := fbValidLoad.IsValid;

// 3. 逻辑层守卫判断
IF NOT BeltSpeed.Valid OR NOT BeltLoad.Valid THEN
    DriveEnable := FALSE;
    BeltFault := TRUE;
    RETURN;
END_IF;

// 4. 原逻辑(此时BeltSpeed.Value和BeltLoad.Value必为有效REAL)
IF (BeltSpeed.Value > 0.0) AND (BeltLoad.Value < 150.0) AND (NoJams) THEN
    DriveEnable := TRUE;
ELSIF (BeltSpeed.Value = 0.0) AND (BeltLoad.Value = 0.0) THEN
    DriveEnable := FALSE;
ELSE
    DriveEnable := FALSE;
    BeltFault := TRUE;
END_IF;

六、调试技巧:快速识别运行时NaN/Infinity

当系统异常且怀疑浮点问题时,执行以下操作:

  1. 在线监控变量值:在PLC编程软件中右键目标 REAL 变量 → Add to Watch Table → 启用 Hex Display 模式。观察其DWORD值:

    • NaN 典型值:7FC00000, FFC00000, 7F800001(任意尾数非零且指数全1)
    • +INF7F800000
    • -INFFF800000
  2. 添加临时诊断输出:在疑似问题区域插入:

// 临时诊断:将REAL转为HEX字符串(需PLC支持STRING操作)
HexStr := DWORD_TO_STRING(REAL_TO_DWORD(BeltSpeed));
// 输出HexStr至HMI诊断页或PLC日志
  1. 使用断点条件:在TIA Portal中,为关键 IF 行设置断点,条件设为 REAL_TO_DWORD(BeltSpeed) = 16#7FC00000#,直接捕获NaN时刻。

七、供应商差异提醒(避免踩坑)

不同PLC厂商对NaN/Infinity的默认处理存在细微差别,务必查阅具体型号手册:

厂商 型号示例 NaN比较行为 特殊说明
西门子 S7-1200/1500 严格遵循IEC 61131-3:所有比较返回FALSE TIA Portal V17+新增IS_NAN()系统函数,推荐直接使用
罗克韦尔 ControlLogix NaN = NaN 返回FALSE,但NaN <> NaN 返回TRUE(与标准一致) 某些旧固件版本对LN(-1)返回0.0而非NaN,需升级
倍福 CX9020 支持编译期警告:#pragma diag_default "1043" 可开启NaN使用警告 TwinCAT 3.1中REAL默认初始化为0.0,降低随机NaN风险
施耐德 M340 SQRT(-1.0) 返回0.0(非NaN),属非标行为 必须手动检查输入符号,不可依赖函数返回值

⚠️ 关键结论绝不能假设所有平台行为一致。必须以IEC 61131-3标准为底线,自行实现检测,而非依赖厂商扩展。


八、终极防护:编译期强制检查(适用于支持静态分析的平台)

在支持ST静态分析的IDE(如Codesys 3.5+、TIA Portal V18)中,启用以下检查项:

  • Enable NaN usage warning
  • Warn on uninitialized REAL variables
  • Detect division by zero in constant expressions

并在项目属性中设置:

  • Initialization value for REAL variables: 0.0(覆盖默认随机值)
  • Enable runtime NaN trap: Enabled(若硬件支持)

此举可将80%的NaN隐患消灭在编码阶段。


在电气自动化领域,逻辑的确定性就是安全的生命线。NaN与Infinity不是数学边缘案例,而是传感器失效、通信中断、配置错误的必然产物。放弃“它不会出现”的侥幸,代之以“它一定会来”的预设,通过分层检测与强制守卫,让每一行ST代码都成为可靠防线。

评论 (0)

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

扫一扫,手机查看

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