跳到主要内容

RTC

在本章中,我们将学习如何读写 RTC 的日期和时间,以及如何实现系统校时。注意:只有 RV1106 内置 RTC,RV1103 无内置 RTC

示例程序:Code.zip

1.RTC

RTC(Real-Time Clock, 实时时钟)是一种能在微控制器或嵌入式系统掉电情况下仍能持续计时的硬件模块。它通常配备独立持久电源供应源,如常见的纽扣电池,以确保即使主电源断开,也能保持时间和日期的准确性。这一特性使得RTC在众多依赖于实时可靠时间戳的应用场景中扮演了关键角色,诸如数据记录、设备唤醒定时以及保持系统时间同步等。

2.RTC读写应用程序

2.1 读写RTC(Shell)

在 Linux 操作系统环境中,与 RTC 进行交互通常依赖于特定的系统命令和接口。以下详细介绍了如何运用 Shell 命令来实现 RTC 时间的读取与设置:

2.1.1 读取RTC时间

Linux 系统中内置了 hwclock 工具,用于便捷地读取 RTC 硬件时钟提供的当前时间和日期。只需执行如下命令,即可展示 RTC 上的精确时间:

hwclock --show

该命令将输出类似于以下格式的时间信息:

Sat Jan 13 12:00:00 2024  0.000000 seconds

2.1.2 设置RTC时间

若要将当前系统时间同步到RTC,可使用 hwclock 命令的 --systohc 选项完成这一操作:

hwclock --systohc

反之,如果需要从 RTC 获取时间并同步到系统时间,可以使用 --hctosys 选项:

hwclock --hctosys

2.1.3 校准RTC时间

对于需要对 RTC 时间进行校准的情况,首要步骤是确保系统时间的准确性,然后将已校准的系统时间同步到 RTC。具体操作流程请参阅"系统校时"部分说明。

2.2 读写RTC(Python程序)

Python 程序通过调用外部 hwclock 命令与系统交互,提供了获取和设置 RTC 时间的功能。

import subprocess

def get_rtc_time():
try:
result = subprocess.run(['hwclock', '-r'], capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error in get_rtc_time: {e}")
return None

def set_rtc_time():
try:
subprocess.run(['hwclock', '-w'], check=True)
print("RTC time set successfully.")
except subprocess.CalledProcessError as e:
print(f"Error in set_rtc_time: {e}")

rtc_time = get_rtc_time()
if rtc_time:
print(f"RTC time: {rtc_time}")

set_rtc_time()

下面对代码进行解析:

2.2.1 读取RTC时间

get_rtc_time() 函数通过运行 hwclock -r 命令获取 RTC 的时间

def get_rtc_time():
try:
result = subprocess.run(['hwclock', '-r'], capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error in get_rtc_time: {e}")
return None

2.2.2 设置RTC时间

set_rtc_time() 函数通过运行 hwclock -w 命令设置 RTC 的时间为系统当前时间

def set_rtc_time():
try:
subprocess.run(['hwclock', '-w'], check=True)
print("RTC time set successfully.")
except subprocess.CalledProcessError as e:
print(f"Error in set_rtc_time: {e}")

2.2.3 运行程序

  1. 使用 nano 工具在终端创建 py 文件,粘贴并保存 python 程序

    # nano rtc.py
  2. 运行程序

    # python3 rtc.py
  3. 实验现象

    输出获取到的 RTC 时间,同步系统时间到 RTC:

2.3 读写RTC(C程序)

在前文中,我们介绍了如何使用 shell 工具、Python 程序与 RTC 进行交互。而在实际的编程实践中,我们可以通过调用 C 库函数或系统调用来直接读取和设置 RTC。对于嵌入式 Linux 环境,访问 RTC 通常涉及打开并操作 /dev/rtc/dev/rtc0 设备文件。请注意,为了在特定的嵌入式系统上运行程序,通常需要使用交叉编译工具来编译代码,以生成可在目标开发板上执行的可执行文件。接下来,让我们一起探讨具体的实施步骤。

2.3.1 读取RTC时间

通过调用该函数,实现读取 RTC 时间。

int rtc_get_time(struct tm *time) {
int ret,fd;
struct rtc_time rtc_tm;

fd=open("/dev/rtc", O_RDONLY);
if(fd == -1) {
perror("error open /dev/rtc");
return -1;
}

ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
if(ret == -1) {
perror("error RTC_RD_TIME ioctl");
close(fd);
return -1;
}
if (!time) {
printf("time is NULL\n");
close(fd);
return -1;
}
time->tm_sec = rtc_tm.tm_sec;
time->tm_min = rtc_tm.tm_min;
time->tm_hour = rtc_tm.tm_hour;
time->tm_mday = rtc_tm.tm_mday;
time->tm_mon = rtc_tm.tm_mon;
time->tm_year = rtc_tm.tm_year;

close(fd);
return 0;
}

下面对函数进行解析:

打开RTC设备

以只读模式打开 RTC 设备文件(/dev/rtc

fd=open("/dev/rtc", O_RDONLY);
if(fd == -1) {
perror("error open /dev/rtc");
return -1;
}

读取RTC时间

调用 ioctl 函数从打开的设备文件中读取 RTC 时间

ret = ioctl(fd, RTC_RD_TIME, &rtc_tm);
if(ret == -1) {
perror("error RTC_RD_TIME ioctl");
close(fd);
return -1;
}

存储RTC时间

将读取到的 RTC 时间的值赋给传入的参数 time

time->tm_sec = rtc_tm.tm_sec;
time->tm_min = rtc_tm.tm_min;
time->tm_hour = rtc_tm.tm_hour;
time->tm_mday = rtc_tm.tm_mday;
time->tm_mon = rtc_tm.tm_mon;
time->tm_year = rtc_tm.tm_year;

2.3.2 设置RTC时间

通过调用该函数,实现设置 RTC 时间。

int rtc_set_time(struct tm *time) {

int fd, ret;
struct rtc_time rtc_tm;

if (!time) {
printf("time is NULL\n");
return -1;
}

fd = open("/dev/rtc",O_RDWR);
if(fd == -1) {
perror("error open /dev/rtc");
return -1;
}

rtc_tm.tm_sec = time->tm_sec ;
rtc_tm.tm_min = time->tm_min ;
rtc_tm.tm_hour = time->tm_hour ;
rtc_tm.tm_mday = time->tm_mday ;
rtc_tm.tm_mon = time->tm_mon - 1;
rtc_tm.tm_year = time->tm_year - 1900;

ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
if(ret == -1) {
perror("error RTC_SET_TIME ioctl");
close(fd);
return -1;
}

close(fd);
return 0;
}

下面对函数进行解析:

打开RTC设备

以读写模式打开 RTC 设备文件(/dev/rtc

fd = open("/dev/rtc",O_RDWR);
if(fd == -1) {
perror("error open /dev/rtc");
return -1;
}

设置RTC时间

将传入的参数 time 的值赋给rtc_tm,准备进行 RTC 时间的设置

rtc_tm.tm_sec  = time->tm_sec ;
rtc_tm.tm_min = time->tm_min ;
rtc_tm.tm_hour = time->tm_hour ;
rtc_tm.tm_mday = time->tm_mday ;
rtc_tm.tm_mon = time->tm_mon - 1;
rtc_tm.tm_year = time->tm_year - 1900;

调用 ioctl 函数设置RTC时间

ret = ioctl(fd, RTC_SET_TIME, &rtc_tm);
if(ret == -1) {
perror("error RTC_SET_TIME ioctl");
close(fd);
return -1;
}

2.3.3 主函数

主函数首先获取当前 RTC 时间,然后设置指定的 RTC 时间

int main() {
int ret;
struct tm *input_time = NULL;
struct tm *output_time = NULL;
input_time = (struct tm *)malloc(sizeof(struct tm));
if (!input_time) {
printf("Malloc input struct tm fail!\n");
return -1;
}

output_time = (struct tm *)malloc(sizeof(struct tm));
if (!output_time) {
printf("Malloc output struct tm fail!\n");
return -1;
}
memset(input_time, 0, sizeof(struct tm));
memset(output_time, 0, sizeof(struct tm));

ret = rtc_get_time(output_time);
if (ret)
printf("rtc_get_time fail!\n");
else
printf("GET RTC TIME : %04d-%02d-%02d,%02d:%02d:%02d\n",
output_time->tm_year + 1900,output_time->tm_mon + 1, output_time->tm_mday,
output_time->tm_hour, output_time->tm_min, output_time->tm_sec);

input_time->tm_year = 2024;
input_time->tm_mon = 1;
input_time->tm_mday = 13;
input_time->tm_hour = 12;
input_time->tm_min = 0;
input_time->tm_sec = 0;

ret = rtc_set_time(input_time);
if (ret)
printf("rtc_set_time fail!\n");
else
printf("SET RTC TIME : %04d-%02d-%02d,%02d:%02d:%02d\n",input_time->tm_year, input_time->tm_mon,
input_time->tm_mday, input_time->tm_hour, input_time->tm_min, input_time->tm_sec);

free(input_time);
free(output_time);
return 0;
}

下面对函数进行解析:

读取RTC时间

通过调用 rtc_get_time() 函数实现读取 RTC 时间,并格式化输出读取到的时间

ret = rtc_get_time(output_time);
if (ret)
printf("rtc_get_time fail!\n");
else
printf("GET RTC TIME : %04d-%02d-%02d,%02d:%02d:%02d\n",
output_time->tm_year + 1900,output_time->tm_mon + 1, output_time->tm_mday,
output_time->tm_hour, output_time->tm_min, output_time->tm_sec);

设置RTC时间

通过调用 rtc_set_time() 函数实现设置 RTC 时间,并格式化输出设置时间

ret = rtc_set_time(input_time);
if (ret)
printf("rtc_set_time fail!\n");
else
printf("SET RTC TIME : %04d-%02d-%02d,%02d:%02d:%02d\n",input_time->tm_year, input_time->tm_mon,
input_time->tm_mday, input_time->tm_hour, input_time->tm_min, input_time->tm_sec);

2.3.4 交叉编译

  1. 指定交叉编译工具

    首先,我们要将交叉编译工具的路径添加到系统的 PATH 环境变量中,以便可以在任何地方使用交叉编译工具,您可以在shell配置文件中添加以下行(通常是 ~/.bashrc~/.bash_profile~/.zshrc,具体取决于您使用的shell),注意 PATH= 后的路径为交叉编译工具所在的目录。

    • gcc路径

      <SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
    • 打开shell配置文件

      vi ~/.bashrc 
    • 将交叉编译工具的路径添加到系统的PATH环境变量中,将 <SDK Directory> 修改为自己的 SDK 路径,如 /home/luckfox/luckfox-pico/

      export PATH=<SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:$PATH
    • 重新加载shell配置文件,使更改生效

      source ~/.bashrc  
  2. 使用交叉编译工具编译程序

    arm-rockchip830-linux-uclibcgnueabihf-gcc rtc.c -o rtc
  3. 交叉编译成功后,将在当前目录下生成可在开发板运行的可执行文件

    # ls
    rtc rtc.c

2.3.5 运行程序

  1. 文件传输

    先将 rtc 从虚拟机传输到 Windows,再通过 TFTP 或 ADB 传输到开发板,将文件从 Windows 通过 ADB 将文件传输到开发板的步骤如下:

    adb push 文件所在路径 开发板存储路径

    eg:(将当前目录下rtc文件传输到开发板的根目录)
    adb push rtc /
  2. 运行程序

    修改 rtc 文件的操作权限后运行程序

    # chmod +x rtc
    # ./rtc
  3. 实验现象

    获取RTC时间,设置RTC时间:

3.系统校时

下面我们介绍如何在Buildroot中添加ntpd软件包,并将ntpd配置为客户端模式以进行网络时间同步。

3.1 添加ntpd软件包

/ 搜索 "ntpd",找到 "ntp" 下的 "ntpd",按 2 进行跳转(详细步骤请参考SDK环境部署的内核配置部分)

Y 分别 "ntp" 和 "ntpd"(使能 "ntp" 后会显示 "ntpd")

3.2 重新烧录固件

  1. 编译选择分支,指定开发板型号

    luckfox@luckfox:~/luckfox-pico$ ./build.sh lunch
  2. 编译

    luckfox@luckfox:~/luckfox-pico$ ./build.sh
  3. 重新烧录固件

    编译完成后重新烧录固件

3.3 网络校时

  1. 修改时区

    打开文件

    vi /etc/profile

    添加内容

    export TZ=CST-8
  2. 重新加载配置文件

    source /etc/profile
  3. 查看当前时区

    date -R
  4. 终止ntpd进程

    # ps | grep ntpd
    204 root /usr/sbin/ntpd -g -p /var/run/ntpd.pid
    423 root grep ntpd
    # kill -9 204
  5. 网络校时

    # ntpd -p cn.ntp.org.cn -qn
    8 Dec 14:27:51 ntpd[423]: ntpd 4.2.8p15@1.3728-o Fri Dec 8 06:12:46 UTC 2023 (1): Starting
    8 Dec 14:27:51 ntpd[423]: Command line: ntpd -p cn.ntp.org.cn -qn
    8 Dec 14:27:51 ntpd[423]: ----------------------------------------------------
    8 Dec 14:27:51 ntpd[423]: ntp-4 is maintained by Network Time Foundation,
    8 Dec 14:27:51 ntpd[423]: Inc. (NTF), a non-profit 501(c)(3) public-benefit
    8 Dec 14:27:51 ntpd[423]: corporation. Support and training for ntp-4 are
    8 Dec 14:27:51 ntpd[423]: available at https://www.nwtime.org/support
    8 Dec 14:27:51 ntpd[423]: ----------------------------------------------------
    8 Dec 14:27:51 ntpd[423]: proto: precision = 1.166 usec (-20)
    8 Dec 14:27:51 ntpd[423]: basedate set to 2023-11-26
    8 Dec 14:27:51 ntpd[423]: gps base set to 2023-11-26 (week 2290)
    8 Dec 14:27:53 ntpd[423]: restrict: ignoring line 11, address/host '[::1]' unusable.
    8 Dec 14:27:53 ntpd[423]: Listen and drop on 0 v4wildcard 0.0.0.0:123
    8 Dec 14:27:53 ntpd[423]: Listen normally on 1 lo 127.0.0.1:123
    8 Dec 14:27:53 ntpd[423]: Listen normally on 2 eth0 192.168.10.148:123
    8 Dec 14:27:53 ntpd[423]: Listen normally on 3 usb0 172.32.0.93:123
    8 Dec 14:27:53 ntpd[423]: Listening on routing socket on fd #20 for interface updates
    8 Dec 14:28:00 ntpd[423]: ntpd: time slew -0.019767 s
    ntpd: time slew -0.019767s
  6. 将系统时间同步到hwclock

    hwclock --systohc
  7. 查看hwclock

    hwclock
  8. 开机启动时,从hwclock同步到系统时间

    hwclock -u -s