第二十三章 RTC实验 本章介绍ESP32-S3实时时钟(RTC)的使用,实时时钟能为系统提供一个准确的时间,即时系统复位或主电源断电,RTC依然能够运行,因此RTC也经常用于各种低功耗场景。通过本章的学习,读者将学习到RTC的使用。 本章分为如下几个小节: 23.1 RTC时钟简介 23.2 硬件设计 23.3 程序设计 23.4 下载验证 23.1 RTC时钟简介 那为什么我们需要一个单独的RTC? 原因是CPU的定时器时钟功能只在“启动”即“通电时”运行,断电时停止。当然,如果时钟不能连续跟踪时间,则必须手动设置时间。 通常,RTC配备一个单独分离的电源,如纽扣电池(备用电池),即使开发板电源关闭,它也能保持运作,随时可以实时显示时间。然后,当开发板再次打开时,计算机内置的定时器时钟从RTC读取当前时间,并在此基础上供电的同时,时间在其自身机制下显示。顺便说一句,由于纽扣电池相对便宜且使用寿命长,因此RTC可以以极低的成本运行。基于此这个作用,它也可以用作内存。 1,ESP32-S3的RTC 在ESP32-S3中,并没有像STM32芯片一样,具有RTC外设,但是存在一个系统时间,利用系统时间,也可以实现实时时钟的功能效果。 ESP32-S3使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为RTC定时器和高分辨率定时器。默认情况下,是使用这两种定时器。下面我们将逐一介绍。 23.2 硬件设计 23.2.1 例程功能 1. 通过LCD实时显示RTC时间 2. LED闪烁,指示程序正在运行 23.2.2 硬件资源 1. LED LED0 - IO1 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) 4. RTC 23.2.3 原理图 本章实验使用的RTC为ESP32-S3的片上资源,因此没有相应的连接原理图。 23.3 程序设计 23.3.1 程序流程图 程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图: ![]() 图23.3.1.1 RTC实验程序流程图 23.3.2 RTC函数解析 由于ESP32并未给出RTC相关的API函数,因而笔者在设计例程时调用了C库中的一些函数来配置RTC时钟,这些函数的描述及其作用如下: 1,获取当前时间 该函数用于获取当前时间,其函数原型如下所示: struct tm *localtime(const time_t *timer); 该函数的形参描述,如下表所示:
表23.3.2.1 函数localtime ()形参描述 返回值:无。 2,设置当前时间 该函数用于设置当前时间,其函数原型如下所示: int settimeofday(const struct timeval *tv, const struct timezone *tz); 该函数的形参描述,如下表所示:
表23.3.2.2 函数settimeofday ()形参描述 返回值:无。 23.3.3 RTC驱动解析 在IDF版的13_rtc例程中,作者在13_rtc \components\BSP路径下新增了一个RTC文件夹,分别用于存放esp_rtc.c、esp_rtc.h两个文件。其中,esp_rtc.h文件负责声明RTC,而esp_rtc.c文件则实现了RTC的驱动代码。下面,我们将详细解析这两个文件的实现内容。 1,esp_rtc.h文件 /* 时间结构体, 包括年月日周时分秒等信息 */ 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; extern _calendar_obj calendar; /* 时间结构体 */ 2,esp_rtc.c文件 calendar_obj calendar; /* 时间结构体 */ /** * @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 datetime; /* 设置时间 */ datetime.tm_year = year - 1900; datetime.tm_mon = mon - 1; datetime.tm_mday = mday; datetime.tm_hour = hour; datetime.tm_min = min; datetime.tm_sec = sec; datetime.tm_isdst = -1; /* 获取1970.1.1以来的总秒数 */ time_t second = mktime(&datetime); struct timeval val = { .tv_sec = second, .tv_usec = 0 }; /* 设置当前时间 */ settimeofday(&val, NULL); } /** * @brief 获取当前的时间 * @param 无 * @retval 无 */ void rtc_get_time(void) { struct tm *datetime; time_t second; /* 返回自(1970.1.1 00:00:00 UTC)经过的时间(秒) */ time(&second); datetime = localtime(&second); calendar.hour = datetime->tm_hour; /* 时 */ calendar.min = datetime->tm_min; /* 分 */ calendar.sec = datetime->tm_sec; /* 秒 */ /* 公历年月日周 */ calendar.year = datetime->tm_year + 1900; /* 年 */ calendar.month = datetime->tm_mon + 1; /* 月 */ calendar.date = datetime->tm_mday; /* 日 */ /* 周 */ calendar.week = rtc_get_week(calendar.year, calendar.month, calendar.date); } /** * @brief 将年月日时分秒转换成秒钟数 * @NOTE 输入公历日期得到星期(起始时间为: 公元0年3月1日开始, 输入往后的任何日期,都 可以获取正确的星期) * 使用 基姆拉尔森计算公式 计算, 原理说明见此贴: * @param syear : 年份 * @param smon : 月份 * @param sday : 日期 * @retval 0, 星期天; 1 ~ 6: 星期一 ~ 星期六 */ uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day) { uint8_t week = 0; if (month < 3) { month += 12; --year; } week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7; return week; } 以上三个获取、设置RTC时间、日期的函数,均是对ESP IDF中RTC驱动的简单封装。 23.3.4 CMakeLists.txt文件 打开本实验BSP下的CMakeLists.txt文件,其内容如下所示: set(src_dirs IIC LCD LED SPI RTC XL9555) set(include_dirs IIC LCD LED SPI RTC XL9555) set(requires driver 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) 上述的红色RTC驱动以及newlib依赖库需要由开发者自行添加,以确保RTC驱动能够顺利集成到构建系统中。这一步骤是必不可少的,它确保了RTC驱动的正确性和可用性,为后续的开发工作提供了坚实的基础。 23.3.5 实验应用代码 打开main/main.c文件,该文件定义了工程入口函数,名为app_main。该函数代码如下。 i2c_obj_t i2c0_master; /* 定义字符数组用于显示周 */ char* weekdays[]={"Sunday","Monday","Tuesday","Wednesday", "Thursday","Friday","Saterday"}; /** * @brief 程序入口 * @param 无 * @retval 无 */ 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()); ret = nvs_flash_init(); } led_init(); /* 初始化LED */ i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */ spi2_init(); /* 初始化SPI2 */ xl9555_init(i2c0_master); /* IO扩展芯片初始化 */ lcd_init(); /* 初始化LCD */ rtc_set_time(2023,8,26,00,00,00); /* 设置RTC时间 */ lcd_show_string(10, 40, 240, 32, 32, "ESP32",RED); lcd_show_string(10, 80, 240, 24, 24, "RTC Test",RED); lcd_show_string(10, 110, 240, 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); printf("Time:%02d:%02d:%02d\r\n", 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); printf("Date:%02d-%02d-%02d\r\n", 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) { LED_TOGGLE(); /* 每200ms,翻转一次LED */ } vTaskDelay(10); } } 从上面的代码中可以看到,在初始化完RTC后便每间隔100毫秒获取一次RTC的时间和日期,并在LCD上进行显示。 23.4 下载验证 在完成编译和烧录操作后,可以看到LCD上实时地显示着RTC的时间,并且可以看到LED在RTC周期性唤醒的驱动下以0.5Hz的频率闪烁着。 ![]() 图23.3.1 SPI LCD显示效果图 |
QuarkPi-CA2 RK3588S卡片电脑:6.0Tops NPU+8K视频编解码+接口丰富,高性能嵌入式开发!
354 浏览 0 评论
1177 浏览 0 评论
1239 浏览 1 评论
1364 浏览 0 评论
1373 浏览 1 评论
【youyeetoo X1 windows 开发板体验】少儿AI智能STEAM积木平台
12837 浏览 31 评论