新闻详情

新闻详情

首页 / 资讯中心 / 详情

Codesys系统时间读取与时间戳换算全攻略:从基础API到日志实战

发布时间:2026/10/2 1:43:50来源:尧图网络
Codesys系统时间读取与时间戳换算全攻略:从基础API到日志实战
1. 这块“系统时间”到底能干嘛做Codesys开发的工程师早晚都会遇到一个需求程序里得知道“现在是几点”。别小看这个功能设备报错要记录发生时刻产量数据要打时间戳配方切换要看当天是第几个班次就连简单的定时保养提醒底层都得靠系统时间撑腰。我见过不少项目前期嫌麻烦没做时间模块后期设备上了产线甲方要求追溯每一笔数据的精确时间只能返工加代码那叫一个被动。Codesys作为目前使用范围很广的PLC编程环境在时间处理上给了我们好几条路但网上资料大多讲得零散要么只贴一段代码要么只提某个函数。这篇文章就把我在实际项目里用过、踩过、优化过的方法整理成一套完整打法从最基础的时间读取到时间戳换算、跨平台兼容、日志记录实战一次性说透。本文面向的读者主要是正在用Codesys做设备开发、产线改造、数据采集的工程师以及刚接触Codesys、被时间处理绕晕的新手。看完之后你不光知道怎么把当前时间读出来还能理解不同API背后的精度差异、时区陷阱和性能代价在项目选型时少走弯路。2. Codesys里读系统时间的常用姿势2.1 三个系统库三条不同的路Codesys里读系统时间核心API分布在两个常用的系统库中另外还经常搭配一个扩展库使用。这三个库名字看起来差不多实际用途差异很大我先把它们的关系理清楚。第一个是OS库全称Operating System库里面提供的是底层操作系统接口。读时间相关的函数在OS库的SystemTime子模块下主要有GETTIME和GETTIMESTAMP两个。第二个是CAA库全称是Codesys Automation Alliance库里面有个CAA.DTUtil工具集封装了一整套日期时间处理函数把底层的时间戳转换成了我们日常习惯的年月日时分秒格式。这个库在处理时间格式转换、时区调整时特别好用。第三个是SysClock库这个库在部分控制器平台上可用提供了更高精度的时钟读取能力能拿到纳秒级别的时间戳。不过它受平台限制比较大不是所有设备都支持。这三个库的关系有点像三层楼底层是操作系统的时间源中间是Codesys系统库的封装顶层是面向业务的时间工具集。实际开发中GETTIME用得最多但只靠它有时候不够用需要结合另外两个库来补足。2.2 先写一个最基础的时间读取程序在Codesys里读系统时间最经典、最稳定的方式就是用OS库的GETTIME函数。这个函数在大多数Codesys版本里都默认可用不需要额外添加库文件。新建一个POU语言选结构化文本ST声明区和实现区分别这样写PROGRAM PLC_PRG VAR dtNow : DT; todNow : TOD; byDate : BYTE; byMonth : BYTE; byDay : BYTE; byHour : BYTE; byMinute : BYTE; bySecond : BYTE; byWeekDay : BYTE; udiMs : UDINT; // 当前时间对应的毫秒数 END_VAR实现区代码// 读取当前系统时间 dtNow : GETTIME(); // 提取日期中的年 byDate : DATE_TO_BYTE(DT_TO_DATE(dtNow)); // 分解出时分秒 todNow : DT_TO_TOD(dtNow); byHour : TOD_TO_BYTE(todNow);这里有个容易懵的地方dtNow是一个DT类型变量它同时包含日期和时间两部分。如果你想单独取出“几月几号”或者“几点几分”需要先做类型转换。比如提取年、月、日最直观的做法是先把DT转成DATE然后用DATE_TO_WORD、DATE_TO_BYTE这类转换函数。但要注意DT_TO_DATE之后得到的DATE类型是一个无符号整数表示从1970年1月1日算起的天数并不能直接变成“2025年3月18日”。真正想把日期拆成年月日得用CAA库。这也是为什么我说不要只停留在GETTIME后面必须引入CAA.DTUtil。2.3 GETTIME和GETTIMESTAMP到底有什么区别GETTIME和GETTIMESTAMP经常被放在一起讨论其实两者的区别很清晰GETTIME返回的是DT类型精确到秒粒度为1秒GETTIMESTAMP返回的是LTIME类型精确到纳秒粒度更细。但实际项目里大多数人用GETTIME就够用了。LTIME类型内部是一个64位整数单位是纳秒。GETTIMESTAMP在某些平台上调用开销比GETTIME大而且它返回的时间原点在不同平台上有差异有的从1970年开始有的从控制器系统启动开始算。这就导致同一个程序在不同硬件上跑GETTIMESTAMP的结果可能含义不同跨平台移植时特别容易埋坑。我的建议是业务层面需要“某年某月某日几点几分”这种人类可读时间就用GETTIME需要高精度计时、计算一段代码执行耗时、做时间差测量才用GETTIMESTAMP或SysClock库里的高精度时钟。两条路分开走别混用。3. 时间戳换算与CAA库的妙用3.1 为什么推荐CAA.DTUtil前面提到基础库拿到的DT类型想拆成年月日时分秒很别扭。DATE_TO_BYTE这套转换函数看着能用实际上返回值不是我们直觉理解的那个数。举个例子DT_TO_DATE得到的是距离1970年的天数DATE_TO_WORD取出的是完整的天数不是年份。CAA.DTUtil库里专门有一套处理这个问题的函数。它把时间拆成结构体DTStructure里面有Year、Month、Day、Hour、Minute、Second、Millisecond、Weekday这些字段字段语义和人类理解完全一致。在项目里添加CAA库的方法很简单在设备树的“库管理器”上右键选择“添加库”然后在查找框里输入CAA.DTUtil选中加入即可。需要注意CAA库依赖CAA.Types库一般会弹窗提示一起加载直接确认就行。3.2 用DTStructure拆解当前时间加入CAA.DTUtil库之后读时间并拆解字段的代码可以写成这样PROGRAM TimeTest VAR dtNow : DT; dtStruct : DTStructure; strTime : STRING(32); END_VARdtNow : GETTIME(); dtStruct : DT_TO_DTSTRUCT(dtNow); strTime : CONCAT(Current: , CONCAT(DT_TO_STRING_DTSTRUCT_DATE(dtStruct), CONCAT( , DT_TO_STRING_DTSTRUCT_TIME(dtStruct))));这里用到了DT_TO_DTSTRUCT这个转换函数传入DT类型返回结构体。结构体里的Weekday字段表示星期几1代表周一7代表周日和有些地方习惯的0代表周日不同做排班逻辑时注意偏移。DT_TO_STRING_DTSTRUCT_DATE和DT_TO_STRING_DTSTRUCT_TIME这两个函数会分别把日期和时间转成格式化的字符串日期格式是YYYY-MM-DD时间格式是HH:MM:SS。用它们来拼日志文本代码简洁也不容易错。3.3 手动换算时间戳一段可以抄的公式有些场景下你手上只有一个时间戳数值比如从第三方设备Modbus寄存器里读到的时间或者HMI传下来的Unix时间戳这时候需要用公式手动换算。这里把核心算法写出来方便直接拿去用。FUNCTION UnixTsToDT : DT VAR_CONSTANT SECONDS_PER_DAY : DINT : 86400; DAYS_FROM_1970_TO_2000 : DINT : 10957; END_VAR VAR_INPUT unixTs : DINT; // Unix秒级时间戳 END_VAR VAR days : DINT; remainingSec : DINT; END_VARdays : unixTs / SECONDS_PER_DAY; remainingSec : unixTs MOD SECONDS_PER_DAY; // Codesys的DT类型无法直接构造用TOD加上日期 UnixTsToDT : DINT_TO_DT(days - DAYS_FROM_1970_TO_2000) TIME_TO_TOD(TIME_OF_DAY(INT_TO_TIME(remainingSec * 1000)));这串代码背后有个关键知识点Codesys的DT类型本质上是“经偏移的日期”和“当日时间”的组合。DINT_TO_DT接受一个距离1980年的天数偏移不同平台有差异常见基准是1980年1月1日而Unix时间戳的基准是1970年1月1日中间差了一万多天。所以做转换时这个偏移量必须加上或减掉否则结果会错得离谱。时区问题也要提一句你从Windows系统同步时间得到的Unix时间戳通常是UTC时区的如果你要显示本地时间还得根据设备所在时区加上相应的小时偏移。比如北京时间比UTC快8小时就要在转换后加上8小时的TOD时长。4. 时间控制的高阶玩法4.1 做一个掉电不丢失的周期计时器系统时间在设备里有个通用场景做一个“设备累计运行时间”或“间隔保养提醒”。粗暴的做法是程序里用一个变量累加设备断电后变量归零还得额外配置保持型变量很麻烦。利用系统时间读取可以从根本规避这个问题。思路是这样的在启动时记录一个Baseline时间然后后续每次扫描都拿当前时间和Baseline做差差值就是经过的时长。因为系统时间由硬件时钟维护掉电后只要硬件有电池时间就不丢累计运行时间自然也不丢。PROGRAM RuntimeCalc VAR startTime : DT; lastScanTime : DT; runDuration : TIME; bFirstScan : BOOL : TRUE; END_VARIF bFirstScan THEN startTime : GETTIME(); lastScanTime : startTime; bFirstScan : FALSE; ELSE // 跨天的情况直接用DT差值会出错转换成LTIME再算 runDuration : runDuration LTIME_TO_TIME(GETTIMESTAMP() - lastTimeStamp); lastTimeStamp : GETTIMESTAMP(); END_IF这里要特别注意DT类型的减法在某些Codesys版本里不支持直接减即使支持结果也不是TIME类型容易编译报错。稳妥的做法是用GETTIMESTAMP拿LTIME时间戳两个LTIME相减得到的是纳秒间隔再用LTIME_TO_TIME转成TIME。扫描周期如果在1毫秒量级用这个方案精度完全够。4.2 用系统时间做设备定时逻辑生产中常遇到“每天早上8点自动启动预热程序”这类的需求。做这种定时逻辑最可靠的方式是周期性读取系统时间并匹配目标时间点。PROGRAM DailySchedule VAR dtNow : DT; dtStruct : DTStructure; bPreheatDone : BOOL : FALSE; nTargetHour : BYTE : 8; nTargetMinute : BYTE : 0; END_VARdtNow : GETTIME(); dtStruct : DT_TO_DTSTRUCT(dtNow); IF dtStruct.Hour nTargetHour AND dtStruct.Minute nTargetMinute AND bPreheatDone FALSE THEN // 执行预热逻辑比如点加热器 bPreheatDone : TRUE; END_IF // 跨天复位 IF dtStruct.Hour 0 AND dtStruct.Minute 0 THEN bPreheatDone : FALSE; END_IF这段代码看着简单实际跑的时候有个坑扫描周期是毫秒级dtStruct.Minute等于目标值的那一条扫描可能会持续一秒左右bPreheatDone的复位逻辑虽然放在同一程序里但因为扫描周期的原因理论上不会在同一次扫描内同时满足8点0分和0点0分的条件所以业务上安全。但如果把复位条件写成了IF bPreheatDone TRUE就可能出现复位后同一次扫描又触发了开启形成抖动。为了避免这种问题建议加一个死区时间判断或者用边缘检测。4.3 闰年和跨年逻辑看似远其实近处理时间逻辑时千万别忽略跨年和闰年。比如你写了一个“明年1月1日零点归档上年数据”的功能如果时间比较逻辑写的是“当前时间大于某个固定DT常量”那闰年的时候2月29日之后程序可能直接失效。CAA库里有现成的函数可以判断是否是闰年比如CAA.DTUtil.IsLeapYear或者类似命名。我个人的习惯是所有涉及年度、月度轮回的定时逻辑必须把“月份日期时间”分开比较而不是把整个DT直接比大小。直接比大小的写法最容易在1月1日、12月31日、闰年2月29日这些边界点出问题。另外Codesys里DT类型的取值范围一般是1980年到2100年左右如果你的系统时间被用户手动设置成了1970年或者2400年那DT转换函数就可能报错。稳妥做法是在启动初始化时校验一下系统时间是否在一个合理范围内。5. 配合生态组件让时间价值倍增5.1 日志记录时间戳是数据的身份证“系统时间”最大的价值之一就是给数据打时间戳。我见过很多产线项目设备偶尔报错但日志里只有错误码没有时间追溯起来全靠猜。给日志加上时间戳之后问题定位效率翻倍。在Codesys里做带时间的SD卡日志核心代码在前面已经铺垫好了。这里给一个可以直接落地的完整示例它会每分钟写一条记录到文件PROGRAM FileLogger VAR dtNow : DT; dtStruct : DTStructure; fileWriter : CAA.FileWriter; // 使用CAA.File库 strLine : STRING(128); udiLineCount : UDINT; bInitDone : BOOL : FALSE; lastLogMinute : BYTE : 255; END_VARdtNow : GETTIME(); dtStruct : DT_TO_DTSTRUCT(dtNow); // 初始化写文件 IF NOT bInitDone THEN fileWriter.Open(FileName : Log001.txt, Mode : CAA.FileWriter.Mode.APPEND); bInitDone : TRUE; END_IF // 每分钟写一行 IF dtStruct.Minute lastLogMinute THEN lastLogMinute : dtStruct.Minute; udiLineCount : udiLineCount 1; strLine : CONCAT(CONCAT(DT_TO_STRING_DTSTRUCT_DATE(dtStruct), ), DT_TO_STRING_DTSTRUCT_TIME(dtStruct)); fileWriter.WriteLine(strLine); END_IF这段代码里的CAA.FileWriter来自CAA.File库很多Codesys安装包自带找不到的话在库管理工具里搜索添加即可。如果文件写入失败硬件上赶紧检查SD卡是否挂载软件上要看路径权限。5.2 数据采集与上位机联动时间戳同步是保命项现在很多项目用PLC-Recorder、数据库中间件或者第三方库采集Codesys变量把数据实时写入Mysql这类数据库。这种跨系统数据链路里PLC的本地时间戳是排查数据错乱的关键依据。举个例子某产线项目用了与Codesys配套的变量采集工具采集周期设成了100毫秒但现场网络抖动导致数据到达服务器的时间晚了几秒。如果每条数据都带上PLC侧的UTC时间戳服务器就能通过时间戳判断实际发生时间不会因为网络延迟而把数据记错到别的时段。没有时间戳这种问题排查起来极其痛苦。我在项目里的做法是在Codesys程序里定义一个全局变量结构体专门存放带时间戳的数据快照。采集工具直接读取这个结构体既有业务数据又有时间信息一条链路全部搞定。5.3 配合符号配置与配方管理时间戳还有一个应用场景是配方管理和符号配置导出。产线切换配方时需要记录切换发生的时间方便后续追溯谁在什么时候选了什么配方。另外有些项目要求把PLC里的符号表定期导出成XML文件如果文件名或文件内容里带上系统时间就能自动生成带版本时间戳的归档文件省去手工命名。这种需求用上面讲的GETTIME加DT_TO_STRING_DTSTRUCT_DATE组合就能实现。唯一的坑是文件名不能有冒号Windows文件系统里冒号是非法字符而时间格式里天然包含冒号。我习惯把时间串里的冒号替换成短线或者直接去掉比如“20250318_083000”这种格式既简练又合法。6. 我踩过的几个时间相关的坑6.1 硬件平台对时间API的支持差异Codesys是一个跨平台运行环境同样一份代码在Windows软PLC、Linux工控机、传统硬PLC上跑时间API的表现可能不一样。有些控制器平台没有后备电池或者超级电容断电重启之后系统时间会回到初始值比如2000年1月1日。如果你的程序在启动时立刻调用了时间比较逻辑就可能触发不该触发的分支。我处理这个问题的方式是在程序启动时读一次时间判断年份是否小于合理值如果时间是“初始值”就先把相关业务功能锁住等待上层系统做时间同步之后再开放。时间同步的方法一般是控制器里的NTP客户端功能或者在上位机组态软件里做校时。6.2 扫描周期对时间精度的影响PLC是循环扫描执行的程序读到的时间其实是“这次扫描开始那一刻”的时间。如果你的扫描周期是10毫秒那时间精度最多也就是10毫秒左右不要指望能测出微秒级的事件间隔。如果你想精确测量两个输入信号发生的时间差光靠GETTIME是不够的至少要用GETTIMESTAMP的纳秒精度如果控制器支持更底层的高速计数或输入中断直接在中断里打时间戳才是正解。把时间打点放到主扫描循环里测出来的时间差会被扫描周期的抖动严重污染数据不可信。6.3 字符串转换引发的效率问题有些工程师习惯在扫描循环里反复做DT_TO_STRING这类字符串转换比如每个周期都拼一条时间字符串用于HMI显示。这样做编译能过运行也可能正常但会白白消耗大量CPU时间因为字符串拼接在PLC里是重操作尤其在老式硬PLC上扫描周期会被拖慢好几毫秒。我自己的习惯是能不用字符串就不用需要显示时间时直接把DT变量映射到HMI让HMI侧做格式化非得生成字符串的场景降低采样频率比如只在时间变化的那一秒拼接一次而不是每一扫描周期都拼。6.4 2038年问题在Codesys里存在吗经典Unix时间是32位有符号整数最大值到2038年。Codesys的DT类型基准和Unix时间戳不同我用的32位设备里部分时间函数确实存在类似的溢出风险。虽然现在市面上的控制器很多已经用了64位内核但老设备、嵌入式小控制器仍有可能存在这个问题。如果你写的程序打算长期运行在老旧设备上建议评估一下目标平台对时间上限的支持情况。尤其是那些用GENERIC类型做时间运算的库可能内部用了32位整数。条件允许的情况下尽量用标准库函数处理时间不要自己拿DINT去存绝对时间戳。7. 写代码之外的几条经验最后分享几条我实际做项目攒下来的经验。第一项目初期就要把时间同步方案定下来。控制器网络里没有NTP服务器的话至少要在设备部署时设计一个校时入口比如HMI上放一个“时间校准”按钮或者允许上位机通过Modbus往PLC写时间。等设备上了现场再补成本翻倍。第二所有时间相关变量的命名里带上时区信息。dtNowLocal和dtNowUTC一眼就能看出区别避免后续同事接手时把UTC时间当本地时间用算错几小时。第三日志记录里的时间格式尽量统一使用ISO标准的“YYYY-MM-DD HH:MM:SS”不要用“2025/3/8 8:30”这种格式因为不同系统解析习惯不同跨系统对接时容易出歧义。第四如果项目要求高精度时间同步硬件选型阶段就要确认控制器是否支持PTP或IEEE 1588协议普通NTP的精度在局域网里能做到几十毫秒就不错了但视觉检测、运动控制这类场景对时间同步精度的要求更高PLC侧的普通时间API并不适合做这种高精度事件标记。Codesys的“系统时间”功能看着简单真要用好得把API选型、时区换算、平台差异、字符串开销、同步机制这些问题全部考虑到。希望这篇文章能把你在时间处理上遇到的坎提前填平让时间成为你项目里一个可靠的基础设施。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

汽车零部件目标检测数据集:VOC转YOLO与训练避坑指南 2026/10/2 2:42:56

汽车零部件目标检测数据集:VOC转YOLO与训练避坑指南

简介:面向汽车零部件目标检测的VOCYOLO格式数据集,收录约1万张真实零部件图像及对应标注,覆盖50个常用类别,包括空气压缩机、交流发电机、制动卡钳、刹车盘、燃油喷射器、大灯等具体部件,同时涵盖发动机、制动、电气等…

阅读更多 →
OpenCV人脸美颜实战:磨皮、美白与瘦脸的原理与参数调优 2026/10/2 2:42:55

OpenCV人脸美颜实战:磨皮、美白与瘦脸的原理与参数调优

简介:面向希望入门OpenCV图像处理的开发者,资源是一套基于Visual Studio的简易人脸美颜程序完整工程。程序以Haar级联分类器定位人脸,通过图像平滑、膨胀、色彩校正等步骤实现磨皮、眼部放大、美白等美颜效果,每个环节都在源码中有…

阅读更多 →
基于Python的BP神经网络从零实现与参数调优实战 2026/10/2 2:42:55

基于Python的BP神经网络从零实现与参数调优实战

简介:面向希望快速上手 BP 神经网络实战的 Python 学习者,资源包用完整代码配齐训练数据,串联了从环境搭建、数据预处理、网络构建、前向/反向传播到模型评估与案例实践的关键环节,可直接对照运行并观察结果。RAR压缩包内共 19 个…

阅读更多 →
Java毕业设计物资管理系统开发指南:从技术选型到答辩 2026/10/2 2:42:55

Java毕业设计物资管理系统开发指南:从技术选型到答辩

简介:这套物资管理系统毕业设计资源,面向Java方向高校学生,提供从系统分析、编码实现到论文答辩的完整配套材料。压缩包共10个文件,包含论文文档、源代码、数据库脚本、系统界面JPG截图,以及两个用于部署和演示讲解的U…

阅读更多 →
大模型工作流选型与部署:本地推理与API接入的实战路径 2026/10/2 2:42:55

大模型工作流选型与部署:本地推理与API接入的实战路径

如果你最近在开发者社区搜索过大模型相关的内容,大概率会看到一组高频关键词:Qwen3.8-27B、GLM-5.3、DeepSeek、Grok-4.6、Fable-5、Kimi-K3。更有意思的是,很多人的搜索词并不是“哪个模型跑分最高”,而是“Qwen3.8-27B 在 4060 …

阅读更多 →
大模型评测榜单怎么看?从选型到本地部署的实战指南 2026/10/2 2:42:49

大模型评测榜单怎么看?从选型到本地部署的实战指南

模型圈的“评测季”又来了。最近 LMArena 官方放出了一轮比较新的模型对比结果,涉及 Qwen3.8-27B、GLM-5.3、DeepSeek、Grok-4.6、Fable-5、Kimi-K3 这几个名字。很多读者在后台问:这类榜单到底该怎么看?是不是排名高的模型就一定适合我的业务…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉