当前位置: 首页 > news >正文

RTC

硬件背景

assets/RTC/file-20250926174541676.jpg

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

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

RTC 配置流程

RTC 寄存器

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

RTC 初始化流程

配置时间

  1. 写 RTC 的时间日期寄存器
  2. 写 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);
}

配置闹钟

  1. 设置闹钟
  2. 设置中断
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 5slocaltime_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 callbackrt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_SET_ALARM, &setup);   //set alarm timert_memset(&p_tm, 0, sizeof(p_tm));rt_device_control(rtc_dev, RT_DEVICE_CTRL_RTC_GET_ALARM, &p_tm);   //get alarm timenow = 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 alarmrt_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 的测试单元。测试平台与结果如下所示。
assets/RTC/IMG_20250927_151548.jpg

OpenSBI v1.2.2\ | /
- RT -     Thread Smart Operating System/ | \     5.2.2 build Sep 19 2025 17:14:562006 - 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中是有实现的,如果使用绝对延时函数应该解决打印时间中存在相同时间的现象。

http://www.hskmm.com/?act=detail&tid=19070

相关文章:

  • 卡特兰数与反射容斥
  • 题解:QOJ9619/洛谷13568 [CCPC 2024 重庆站] 乘积,欧拉函数,求和(数论+状压DP)
  • Momentum Gradient Descent(动量梯度下降)
  • README
  • Halcon算子——2D几何变换
  • 深入解析:深度解析 CUDA-QX 0.4 加速 QEC 与求解器库
  • 实验作业1
  • 《C++程序设计》笔记p4 - 指南
  • 电脑显示器黑屏(闪烁:隔几秒中黑一两秒),向日葵远程正常——DeepSeek问答
  • 实用指南:iOS 26 兼容测试实战,机型兼容、SwiftUI 兼容性改动
  • 大中午记梦
  • 概率/期望 $dp$
  • 9.21~9.27 周总结
  • Jetbrains 全家桶激活码激活
  • 【深度学习计算机视觉】07:单发多框检测(SSD) - 指南
  • MZOI 2025.9.27
  • 原码 反码 补码
  • Spring Framework 远程命令执行漏洞
  • 配置本地环境以管理Git多账户SSH连接
  • Pod、 PVC 、PV的
  • 百度网盘ByPy使用配置指南
  • 完整教程:AI 术语通俗词典:Diffusion Models(扩散模型)
  • pip安装依赖包报错内容为User defined options,Native files 如何解决
  • edu 107 E(概率期望, dp)
  • 2025 年空气离合器生产厂家推荐榜:电网冲击缓解技术与可靠性测评,单片空气离合器,多片空气离合器,空气离合器摩擦片,空气离合器密封件公司推荐
  • Spring MVC的双向数据绑定
  • 抽象化编程(Abstraction in Programming)
  • 9月27日
  • 配置RedisTemplate序列化机制
  • 优化器(Optimizer)