图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; /* 时间结构体 */
/**
* @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的频率闪烁着。