本帖最后由 DKENNY 于 2024-5-30 11:06 编辑
#申请原创# @21小跑堂
前言 在嵌入式系统开发中,时间管理对于许多应用程序至关重要。无论是日程安排、数据记录还是事件同步,精确的时间记录都是不可或缺的。在Mbed OS中,RTC(Real Time Clock,实时时钟)起着至关重要的作用,为开发人员提供了可靠且准确的时间跟踪功能。RTC不仅仅是一个简单的时钟,它是系统中的重要组成部分,用于记录时间、定时任务以及事件的时间戳。通过Mbed OS的RTC功能,开发人员可以轻松地处理时间相关的任务,确保系统在各种应用场景下都能准确运行。
1、RTC原理及介绍 RTC(Real Time Clock,实时时钟)是一种特殊的定时器,在设备掉电后仍然能够继续运行。与普通定时器相比,RTC相对简单,主要用于计时和触发中断。它的特殊之处在于,即使主电源断开,它也可以通过连接锂电池来继续工作,因此在整个APM32芯片中具有特殊的作用。 RTC的主要特性如下: 计时功能:RTC具有32位计数器,只能向上计数。它可以使用高速外部时钟的128分频、低速内部时钟LSI以及低速外部时钟LSE作为时钟源。 掉电继续运行:通过连接锂电池到APM32的RTC备份发卡(通过VBAT引脚供电),即使主电源断开,RTC仍然可以工作。RTC中的数据保存在备份域中,包括RTC模块寄存器以及42个16位寄存器,这些寄存器可以在主电源和备份电源断电时保持数据,不会被复位。 时钟源选择:由于掉电后高速外部时钟和低速内部时钟会受到影响,所以RTC通常使用低速外部时钟LSE作为时钟源,频率通常为32.768KHz。 总的来说,RTC在APM32中是一个简单但功能强大的外设,不仅具有计时和中断触发功能,还能在掉电情况下继续运行,保留重要数据。
如下是APM32F4系列的RTC外设框图。
运行流程: 当APM32的主电源VDD正常工作时,RTC可以触发三种事件:RTC_Second(每秒触发的中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断)。但是,通过结构图可以看出,定时器溢出事件无法配置为中断触发。如果APM32处于待机状态,它可以被闹钟事件或外部唤醒事件(WKUP事件,不属于RTC,属于EXTI模块)唤醒。闹钟事件会在RTC计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发。
在备份域中,所有寄存器都是16位的,包括控制RTC的相关寄存器。在配置RTC模块时钟时,通常使用输入频率为32768Hz的RTCCLK,经过32768分频得到实际驱动计数器的时钟TR_CLK=RTCCLK/32768=1Hz,这意味着计时周期为1秒,计时器在1Hz的驱动下计数,即每秒计数器RTC_CNT的值加1。
更通俗易懂的解释:当APM32电源正常时,RTC相当于是一个时钟守护者,可以在每秒发出提醒、显示时间的效果。如果要把APM32从休眠状态唤醒,可以通过设置闹钟或者外部唤醒按钮来实现。就像闹钟会在设定的时间响起一样,RTC在特定数值时也可以触发操作。备份寄存器在这里起到储存关键数据的作用,类似于备用电源。配置RTC时钟就像是调整时钟上的秒针,确保计时器能够准确计数。
举个例子:想象你把APM32当做是一台记时器。每当总秒数达到60秒时,记时器会溢出,这个事件无法被配置为提醒。如果你希望在记时器显示特定分钟时提醒你,就相当于设置了一个闹钟。当记时器显示的分钟数等于你设定的值时,闹钟就会响起。同时,备份寄存器就像是一个盒子,可以在关掉电源后保存你的设置,让记时器不会忘记你的设定。配置RTC时钟的过程就像是调整记时器的时钟,以确保时间显示的准确性。
2、mbedos的RTC Mbed OS(Mbed Operating System)是一款由Arm开发的开源嵌入式操作系统,专为物联网(IoT)设备和嵌入式系统设计。其 RTC(Real-Time Clock,实时时钟)模块提供了在嵌入式系统中管理时间和日期的功能。下面是相关的API说明。
Mbedos的RTC API说明 函数名 | | | 提供基于微控制器实时时钟 (RTC) 设置和读取当前时间的机制,以及一些标准 C 操作和格式化函数。
参数说明:
t 自 1970 年 1 月 1 日以来的秒数(UNIX 时间戳) | void attach_rtc (time_t(*read_rtc)(void), void(*write_rtc)(time_t), void(*init_rtc)(void), int(*isenabled_rtc)(void)) | 连接一个外部 RTC,用于 C 时间功能。
参数说明: read_rtc 指向返回当前 UNIX 时间戳的函数的指针 write_rtc 指向设置当前 UNIX 时间戳的函数的指针,可以为 NULL init_rtc 指向初始化 RTC 的函数的指针,可以为 NULL isenabled_rtc 指向返回 RTC 是否已启用的函数的指针,可以为 NULL | int gettimeofday (struct timeval *tv, void *tz) | 标准库重新定位,获取时间。
参数说明:
tv 结构包含 time_t 秒和 useconds_t 微秒。由于 RTC 实现针对不同的目标,因此仅使用秒部分。 tz 标准中已弃用:此参数保留在旧代码中。它不再使用。 | int settimeofday (const struct timeval *tv, const struct timezone *tz) | 标准库重新定位,设置时间。
参数说明: tv 结构包含 time_t 秒和 useconds_t 微秒。由于 RTC 实现针对不同的目标,因此仅使用秒部分。 tz 标准中已弃用:此参数保留在旧代码中。它不再使用。 |
3、UNIX时间戳 在使用RTC外设之前,我们需要了解UNIX时间戳的概念。 想象一下,如果我们将RTC_CNT计数器从现在开始置为0,然后每秒加1,那么这个计数器什么时候会溢出呢?由于RTC_CNT是一个32位寄存器,它可以存储的最大值是(2的32次方-1),即大约需要232秒才会溢出,这相当于136年的时间:
N = (2^32)/365/24/60/60 ≈136年
举个例子,假设某时刻读取到计数器的数值为X = 60 x 60 x 24x 2,也就是两天的秒数。如果我们知道这个计数器在2011年1月1日的0时0分0秒被置为0,通过这个相对时间数值X,我们可以计算出这个时刻实际对应的是2011年1月3日的0时0分0秒。但是计数器会在(2011+136)年左右溢出,意味着在未来的(2011+136)年,如果我们仍然使用这个计数器来提供时间,就会遇到问题。
在操作系统中,通常会使用时间戳和计时元年来计算当前时间。大多数系统都采用UNIX时间戳和UNIX计时元年作为共同的参考标准。UNIX计时元年被设定为1970年1月1日0时0分0秒格林尼治时间,这主要是为了纪念UNIX系统的诞生。UNIX时间戳表示当前时间与UNIX计时元年之间经过的秒数。虽然UNIX时间戳主要用于表示当前时间或与计算机相关的时间,考虑到所有文件不可能在1970年前创建,因此UNIX时间戳很少用于表示1970年之前的时间。 在这种时间计数系统中,使用有符号的32位整数来存储UNIX时间戳,相比之前的例子,实际可用的计数位少了一位。这意味着UNIX计时元年相对较早,而这种计时机制将在2038年1月19日03时14分07秒发生溢出,这个日期并不遥远。由于UNIX时间戳被广泛应用于各种系统中,溢出可能引起严重问题。因此,如果我们在设计长期使用的设备时,就需要特别留意这个潜在问题。
4、RTC例程设计
/*
* Copyright (c) 2006-2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
int main()
{
// 设置时间戳
set_time(1716973801);
while (true) {
time_t seconds = time(NULL);
printf("Time as seconds since January 1, 1970 = %u\n", (unsigned int)seconds);
printf("Time as a basic string = %s", ctime(&seconds));
char buffer[32];
strftime(buffer, 32, "%I:%M %p\n", localtime(&seconds));
printf("Time as a custom formatted string = %s", buffer);
ThisThread::sleep_for(1s);
}
}
这段代码是一个基于Mbed OS的简单程序,用于获取当前时间并以不同的格式打印出来。
1. 设置时间戳: set_time(1716973801); 这行代码设置系统的时间戳为 1716973801,这个时间戳代表了从1970年1月1日0时0分0秒开始计算的秒数。
2. 获取当前时间: time_t seconds = time(NULL); 使用 time(NULL) 函数获取当前时间的秒数,并将其存储在 seconds 变量中。
3. 打印时间: printf("Time as seconds since January 1, 1970 = %u\n", (unsigned int)seconds); 这行代码以整数形式打印从1970年1月1日0时0分0秒开始到当前时间的秒数。
printf("Time as a basic string = %s", ctime(&seconds)); 这行代码以基本的字符串格式打印当前时间,使用了 ctime 函数将时间转换为字符串格式。
char buffer[32]; strftime(buffer, 32, "%I:%M %p\n", localtime(&seconds)); printf("Time as a custom formatted string = %s", buffer); 这段代码以自定义格式打印当前时间。首先,使用 localtime 函数将时间转换为本地时间,然后使用 strftime 函数按照自定义格式 "%I:%M %p\n" 格式化时间,并将格式化后的时间存储在 buffer 数组中,最后通过 printf 函数打印出来。
note:strftime 函数介绍:https://www.ibm.com/docs/zh/i/7.5?topic=functions-strftime-convert-datetime-string
4. 延时: ThisThread::sleep_for(1s); 这行代码使程序暂停1秒钟,然后再次执行循环中的代码,以模拟每隔1秒钟获取一次时间并打印出来的效果。
5、获取系统时间戳 系统当前时间戳的获取可以参考如下的代码。 //获取系统当前时间戳
#include <iostream>
#include <chrono>
#include <ctime>
const std::time_t base_time = 0;
int main()
{
// 获取当前系统的时间
auto now = std::chrono::system_clock::now();
// 将当前时间点转换为time_t格式
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
// 计算时间戳
std::time_t timestamp = now_time - base_time;
printf("%u", timestamp);
}
用一根usb线把pc端和APM32F407IG-Tiny板连接后,串口就会一直打印当前系统的时间了。
RTC不仅仅是一个简单的时钟,它是嵌入式系统中的关键组件,为开发人员提供了准确的时间跟踪功能,帮助应用程序处理时间相关的任务。通过合理利用RTC,开发人员可以确保系统在各种应用场景下都能准确运行,并实现精准的时间记录和管理。 以上就是本次分享的全部内容,欢迎各位讨论交流。
附件(本帖中所使用的源码):
source.zip
(941 Bytes)
|
详细介绍RTC的原理与应用,并在APM32平台上使用mbed os操作系统完成程序设计。文章整体结构完整,思路清晰,整体感觉较佳。