在工业自动化控制系统中,时间管理是核心功能之一。无论是记录故障发生时刻、统计设备运行时长,还是实现复杂的定时逻辑,都需要精确的日期时间处理。Codesys 作为符合 IEC 61131-3 标准的主流开发环境,提供了一套完整的时间数据类型和功能块。本文将深入解析如何在 Codesys 中高效使用日期时间功能块。
核心数据类型与声明规范
在编写逻辑之前,必须正确声明变量。Codesys 中的日期时间数据类型不同于普通的整数或浮点数,它们有特定的内部存储格式和赋值语法。
1. 基础数据类型
日期时间类变量在内存中通常以 DWORD 或 LWORD 形式存储,数值代表从特定时间点(如 1970 年 1 月 1 日)开始的毫秒数或秒数。以下是四种核心类型:
| 类型名称 | 关键字 | 说明 | 字面量示例 |
|---|---|---|---|
| 短时间 | TIME |
用于表示时长(毫秒级) | T#5s100ms |
| 长时间 | LTIME |
用于表示高精度时长(纳秒级) | LTIME#1h30m |
| 日期 | DATE |
仅包含年月日 | D#2023-10-25 |
| 时刻 | TOD |
仅包含时分秒 | TOD#14:30:00 |
| 日期时刻 | DT |
包含年月日时分秒 | DT#2023-10-25-14:30:00 |
2. 变量声明实操
在变量声明区(VAR ... END_VAR),需严格遵循 类型名称#值 的格式。注意:时间值内部不区分大小写,但关键字 T#、D# 等通常大写以示区分。
- 打开 Codesys 开发环境,进入 POU (Program Organization Unit) 的声明编辑区。
- 输入 以下变量定义代码:
VAR
tRunTime : TIME := T#0s; (* 设备运行计时 *)
tPresetTime : TIME := T#1h30m; (* 预设时间:1小时30分 *)
dtFaultLog : DT; (* 故障记录时间戳 *)
dCurrentDate : DATE := D#2024-01-01; (* 当前日期初值 *)
todStartTime : TOD := TOD#08:00:00; (* 每天启动时刻 *)
ltHighPrecision : LTIME; (* 高精度长时长变量 *)
END_VAR
关键点:
- 赋值符号
:=后必须跟随类型标识符(如T#)。 - 时间单位支持组合,如
1h30m表示 1 小时 30 分钟,500ms表示 500 毫秒。 - 严禁将
TIME类型直接赋值给INT类型,必须通过转换函数处理。
系统时间获取与转换
PLC 运行时需要获取当前的系统时间。这通常通过系统库或标准功能块实现。Codesys 揁供了 SysRtcGetTime 或标准 RTC 功能块来读取硬件时钟。
1. 使用标准 RTC 功能块
RTC (Real-Time Clock) 功能块用于读取 PLC 的硬件实时时钟。
- 调用 功能块实例:在程序体中声明
RTC实例。 - 连接 输入输出引脚:将输入
IN置为TRUE,当前时间将输出到DT类型变量中。
代码示例如下:
VAR
fbRTC : RTC; (* RTC功能块实例 *)
dtNow : DT; (* 当前时间存储变量 *)
bEnable : BOOL := TRUE; (* 使能信号 *)
END_VAR
(* 程序逻辑 *)
fbRTC(IN := bEnable, PDT => dtNow);
执行上述代码后,变量 dtNow 将实时更新为 PLC 的当前日期和时间。
2. 时间类型的数学运算
TIME 类型支持直接的加减运算,结果仍为 TIME 类型,常用于计时器逻辑或延时判断。
- 加法:
T#10s + T#5s结果为T#15s。 - 减法:
T#1m - T#30s结果为T#30s。
注意:不同类型之间(如 TIME 与 DATE)不能直接运算,必须先转换。
Util 库核心功能块详解
Codesys 内置的 Util 库提供了强大的日期时间处理函数。使用前,需在“库管理器”中添加 Util.library。
1. 时间单位拆分:SPLITTIME
当需要将 TIME 类型的总时长转换为“时、分、秒、毫秒”以便在 HMI 上显示时,使用 SPLITTIME 函数。
操作步骤:
- 声明 用于接收拆分结果的中间变量(DINT 类型)。
- 调用 函数并传入
TIME变量。
代码示例:
VAR
tDuration : TIME := T#3661500ms; (* 1小时1分1秒500毫秒 *)
diHour : DINT;
diMin : DINT;
diSec : DINT;
diMilli : DINT;
END_VAR
SPLITTIME(IN := tDuration, HOURS => diHour, MINUTES => diMin, SECONDS => diSec, MILLISECONDS => diMilli);
执行后,diHour 为 1,diMin 为 1,diSec 为 1,diMilli 为 500。
2. 日期解析:SPLITDATE
类似地,SPLITDATE 用于将 DATE 类型拆分为年、月、日。
VAR
dTest : DATE := D#2023-05-20;
diYear, diMonth, diDay : DINT;
END_VAR
SPLITDATE(IN := dTest, YEAR => diYear, MONTH => diMonth, DAY => diDay);
(* 结果:diYear=2023, diMonth=5, diDay=20 *)
3. 星期计算:DAYOFWEEK
判断某一天是星期几是常见的逻辑需求(如工作日/周末模式)。DAYOFWEEK 返回整数 1 到 7,通常 1 代表星期一,7 代表星期日。
VAR
dCheck : DATE := D#2023-10-01; (* 假设为星期日 *)
iWeekDay : INT;
END_VAR
iWeekDay := DAYOFWEEK(dCheck);
(* 结果:iWeekDay = 7 *)
高级应用:定时任务与时间段判断
工业现场常要求在特定时间段执行操作,例如“每天 8:00 到 17:00 开启照明”。这需要比较当前时刻 TOD。
1. 时间段比较逻辑
TOD 类型支持直接比较运算(>, <, >=, <=)。
逻辑实现步骤:
- 获取 当前时刻
TOD。 - 定义 起始和结束时刻常量。
- 比较 当前时刻是否在区间内。
代码实现:
VAR
(* 假设 fbRTC 已运行,dtNow 已获取 *)
todNow : TOD;
todStart : TOD := TOD#08:00:00;
todEnd : TOD := TOD#17:00:00;
bLightOn : BOOL;
END_VAR
(* 将 DT 转换为 TOD,利用 DT_TO_TOD 转换函数 *)
todNow := DT_TO_TOD(dtNow);
(* 区间判断 *)
IF todNow >= todStart AND todNow <= todEnd THEN
bLightOn := TRUE;
ELSE
bLightOn := FALSE;
END_IF;
2. 流程图解
为了更直观地理解时间段判断逻辑,以下 Mermaid 流程图展示了核心判断过程:
3. 毫秒级高精度计时
对于运动控制或高速采集,标准的 TIME 精度(毫秒)可能不足,应使用 LTIME 和 LREAL 进行运算。
LTIME 精度可达纳秒级。计算公式为:
$$ T_{interval} = \frac{Counts_{end} - Counts_{start}}{Frequency} $$
其中 $Counts$ 为系统时钟计数器值,$Frequency$ 为时钟频率。在 Codesys 中,可直接利用 LTIME 运算简化此过程:
VAR
ltStart : LTIME;
ltEnd : LTIME;
ltDiff : LTIME;
END_VAR
ltDiff := ltEnd - ltStart;
综合实战:设备运行时长统计
本节将结合上述知识,编写一个完整的“设备维护提醒”功能块。功能需求:统计设备总运行时间,当超过 500 小时时触发维护报警,并能通过按钮复位计时。
1. 变量定义
需要定义输入(启动、复位)、输出(报警)以及内部计时变量。
FUNCTION_BLOCK FB_MaintenanceMonitor
VAR_INPUT
bRun : BOOL; (* 设备运行信号 *)
bReset : BOOL; (* 复位信号 *)
END_VAR
VAR_OUTPUT
bAlarm : BOOL; (* 维护报警输出 *)
tTotalRun : TIME; (* 当前总运行时间,用于HMI显示 *)
END_VAR
VAR
tAccumulated : TIME := T#0s; (* 累积时间 *)
fbTP : TP; (* 脉冲发生器,用于周期性累加 *)
tCycleTime : TIME := T#1s; (* 计算周期:1秒 *)
END_VAR
2. 程序逻辑编写
由于 TIME 类型累加在长时间运行下可能存在误差,这里采用“周期累加法”,即每隔固定周期(如 1 秒)将变量增加 T#1s。虽然 Codesys 支持 TIME 直接运算,但在某些低端控制器上,利用定时器触发累加能保证主循环扫描周期不影响计时精度。
(* 实例化定时器,产生周期为1s的脉冲 *)
fbTP(IN := NOT fbTP.Q, PT := tCycleTime);
(* 如果设备运行且定时器时间到 *)
IF bRun AND fbTP.Q THEN
tAccumulated := tAccumulated + tCycleTime;
END_IF
(* 复位逻辑 *)
IF bReset THEN
tAccumulated := T#0s;
bAlarm := FALSE;
END_IF
(* 判断是否超时:500小时 = 1800000秒 *)
IF tAccumulated >= T#500h THEN
bAlarm := TRUE;
END_IF
(* 输出映射 *)
tTotalRun := tAccumulated;
3. 高精度优化方案(可选)
若控制器支持,更推荐直接利用系统时钟差值计算运行时长,这比累加法更精确,且不受 PLC 停机影响。
算法逻辑如下:
$$ T_{total} = T_{saved} + (T_{current\_stop} - T_{current\_start}) $$
但在 Codesys 标准编程习惯中,使用 TON (接通延时定时器) 的组合也是一种常见做法。以下是一个利用 TON 进行状态监测的简化逻辑图:
常见错误与排查指南
在日期时间编程中,初学者常遇到以下几类错误。
1. 类型不匹配错误
现象:编译报错“Cannot convert type TIME to type DINT”。
原因:试图直接将 TIME 赋值给整数变量,或在 IF 语句中直接比较 TIME 与整数。
解决:
使用类型转换函数。
TIME_TO_DINT:将时间转为毫秒数(注意:仅转换数值,不含单位)。DINT_TO_TIME:将毫秒数转为时间变量。
示例:将 5000 毫秒转为 TIME。
diValue : DINT := 5000;
tValue : TIME;
tValue := DINT_TO_TIME(diValue); (* 结果为 T#5s *)
2. 溢出风险
现象:计时器运行一段时间后数值归零或乱码。
原因:TIME 类型本质是 32 位无符号整数(部分实现为有符号),最大值约为 49.7 天(毫秒级)。若设备常年不重启且一直累加,会导致溢出。
解决:
对于长周期计时(月或年级别),应使用 LTIME(64 位)或将时间转换为分钟/小时存储在 DINT 或 LINT 变量中。
3. 时区问题
现象:PLC 显示时间比实际时间慢/快几小时。
原因:SysRtcGetTime 获取的可能是 UTC 时间或硬件时钟时间,未处理时区偏移。
解决:
手动在程序中增加时区偏移量。
VAR
dtUTC : DT;
dtLocal : DT;
tOffset : TIME := T#8h; (* 北京时间 UTC+8 *)
END_VAR
dtLocal := dtUTC + tOffset; (* 手动修正时区 *)
附录:常用转换函数速查表
以下是 Codesys 编程中高频使用的时间转换函数。
| 函数名称 | 功能描述 | 示例输入 | 输出结果 |
|---|---|---|---|
DATE_TO_DT |
日期转日期时间 | D#2023-01-01 |
DT#2023-01-01-00:00:00 |
DT_TO_TOD |
提取时刻部分 | DT#2023-01-01-12:30:00 |
TOD#12:30:00 |
TIME_TO_STRING |
时间转字符串 | T#1h30m |
'T#1h30m' (格式依存) |
STRING_TO_TIME |
字符串转时间 | 'T#500ms' |
T#500ms |
DT_TO_DATE |
提取日期部分 | DT#2023-01-01-12:30:00 |
D#2023-01-01 |

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