第十八章 RTC实验
本章,我们将介绍如何使用ESP32-S3的系统时间,实现一个简单的实时时钟。
本章分为如下几个小节:
18.1 RTC介绍
18.2 硬件设计
18.3 软件设计
18.4 下载验证
18.1 RTC介绍
RTC,Real Time Clock,实时时钟,专门用来记录时间的。
在ESP32-S3中,并没有像STM32芯片一样,具有RTC外设,但是存在一个系统时间,利用系统时间,也可以实现实时时钟的功能效果。
ESP32-S3使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为RTC定时器和高分辨率定时器。默认情况下,是使用这两种定时器。
在Arduino开发中,我们需要下载一个“ESP32Time”软件库,该库用于设置和读取ESP32-S3内部RTC时间,我们就是基于这个库进行开发RTC功能。这个库,可以在Arduino IDE中库管理搜索到,具体下载操作如下图所示。

图18.1.1 ESP32Time库下载过程
假如在线安装不成功,也可以尝试使用手动安装方法,跟16.1.2小节描述的步骤是一样的。“ESP32Time.zip”压缩包在“资料盘→6,软件资料→2,Arduino软件包”文件夹下,下载成功后,便可以在“C:\Users\用户名\ Documents\Arduino\libraries\”路径下找到该文件。
ESP32Time库提供有三个示例工程,如下图所示:

图18.1.2 ESP32Time库示例工程
最快速了解库使用方法莫过于查看示例工程了,大家可以自行查看学习,下载看现象。
在ESP32Time库中,总的来说,分为三类函数:设置时间、获取日期时间、提取日期时间的某个数据(年/月/日/时/分/秒)。
要使用该库的函数,所以需要先定义ESP32Time对象实例,操作如下:
ESP32Time rtc; ESP32Time rtc(offset); // create an instance with a specifed offset in seconds
这里有两种方式,一种是不带参数,另一种是带参数的。参数offset的意思是时间的偏移,单位为秒。这两种有什么区别?在我的理解看来,不带参数的操作属于以自我为参照,带参数的操作属于以其他为参照。若只需要显示一个时间时,就可以使用不带参数的,即“ESP32Time rtc”;若要显示多个时间,则参考的还是使用“ESP32Time rtc”进行操作,而第二个就需要使用“ESP32Time rtc1(offset)”,offset表明跟参考时间的关系。
以上的设计考虑,其实是因为:每个国家都会有自己的时间时,比如中国的北京时间,英国的伦敦时间,日本的东京时间。它们处于不同的时区,但是都可以以格林尼治时间(GMT)为参考,向东的时区以依次为GMT+1,GMT+2,……,GMT+12;向西的时区依次为GMT-1,GMT-2,……,GMT-12。中国通用的北京时间处于GMT+8。
打个比方,我们显示两个时区,rtc一开始以格林尼治时间为标准进行设置,然后rtc1设置为北京时间。
ESP32Time rtc; // 格林尼治时间(伦敦时间) ESP32Time rtc1(28800); // 北京时间,跟rtc相差8小时,8*60*60=28800秒
这样定义ESP32Time对象实例,只需要rtc通过setTime函数设置好时间,rtc1就会在它的基础上+8小时,而不需要rtc1自己设置时间。简单理解,该功能就是可以设置相对时间。
接下来介绍一下,ESP32Time库的函数,如下所示。
setTime(30, 24, 15, 17, 1, 2021); // 17th Jan 2021 15:24:30
setTime(1609459200); // 1st Jan 2021 00:00:00
setTimeStruct(time); // set with time struct
getTime() // (String) 15:24:38
getDate() // (String) Sun, Jan 17 2021
getDate(true) // (String) Sunday, January 17 2021
getDateTime() // (String) Sun, Jan 17 2021 15:24:38
getDateTime(true) // (String) Sunday, January 17 2021 15:24:38
getTimeDate() // (String) 15:24:38 Sun, Jan 17 2021
getTimeDate(true) // (String) 15:24:38 Sunday, January 17 2021
getMicros() // (unsigned long) 723546
getMillis() // (unsigned long) 723
getEpoch() // (unsigned long) 1609459200
getLocalEpoch() // (unsigned long) 1609459200
getSecond() // (int) 38 (0-59)
getMinute() // (int) 24 (0-59)
getHour() // (int) 3 (0-12)
getHour(true) // (int) 15 (0-23)
getAmPm() // (String) PM
getAmPm(true) // (String) pm
getDay() // (int) 17 (1-31)
getDayofWeek() // (int) 0 (0-6)
getDayofYear() // (int) 16 (0-365)
getMonth() // (int) 0 (0-11)
getYear() // (int) 2021
getTime("%A, %B %d %Y %H:%M:%S") //(String)returns time with specified format
通过看前面注释的说明,我们也比较清楚函数的使用了。
本例程主要用到setTime函数和getTimeStruct函数,使用方法如下:
rtc.setTime(00, 51, 17, 1, 12, 2023); /* 2023年12月1日17:52:00 */ struct tm timeinfo = rtc.getTimeStruct();
使用getTimeStruct函数,把时间信息存进timeinfo结构体。通过这个结构体就可以获取年、月、日、时、分、秒等信息,该结构体类型如下:
struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
18.2 硬件设计
1. 例程功能
通过LCD显示模块实时显示RTC时间,包括年、月、日、时、分、秒等信息。
2. 硬件资源
1)USART0
U0TXD-IO43 U0RXD-IO44
2)XL9555
IIC_SDA-IO41 IIC_SCL-IO42
3)SPILCD
CS-IO21 SCK-IO12 SDA-IO11
DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)
PWR- IO1_3(XL9555) RST- IO1_2(XL9555)
3. 原理图
RTC为ESP32-S3的片上资源,因此并没有相应的连接原理图。
18.3 软件设计
18.3.1 程序流程图
下面看看本实验的程序流程图:

图18.3.1 程序流程图
18.3.2 程序解析
1. 12_rtc.ino代码
ESP32Time库的函数已经满足了例程需求,所以没有另外写rtc.cpp和rtc.h,所以这里直接介绍12_rtc.ino文件。
在12_rtc.ino里面编写如下代码:
#include "uart.h" #include "xl9555.h" #include "spilcd.h" #include/* 需要安装ESP32Time库 */ ESP32Time rtc; uint8_t tbuf[100]; /* 存放RTC信息 */ /** * @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等 * @param 无 * @retval 无 */ void setup() { uart_init(0, 115200); /* 串口0初始化 */ xl9555_init(); /* IO扩展芯片初始化 */ lcd_init(); /* LCD初始化 */ rtc.setTime(00, 51, 17, 1, 12, 2023); /* 2023年12月1日17:52:00 */ lcd_show_string(30, 50, 200, 16, LCD_FONT_16, "ESP32-S3", RED); lcd_show_string(30, 70, 200, 16, LCD_FONT_16, "RTC TEST", RED); lcd_show_string(30, 90, 200, 16, LCD_FONT_16, "ATOM@ALIENTEK", RED); } /** * @brief 循环函数,通常放程序的主体或者需要不断刷新的语句 * @param 无 * @retval 无 */ void loop() { struct tm timeinfo = rtc.getTimeStruct(); /* 根据time.h头文件中tm结构体的定义进行调整显示 */ sprintf((char *)tbuf, "Time:%02d:%02d:%02d", timeinfo.tm_hour-1, timeinfo.tm_min, timeinfo.tm_sec); lcd_show_string(30, 130, 210, 16, LCD_FONT_16, (char *)tbuf, RED); sprintf((char *)tbuf, "Date:%04d-%02d-%02d", timeinfo.tm_year+1900, timeinfo.tm_mon+1, timeinfo.tm_mday); lcd_show_string(30, 150, 210, 16, LCD_FONT_16, (char *)tbuf, RED); sprintf((char *)tbuf, "Week:%d", timeinfo.tm_wday); lcd_show_string(30, 170, 210, 16, LCD_FONT_16, (char *)tbuf, RED); delay(1000); }
在setup函数中,调用uart_init函数完成串口初始化,调用xl9555_init函数完成XL9555初始化,调用lcd_init函数完成LCD初始化,然后调用rtc.setTime函数设置年月日时分秒信息,然后LCD显示实验信息。
在loop函数中,调用rtc.getTimeStruct函数实现时间信息的获取,通过sprintf函数组合成字符串,然后调用lcd_show_string函数进行显示,这个过程是间隔一秒进行。
18.4 下载验证
下载代码后,LCD显示RTC实验信息,然后间隔一秒更新时间信息。

图18.4.1 RTC实验测试图