《ESP32-P4开发指南—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 

       接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:


       1,获取时间函数time

       该函数用于从系统中获取时间,其函数原型如下:

time_t time(time_t *_Time);

       函数形参:


表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);

       函数形参:


表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);

       函数形参:


表25.3.1.3 mktime函数形参描述


       函数返回值:

       返回自1970年1月1日以来持续时间的秒数,发生错误时返回-1。


       4,设置当前时间函数settimeofday

       该函数会把时间设置成由tv所指向的信息,当地时区信息则设置成tz,其函数原型如下:

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

       函数形参:


表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 程序流程图


图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)


       3. 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所示:


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


请使用浏览器的分享功能分享到微信等