文章目录

ST类型转换安全:REAL_TO_INT 四舍五入与截断的误差处理

发布于 2026-03-19 17:58:56 · 浏览 2 次 · 评论 0 条

在电气自动化系统中,PLC(可编程逻辑控制器)程序常需将浮点数(REAL)转换为整数(INT)。这类转换看似简单,但若未明确处理舍入方式,极易引入隐性误差——轻则导致控制偏差(如变频器频率设定值偏移0.5Hz),重则引发设备误动作(如定位轴超程停机)。REAL_TO_INT 是 IEC 61131-3 标准中定义的基础类型转换函数,但它本身不规定舍入行为:其实际结果完全取决于 PLC 厂商的实现和当前编译/运行时配置。因此,“安全使用 REAL_TO_INT”的本质,是主动剥离平台依赖性,用确定性逻辑替代默认行为


一、认清问题根源:为什么 REAL_TO_INT 不可靠?

REAL_TO_INT 的不确定性来自三方面:

  1. 标准未定义舍入规则
    IEC 61131-3 第 3 版第 7.3.2 节仅说明:“REAL_TO_INTREAL 值转换为最接近的 INT 值”,但未定义“最接近”的判定方式(四舍五入?向零截断?向负无穷取整?)。这导致不同品牌 PLC 表现各异:

    • 西门子 S7-1200/S7-1500 默认采用 向零截断(Truncation):REAL_TO_INT(3.9)3REAL_TO_INT(-3.9)-3
    • 罗克韦尔 Logix 5000 中 REAL_TO_INT 实际调用底层 ROUND 指令,执行 四舍五入(Round Half Up):REAL_TO_INT(3.5)4REAL_TO_INT(-3.5)-4
    • 倍福 TwinCAT 3 默认为 向偶数舍入(Round Half to Even),即银行家舍入:REAL_TO_INT(2.5)2REAL_TO_INT(3.5)4
  2. 编译器优化可能改变行为
    某些 PLC 编译器在“优化等级”开启时,会将连续的 REAL_TO_INT 调用合并为单次硬件指令。而该硬件指令的舍入模式可能与软件模拟不同(例如,FPU 单元默认使用 IEEE 754 “舍入到最近偶数” 模式)。

  3. 边界值存在隐式溢出风险
    REAL 可表示 ±3.402823E+38,而 INT 范围仅为 -32768 至 +32767(16位)。当输入 REAL 值超出 INT 表示范围时,REAL_TO_INT 的返回值是未定义的(厂商通常返回 INT#MININT#MAX,但无统一规范)。例如:REAL_TO_INT(50000.0) 在多数 PLC 上返回 32767,但该过程不触发任何错误标志,故障难以追踪。

✅ 安全原则第一条:永远不直接使用 REAL_TO_INT 处理关键控制变量。必须显式声明舍入意图,并校验数值范围。


二、两种核心策略:四舍五入 vs 截断——何时用哪个?

选择舍入方式不是数学偏好问题,而是控制语义问题。需根据物理量的实际意义决定:

场景类型 推荐舍入方式 原因说明
设定值类(如温度设定 SP := 23.7°C 四舍五入 用户期望“23.7”被理解为“接近24”,控制目标应向最近整数对齐;避免因截断导致长期偏低(如恒温箱始终低0.4°C)。
计数类(如脉冲计数 Pulses := 1023.8 向零截断 脉冲不可分割,0.8个脉冲不存在;截断保留“已完整发生的次数”,符合物理事实。
定位类(如伺服位置 Pos := -15.3mm 向负无穷取整(Floor) 安全起见,宁可少走(-16mm)也不多走(-15mm),防止撞限位;需结合方向判断。
速率类(如传送带速度 Speed := 60.4 rpm 四舍五入 电机驱动器内部以整数步进调节,60.4 rpm 最接近 60 rpm 或 61 rpm,取更近者误差最小。

⚠️ 注意:所谓“四舍五入”在 PLC 中并非 ROUND(x) 函数(该函数在部分平台仍依赖底层实现),而是需用 确定性数学表达式重构


三、手写安全转换函数(ST语言):零依赖、可验证

以下代码适用于所有支持 IEC 61131-3 ST 的 PLC(包括 CODESYS、TwinCAT、博途、Logix),不调用任何厂商专有函数,仅用基础运算符实现。

3.1 安全四舍五入:REAL_TO_INT_Round

FUNCTION REAL_TO_INT_Round : INT
VAR_INPUT
    x : REAL;
    minVal : INT := -32768;
    maxVal : INT := 32767;
END_VAR
VAR
    temp : REAL;
    result : INT;
END_VAR

// 步骤1:范围预检(防止溢出)
IF x > REAL#maxVal THEN
    REAL_TO_INT_Round := maxVal;
    EXIT;
ELSIF x < REAL#minVal THEN
    REAL_TO_INT_Round := minVal;
    EXIT;
END_IF;

// 步骤2:执行四舍五入(Round Half Up)
// 公式:sign(x) * floor(|x| + 0.5)
temp := ABS(x);
temp := temp + 0.5;
temp := FLOOR(temp); // FLOOR 是 IEC 标准函数,行为确定:向下取整
IF x >= 0.0 THEN
    result := INT#temp;
ELSE
    result := -INT#temp;
END_IF;

// 步骤3:二次范围校验(因 FLOOR 可能产生临界溢出)
IF result > maxVal THEN
    REAL_TO_INT_Round := maxVal;
ELSIF result < minVal THEN
    REAL_TO_INT_Round := minVal;
ELSE
    REAL_TO_INT_Round := result;
END_IF;

关键点解析

  • 使用 FLOOR() 替代 REAL_TO_INTFLOOR 在 IEC 61131-3 中明确定义为“向负无穷取整”,行为唯一;
  • ABS(x) + 0.5FLOOR,等效于数学上的 Round Half Up;
  • 两次范围检查:一次在加法前(防 x+0.5 溢出),一次在转换后(防 FLOOR 结果越界);
  • 手动处理符号,避免负数 FLOOR(-3.5) = -4 导致错误(-3.5 + 0.5 = -3.0 → FLOOR = -3,但我们需要 -4)。

3.2 安全截断:REAL_TO_INT_Trunc

FUNCTION REAL_TO_INT_Trunc : INT
VAR_INPUT
    x : REAL;
    minVal : INT := -32768;
    maxVal : INT := 32767;
END_VAR
VAR
    absX : REAL;
    sign : INT;
    result : INT;
END_VAR

// 步骤1:范围预检
IF x > REAL#maxVal THEN
    REAL_TO_INT_Trunc := maxVal;
    EXIT;
ELSIF x < REAL#minVal THEN
    REAL_TO_INT_Trunc := minVal;
    EXIT;
END_IF;

// 步骤2:向零截断(Truncate toward zero)
absX := ABS(x);
result := INT#FLOOR(absX); // 得到正数部分整数
sign := 1;
IF x < 0.0 THEN sign := -1; END_IF;
result := sign * result;

// 步骤3:二次校验
IF result > maxVal THEN
    REAL_TO_INT_Trunc := maxVal;
ELSIF result < minVal THEN
    REAL_TO_INT_Trunc := minVal;
ELSE
    REAL_TO_INT_Trunc := result;
END_IF;

验证示例(手算核对)

  • REAL_TO_INT_Trunc(2.9)absX=2.9 → FLOOR=2 → sign=1 → 2
  • REAL_TO_INT_Trunc(-2.9)absX=2.9 → FLOOR=2 → sign=-1 → -2
  • REAL_TO_INT_Trunc(0.1)0REAL_TO_INT_Trunc(-0.1)0

四、工业现场典型误差案例与修复方案

案例1:PID输出限幅导致积分饱和漂移

现象:某温度 PID 控制器输出 REAL 值为 100.48,经 REAL_TO_INT 后送至 4-20mA 输出模块(需 0–100% 整数标度)。系统显示输出 100%,但实测电流为 19.92mA(对应 99.6%),温度持续缓慢上升。
根因:PLC 使用向零截断,REAL_TO_INT(100.48) = 100,但模块内部将整数 100 解释为 100.00%,而硬件 DAC 实际分辨率仅 0.1%。100.48% 应输出 20.008mA,截断丢失了 0.48% 的调节裕度。
修复:改用 REAL_TO_INT_Round(100.48)100(不变),但对 100.5 及以上值可提升至 101(需确认模块是否支持 101% 过载)。更优解:将 PID 输出直接映射为 REAL 型 4–20mA 电流值,由 AO 模块硬件自动完成高精度 REAL→DAC 转换,绕过 INT 中间层。

案例2:编码器计数同步丢失

现象:高速旋转轴编码器每转 1000 脉冲,PLC 读取 REAL 型累计值 12345.7,用 REAL_TO_INT 转换后参与位置比较,每次停机位置偏差 ±0.3 转。
根因REAL_TO_INT(12345.7) 截断得 12345,丢失 0.7 个脉冲;累积 10 次启停,偏差达 7 个脉冲(0.007 转)。
修复:改用 REAL_TO_INT_Trunc 并增加小数部分缓存:

// 全局变量
persistent pulseFrac : REAL := 0.0;

// 每次读取新计数 realCount 后:
pulseFrac := pulseFrac + (realCount - FLOOR(realCount));
IF pulseFrac >= 1.0 THEN
    pulseFrac := pulseFrac - 1.0;
    intCount := INT#FLOOR(realCount) + 1;
ELSE
    intCount := INT#FLOOR(realCount);
END_IF;

此法将小数部分跨周期累加,实现亚脉冲级精度。


五、长效防护机制:编译期与运行期双重保障

5.1 编译期强制检查(CODESYS / TwinCAT)

在项目属性中启用 “严格类型检查”“禁用隐式类型转换”。此时,任何未显式调用转换函数的 REALINT 赋值(如 myInt := myReal;)将报编译错误,迫使工程师必须选择 REAL_TO_INT_RoundREAL_TO_INT_Trunc

5.2 运行期自检函数

部署一个诊断 FB(功能块),周期性注入测试值并比对:

FUNCTION_BLOCK SafeConvertDiag
VAR
    testValues : ARRAY[0..4] OF REAL := [3.5, -3.5, 100.0, -0.1, 32767.9];
    expectedRound : ARRAY[0..4] OF INT := [4, -4, 100, 0, 32767];
    expectedTrunc : ARRAY[0..4] OF INT := [3, -3, 100, 0, 32767];
    roundOK : BOOL := TRUE;
    truncOK : BOOL := TRUE;
END_VAR

FOR i := 0 TO 4 DO
    IF REAL_TO_INT_Round(testValues[i]) <> expectedRound[i] THEN
        roundOK := FALSE;
    END_IF;
    IF REAL_TO_INT_Trunc(testValues[i]) <> expectedTrunc[i] THEN
        truncOK := FALSE;
    END_IF;
END_FOR;

// 若任一失败,置位报警位并记录事件码
IF NOT roundOK OR NOT truncOK THEN
    DiagAlarm := TRUE;
    LastErrorCode := 1024; // 自定义:转换函数异常
END_IF;

该 FB 可在启动时执行一次,或每小时运行,确保函数行为未被固件升级意外修改。


六、终极建议:何时可以不用 REAL_TO_INT

答案是:当物理量本质为离散时,从源头避免 REAL。例如:

  • 温度传感器若支持 Modbus RTU 协议,优先读取原始 INT 型寄存器(如 30001 返回 237 表示 23.7°C),再除以 10 得 REAL;而非读取 REAL 型浮点寄存器后反向转换。
  • 运动控制中,用 LREAL(64位)存储位置,但通过 POSITION_IN_PULSES 等专用数据类型传递给轴控指令,由运动库内部处理精度。
  • HMI 设定值直接绑定 INT 变量,前端用滑块+步长=1,杜绝用户输入小数。

减少浮点-整数转换次数,就是减少误差源的最高效方式。


REAL_TO_INT 后面加一个下划线,不是语法要求,而是工程敬畏——它提醒你:此处有魔鬼,必须亲手签下契约。

评论 (0)

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

扫一扫,手机查看

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