2. Cvitek RTC 具体操作指南¶
2.1. 模块介绍¶
RTC(real time clock)实时时钟,是处理器内一个独立供电的恒电模块,为 Linux 系统提供时间。由于RTC 由电池供电,当处理器处于下电关机或休眠状态时,RTC 仍会维持工作状态时间不丢失。Linux 内核将 RTC 作为时间与日期维护器。亦可作为内核睡眠时唤醒内核的闹钟。
应用程序可以用 RTC 提供的周期中断做周期性任务。
2.1.1. 计数时钟频率¶
RTC 的计数时钟采用 32.768KHz 时钟,运行基于一个 32-bit 加法计数器提供秒计数,计数最大时间为 :
2^32秒 = 49710 天 = 136 年
2.2. 操作准备¶
2.2.1. 使用 RTC(默认)¶
使用 SDK 发布的 U-boot 与 Kernel 即可,无需修改 DTS。
设备节点:
/dev/rtc0。
2.2.2. 移除 RTC¶
若要在板级禁用 RTC,在板级 DTS(或 overlay)里用 delete-node 删除 RTC 节点。
本 SDK 中 RTC 节点名为 rtc (在 cv184x_base.dtsi / cv1835_asic.dtsi 中定义),删除示例如下:
/ {
/delete-node/ rtc;
};
若文档或其它 BSP 中节点名为 cvitek-rtc@3005000,则使用:
/delete-node/ cvitek-rtc@3005000;
同文件中删除其它节点示例(仅作参考):
/delete-node/ i2s@04120000;
/delete-node/ sound_ext1;
/delete-node/ sound_ext2;
/delete-node/ sound_PDM;
/delete-node/ rtc; /* 删除 RTC */
aliases {
/delete-property/ ethernet1;
};
2.3. 应用层使用方式¶
2.3.1. 设备节点与头文件¶
设备节点:
/dev/rtc0头文件:
#include <linux/rtc.h>、#include <sys/ioctl.h>
2.3.2. 常用 ioctl 指令¶
指令 |
描述 |
|---|---|
RTC_RD_TIME |
读取当前时间 |
RTC_SET_TIME |
设置当前时间 |
RTC_ALM_READ |
读取闹钟时间 |
RTC_ALM_SET |
设置闹钟时间 |
RTC_AIE_ON |
使能闹钟中断 |
RTC_AIE_OFF |
禁止闹钟中断 |
RTC_UIE_ON |
使能更新中断 |
RTC_UIE_OFF |
禁止更新中断 |
RTC_PIE_ON |
开周期中断 |
RTC_PIE_OFF |
关周期中断 |
RTC_IRQP_SET |
设置周期中断频率 |
2.3.3. 结构体 rtc_time 说明¶
struct rtc_time {
int tm_sec; /* 秒 [0,59] */
int tm_min; /* 分 [0,59] */
int tm_hour; /* 时 [0,23] */
int tm_mday; /* 日 [1,31] */
int tm_mon; /* 月 [0,11],0=1月 */
int tm_year; /* 年,实际年 = tm_year + 1900 */
int tm_wday; /* 星期几 [0,6],0=周日,1=周一 */
int tm_yday; /* 一年中第几天 [0,365],0=1月1日 */
int tm_isdst; /* 夏令时:1=是,0=否 */
};
注意:
tm_mon:0 表示 1 月,11 表示 12 月。tm_year:存的是“年份 − 1900”,如 2025 年填 125。tm_wday:0=周日,1=周一,依此类推。tm_yday:0=1月1日,1=1月2日。
2.4. 示例代码¶
2.4.1. 用户态操作示例(rtc_example.c)¶
该示例将“读取时间 / 设置时间 / 设置闹钟”整合为一个程序,通过参数选择具体操作。
编译
arm-none-linux-uclibcgnueabihf-gcc -o rtc_example rtc_example.c -static
运行
./rtc_example [read|set|alarm]
read:读取并打印当前 RTC 时间。set:将 RTC 设置为示例时间(2025-03-18 12:00:00),并打印设置后的时间。alarm:将闹钟设为当前时间 + 5 秒,并开启闹钟中断(部分 RTC 可能不支持闹钟中断,会返回错误)。
/*
* Cvitek RTC 使用示例:读取时间、设置时间、设置闹钟
* 编译: arm-none-linux-uclibcgnueabihf-gcc -o rtc_example rtc_example.c -static
* 运行: ./rtc_example [read|set|alarm]
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <linux/rtc.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define RTC_DEV "/dev/rtc0"
static void print_rtc_time(const struct rtc_time *t)
{
printf("RTC 时间: %04d-%02d-%02d %02d:%02d:%02d 星期%d\n",
t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, t->tm_wday);
}
/* 读取并打印当前 RTC 时间 */
static int do_read(int fd)
{
struct rtc_time rtc_tm;
if (ioctl(fd, RTC_RD_TIME, &rtc_tm) < 0) {
perror("RTC_RD_TIME");
return -1;
}
print_rtc_time(&rtc_tm);
return 0;
}
/* 设置 RTC 时间(示例:2025-03-18 12:00:00) */
static int do_set(int fd)
{
struct rtc_time rtc_tm = {
.tm_sec = 0,
.tm_min = 0,
.tm_hour = 12,
.tm_mday = 18,
.tm_mon = 2, /* 3 月 */
.tm_year = 125, /* 2025 */
.tm_wday = 2,
.tm_isdst = 0
};
if (ioctl(fd, RTC_SET_TIME, &rtc_tm) < 0) {
perror("RTC_SET_TIME");
return -1;
}
printf("已设置 RTC,当前为: ");
return do_read(fd);
}
/* 设置闹钟为当前时间 + 5 秒,并开启闹钟中断 */
static int do_alarm(int fd)
{
struct rtc_time rtc_tm;
if (ioctl(fd, RTC_RD_TIME, &rtc_tm) < 0) {
perror("RTC_RD_TIME");
return -1;
}
rtc_tm.tm_sec += 5;
if (rtc_tm.tm_sec >= 60) {
rtc_tm.tm_sec %= 60;
rtc_tm.tm_min++;
}
if (rtc_tm.tm_min >= 60) {
rtc_tm.tm_min = 0;
rtc_tm.tm_hour++;
}
if (rtc_tm.tm_hour >= 24)
rtc_tm.tm_hour = 0;
if (ioctl(fd, RTC_ALM_SET, &rtc_tm) < 0) {
if (errno == EINVAL)
fprintf(stderr, "此 RTC 不支持闹钟中断\n");
else
perror("RTC_ALM_SET");
return -1;
}
ioctl(fd, RTC_AIE_ON, 0);
printf("闹钟已设为 5 秒后,已开启闹钟中断\n");
return 0;
}
int main(int argc, char **argv)
{
const char *cmd = (argc > 1) ? argv[1] : "read";
int fd = open(RTC_DEV, O_RDONLY);
if (fd < 0) {
perror("open " RTC_DEV);
return 1;
}
if (strcmp(cmd, "read") == 0)
do_read(fd);
else if (strcmp(cmd, "set") == 0)
do_set(fd);
else if (strcmp(cmd, "alarm") == 0)
do_alarm(fd);
else {
fprintf(stderr, "用法: %s [read|set|alarm]\n", argv[0]);
close(fd);
return 1;
}
close(fd);
return 0;
}
2.5. 小结¶
操作 |
做法 |
|---|---|
使用 RTC |
不删节点,应用直接操作 |
移除 RTC |
在板级 DTS 中加 |
读时间 |
|
设时间 |
填充 |
设闹钟 |
填充时间后 |
2.6. RTC 中断定时重启(软重启)¶
软重启指不断电、不拉闸,仅让 CPU/SoC 重新执行启动流程(内核重新跑一遍),电源保持供电。通过 RTC 闹钟到点触发,可实现定时软重启。
2.6.1. 执行方式¶
在设备上查找
auto_restart_after节点路径:
find /sys/devices/ -name auto_restart_after
向该节点写入秒数即可在指定秒数后触发软重启。下面示例为 30 秒后重启,路径需替换为上一步
find得到的实际节点(通常形如/sys/devices/platform/5026000.rtc/auto_restart_after):
echo 30 > /sys/devices/platform/5026000.rtc/auto_restart_after
2.6.2. 从 RTC 闹钟到 SoC 复位的流程¶
设置 RTC 闹钟:用户通过 sysfs 写入属性
auto_restart_after,例如命令echo 30,表示约 30 秒后重启。驱动读取 RTC 当前秒数,将「当前秒数 + 设定秒数」写入 RTC 闹钟寄存器并使能闹钟。RTC 闹钟中断:到点后 RTC 硬件比较秒计数与闹钟时间,拉高中断。内核 RTC 驱动在中断处理函数中清除中断、通过
rtc_update_irq()通知用户态/dev/rtc0,并提交一个延迟工作项。工作队列中触发重启:在工作函数中,若此前已通过
auto_restart_after设定了定时重启,则调用orderly_reboot();若超时未重启则再调用kernel_restart(NULL)。orderly_reboot:先尝试执行
/sbin/reboot,给用户态做收尾(sync、脚本等);若执行失败则直接调用kernel_restart(NULL)。kernel_restart:进入重启准备阶段(通知链、设备 shutdown、迁移到重启 CPU、系统核心关闭、内核日志 dump),最后调用架构相关的
machine_restart()。machine_restart(ARM64):关中断、停止其它 CPU,再调用
do_kernel_restart()。do_kernel_restart:调用所有通过
register_restart_handler()注册的重启回调。Cvitek 重启回调:在
cvi-reboot.c中注册的cvi_restart_handler()写 RTC 域寄存器请求温复位(RTC_EN_WARM_RST_REQ),等待状态就绪后写 RTC_CTRL0 触发 SoC 内部 warm reset。CPU 从 BootROM/固件重新启动,整机不断电,完成软重启。
2.6.3. 关键点小结¶
RTC 闹钟:提供“到点触发”,用 RTC 当前秒数 + N 秒设闹钟,与系统时间解耦。
orderly_reboot:先执行
/sbin/reboot做用户态收尾,失败则直接kernel_restart。kernel_restart:关设备、迁 CPU、dump 日志,再交给架构的
machine_restart。cvi_restart_handler:Cvitek SoC 的软复位实现,通过写 RTC 域 WARM_RST_REQ 由硬件做 CPU/SoC 温复位,电源保持供电。