文章目录

Codesys的日期时间功能块编程

发布于 2026-03-24 14:33:15 · 浏览 12 次 · 评论 0 条

在工业自动化控制系统中,时间管理是核心功能之一。无论是记录故障发生时刻、统计设备运行时长,还是实现复杂的定时逻辑,都需要精确的日期时间处理。Codesys 作为符合 IEC 61131-3 标准的主流开发环境,提供了一套完整的时间数据类型和功能块。本文将深入解析如何在 Codesys 中高效使用日期时间功能块。


核心数据类型与声明规范

在编写逻辑之前,必须正确声明变量。Codesys 中的日期时间数据类型不同于普通的整数或浮点数,它们有特定的内部存储格式和赋值语法。

1. 基础数据类型

日期时间类变量在内存中通常以 DWORDLWORD 形式存储,数值代表从特定时间点(如 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# 等通常大写以示区分。

  1. 打开 Codesys 开发环境,进入 POU (Program Organization Unit) 的声明编辑区。
  2. 输入 以下变量定义代码:
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 的硬件实时时钟。

  1. 调用 功能块实例:在程序体中声明 RTC 实例。
  2. 连接 输入输出引脚:将输入 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

注意:不同类型之间(如 TIMEDATE)不能直接运算,必须先转换。


Util 库核心功能块详解

Codesys 内置的 Util 库提供了强大的日期时间处理函数。使用前,需在“库管理器”中添加 Util.library

1. 时间单位拆分:SPLITTIME

当需要将 TIME 类型的总时长转换为“时、分、秒、毫秒”以便在 HMI 上显示时,使用 SPLITTIME 函数。

操作步骤

  1. 声明 用于接收拆分结果的中间变量(DINT 类型)。
  2. 调用 函数并传入 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);

执行后,diHour1diMin1diSec1diMilli500

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 类型支持直接比较运算(>, <, >=, <=)。

逻辑实现步骤

  1. 获取 当前时刻 TOD
  2. 定义 起始和结束时刻常量。
  3. 比较 当前时刻是否在区间内。

代码实现:

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 流程图展示了核心判断过程:

graph TD A["开始"] --> B["读取系统时间 dtNow"] B --> C["提取时刻部分 todNow"] C --> D{"判断: todStart <= todNow <= todEnd ?"} D -- "是 (工作时段)" --> E["执行动作: 开启设备"] D -- "否 (非工作时段)" --> F["执行动作: 关闭设备"] E --> G["结束"] F --> G

3. 毫秒级高精度计时

对于运动控制或高速采集,标准的 TIME 精度(毫秒)可能不足,应使用 LTIMELREAL 进行运算。

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 进行状态监测的简化逻辑图:

graph LR A["输入: bRun信号"] --> B{"上升沿检测?"} B -- "是" --> C["记录: 记录起始时间 dtStart"] B -- "否" --> D{"下降沿检测?"} D -- "是" --> E["计算: 累加本次运行时长"] D -- "否" --> F["维持: 保持当前状态"] E --> G["更新: 更新总时长 tTotalRun"] F --> G C --> G

常见错误与排查指南

在日期时间编程中,初学者常遇到以下几类错误。

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 位)或将时间转换为分钟/小时存储在 DINTLINT 变量中。

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

评论 (0)

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

扫一扫,手机查看

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