超级版主
 
- 积分
- 5280
- 金钱
- 5280
- 注册时间
- 2019-5-8
- 在线时间
- 1347 小时
|
|
第二十五章 RTC实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章将学习如何使用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库函数。使用时间相关功能函数,必须先导入以下头文件:
接下来,作者将介绍一些常用的函数,这些函数的描述及其作用如下:
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结构体类型,其余都是函数声明,代码如下:
- <font size="3">/* 时间结构体, 包括年月日周时分秒等信息 */</font>
- <font size="3">typedef struct</font>
- <font size="3">{</font>
- <font size="3"> uint8_t hour; /* 时 */</font>
- <font size="3"> uint8_t min; /* 分 */</font>
- <font size="3"> uint8_t sec; /* 秒 */</font>
- <font size="3"> /* 公历年月日周 */</font>
- <font size="3"> uint16_t year; /* 年 */</font>
- <font size="3"> uint8_t month; /* 月 */</font>
- <font size="3"> uint8_t date; /* 日 */</font>
- <font size="3"> uint8_t week; /* 周 */</font>
- <font size="3">} _calendar_obj;</font>
复制代码 该结构体类型将会被用来存放时钟的年月日时分秒等信息。在esp_rtc.c文件中定义了_calendar_obj结构体类型的变量calendar,方便其他函数调用。
接下来,解析一下esp_rtc.c的程序,首先来看一下设置时间函数rtc_set_time,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief RTC设置时间</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] year :年</font>
- <font size="3"> * @param mon :月</font>
- <font size="3"> * @param mday :日</font>
- <font size="3"> * @param hour :时</font>
- <font size="3"> * @param min :分</font>
- <font size="3"> * @param sec :秒</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void rtc_set_time(int year, int mon, int mday, int hour, int min, int sec)</font>
- <font size="3">{</font>
- <font size="3"> struct tm time_set; /* 存放时间的各个量(年月日时分秒)结构体 */</font>
- <font size="3"> time_t second; /* 存放时间的总秒数 */</font>
- <font size="3"> time_set.tm_year = year - 1900;</font>
- <font size="3"> time_set.tm_mon = mon - 1;</font>
- <font size="3"> time_set.tm_mday = mday;</font>
- <font size="3"> time_set.tm_hour = hour;</font>
- <font size="3"> time_set.tm_min = min;</font>
- <font size="3"> time_set.tm_sec = sec;</font>
- <font size="3"> time_set.tm_isdst = -1;</font>
- <font size="3"> second = mktime(&time_set); /* 将时间转换为自1970年1月1日以来持续时间秒数 */</font>
- <font size="3"> struct timeval val = { </font>
- <font size="3"> .tv_sec = second, </font>
- <font size="3"> .tv_usec = 0 </font>
- <font size="3"> };</font>
- <font size="3"> settimeofday(&val, NULL); /* 设置当前时间 */</font>
- <font size="3">}</font>
复制代码 在rtc_set_time函数中,首先定义struct tm结构体类型的变量time_set,根据要设置的时间对其成员进行赋值,这里需要注意成员的取值范围,然后调用mktime函数将time_set存放的时间信息转换为总秒数,最后通过settimeofday函数设置当前时间。
接下来看一下获取当前时间函数rtc_get_time,代码如下
- <font size="3">/**</font>
- <font size="3"> * @brief 获取当前的时间</font>
- <font size="3"> * @param 无</font>
- <font size="3"> * @retval 无</font>
- <font size="3"> */</font>
- <font size="3">void rtc_get_time(void)</font>
- <font size="3">{</font>
- <font size="3"> struct tm *time_block; /* 存放时间的各个量(年月日时分秒)结构体 */</font>
- <font size="3"> time_t second; /* 存放时间的总秒数 */</font>
- <font size="3"> time(&second); /* 获取当前的系统时间总秒数 */</font>
- <font size="3"> time_block = localtime(&second); /* 把总秒数格式化存放到struct tm变量 */</font>
- <font size="3"> /* 公历年 月 日 星期 时 分 秒 */</font>
- <font size="3"> calendar.year = time_block->tm_year + 1900; /* 年(从1900开始) */</font>
- <font size="3"> calendar.month = time_block->tm_mon + 1; /* 月(从0(1月)到11(12月)) */</font>
- <font size="3"> calendar.date = time_block->tm_mday; /* 日(从1到31) */</font>
- <font size="3"> calendar.week = time_block->tm_wday; /* 周几(从0(周日)到6(周六)) */</font>
- <font size="3"> calendar.hour = time_block->tm_hour; /* 时(从0到23) */</font>
- <font size="3"> calendar.min = time_block->tm_min; /* 分(从0到59) */</font>
- <font size="3"> calendar.sec = time_block->tm_sec; /* 秒(从0到59) */</font>
- <font size="3">}</font>
复制代码 在rtc_get_time函数中,首先通过time函数获取当前系统时间换算成的总秒数,然后通过localtime函数得到struct tm结构体类型的数据,同时更新到calendar结构体变量中,方便其他函数调用。
2. CMakeLists.txt文件
本例程的功能实现主要依靠RTC驱动。要在main函数中,成功调用RTC文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件,修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> RTC)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> LCD</font>
- <font size="3"> RTC)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver</font>
- <font size="3"> esp_lcd</font>
- <font size="3"> esp_common</font>
- <font size="3"> newlib)</font>
- <font size="3">idf_component_register( SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码 3. main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">/* 定义字符数组用于显示星期 */</font>
- <font size="3">char *weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", </font>
- <font size="3">"Friday", "Saturday"};</font>
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> uint8_t tbuf[40];</font>
- <font size="3"> uint8_t t = 0;</font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3"> if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* LED初始化 */</font>
- <font size="3"> lcd_init(); /* LCD屏初始化 */</font>
- <font size="3"> rtc_set_time(2025, 1, 1, 8, 8, 00); /* 设置RTC时间 */</font>
- <font size="3"> lcd_show_string(10, 50, 200, 16, 16, "ESP32-P4", RED);</font>
- <font size="3"> lcd_show_string(10, 70, 200, 16, 16, "RTC TEST", RED);</font>
- <font size="3"> lcd_show_string(10, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);</font>
- <font size="3"> while (1)</font>
- <font size="3"> {</font>
- <font size="3"> t++;</font>
- <font size="3"> if ((t % 10) == 0) /* 每100ms更新一次显示数据 */</font>
- <font size="3"> {</font>
- <font size="3"> rtc_get_time();</font>
- <font size="3"> sprintf((char *)tbuf, "Time:%02d:%02d:%02d", calendar.hour,</font>
- <font size="3"> calendar.min, calendar.sec);</font>
- <font size="3"> lcd_show_string(10, 130, 210, 16, 16, (char *)tbuf,BLUE);</font>
- <font size="3"> sprintf((char *)tbuf, "Date:%04d-%02d-%02d", calendar.year,</font>
- <font size="3"> calendar.month, calendar.date);</font>
- <font size="3"> lcd_show_string(10, 150, 210, 16, 16, (char *)tbuf,BLUE);</font>
- <font size="3"> sprintf((char *)tbuf, "Week:%s", weekdays[calendar.week]);</font>
- <font size="3"> lcd_show_string(10, 170, 210, 16, 16, (char *)tbuf,BLUE);</font>
- <font size="3"> }</font>
- <font size="3"> if ((t % 20) == 0)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> }</font>
- <font size="3"> vTaskDelay(pdMS_TO_TICKS(10));</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 在app_main函数中,首先通过rtc_set_time函数设置时间,然后在循环中,每隔10毫秒调用rtc_get_time函数,然后调用lcd_show_string函数对时间信息进行显示。LED0闪烁,以提示程序正在运行。
25.4 下载验证
将程序下载到开发板后,可以看到LED0不停闪烁,提示程序已经在运行了,可看到LCD显示的内容如图25.4.1所示:
图25.4.1 RTC实验程序运行效果图 |
|