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);
    }
}
posted @ 2025-03-19 22:06  星光映梦  阅读(676)  评论(0)    收藏  举报