《DNESP32P4开发指南_V1.0》第二十五章 RTC实验

第二十五章 RTC实验

本章将学习如何使用ESP32-P4的系统时间,实现一个简单的实时时钟。
本章分为如下几个小节:
25.1 RTC介绍
25.2 硬件设计
25.3 程序设计
25.4 下载验证

25.1 RTC介绍
RTC,Real Time Clock,实时时钟,专门用来记录时间的。
在ESP32-P4中,并没有像STM32芯片一样,具有RTC外设,但是存在一个系统时间,利用系统时间,也可以实现实时时钟的功能效果。
ESP32-P4使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为RTC定时器和高分辨率定时器。
RTC定时器:RTC定时器在任何睡眠模式下及在任何复位后均可保持系统时间(上电复位除外,因为上电复位会重置RTC定时器)。时钟频率偏差取决于RTC定时器时钟源,该偏差只会在水面模式下影响时间精度。睡眠模式下,时间分辨率为6.667us。
高分辨率定时器:高分辨率定时器在睡眠模式下及在复位后不可用,但其时间精度更高。该定时器使用APB_CLK时钟源(通常为80MHz),时钟频率偏差小于±10ppm,时间分辨率为1us。
默认情况下,是使用这两种定时器。

25.2 硬件设计
25.2.1 例程功能
通过LCD屏实时显示RTC时间,包括年、月、日、时、分、秒等信息。

25.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)RGBLCD / MIPILCD(引脚太多,不罗列出来)

25.2.3 原理图
RTC使用ESP32-P4内部定时器实现,而定时器属于片上资源,因此没有对应的连接原理图。

25.3 程序设计
25.3.1 RTC的驱动接口
RTC数据的获取,可通过使用POSIX函数或使用标准C库函数。使用时间相关功能函数,必须先导入以下头文件:

#include <sys/time.h>

接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:
1,获取时间函数time
该函数用于从系统中获取时间,其函数原型如下:

time_t time(time_t *_Time);

函数形参:

QQ截图20260602094602

表25.3.1.1 time函数形参描述

函数返回值:
成功时,返回time_t类型的当前日历时间。错误时,返回-1。
注意:time_t类型最终是long int类型,这个会跟运行硬件平台相关,库中也有很多平台相关的编译开关。
2,得到struct tm格式及本地时间表达的日历时间函数localtime
该函数用于获取struct tm格式的时间,其函数原型如下:

struct tm localtime(const time_t *_Time);

函数形参:

QQ截图20260602094612

表25.3.1.2 tm_localtime函数形参描述

函数返回值:
成功时为指向内部静态struct tm对象的指针,否则为NULL。
struct tm结构体其定义如下:

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 */
  int tm_year;		/* 自1900年起的年数 */
  int tm_wday;		/* 一周中的第几天,范围从0到6 */
  int tm_yday;		/* 一年中的第几天,范围从0到365 */
  int tm_isdst;		/* 夏令时 */
};

3,得到总秒数函数mktime
该函数得到总秒数,其函数原型如下:

time_t mktime(struct tm *_Tm);

函数形参:

QQ截图20260602094631

表25.3.1.3 mktime函数形参描述

函数返回值:
返回自1970年1月1日以来持续时间的秒数,发生错误时返回-1。
4,设置当前时间函数settimeofday
该函数会把时间设置成由tv所指向的信息,当地时区信息则设置成tz,其函数原型如下:

int settimeofday(const struct timeval *tv, const struct timezone *tz);

函数形参:

QQ截图20260602094643

表25.3.1.4 settimeofday函数形参描述

函数返回值:
成功返回0,失败返回-1。
tv是指向struct timeval结构体指针。struct timeval结构体中各个成员,如下代码所示:

struct timeval {
    time_t      tv_sec;     /* 秒 */
    suseconds_t tv_usec;    /* 微妙 */
};

注意:获取时区tz信息依赖于系统,有的系统无法获取,所以一般调用时该函数第二个参数为NULL。对于ESP32-P4而言,该函数就是更新内部定时器时间。
25.3.2 程序流程图

image002

图25.3.2.1 RTC实验程序流程图

25.3.3 程序解析
在16_rtc例程中,作者在16_rtc \components\BSP路径下新建了1个文件夹RTC,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。

  1. RTC驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RTC驱动源码包括两个文件:esp_rtc.c和esp_rtc.h。
    下面先解析esp_rtc.h的程序。定义了一个_calendar_obj结构体类型,其余都是函数声明,代码如下:
/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    /* 公历年月日周 */
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
} _calendar_obj;

该结构体类型将会被用来存放时钟的年月日时分秒等信息。在esp_rtc.c文件中定义了_calendar_obj结构体类型的变量calendar,方便其他函数调用。
接下来,解析一下esp_rtc.c的程序,首先来看一下设置时间函数rtc_set_time,代码如下:

/**
 * @brief     	RTC设置时间
 * @param     	year 	:年
 * @param     	mon 	:月
 * @param    	mday    :日
 * @param     	hour    :时
 * @param     	min     :分
 * @param     	sec     :秒
 * @retval    	无
 */
void rtc_set_time(int year, int mon, int mday, int hour, int min, int sec)
{
    struct tm time_set; /* 存放时间的各个量(年月日时分秒)结构体 */
    time_t second;      /* 存放时间的总秒数 */

    time_set.tm_year  = year - 1900;
    time_set.tm_mon   = mon - 1;
    time_set.tm_mday  = mday;
    time_set.tm_hour  = hour;
    time_set.tm_min   = min;
    time_set.tm_sec   = sec;
    time_set.tm_isdst = -1;

    second = mktime(&time_set);		/* 将时间转换为自1970年1月1日以来持续时间秒数 */

    struct timeval val = { 
        .tv_sec = second, 
        .tv_usec = 0 
    };

    settimeofday(&val, NULL);  		/* 设置当前时间 */
}

在rtc_set_time函数中,首先定义struct tm结构体类型的变量time_set,根据要设置的时间对其成员进行赋值,这里需要注意成员的取值范围,然后调用mktime函数将time_set存放的时间信息转换为总秒数,最后通过settimeofday函数设置当前时间。
接下来看一下获取当前时间函数rtc_get_time,代码如下

/**
 * @brief    	获取当前的时间
 * @param    	无
 * @retval  	无
 */
void rtc_get_time(void)
{
    struct tm *time_block;  /* 存放时间的各个量(年月日时分秒)结构体 */
    time_t second;          /* 存放时间的总秒数 */

    time(&second);						/* 获取当前的系统时间总秒数 */
    time_block = localtime(&second); 	/* 把总秒数格式化存放到struct tm变量 */

    /* 公历年 月 日 星期 时 分 秒 */
    calendar.year  = time_block->tm_year + 1900;    /* 年(从1900开始) */
    calendar.month = time_block->tm_mon  + 1;       /* 月(从0(1月)到11(12月)) */
    calendar.date  = time_block->tm_mday;           /* 日(从1到31) */
    calendar.week  = time_block->tm_wday;           /* 周几(从0(周日)到6(周六)) */
    calendar.hour  = time_block->tm_hour;           /* 时(从0到23) */
    calendar.min   = time_block->tm_min;            /* 分(从0到59) */
    calendar.sec   = time_block->tm_sec;            /* 秒(从0到59) */
}

在rtc_get_time函数中,首先通过time函数获取当前系统时间换算成的总秒数,然后通过localtime函数得到struct tm结构体类型的数据,同时更新到calendar结构体变量中,方便其他函数调用。
2. CMakeLists.txt文件
本例程的功能实现主要依靠RTC驱动。要在main函数中,成功调用RTC文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:

set(src_dirs
           	LED
			LCD
           	RTC)

set(include_dirs
          	LED
			LCD
           	RTC)

set(requires
           	driver
           	esp_lcd
			esp_common
           	newlib)

idf_component_register(	SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
  1. main.c驱动代码
    在main.c里面编写如下代码。
/* 定义字符数组用于显示星期 */
char *weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", 
"Friday", "Saturday"};

void app_main(void)
{
    esp_err_t ret;
    uint8_t tbuf[40];
    uint8_t t = 0;

    ret = nvs_flash_init();   	/* 初始化NVS */
    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    }

    led_init();     			/* LED初始化 */
    lcd_init();     			/* LCD屏初始化 */

    rtc_set_time(2025, 1, 1, 8, 8, 00);   /* 设置RTC时间 */

    lcd_show_string(10, 50,  200, 16, 16, "ESP32-P4", RED);
    lcd_show_string(10, 70,  200, 16, 16, "RTC TEST", RED);
    lcd_show_string(10, 90,  200, 16, 16, "ATOM@ALIENTEK", RED);

    while (1)
    {
        t++;

        if ((t % 10) == 0)      /* 每100ms更新一次显示数据 */
        {
            rtc_get_time();
            sprintf((char *)tbuf, "Time:%02d:%02d:%02d", calendar.hour,
                                   calendar.min, calendar.sec);
            lcd_show_string(10, 130, 210, 16, 16, (char *)tbuf,BLUE);
            sprintf((char *)tbuf, "Date:%04d-%02d-%02d", calendar.year,
                                   calendar.month, calendar.date);
            lcd_show_string(10, 150, 210, 16, 16, (char *)tbuf,BLUE);
            sprintf((char *)tbuf, "Week:%s", weekdays[calendar.week]);
            lcd_show_string(10, 170, 210, 16, 16, (char *)tbuf,BLUE);
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();
        }

        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

在app_main函数中,首先通过rtc_set_time函数设置时间,然后在循环中,每隔10毫秒调用rtc_get_time函数,然后调用lcd_show_string函数对时间信息进行显示。LED0闪烁,以提示程序正在运行。

25.4 下载验证
将程序下载到开发板后,可以看到LED0不停闪烁,提示程序已经在运行了,可看到LCD显示的内容如图25.4.1所示:

image003

图25.4.1 RTC实验程序运行效果图

posted @ 2026-06-02 10:31  正点原子  阅读(40)  评论(0)    收藏  举报