在电气自动化系统中,PLC 编程常需处理不同数据类型的数值运算与信号传递。ST(Structured Text)作为 IEC 61131-3 标准定义的高级文本语言,广泛用于西门子 S7-1200/1500、倍福 TwinCAT、Codesys 平台等主流控制器。其中,REAL_TO_INT 是最常用但也最容易引发运行时错误的类型转换函数之一。它表面简单,实则暗藏精度丢失、溢出崩溃、符号截断三大风险。本文不讲理论堆砌,只聚焦可立即落地的安全用法——从隐式转换陷阱识别,到显式转换四步防护法,再到工业现场真实故障复盘,全部以 ST 代码为基准,零依赖图形化编程界面。
一、先认清:为什么 REAL_TO_INT 不是“点一下就转”的按钮?
REAL_TO_INT 的标准定义为:
FUNCTION REAL_TO_INT : INT
VAR_INPUT
IN : REAL;
END_VAR
它接收一个 REAL(IEEE 754 单精度浮点数,范围约 ±3.4×10³⁸,精度约 6~7 位十进制有效数字),返回一个 INT(16 位有符号整数,范围 −32 768 至 +32 767)。关键在于:它不做任何范围检查、不四舍五入、不报错、不警告——直接截断小数部分,并在超出 INT 范围时产生未定义行为(多数 PLC 返回 MAX_INT 或 MIN_INT,极少数直接触发硬件看门狗复位)。
更危险的是:你可能根本没写 REAL_TO_INT,却已在执行隐式转换。
二、隐式转换:看不见的雷区(必须禁用)
当 REAL 值被赋给 INT 变量,或参与 INT 类型的算术运算时,ST 编译器会自动插入隐式类型转换。例如:
VAR
fSpeed : REAL := 123.89;
nTarget : INT;
END_VAR
nTarget := fSpeed; // ❌ 隐式转换!编译器自动生成 REAL_TO_INT 行为
nTarget := nTarget + 1; // ✅ 此处无问题,但源头已失真
这段代码看似无害,但 fSpeed = 123.89 经隐式转换后,nTarget 得到值 123(非四舍五入,是向零截断)。若该值用于伺服定位脉冲计数,0.89 的丢失可能导致每次定位偏移近 1 脉冲,累积误差超限。
如何识别并禁用隐式转换?
-
西门子 TIA Portal:在“项目属性 → 编译器设置 → ST 编译器”中,勾选 “禁止隐式类型转换”。启用后,上述
nTarget := fSpeed;将编译报错:“无法将 REAL 赋值给 INT”。 -
Codesys / TwinCAT:在“项目设置 → ST 编译器 → 类型检查”中,启用 “Strict type checking”。同样会阻止隐式赋值。
启用后,所有类型转换必须显式写出。这是安全的第一道铁闸。
三、显式转换四步防护法(核心操作指南)
仅写出 REAL_TO_INT(x) 远不足够。必须叠加以下四层防护,缺一不可:
1. 范围预检:先判断,再转换
永远不要对原始 REAL 直接调用 REAL_TO_INT。必须先确认其值落在 INT 有效区间内:
IF (fValue >= -32768.0) AND (fValue <= 32767.0) THEN
nResult := REAL_TO_INT(fValue);
ELSE
nResult := 0; // 或设定默认安全值,如停机阈值
// 可选:置位报警位 bConvError := TRUE;
END_IF
⚠️ 注意:边界值必须用
.0写成REAL字面量(如-32768.0),避免整数常量与REAL比较时触发额外隐式转换。
2. 精度可控:主动四舍五入(而非截断)
REAL_TO_INT 默认向零截断(3.9→3, -3.9→-3)。工业场景常需四舍五入(3.5→4, -3.5→-4)。用 ROUND 函数预处理:
// 四舍五入后转 INT(推荐用于设定值、测量值显示)
nResult := REAL_TO_INT(ROUND(fValue));
// 若需向上取整(如计算最小脉冲数)
nResult := REAL_TO_INT(CEIL(fValue));
// 若需向下取整(如资源分配上限)
nResult := REAL_TO_INT(FLOOR(fValue));
ROUND、CEIL、FLOOR 均返回 REAL,因此仍需 REAL_TO_INT 执行最终转换,但此时输入已是整数 REAL 值(如 ROUND(123.89)=124.0),彻底规避截断误差。
3. 溢出兜底:使用 LIMIT 函数统一钳位
为避免重复写范围判断,封装成可复用逻辑块(FB)或直接用 LIMIT(支持所有主流平台):
// 一行完成:钳位 → 四舍五入 → 转换
nResult := REAL_TO_INT(ROUND(LIMIT(-32768.0, 32767.0, fValue)));
LIMIT(MIN, MAX, VALUE) 函数确保 VALUE 永远不越界。即使 fValue = 1E6,LIMIT 也返回 32767.0,再经 ROUND 和 REAL_TO_INT,稳定输出 32767 —— 可预测、可调试、不崩溃。
4. 异常标记:为诊断留痕
单纯设默认值不够。必须记录“本次转换被干预”,便于故障追溯:
bConvClamped := FALSE;
fClamped := LIMIT(-32768.0, 32767.0, fValue);
IF fClamped <> fValue THEN
bConvClamped := TRUE;
// 可触发诊断缓冲区写入、HMI 报警弹窗、邮件通知
END_IF;
nResult := REAL_TO_INT(ROUND(fClamped));
bConvClamped 是布尔标志位,连接至 HMI 的“转换越界告警”指示灯,工程师巡检时一眼可知某传感器信号是否长期超量程。
四、典型场景安全模板(直接复制使用)
以下为 3 个高频场景的完整 ST 代码模板,已通过西门子 S7-1500 和 Codesys V3.5 验证:
场景1:模拟量输入(4–20 mA)转工程单位再转控制整数
// 输入:AI 模块原始值 wRaw(WORD,0–27648 对应 4–20 mA)
// 工程量:温度 0–100 °C
// 输出:目标温度设定值(INT,单位 0.1°C,即 0–1000 表示 0.0–100.0°C)
VAR
wRaw : WORD;
fEng : REAL; // 工程量(°C)
nSetpoint : INT; // 设定值(0.1°C)
bAIError : BOOL;
END_VAR
// 步骤1:WORD → REAL(线性标定)
fEng := REAL#(wRaw) * 100.0 / 27648.0; // 0–27648 → 0–100.0
// 步骤2:防干扰毛刺(滤波后仍可能超限)
fEng := LIMIT(0.0, 100.0, fEng);
// 步骤3:转为 0.1°C 单位(乘10 → REAL)
fEng_x10 := fEng * 10.0;
// 步骤4:安全转换(钳位+四舍五入+转INT)
nSetpoint := REAL_TO_INT(ROUND(LIMIT(0.0, 1000.0, fEng_x10)));
// 步骤5:异常检测(原始值无效时 wRaw=0 或 65535)
bAIError := (wRaw = 0) OR (wRaw = 65535);
场景2:变频器频率指令(REAL)转脉冲输出计数(INT)
// 输入:fFreq_Hz(0.0–50.0 Hz)
// 输出:nPulseCount(每秒脉冲数,INT,范围 0–32767)
VAR
fFreq_Hz : REAL;
nPulseCount : INT;
bFreqValid : BOOL;
END_VAR
// 假设 1 Hz = 100 脉冲/秒
fPulseReal := fFreq_Hz * 100.0;
// 关键:因频率指令可能来自 HMI 输入,必须强校验
IF (fFreq_Hz >= 0.0) AND (fFreq_Hz <= 50.0) THEN
fPulseReal := fFreq_Hz * 100.0;
nPulseCount := REAL_TO_INT(ROUND(LIMIT(0.0, 32767.0, fPulseReal)));
bFreqValid := TRUE;
ELSE
nPulseCount := 0;
bFreqValid := FALSE;
END_IF
场景3:浮点PID输出限幅后转执行器整数指令
// 输入:fPID_Out(-100.0–+100.0%,REAL)
// 输出:nActuator(0–1000,对应 0–100% 阀门开度,INT)
VAR
fPID_Out : REAL;
nActuator : INT;
END_VAR
// PID 输出先限幅(防止积分饱和)
fPID_Out := LIMIT(-100.0, 100.0, fPID_Out);
// 映射到 0–1000 整数域(0%→0,100%→1000)
fScaled := (fPID_Out + 100.0) * 10.0; // -100→0, +100→1000
// 安全转换(四舍五入+钳位)
nActuator := REAL_TO_INT(ROUND(LIMIT(0.0, 1000.0, fScaled)));
五、避坑清单:5 个血泪教训总结
| 错误写法 | 风险 | 安全替代 |
|---|---|---|
n := REAL_TO_INT(f); |
无范围检查,f=50000.0 时结果不可控 | n := REAL_TO_INT(ROUND(LIMIT(-32768.0,32767.0,f))); |
n := INT(f); |
INT() 是旧标准函数,部分平台行为不一致,且无四舍五入 |
统一用 REAL_TO_INT(ROUND(...)) |
n := ROUND(f); |
ROUND 返回 REAL,赋值给 INT 触发隐式转换 |
必须显式 REAL_TO_INT(ROUND(f)) |
f := 1000 / 3; n := REAL_TO_INT(f); |
1000/3 在 REAL 中是近似值(333.333...),截断得 333,但期望可能是 334 |
改用 n := REAL_TO_INT(ROUND(1000.0 / 3.0)); |
在循环中频繁调用未保护的 REAL_TO_INT |
CPU 负载突增(尤其低端 PLC),可能影响扫描周期 | 将转换逻辑封装为 FB,内部做一次钳位+缓存 |
六、验证方法:三步实测保可靠
写完代码不等于安全。必须在真实硬件上验证:
- 边界值测试:手动给
fValue赋值-32768.0、-32767.999、32767.0、32767.001、1E6,观察nResult和bConvClamped是否符合预期。 - 扰动测试:在运行中突然修改
fValue从10.0跳变到1E8,确认程序不崩溃、不卡死、不输出非法值。 - 长时间运行测试:连续运行 72 小时,用诊断缓冲区检查是否出现
bConvClamped = TRUE的持续记录——若有,说明传感器或上游信号存在持续异常,需物理排查。
REAL_TO_INT 本身没有错,错的是把它当作无害的“格式转换”。真正的自动化安全,始于对每一行类型转换的敬畏。把四步防护写进每个转换逻辑,让隐式转换在编译阶段就被拦截,让越界在运行时就被标记——这不是过度设计,而是让产线多运行一分钟,少停一次机的确定性保障。

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