在结构化文本(ST)编程中,计算两个时间点之间的时间差是自动化控制系统中最基础也最易出错的操作之一。典型写法如 Diff := Time_End - Time_Start; 表面简洁,但若忽略数据类型匹配、时基单位、溢出边界与隐式转换规则,轻则结果偏差数秒,重则触发运行时错误或 PLC 停机。以下内容不讲理论,只列可立即验证、可逐行执行的实操步骤。
一、明确 ST 中时间数据类型的本质
PLC 的 ST 语言(IEC 61131-3 标准)中,时间值不是浮点数,也不是整数,而是带单位的结构化数据类型。常见时间类型有:
| 类型 | 含义 | 存储方式(典型) | 示例赋值 |
|---|---|---|---|
TIME |
相对时间间隔(时:分:秒.ms) | 32位有符号整数(毫秒为单位) | t#5s, t#1h2m300ms |
DATE |
日历日期(年-月-日) | 32位整数(自1970-01-01起天数) | d#2024-06-15 |
TIME_OF_DAY / TOD |
当日时刻(时:分:秒.ms) | 32位无符号整数(毫秒为单位) | tod#14:30:00.500 |
DATE_AND_TIME / DT |
日期+时刻完整时间戳 | 64位整数(微秒级精度) | dt#2024-06-15-14:30:00.500 |
⚠️ 关键结论:
- 只有
TIME和TOD类型支持直接相减,且结果仍为TIME; DATE相减得INT(单位:天);DT相减得TIME(单位:纳秒级,但实际精度取决于 PLC 硬件);TIME与TOD不可混用相减(编译报错);- 所有时间字面量(如
t#5s)必须带单位前缀t#、d#、tod#、dt#。
二、正确写出时间差计算的 4 个必要步骤
-
声明变量时显式指定类型,禁止依赖默认推导:
VAR Time_Start : TIME; Time_End : TIME; Diff : TIME; // 必须是 TIME,不是 INT 或 REAL END_VAR -
确保两个时间变量已赋有效值(未初始化的
TIME默认为t#0s,但逻辑上可能不满足业务起点):
触发采集动作(例如上升沿检测),执行Time_Start := TON1.Q ? TON1.ET : Time_Start;;
触发结束条件(例如传感器到位),执行Time_End := TON2.Q ? TON2.ET : Time_End;;注:
TON1.ET是定时器经过时间,其类型恒为TIME,可直接赋值。 -
执行减法前确认顺序与符号安全性:
判断Time_End >= Time_Start成立后,执行Diff := Time_End - Time_Start;;
若无法保证顺序(如手动输入、HMI 修改),必须加保护:IF Time_End >= Time_Start THEN Diff := Time_End - Time_Start; ELSE Diff := t#0s; // 或触发报警:Alarm_TimeReverse := TRUE; END_IF -
验证结果是否在
TIME有效范围内:
TIME类型最大值为t#24d_20h_31m_23.647s(即 2^31−1 毫秒 ≈ 24.8 天)。若预期差值超此限:- 改用
DWORD存储毫秒值:Time_Start_ms : DWORD := UINT_TO_DWORD(ULINT_TO_UINT(TIME_TO_DT(Time_Start))); // 不推荐,精度损失 - 更优方案:用
LREAL存毫秒浮点值:VAR Start_ms : LREAL; End_ms : LREAL; Diff_ms : LREAL; END_VAR Start_ms := TIME_TO_MS(Time_Start); // IEC 61131-3 内置函数,返回毫秒数值 End_ms := TIME_TO_MS(Time_End); Diff_ms := End_ms - Start_ms;✅
TIME_TO_MS()返回LREAL,无溢出风险,支持高达1e15毫秒(约 31,688 年)。
- 改用
三、避坑清单:9 个高频错误及修正方法
| 错误代码示例 | 错误原因 | 正确写法 |
|---|---|---|
Diff := 1000 - Time_Start; |
INT 与 TIME 不能直接运算 |
Diff := t#1000ms - Time_Start; |
Diff := TOD_END - TOD_START; |
TOD 类型名错误(应为 TOD_END) |
Diff := TOD_END - TOD_START;(变量名需全大写或按项目规范) |
Diff := Time_End + (-Time_Start); |
ST 不支持一元负号作用于 TIME |
改用 IF 判断顺序,不强行套数学恒等式 |
Diff := INT_TO_TIME(1000); |
INT_TO_TIME 非标准函数(多数 PLC 不支持) |
用 t#1000ms 字面量或 TIME#1000MS(品牌语法差异) |
Diff := Time_End - Time_Start * 1000; |
乘法优先级导致 Time_Start*1000 先算(非法) |
加括号:Diff := Time_End - (Time_Start * 1000); ❌ 仍错 → 改为 Diff := Time_End - t#1s; |
Diff := ABS(Time_End - Time_Start); |
ABS() 不接受 TIME 参数 |
先转数值:ABS(TIME_TO_MS(Time_End) - TIME_TO_MS(Time_Start)) |
Diff := Time_End - Time_Start; // 结果为负 |
未检查时间顺序,负值会截断为最大正数 | 加 IF Time_End >= Time_Start THEN ... ELSE ... END_IF |
Diff := T#5S - T#3S; |
T# 是旧版语法(部分 PLC 已弃用) |
统一使用 t#5s(小写 t + 井号 + 小写单位) |
Diff := Time_End - Time_Start; // 在 FB 初始化段 |
变量未初始化即运算,值为 t#0s |
在 FB 的 EN 为 TRUE 后首次触发时赋初值 |
四、跨品牌实操对照表(主流 PLC)
不同厂商对时间函数的支持存在细微差异,以下为实测有效的写法(基于最新固件):
| 功能 | Siemens S7-1200/1500 (TIA Portal) | Rockwell Logix 5000 (Studio 5000) | Beckhoff TwinCAT 3 |
|---|---|---|---|
| 获取当前运行时间 | Tonic() : TIME(需启用系统时钟) |
GSV(“WallClock”, “DateTime”, …) → 提取 TimeOfDay |
GET_LOCALTIME() → TOD |
| 时间转毫秒数值 | TIME_TO_MS(Time_Var) |
TOD_TO_DINT(TOD_Var) / 1000(微秒→毫秒) |
TOD_TO_LREAL(TOD_Var) / 1000.0 |
| 毫秒数值转 TIME | MS_TO_TIME(LREAL_Var) |
DINT_TO_TIME(INT_Var * 1000)(需先×1000) |
LREAL_TO_TIME(LREAL_Var * 1000.0) |
| 安全时间差计算宏 | 自定义 FC:输入 T1,T2,输出 TIME 或 LREAL ms |
AOI 封装:含溢出检测与单位选择 | FUNCTION_BLOCK F_TimeDiff(内置 LREAL 输出选项) |
✅ 统一建议:所有新项目,一律使用
TIME_TO_MS()+LREAL存储差值。它规避了TIME类型所有边界问题,且LREAL在 PLC 中普遍支持双精度(15 位有效数字),对毫秒级计时足够精确。
五、真实场景调试技巧(现场可用)
当 Diff := Time_End - Time_Start; 运行结果异常时,按以下顺序排查:
-
用在线监控看原始值:
- 在变量表中添加
Time_Start、Time_End、Diff,设置显示格式为HH:MM:SS.mmm; - 触发一次流程,观察三者是否均为非零且符合逻辑顺序。
- 在变量表中添加
-
检查定时器 ET 是否被复位:
- 若
Time_Start来自TON的ET,确认TON的IN信号在采集后保持TRUE至少一个扫描周期,否则ET归零。
- 若
-
验证时间字面量单位:
- 写
t#1s表示 1 秒,t#1000ms等价,但t#1000默认单位是毫秒(某些品牌),务必统一用带单位写法。
- 写
-
抓取中间变量做断点:
Temp_Start := Time_Start; // 在线监控此变量 Temp_End := Time_End; Diff := Temp_End - Temp_Start;避免编译器优化隐藏中间值。
-
用 HMI 强制写入测试边界值:
- 强制
Time_Start := t#24d_20h_31m_23s;,再设Time_End := t#24d_20h_31m_24s;,观察Diff是否溢出(应为t#1s,而非归零或负值)。
- 强制
六、终极安全模板(可直接复制使用)
// ======== 声明区(全局或 FB 内)========
VAR
Time_Start : TIME := t#0s;
Time_End : TIME := t#0s;
Diff_ms : LREAL := 0.0; // 推荐:毫秒级浮点结果
Is_Valid : BOOL := FALSE;
END_VAR
// ======== 执行区(循环调用)========
// 假设 Start_Trigger 和 End_Trigger 为 BOOL 信号
IF Start_Trigger AND NOT Start_Trigger_LAST THEN
Time_Start := TON_Start.ET; // 或 GET_LOCALTIME()
END_IF;
Start_Trigger_LAST := Start_Trigger;
IF End_Trigger AND NOT End_Trigger_LAST THEN
Time_End := TON_End.ET;
END_IF;
End_Trigger_LAST := End_Trigger;
// 安全计算(含顺序检查 + 溢出防护)
IF Time_End >= Time_Start THEN
Diff_ms := TIME_TO_MS(Time_End) - TIME_TO_MS(Time_Start);
Is_Valid := TRUE;
ELSE
Diff_ms := 0.0;
Is_Valid := FALSE;
END_IF;
此模板满足:类型明确、顺序防护、溢出免疫、结果可读、易于调试。

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