[K230学习笔记 01] RTC
硬件背景

如上图所示,k230 的 RTC 是在 PMU 子系统下边的,并且 RTC 的中断会通过 PMU 向 CPU 传递,特别的在PMU下有相应的 INT 管脚,可通过 INT 管脚检测外部事件,然后触发 OUT 输出,如检测外部按键长按后,OUT 控制总电源的使能完成系统上电。比如使用 INT0/INT4 可以用来实现长按、短按上电的功能,在庐山派上边使用的是在 INT4 上加上拉电阻来实现管理除 RTC 与 ADC 外的电源上电。

而对于 RTC 的框图来说,配置 RTC 是通过 APB 总线完成的,并且在框图中说明了 RTC 可以实现的中断有闹钟中断与计数器中断。这两个中断分别是 PMU 下的 int6 与 int7,要使用这两个中断不仅要在 RTC 自身的寄存器下使能,还要使能 PMU 下中断检测使能寄存器下的相应位,这样才能让 CPU 检测到。
RTC 配置流程
RTC 寄存器

k230 的 RTC 的寄存器包括了日期、时间、闹钟日期、闹钟时间、计数、中断与控制寄存器。如下图 RTC 内部框图所示,当设置好这些寄存器后置位 INT_CTRL 写使能位后定时器的配置就会被写入到 RTC 内部的定时器中,当 RTC 开始运行时 RTC 内部的定时器就会开始工作,然后更新 RTC 的相应寄存器,然后会触发相应的中断。当要读取当前时间时也需要置位 INT_CTRL 的读使能位。

RTC 初始化流程
配置时间
- 写 RTC 的时间日期寄存器
- 写 RTC 的计数器寄存器
static void rtc_date_time_set(struct k230_rtc_dev *dev, int year, int month, int day, int hour, int minute, int second, int week)
{
rtc_date_t date;
rtc_time_t time;
rtc_count_t count;
volatile rtc_t *rtc = (rtc_t *)dev->base;
int val = year % 100;
int year_l, year_h;
if(val == 0)
{
year_l = 100;
year_h = year / 100 - 1;
}
else
{
year_l = val;
year_h = (year - val) / 100;
}
rtc->int_ctrl.timer_w_en = 1;
date.year_h = year_h;
date.year_l = year_l;
date.month = month;
date.day = day;
date.leap_year = rtc_year_is_leap(year);
time.week = week;
time.hour = hour;
time.minute = minute;
time.second = second;
rtc->date = date;
rtc->time = time;
}
static void rtc_timer_set_clock_count_value(struct k230_rtc_dev *dev, uint16_t count)
{
volatile volatile rtc_t *rtc = (rtc_t *)dev->base;
rtc->count.curr_count = count;
rtc->count.sum_count = 0x7FFF;
rtc->int_ctrl.timer_w_en = 1;
rt_thread_mdelay(1);
rtc->int_ctrl.timer_w_en = 0;
rtc->int_ctrl.timer_r_en = 1;
}
static void rtc_timer_set(struct k230_rtc_dev *dev, time_t *t)
{
struct tm p_tm;
gmtime_r(t, &p_tm);
rtc_date_time_set(dev, (p_tm.tm_year + 1900), p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec, p_tm.tm_wday);
rtc_timer_set_clock_count_value(dev, 0);
}
配置闹钟
- 设置闹钟
- 设置中断
static void rtc_alarm_set(struct k230_rtc_dev *dev, void *args)
{
rtc_alarm_setup_t *setup = (rtc_alarm_setup_t *)args;
struct tm tm = setup->tm;
time_t t;
struct tm p_tm;
volatile rtc_t *rtc = (rtc_t *)dev->base;
rtc_alarm_time_t alarm_time;
rtc_alarm_date_t alarm_date;
rtc_date_t date = rtc->date;
int year, year_l, year_h, val;
t = mktime(&tm);
gmtime_r(&t, &p_tm);
year = p_tm.tm_year + 1900;
val = year % 100;
if(val == 0)
{
year_l = 100;
year_h = year / 100 - 1;
}
else
{
year_l = val;
year_h = (year - val) / 100;
}
alarm_date.alarm_year_h = year_h;
alarm_date.alarm_year_l = year_l;
alarm_date.alarm_month = p_tm.tm_mon + 1;
alarm_date.alarm_day = p_tm.tm_mday;
alarm_time.alarm_hour = p_tm.tm_hour;
alarm_time.alarm_minute = p_tm.tm_min;
alarm_time.alarm_second = p_tm.tm_sec;
rtc->alarm_date = alarm_date;
rtc->alarm_time = alarm_time;
rtc_alarm_clear_interrupt(dev);
rt_hw_interrupt_install(dev->vector, rtc_irq, dev, "rtc");
rt_hw_interrupt_umask(dev->vector);
rtc_interrupt_ctrl_set(dev, setup->flag);
}
值得注意的是,在配置完 RTC 寄存器的相关中断设置后其实 CPU 还不会响应中断,还需要置位 PMU 下中断检测使能寄存器下的相应位,在 k230 中需要配置 int6_en (RTC 的闹钟中断检测使能位),int7_en(RTC 的计数中断检测使能位)
static void pmu_isolation_rtc(void)
{
/* map pwr base address */
volatile void *reg_pmu_pwr = rt_ioremap((void *)PWR_BASE_ADDR, PWR_IO_SIZE);
uint32_t *addr = (uint32_t *)(reg_pmu_pwr + 0x158); /* pmu power control register */
uint32_t data;
/* disable pmu isolation */
data = *addr;
data &= ~0x20;
*addr = data;
rt_iounmap(reg_pmu_pwr);
/* map pmu base address */
volatile void *reg_pmu = rt_ioremap((void*)PMU_BASE_ADDR, PMU_IO_SIZE);
addr = (uint32_t*)(reg_pmu + 0x48); /* pmu int0 to cpu register */
/* enable int6 int7 */
data = *addr;
data |= 0x06;
*addr = data;
addr = (uint32_t*)(reg_pmu + 0x4c); /* pmu int detect en register */
/* enable int6 rtc alarm detection and int7 rtc tick detection */
data = *addr;
data |= 0x06;
*addr = data;
rt_iounmap(reg_pmu);
}
读取时间
读取时间唯一需要注意的是需要置位 INT_CTRL 的读使能位,这样 RTC 定时器的值才会更新到 RTC 寄存器中,有点影子寄存器的意思了。
static void rtc_timer_get(struct k230_rtc_dev *dev, time_t *t)
{
volatile rtc_t *rtc = (rtc_t *)dev->base;
struct tm tm;
if (rtc->int_ctrl.timer_r_en == 0)
{
rtc->int_ctrl.timer_r_en = 1;
}
tm.tm_sec = rtc->time.second;
tm.tm_min = rtc->time.minute;
tm.tm_hour = rtc->time.hour;
tm.tm_mday = rtc->date.day;
tm.tm_mon = rtc->date.month - 1;
tm.tm_year = (rtc->date.year_h * 100 + rtc->date.year_l) - 1900;
tm.tm_wday = rtc->time.week;
*t = timegm(&tm);
}
读取闹钟的时间
相比读取时间,读取闹钟的时间就不需要置位 INT_CTRL 的读使能位了,直接读取 RTC 寄存器即可。
static void rtc_alarm_get(struct k230_rtc_dev *dev, void *args)
{
struct tm *tm = (struct tm*)args;
volatile rtc_t *rtc = (rtc_t *)dev->base;
rtc_alarm_date_t alarm_date = rtc->alarm_date;
rtc_alarm_time_t alarm_time = rtc->alarm_time;
tm->tm_year = (alarm_date.alarm_year_h * 100 + alarm_date.alarm_year_l) -1900;
tm->tm_mon = alarm_date.alarm_month - 1;
tm->tm_mday = alarm_date.alarm_day;
tm->tm_hour = alarm_time.alarm_hour;
tm->tm_min = alarm_time.alarm_minute;
tm->tm_sec = alarm_time.alarm_second;
}
RTC 中的注意事项
在一些复杂的 RTC 应用中往往需要引入时区,而引入时区时在配置 RTC 的时间和闹钟时间时就要注意不要混淆时区,也就是不要时间用的是 UTC 时间,而闹钟时间又设置的本地时间,如果这样的话,响应闹钟时已经相差了时区的时间了。
RTC 测试
使用 rt-thread 的 utest 框架编写相应的测试单元进行测试。
static void test_rtc_set(void)
{
rt_err_t ret = RT_EOK;
time_t now;
uint32_t i;
rt_device_t rtc_dev = RT_NULL;
LOG_I("rtc set time test\n");
rtc_dev = rt_device_find(RTC_NAME);
uassert_not_null(rtc_dev);
ret = rt_device_open(rtc_dev, RT_DEVICE_OFLAG_RDWR);
uassert_int_equal(ret, RT_EOK);
ret = set_time(23, 59, 59);
uassert_int_equal(ret, RT_EOK);
ret = set_date(2025, 9, 16);
uassert_int_equal(ret, RT_EOK);
rt_thread_mdelay(500);
/* 设置完时间后打印10次时间 */
for (i=0; i<10; i++)
{
now = time(RT_NULL);
LOG_I("%s\n", ctime(&now));
rt_thread_mdelay(1000);
}
rt_device_close(rtc_dev);
}
static void test_rtc_alarm_callback(void)
{
LOG_I("rtc alarm triggered!\n");
}
static void test_rtc_alarm(void)
{
rt_err_t ret = RT_EOK;
time_t now;
uint32_t i;
struct tm p_tm;
rt_device_t rtc_dev = RT_NULL;
struct rt_alarm *alarm = RT_NULL;
rtc_alarm_setup_t setup;
LOG_I("rtc alarm test\n");
rtc_dev = rt_device_find(RTC_NAME);
uassert_not_null(rtc_dev);
ret = rt_device_open(rtc_dev, RT_DEVICE_OFLAG_RDWR);
uassert_int_equal(ret, RT_EOK);
ret = set_time(23, 59, 59);
uassert_int_equal(ret, RT_EOK);
ret = set_date(2025, 9, 16);
uassert_int_equal(ret, RT_EOK);
rt_thread_mdelay(500);
now = time(RT_NULL);
LOG_I("%s\n", ctime(&now));
now += 5; //alarm after 5s
localtime_r(&now, &p_tm);
setup.flag = RTC_INT_ALARM_MINUTE | RTC_INT_ALARM_SECOND;
setup.tm.tm_year = p_tm.tm_year;
setup.tm.tm_mon = p_tm.tm_mon;
setup.tm.tm_mday = p_tm.tm_mday;
setup.tm.tm_wday = p_tm.tm_wday;
setup.tm.tm_hour = p_tm.tm_hour;
setup.tm.tm_min = p_tm.tm_min;
setup.tm.tm_sec = p_tm.tm_sec;
rt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_SET_CALLBACK, &test_rtc_alarm_callback); //set rtc intr callback
rt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_SET_ALARM, &setup); //set alarm time
rt_memset(&p_tm, 0, sizeof(p_tm));
rt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_GET_ALARM, &p_tm); //get alarm time
now = timegm(&p_tm);
LOG_I("get alarm time: %s\n", ctime(&now));
for (i=0; i<10; i++)
{
now = time(RT_NULL);
LOG_I("%s\n", ctime(&now));
rt_thread_mdelay(1000);
}
rt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_STOP_ALARM, RT_NULL); //stop alarm
rt_device_close(rtc_dev);
}
static void test_rtc_interface(void)
{
rt_err_t ret = RT_EOK;
uint32_t i;
rt_device_t rtc_dev = RT_NULL;
time_t now;
struct tm tm;
LOG_I("rtc interface test\n");
rtc_dev = rt_device_find(RTC_NAME);
uassert_not_null(rtc_dev);
ret = rt_device_open(rtc_dev, RT_DEVICE_OFLAG_RDWR);
uassert_int_equal(ret, RT_EOK);
LOG_I("write rtc\n");
tm.tm_year = 2025 - 1900;
tm.tm_mon = 9 - 1;
tm.tm_mday = 16;
tm.tm_wday = 2;
tm.tm_hour = 23;
tm.tm_min = 59;
tm.tm_sec = 59;
rt_device_write(rtc_dev, RT_NULL, (void*)&tm, sizeof(tm));
rt_thread_mdelay(500);
/* 设置完时间后打印10次时间 */
for (i=0; i<10; i++)
{
now = time(RT_NULL);
LOG_I("[sys]:%s\n", ctime(&now));
rt_thread_mdelay(1000);
}
LOG_I("read rtc\n");
for (i=0; i<10; i++)
{
rt_device_read(rtc_dev, RT_NULL, (void*)&now, sizeof(now));
LOG_I("[read]: %s\n", ctime(&now));
rt_thread_mdelay(1000);
}
rt_device_close(rtc_dev);
}
上述代码主要测试 RTC 的时间功能与闹钟功能,在RTC 的寄存器中保存的时间信息是以 UTC 时间保存的,要使用本地时间需要配置时区,当前配置的时区是东八区,即北京时间(CST),闹钟会在设置后的 5s 触发。
在 SD 卡编译烧录好镜像后,将 SD 卡插入板子上电启动,然后在控制台运行 rtc 的测试单元。测试平台与结果如下所示。

OpenSBI v1.2.2
\ | /
- RT - Thread Smart Operating System
/ | \ 5.2.2 build Sep 19 2025 17:14:56
2006 - 2024 Copyright by RT-Thread team
lwIP-2.1.2 initialized!
[I/drv_rtc] rtc driver register OK
[I/sal.skt] Socket Abstraction Layer initialize success.
[I/utest] utest is initialize success.
[I/utest] total utest testcase num: (4)
[I/drivers.serial] Using /dev/ttyS0 as default console
[W/SDIO] Card ocr below the defined voltage rang.
[W/SDIO] Can't support the low voltage SDIO card.
Timeout waiting for MMCSD host to be plugged!
Press any key to stop init process startup ... 3
Press any key to stop init process startup ... 2
Press any key to stop init process startup ... 1
Starting init ...
Hello RISC-V
msh />utest_list
[I/utest] Commands list :
[I/utest] [testcase name]:bsp.k230.drivers.gpio; [run timeout]:100
[I/utest] [testcase name]:bsp.k230.drivers.gpio_irq; [run timeout]:100
[I/utest] [testcase name]:bsp.k230.drivers.rtc; [run timeout]:100
[I/utest] [testcase name]:bsp.k230.drivers.uart; [run timeout]:10
msh />utest_run bsp.k230.drivers.rtc
[I/utest] [==========] [ utest ] loop 1/1
[I/utest] [==========] [ utest ] started
[I/utest] [----------] [ testcase ] (bsp.k230.drivers.rtc) started
[I/utest] This is a rtc test case.
[I/utest] [==========] utest unit name: (test_rtc)
[I/utest] rtc set time test
[I/utest] Tue Sep 16 23:59:59 2025
[I/utest] Wed Sep 17 00:00:00 2025
[I/utest] Wed Sep 17 00:00:01 2025
[I/utest] Wed Sep 17 00:00:02 2025
[I/utest] Wed Sep 17 00:00:03 2025
[I/utest] Wed Sep 17 00:00:04 2025
[I/utest] Wed Sep 17 00:00:05 2025
[I/utest] Wed Sep 17 00:00:05 2025
[I/utest] Wed Sep 17 00:00:06 2025
[I/utest] Wed Sep 17 00:00:07 2025
[I/utest] rtc alarm test
[I/utest] Tue Sep 16 23:59:59 2025
[I/utest] get alarm time: Wed Sep 17 00:00:04 2025
[I/utest] Tue Sep 16 23:59:59 2025
[I/utest] Wed Sep 17 00:00:00 2025
[I/utest] Wed Sep 17 00:00:01 2025
[I/utest] Wed Sep 17 00:00:02 2025
[I/utest] Wed Sep 17 00:00:03 2025
[I/utest] rtc alarm triggered!
[I/utest] Wed Sep 17 00:00:04 2025
[I/utest] Wed Sep 17 00:00:05 2025
[I/utest] Wed Sep 17 00:00:06 2025
[I/utest] Wed Sep 17 00:00:06 2025
[I/utest] Wed Sep 17 00:00:07 2025
[I/utest] rtc interface test
[I/utest] write rtc
[I/utest] [sys]:Tue Sep 16 23:59:59 2025
[I/utest] [sys]:Wed Sep 17 00:00:00 2025
[I/utest] [sys]:Wed Sep 17 00:00:01 2025
[I/utest] [sys]:Wed Sep 17 00:00:02 2025
[I/utest] [sys]:Wed Sep 17 00:00:03 2025
[I/utest] [sys]:Wed Sep 17 00:00:04 2025
[I/utest] [sys]:Wed Sep 17 00:00:05 2025
[I/utest] [sys]:Wed Sep 17 00:00:05 2025
[I/utest] [sys]:Wed Sep 17 00:00:06 2025
[I/utest] [sys]:Wed Sep 17 00:00:07 2025
[I/utest] read rtc
[I/utest] [read]: Wed Sep 17 00:00:08 2025
[I/utest] [read]: Wed Sep 17 00:00:09 2025
[I/utest] [read]: Wed Sep 17 00:00:10 2025
[I/utest] [read]: Wed Sep 17 00:00:11 2025
[I/utest] [read]: Wed Sep 17 00:00:12 2025
[I/utest] [read]: Wed Sep 17 00:00:13 2025
[I/utest] [read]: Wed Sep 17 00:00:14 2025
[I/utest] [read]: Wed Sep 17 00:00:15 2025
[I/utest] [read]: Wed Sep 17 00:00:16 2025
[I/utest] [read]: Wed Sep 17 00:00:17 2025
[I/utest] [ PASSED ] [ result ] testcase (bsp.k230.drivers.rtc)
[I/utest] [----------] [ testcase ] (bsp.k230.drivers.rtc) finished
[I/utest] [==========] [ utest ] 1 tests from 4 testcase ran.
[I/utest] [ PASSED ] [ result ] 1 tests.
[I/utest] [==========] [ utest ] finished
在打印的时间中出现了相同的时间的原因,应该是 rt_thread_mdelay 函数存在误差导致,因为我没有找到 rt-thread 的绝对延时函数,这个绝对延时函数在FreeRTOS中是有实现的,如果使用绝对延时函数应该解决打印时间中存在相同时间的现象。

浙公网安备 33010602011771号