在电气自动化系统中,PLC(可编程逻辑控制器)程序常需将浮点数(REAL)转换为整数(INT)。这类转换看似简单,但若未明确处理舍入方式,极易引入隐性误差——轻则导致控制偏差(如变频器频率设定值偏移0.5Hz),重则引发设备误动作(如定位轴超程停机)。REAL_TO_INT 是 IEC 61131-3 标准中定义的基础类型转换函数,但它本身不规定舍入行为:其实际结果完全取决于 PLC 厂商的实现和当前编译/运行时配置。因此,“安全使用 REAL_TO_INT”的本质,是主动剥离平台依赖性,用确定性逻辑替代默认行为。
一、认清问题根源:为什么 REAL_TO_INT 不可靠?
REAL_TO_INT 的不确定性来自三方面:
-
标准未定义舍入规则
IEC 61131-3 第 3 版第 7.3.2 节仅说明:“REAL_TO_INT将REAL值转换为最接近的INT值”,但未定义“最接近”的判定方式(四舍五入?向零截断?向负无穷取整?)。这导致不同品牌 PLC 表现各异:- 西门子 S7-1200/S7-1500 默认采用 向零截断(Truncation):
REAL_TO_INT(3.9)→3,REAL_TO_INT(-3.9)→-3; - 罗克韦尔 Logix 5000 中
REAL_TO_INT实际调用底层ROUND指令,执行 四舍五入(Round Half Up):REAL_TO_INT(3.5)→4,REAL_TO_INT(-3.5)→-4; - 倍福 TwinCAT 3 默认为 向偶数舍入(Round Half to Even),即银行家舍入:
REAL_TO_INT(2.5)→2,REAL_TO_INT(3.5)→4。
- 西门子 S7-1200/S7-1500 默认采用 向零截断(Truncation):
-
编译器优化可能改变行为
某些 PLC 编译器在“优化等级”开启时,会将连续的REAL_TO_INT调用合并为单次硬件指令。而该硬件指令的舍入模式可能与软件模拟不同(例如,FPU 单元默认使用 IEEE 754 “舍入到最近偶数” 模式)。 -
边界值存在隐式溢出风险
REAL可表示 ±3.402823E+38,而INT范围仅为 -32768 至 +32767(16位)。当输入REAL值超出INT表示范围时,REAL_TO_INT的返回值是未定义的(厂商通常返回INT#MIN或INT#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_INT:FLOOR在 IEC 61131-3 中明确定义为“向负无穷取整”,行为唯一; ABS(x) + 0.5再FLOOR,等效于数学上的 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 → 2REAL_TO_INT_Trunc(-2.9)→absX=2.9 → FLOOR=2 → sign=-1 → -2REAL_TO_INT_Trunc(0.1)→0,REAL_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)
在项目属性中启用 “严格类型检查” 和 “禁用隐式类型转换”。此时,任何未显式调用转换函数的 REAL→INT 赋值(如 myInt := myReal;)将报编译错误,迫使工程师必须选择 REAL_TO_INT_Round 或 REAL_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 后面加一个下划线,不是语法要求,而是工程敬畏——它提醒你:此处有魔鬼,必须亲手签下契约。

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