11. RTC实时时钟
一、RTC时钟简介
RTC(实时时钟)是指安装在电子设备或实现其功能的 IC(集成电路)上的时钟。RTC 实时时钟能为系统提供一个准确的时间,即时系统复位或主电源断电,RTC 依然能够运行,因此 RTC 也经常用于各种低功耗场景。
通常,RTC 配备一个单独分离的电源,如纽扣电池(备用电池),即使开发板电源关闭,它也能保持运作,随时可以实时显示时间。然后,当开发板再次打开时,计算机内置的定时器时钟从 RTC 读取当前时间,并在此基础上供电的同时,时间在其自身机制下显示。
在 ESP32 S3 中,并没有像 STM32 芯片一样,具有 RTC 外设,但是存在一个系统时间,利用系统时间,也可以实现实时时钟的功能效果。
ESP32 S3 使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为 RTC 定时器和高分辨率定时器。默认情况下,是使用这两种定时器。
二、时间日期常用的函数
由于 ESP32 并未给出 RTC 相关的 API 函数,因此,我们这里调用 C 库中的一些函数来配置 RTC 时钟。
2.1、获取时间戳
我们使用 time() 函数来 获取自 1970-01-01 00:00:00 经历的秒数,其函数原型如下:
/**
* @brief 获取自1970-01-01 00:00:00经历的秒数
*
* @param _timer 保存经历的秒数
* @return time_t 返回经历的秒数
*/
time_t time(time_t *_timer);
2.2、获取当前时间
我们使用 localtime() 函数用于 获取当前时间,其函数原型如下所示:
/**
* @brief 获取当前时间和日期并转换为本地时间
*
* @param _timer 时间戳
* @return struct tm* 保存时间日期的结构体
*/
struct tm *localtime(const time_t *_timer);
该函数的返回的结构体空间由内核自动分配,并且不要手动去释放它。它的定义如下:
struct tm
{
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代表一月
int tm_year; // 年,其值等于实际年份减去1900
int tm_wday; // 星期,取值区间为[0,6],其中0代表星期天,1代表星期一,剩下的一次类推
int tm_yday; // 天数,取值区间为[0,365],其中0代表1月1日,1代表1月2日,剩下的一次类推
int tm_isdst; // 夏令时标识符,值大于零表示当前使用了夏令时,等于零表示当前未使用夏令时,小于零则表示该信息不可用
#ifdef __TM_GMTOFF
long __TM_GMTOFF;
#endif
#ifdef __TM_ZONE
const char *__TM_ZONE;
#endif
};
2.3、设置当前时间
我们可以使用 settimeofday() 函数 设置当前时间,其函数原型如下:
/**
* @brief 将目前时间设成tv所指的结构信息,当地时区信息则设成tz所指的结构。
*
* @param tv 当前时间的结构信息
* @param tz 当前时区的结构信息
* @return int 成功执行时,返回0。失败返回-1
*/
int settimeofday(const struct timeval *tv, const struct timezone *tz);
形参 tv 是 当前时间的结构体指针,如果不关心此信息,可以传入 NULL。
struct timeval
{
time_t tv_sec; // 秒数
suseconds_t tv_usec; // 微秒数
};
形参 tz 是 当前时区的结构体指针,如果不关心此信息,可以传入 NULL。
struct timezone
{
int tz_minuteswest; // 格林威治时区
int tz_dsttime; // 夏令时
};
2.4、将当前时间转换为时间戳
我们可以使用 mktime() 函数 将当前时间转换为时间戳,其函数原型如下:
/**
* @brief 将当前时间转换为时间戳
*
* @param _timeptr 保存时间日期的结构体
* @return time_t 自1970-01-01 00:00:00经历的秒数
*/
time_t mktime(struct tm *_timeptr);
三、实验例程
这里,我们在【components】文件夹下的【peripheral】文件夹下的【inc】文件夹(用来存放头文件)新建一个 bsp_rtc.h 文件,在【components】文件夹下的【peripheral】文件夹下的【src】文件夹(用来存放源文件)新建一个 bsp_rtc.c 文件。
#ifndef __BSP_RTC_H__
#define __BSP_RTC_H__
#include <stdint.h>
#include <sys/time.h>
/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct Calendar_t
{
uint8_t hour;
uint8_t min;
uint8_t sec;
uint16_t year;
uint8_t month;
uint8_t date;
uint8_t week;
}calendar_t;
void bsp_rtc_set_date_time(int year, int month, int day, int hour, int min, int sec);
calendar_t bsp_rtc_get_date_time(calendar_t *calendar);
#endif // !__BSP_RTC_H__
#include "bsp_rtc.h"
/**
* @brief 设置RTC的时间
*
* @param year 年
* @param month 月
* @param day 日
* @param hour 时
* @param min 分
* @param sec 秒
*/
void bsp_rtc_set_date_time(int year, int month, int day, int hour, int min, int sec)
{
time_t total_seconds = 0;
struct tm date_time = {0};
struct timeval time_value = {0};
date_time.tm_year = year - 1900;
date_time.tm_mon = month - 1;
date_time.tm_mday = day;
date_time.tm_hour = hour;
date_time.tm_min = min;
date_time.tm_sec = sec;
// 该值大于零表示当前使用了夏令时,等于零表示当前未使用夏令时,小于零则表示该信息不可用
date_time.tm_isdst = -1;
// 获取自1970年1月1日0时0分0秒(UTC/GMT 时间)到当前时间的总秒数
total_seconds = mktime(&date_time);
time_value.tv_sec = total_seconds;
// 设置当前时间
settimeofday(&time_value, NULL);
}
/**
* @brief 获取RTC的时间
*
* @param calendar 时间结构体指针
* @return calendar_t 时间结构体
*/
calendar_t bsp_rtc_get_date_time(calendar_t *calendar)
{
struct tm *date_time;
time_t total_seconds;
// 返回自(1970.1.1 00:00:00 UTC)经过的时间(秒)
time(&total_seconds);
// 将时间戳转换为tm结构体
date_time = localtime(&total_seconds);
if (calendar == NULL)
{
calendar_t temp_calendar =
{
.year = date_time->tm_year + 1900,
.month = date_time->tm_mon + 1,
.date = date_time->tm_mday,
.hour = date_time->tm_hour,
.min = date_time->tm_min,
.sec = date_time->tm_sec,
.week = date_time->tm_wday,
};
return temp_calendar;
}
calendar->year = date_time->tm_year + 1900;
calendar->month = date_time->tm_mon + 1;
calendar->date = date_time->tm_mday;
calendar->hour = date_time->tm_hour;
calendar->min = date_time->tm_min;
calendar->sec = date_time->tm_sec;
calendar->week = date_time->tm_wday;
return *calendar;
}
修改【main】文件夹下的 main.c 文件。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "bsp_rtc.h"
// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{
calendar_t calendar;
bsp_rtc_set_date_time(2025, 3, 8, 10, 30, 15);
while (1)
{
bsp_rtc_get_date_time(&calendar);
printf("date: %4d-%2d-%2d, week: %d\r\n", calendar.year, calendar.month, calendar.date, calendar.week);
printf("time: %2d:%2d:%2d\r\n", calendar.hour, calendar.min, calendar.sec);
vTaskDelay(1000);
}
}

浙公网安备 33010602011771号