RT-Thread 实时时钟 (RTC) 设备驱动开发指南
实时时钟 (RTC) 设备
RTC 概述
实时时钟(RTC)模块是嵌入式系统中用于维持精确时间的核心组件。它通常基于高精度晶体振荡器,能够提供年、月、日、时、分、秒等时间信息。许多 RTC 芯片还配备备用电池,以确保在主电源断电时仍能继续计时,从而保证时间信息的连续性。
在 RT-Thread 操作系统中,RTC 设备为整个系统的时间管理提供了基础服务。随着物联网(IoT)应用的普及,RTC 已成为许多产品的标准配置。在安全通信协议(如 SSL/TLS)中,精确的时间同步也是不可或缺的一环。
访问 RTC 设备
在启用 RTC 设备框架和相应驱动后,开发者可以通过包含标准头文件 <sys/time.h> 来使用 C 标准库提供的时间操作函数,例如 time()、ctime()、localtime() 等。这些函数的使用方式与在 Unix 或 Windows 系统中一致,是推荐的操作 RTC 的方式。
此外,RT-Thread 也提供了便捷的专用函数 rtc_set_date() 和 rtc_set_time() 用于快速设置日期和时间。然而,这些函数在处理特殊时间点(如闰秒或年末时刻)时可能存在局限性,因此通常仅建议用于硬件测试或简单演示,不建议在核心业务逻辑中使用。
设置日期和时间
以下是用于设置 RTC 设备日期和时间的函数原型及其说明:
rt_err_t rtc_set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day);
rt_err_t rtc_set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second);
| 函数 | 描述 |
|---|---|
rtc_set_date() |
设置当前时区的日期,包括年、月、日。 |
rtc_set_time() |
设置当前时区的时间,包括时、分、秒。 |
| 参数 | 描述 |
|---|---|
year |
目标年份 |
month |
目标月份 |
day |
目标日期 |
hour |
目标小时 |
minute |
目标分钟 |
second |
目标秒数 |
| 返回值 | 描述 |
|---|---|
RT_EOK |
操作成功 |
-RT_ERROR |
失败,未找到 RTC 设备 |
| 其他错误码 | 操作失败 |
设置日期示例:
/* 将日期设置为2023年10月27日 */
rtc_set_date(2023, 10, 27);
设置时间示例:
/* 将时间设置为14点30分0秒 */
rtc_set_time(14, 30, 0);
获取当前时间
获取当前时间戳(通常为格林尼治标准时间)最常用的方法是调用 C 标准库函数 time()。
time_t time(time_t *t);
| 参数 | 描述 |
|---|---|
t |
指向 time_t 变量的指针,用于存储获取的时间。若为 NULL,则仅返回时间值。 |
| 返回值 | 描述 |
|---|---|
| 当前时间值 | 自 Epoch(通常为1970年1月1日)以来的秒数。 |
获取并打印时间示例:
time_t current_time; /* 用于存储获取的时间 */
/* 获取当前时间戳 */
current_time = time(RT_NULL);
/* 将时间戳转换为可读的字符串并打印 */
rt_kprintf("Current time: %s\n", ctime(¤t_time));
注意: 在 RT-Thread 系统中,目前只支持一个名为 "rtc" 的 RTC 设备实例。
功能配置
启用软件模拟 RTC
对于没有硬件 RTC 或对时间精度要求不高的应用,可以在菜单配置中启用软件模拟 RTC 功能。配置路径如下:
RT-Thread Components →
Device Drivers:
[*] 使用 RTC 设备驱动
[ ] 使用软件模拟 RTC 设备
启用 NTP 时间自动同步
如果 RT-Thread 系统已连接到网络,可以启用 NTP(网络时间协议)客户端来自动同步系统时间。
首先,在包管理器中开启 NTP 功能:
RT-Thread online packages →
IoT - internet of things →
netutils: Networking utilities for RT-Thread:
[*] 启用 NTP(Network Time Protocol) 客户端
然后,在设备驱动配置中启用 NTP 自动同步:
RT-Thread Components →
Device Drivers:
[*] 使用 RTC 设备驱动
[ ] 使用软件模拟 RTC 设备
[*] 使用 NTP 自动同步 RTC 时间
(30) NTP 首次同步延时(秒) /* 网络连接后,首次同步的等待时间 */
(3600) NTP 自动同步周期(秒) /* 每隔多久同步一次时间 */
注意: 在 2021-05-09 版本之后的 RT-Thread 内核源码中,NTP 自动同步 RTC 时间的配置项已被移除。
FinSH 命令
可以通过 FinSH 命令行工具查看和设置系统时间。
查看当前时间:
msh />date
Fri Feb 16 01:11:56 2018
msh />
设置当前时间:
msh />date 2024 5 20 10 0 0
msh />
RTC 设备使用示例
以下示例代码演示了如何查找、初始化、设置 RTC 设备,并在延时后读取当前时间。
/*
* 程序名称:RTC 设备操作示例
* 功能:初始化 RTC 设备,设置日期和时间,然后读取并打印当前时间。
* 命令:rtc_demo
*/
#include <rtthread.h>
#include <rtdevice.h>
#include <time.h>
#define RTC_DEVICE_NAME "rtc"
static int rtc_demo(int argc, char *argv[])
{
rt_err_t result;
time_t timestamp;
rt_device_t rtc_dev;
/* 1. 查找并获取 RTC 设备句柄 */
rtc_dev = rt_device_find(RTC_DEVICE_NAME);
if (rtc_dev == RT_NULL) {
rt_kprintf("错误:未找到 RTC 设备 '%s'\n", RTC_DEVICE_NAME);
return -RT_ERROR;
}
/* 2. 以读写模式打开 RTC 设备 */
result = rt_device_open(rtc_dev, RT_DEVICE_OFLAG_RDWR);
if (result != RT_EOK) {
rt_kprintf("错误:无法打开 RTC 设备\n");
return result;
}
/* 3. 设置日期为 2024 年 5 月 20 日 */
result = rtc_set_date(2024, 5, 20);
if (result != RT_EOK) {
rt_kprintf("设置日期失败\n");
rt_device_close(rtc_dev);
return result;
}
/* 4. 设置时间为 10 点 0 分 0 秒 */
result = rtc_set_time(10, 0, 0);
if (result != RT_EOK) {
rt_kprintf("设置时间失败\n");
rt_device_close(rtc_dev);
return result;
}
/* 5. 延时 5 秒 */
rt_thread_mdelay(5000);
/* 6. 获取当前时间戳 */
timestamp = time(RT_NULL);
/* 7. 将时间戳转换为字符串并打印 */
rt_kprintf("当前时间: %s\n", ctime(×tamp));
/* 8. 关闭设备 */
rt_device_close(rtc_dev);
return RT_EOK;
}
/* 注册为 msh 命令 */
MSH_CMD_EXPORT(rtc_demo, 演示 RTC 设备的基本操作);
Alarm (闹钟) 功能
Alarm 概述
Alarm 功能基于 RTC 设备实现,允许在特定时间点触发预定的事件。虽然硬件 RTC 本身可能只支持有限数量的硬件闹钟,但 RT-Thread 通过软件抽象层,理论上可以支持无限个闹钟。需要注意的是,每个闹钟实例最终只保留最后一次的设定有效。
要使用 Alarm 组件,必须在菜单配置中启用该功能,并且底层 RTC 驱动必须实现必要的接口(如 set_alarm 和 get_alarm)。配置路径如下:
RT-Thread Components --->
Device Drivers --->
[*] 使用 RTC alarm
Alarm 设备操作
应用程序通过 Alarm 组件提供的 API 来创建和管理闹钟。主要接口函数如下:
rtc_alarm_create(): 创建一个闹钟实例rtc_alarm_start(): 启动闹钟rtc_alarm_stop(): 停止闹钟rtc_alarm_delete(): 删除闹钟rtc_alarm_control(): 控制闹钟(如修改设置)rtc_alarm_dump(): 打印所有已创建的闹钟信息
创建闹钟
使用 rtc_alarm_create() 函数创建一个新的闹钟。
rt_alarm_t rtc_alarm_create(rt_alarm_callback_t callback, struct rt_alarm_setup *setup);
| 参数 | 描述 |
|---|---|
callback |
闹钟触发时调用的回调函数指针。 |
setup |
指向 struct rt_alarm_setup 的指针,用于配置闹钟的时间和模式。 |
| 返回值 | 描述 |
|---|---|
| 非 NULL | 成功,返回闹钟句柄。 |
RT_NULL |
失败。 |
struct rt_alarm_setup 结构体定义了闹钟的触发时间和模式:
struct rt_alarm_setup
{
rt_uint32_t flag; /* 闹钟模式标志 */
struct tm wktime; /* 闹钟触发的具体日期和时间 */
};
闹钟模式通过 flag 字段设置,可组合使用:
#define RTC_ALARM_ONESHOT 0x000 /* 单次闹钟 */
#define RTC_ALARM_DAILY 0x100 /* 每天重复 */
#define RTC_ALARM_WEEKLY 0x200 /* 每周重复 (指定星期) */
#define RTC_ALARM_MONTHLY 0x400 /* 每月重复 (指定日期) */
#define RTC_ALARM_YEARLY 0x800 /* 每年重复 (指定日期) */
#define RTC_ALARM_HOUR 0x1000 /* 每小时重复 (指定分钟:秒) */
#define RTC_ALARM_MINUTE 0x2000 /* 每分钟重复 (指定秒) */
#define RTC_ALARM_SECOND 0x4000 /* 每秒重复 */
注意: 闹钟创建后处于停止状态,需要显式启动。
启动、停止和删除闹钟
/* 启动闹钟 */
rt_err_t rtc_alarm_start(rt_alarm_t alarm);
/* 停止闹钟 */
rt_err_t rtc_alarm_stop(rt_alarm_t alarm);
/* 删除闹钟 */
rt_err_t rtc_alarm_delete(rt_alarm_t alarm);
控制闹钟
可以使用 rtc_alarm_control() 函数修改已创建闹钟的设置。
rt_err_t rtc_alarm_control(rt_alarm_t alarm, int cmd, void *arg);
| 参数 | 描述 |
|---|---|
alarm |
要控制的闹钟句柄。 |
cmd |
控制命令,目前支持 RTC_ALARM_CTRL_MODIFY(修改设置)。 |
arg |
指向 struct rt_alarm_setup 的指针,包含新的设置。 |
注意: 修改设置后,闹钟不会自动重启,需要调用 rtc_alarm_start()。
Alarm 使用示例
以下示例创建一个每秒触发一次的闹钟,并在回调函数中打印信息。
/*
* 程序名称:Alarm 功能示例
* 功能:创建一个每秒触发一次的闹钟,并打印触发信息。
* 命令:alarm_demo
*/
#include <rtthread.h>
#include <rtdevice.h>
/* 闹钟回调函数 */
void alarm_callback(rt_alarm_t alarm, time_t timestamp)
{
rt_kprintf("闹钟触发!当前时间: %s", ctime(×tamp));
}
void alarm_demo(void)
{
rt_device_t rtc_dev;
struct rt_alarm_setup alarm_cfg;
struct rt_alarm *my_alarm;
time_t now;
struct tm current_tm;
/* 查找 RTC 设备 */
rtc_dev = rt_device_find("rtc");
if (rtc_dev == RT_NULL) {
rt_kprintf("错误:未找到 RTC 设备\n");
return;
}
/* 获取当前时间,并设置为下一秒 */
now = time(RT_NULL) + 1;
gmtime_r(&now, ¤t_tm);
/* 配置闹钟:每秒触发 */
alarm_cfg.flag = RTC_ALARM_SECOND;
alarm_cfg.wktime = current_tm;
/* 创建闹钟 */
my_alarm = rtc_alarm_create(alarm_callback, &alarm_cfg);
if (my_alarm != RT_NULL) {
/* 启动闹钟 */
rtc_alarm_start(my_alarm);
rt_kprintf("闹钟已启动,将持续打印信息...\n");
} else {
rt_kprintf("创建闹钟失败\n");
}
}
/* 注册为 msh 命令 */
MSH_CMD_EXPORT(alarm_demo, 演示 Alarm 功能的使用);
执行该命令后,控制台会每秒打印一次 "闹钟触发!" 的信息。