RTC 及 alarm 使用例程说明

中文|English

简介

本例程主要介绍了如何在 Titan Board 上使用 RA8 系列 MCU 的 RTC 模块,结合 RT-Thread RTC 驱动框架,实现基本的实时时钟功能及闹钟功能。RTC 可以提供精确的实时时间,它可以用于产生年、月、日、时、分、秒等信息。目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。有些时钟芯片为了在主电源掉电时还可以工作,会外加电池供电,使时间信息一直保持有效。

RT-Thread 的 RTC 设备为操作系统的时间系统提供了基础服务。面对越来越多的 IoT 场景,RTC 已经成为产品的标配,甚至在诸如 SSL 的安全传输过程中,RTC 已经成为不可或缺的部分。

RTC(实时时钟)简介

1. 概述

RTC(Real-Time Clock) 是一种用于 计量和跟踪实际时间(年、月、日、时、分、秒) 的电子模块或芯片。它通常内置在 微控制器(MCU) 或作为独立芯片存在,用于提供 系统时间定时唤醒定时事件触发 等功能。

RTC 的核心特点是 低功耗、长时间稳定运行,即使系统主电源断电,也可通过备用电源(如电池或超级电容)继续计时。

2. 工作原理

RTC 基本上是一个 低功耗振荡器 + 计数器 系统:

  1. 时钟源

    • RTC 通常使用 32.768 kHz 石英晶振(1 Hz = 2^15 周期)

    • 提供稳定时基

  2. 分频计数

    • 将晶振信号分频得到 1 Hz 的秒脉冲

    • 累计计数生成分钟、小时、天、月、年

  3. 寄存器存储

    • RTC 内部寄存器保存当前时间、日期、闹钟设置等

  4. 电源冗余

    • 内部电池或超级电容供电,使 RTC 在主电源断电时继续运行

3. RTC 类型

  1. 内部 RTC

    • 集成在 MCU 内部

    • 优点:节省芯片数量,成本低

    • 缺点:晶振精度受 PCB 布线、温度影响

  2. 外部 RTC 芯片

    • 独立芯片,如 DS3231、PCF8563

    • 优点:高精度、可用 I²C/SPI 接口连接 MCU

    • 缺点:增加 PCB 封装面积和成本

4. 关键参数

参数

描述

振荡器频率

通常 32.768 kHz,低功耗稳定

时间精度

ppm / 秒/天,决定时钟漂移

电源电压

1.8~5V,支持备用电源

功耗

1~5 µA(低功耗模式)

接口类型

I²C、SPI 或 MCU 内部总线

功能扩展

闹钟输出、方波输出、温度补偿、定时唤醒

5. RTC 功能

  1. 实时时钟

    • 提供当前时间和日期

  2. 闹钟功能(Alarm)

    • 可设置指定时间触发中断或唤醒 MCU

  3. 定时唤醒(Wake-up Timer)

    • MCU 进入低功耗模式时,可由 RTC 定时唤醒

  4. 温度补偿(部分高精度芯片,如 DS3231)

    • 自动校正晶振频率,减少漂移

  5. 方波输出(Square Wave Output)

    • 提供固定频率脉冲,用于驱动 MCU 定时器或其他模块

6. RTC 应用场景

  • 嵌入式设备

    • MCU 实时时间追踪,事件记录

  • 低功耗物联网设备

    • RTC 唤醒 MCU 进行周期性数据采集

  • 手表与智能穿戴设备

    • 提供精准计时功能

  • 数据记录仪与工业控制

    • 时间戳、日志记录

  • 汽车电子

    • 行车记录仪、车载娱乐系统计时

RA8 系列 RTC 模块概述

RA8 系列 MCU 内置高度灵活的 实时时钟(RTC) 外设,适用于低功耗、高精度的嵌入式时间管理应用。

1. 总体特性

  • 时间管理:支持秒、分、时、日、月、年及星期。

  • 时钟源

    • 外部低速晶振(通常为 32.768 kHz),高精度。

    • 内部低速 RC 振荡器,低功耗,精度稍低。

  • 闹钟功能:支持单次和重复闹钟,可生成中断。

  • 定时器:子秒定时器、周期性唤醒定时器。

  • 防篡改检测(可选):可在配置的引脚检测外部篡改事件。

  • 低功耗操作:RTC 可在待机或睡眠模式下运行,确保 MCU 断电时仍保持时间。

2. RTC 模块架构

RA8 RTC 模块包含以下功能模块:

  1. 时间计数器(Calendar Counter)

    • 维护当前时间和日期。

    • 支持 24 小时或 12 小时制。

    • 自动处理闰年调整。

  2. 闹钟与比较单元

    • 将当前时间与预设闹钟值比较。

    • 匹配时生成中断。

    • 支持重复闹钟(每日、每小时等)。

  3. 时钟源与分频器

    • 将外部 32.768 kHz 或内部 RC 振荡器分频生成 1 Hz 时基。

    • 可选择时钟精度与功耗的平衡。

  4. 中断控制器

    • 支持闹钟中断、周期中断、溢出中断。

    • 可配置中断屏蔽、使能及状态标志。

  5. 防篡改及备份寄存器(可选)

    • 非易失性备份寄存器,可存储断电保留数据。

    • 防篡改引脚可触发中断,实现安全应用。

3. 关键特性

特性

描述

时间分辨率

秒级,可选子秒计数

时间范围

年、月、日、时、分、秒

闹钟

可配置闹钟并触发中断

时钟源

外部 32.768 kHz 晶振,内部低速 RC 振荡器

功耗

超低功耗,支持待机/睡眠模式

中断

闹钟、周期性、溢出

备份寄存器

非易失性寄存器,可在断电期间保存数据

防篡改检测(可选)

外部引脚事件可触发中断

4. 工作原理

  1. 选择时钟源:外部晶振或内部 RC 振荡器。

  2. 设置时间:将初始时间和日期写入日历计数器。

  3. 设置闹钟:配置闹钟时间并使能闹钟中断。

  4. 中断处理:MCU 可通过 RTC 中断标志响应闹钟或周期性事件。

  5. 电源管理:RTC 在低功耗模式下继续计时,确保时间准确。

RT-Thread RTC 驱动框架

RT-Thread RTC(Real-Time Clock)框架 是 RT-Thread 设备驱动层提供的统一接口,用于管理各类 MCU 的实时时钟硬件模块。该框架将底层 RTC 硬件抽象为标准化设备接口,使应用层能够通过统一的 API 进行日期时间管理、时间戳获取及闹钟(Alarm)设置等功能,实现跨平台的时间管理能力。

1. 设备模型

在 RT-Thread 中,RTC 被作为 设备对象struct rt_device 的子类,类型为 RT_Device_Class_RTC)进行管理。开发者无需操作底层寄存器,只需通过标准接口即可完成时间设置、读取和闹钟控制等操作。

2. 操作接口

应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问 RTC 设备,相关接口如下所示:

  • 查找 RTC 设备

rt_device_t rt_device_find(const char* name);
  • 打开 RTC 设备

rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
  • 设置日期

rt_err_t set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day);
  • 设置时间

rt_err_t set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second);
  • 获取当前时间

可通过 C 标准库函数获取当前时间戳(UTC 时间):

time_t time(time_t *t);

3. Alarm 功能

RTC 框架支持 闹钟(Alarm)功能,可用于定时唤醒、任务提醒或周期事件。 应用程序可通过 RT-Thread 提供的 alarm 组件接口进行操作:

  • 创建闹钟

rt_alarm_t rt_alarm_create(rt_alarm_callback_t callback, struct rt_alarm_setup *setup);

rt_alarm_setup 结构体定义如下:

struct rt_alarm_setup
{
    rt_uint32_t flag;                /* alarm flag */
    struct tm wktime;                /* alarm trigger time */
};

闹钟模式可通过 flag 字段设置:

#define RT_ALARM_ONESHOT       0x000   /* 仅触发一次 */
#define RT_ALARM_DAILY         0x100   /* 每天触发 */
#define RT_ALARM_WEEKLY        0x200   /* 每周特定日触发 */
#define RT_ALARM_MONTHLY       0x400   /* 每月特定日触发 */
#define RT_ALARM_YEARLY        0x800   /* 每年特定日期触发 */
#define RT_ALARM_HOUR          0x1000  /* 每小时触发 */
#define RT_ALARM_MINUTE        0x2000  /* 每分钟触发 */
#define RT_ALARM_SECOND        0x4000  /* 每秒触发 */
  • 启动闹钟

rt_err_t rt_alarm_start(rt_alarm_t alarm);
  • 停止闹钟

rt_err_t rt_alarm_stop(rt_alarm_t alarm);
  • 删除闹钟

rt_err_t rt_alarm_delete(rt_alarm_t alarm);
  • 控制闹钟

rt_err_t rt_alarm_control(rt_alarm_t alarm, int cmd, void *arg);

4. 框架特点

  • 接口统一:统一的设备接口屏蔽硬件差异,简化时间管理操作。

  • 跨平台支持:适配多种 MCU 的 RTC 模块,应用可无缝移植。

  • 支持闹钟机制:支持一次性、周期性、多级别触发模式。

  • 系统时间同步:与 POSIX 时间接口兼容,支持标准时间戳管理。

  • 低功耗特性:支持深度睡眠唤醒、掉电保持等特性。

参考RT-Thread RTC 设备

硬件说明

本例程使用的RTC 设备依赖于LOCO 时钟,此外无需过多连接。

FSP 配置

  • 打开 FSP,选择对应的工程文件下的 configuration.xml,新增 RTC Stack;

image-20250728124756259

  • 下面进行 RTC 参数的配置,设置 rtc stack name 为 g_rtc,设置 RTC 中断回调函数为 rtc_callback,并配置中断回调优先级;

image-20250728125146813

RT-Thread Settings 配置

  • 打开 RT-Thread Settings,找到硬件选项,使能 RTC;

image-20250814164856046

  • 接下来我们配置 RTC,首先需要使能 RT-Thread 的 RTC 设备框架,同时使能 alarm 功能;

image-20250814164926021

示例代码说明

本例程的源码位于/src/hal_entry.c

    rt_err_t ret = RT_EOK;
    time_t now;
    rt_device_t device = RT_NULL;

    device = rt_device_find(RTC_NAME);
    if (!device)
    {
      rt_kprintf("find %s failed!\n", RTC_NAME);
    }

    if(rt_device_open(device, 0) != RT_EOK)
    {
      rt_kprintf("open %s failed!\n", RTC_NAME);
    }

    /* 设置日期 */
    ret = set_date(2025, 8, 1);
    rt_kprintf("set RTC date to 2025-8-1\n");
    if (ret != RT_EOK)
    {
        rt_kprintf("set RTC date failed\n");
    }

    /* 设置时间 */
    ret = set_time(15, 00, 00);
    if (ret != RT_EOK)
    {
        rt_kprintf("set RTC time failed\n");
    }

    /* 延时3秒 */
    rt_thread_mdelay(3000);

    /* 获取时间 */
    get_timestamp(&now);
    rt_kprintf("now: %.*s", 25, ctime(&now));

下面代码可创建一个 RTC 闹钟,然后设置 1 秒后唤醒,最后把该函数导入 msh 命令行中。

void user_alarm_callback(rt_alarm_t alarm, time_t timestamp)
{
    LOG_I("user alarm callback function.");
}

void alarm_sample(void)
{
    rt_device_t dev = rt_device_find("rtc");
    struct rt_alarm_setup setup;
    struct rt_alarm * alarm = RT_NULL;
    static time_t now;
    struct tm p_tm;

    if (alarm != RT_NULL)
        return;

    /* 获取当前时间戳,并把下一秒时间设置为闹钟时间 */
    now = get_timestamp(NULL) + 1;
    gmtime_r(&now,&p_tm);

    setup.flag = RT_ALARM_SECOND;
    setup.wktime.tm_year = p_tm.tm_year;
    setup.wktime.tm_mon = p_tm.tm_mon;
    setup.wktime.tm_mday = p_tm.tm_mday;
    setup.wktime.tm_wday = p_tm.tm_wday;
    setup.wktime.tm_hour = p_tm.tm_hour;
    setup.wktime.tm_min = p_tm.tm_min;
    setup.wktime.tm_sec = p_tm.tm_sec;

    alarm = rt_alarm_create(user_alarm_callback, &setup);
    if(RT_NULL != alarm)
    {
        rt_alarm_start(alarm);
    }
}
/* export msh cmd */
MSH_CMD_EXPORT(alarm_sample,alarm sample);

编译&下载

  • RT-Thread Studio:在 RT-Thread Studio 的包管理器中下载 Titan Board 资源包,然后创建新工程,执行编译。

编译完成后,将开发板的 USB-DBG 接口与 PC 机连接,然后将固件下载至开发板。

运行效果

按下复位按键重启开发板,输入 alarm_sample 命令运行闹钟示例,可以看到板子上会打印如下信息:

image-20250814165115208

拓展:RTC 掉电运行

当板卡主电源断开后,如需让 RTC 掉电继续运行,需要通过 VBAT 引脚接上外部供电,需要注意的是,当 VBAT 给 RTC 供电时,只会继续 Calendar 计数器的计时,其他 RTC 的功能无法使用,下面是一个参考代码:

ps: rtc 如果打算掉电运行,那么应该确保 rtc_init 只初始化一次,后续可通过 rtc_get 获取时间

#define RTC_YEAR_SET 2025
#define RTC_MON_SET  8
#define RTC_MDAY_SET 5

#define RTC_WDAY_SET (RTC_YEAR_SET-2000 \
                  + ((RTC_YEAR_SET-2000)/4) \
                  - 35 + (26*(RTC_MON_SET+1))/10 \
                  + RTC_MDAY_SET -1 )%7

#define RTC_HOUR_SET 12
#define RTC_SEC_SET 0
#define RTC_MIN_SET 0

void rtc_get()
{
    rtc_time_t get_time;
    R_RTC_CalendarTimeGet(g_rtc.p_ctrl, &get_time);
    rt_kprintf ("%d-%d-%d-%d:%d:%d\r\n", get_time.tm_year + 1900, get_time.tm_mon + 1, get_time.tm_mday,
             get_time.tm_hour, get_time.tm_min, get_time.tm_sec);
}
MSH_CMD_EXPORT(rtc_get, rtc_get);

void rtc_init(void)
{
   rtc_time_t set_time =
   {
     .tm_min = RTC_MIN_SET,
     .tm_hour = RTC_HOUR_SET,
     .tm_mday = RTC_MDAY_SET,
     .tm_wday = RTC_WDAY_SET,
     .tm_mon = RTC_MON_SET - 1,
     .tm_year = RTC_YEAR_SET-1900,
   };

   R_RTC_Open (g_rtc.p_ctrl, g_rtc.p_cfg);

   R_RTC_CalendarTimeSet (g_rtc.p_ctrl, &set_time);
}
MSH_CMD_EXPORT(rtc_init, rtc_init);

FSP配置:

image-20250902112150982

image-20250902112213084