本帖最后由 正点原子运营 于 2024-4-16 11:31 编辑
1)实验平台:正点原子 M144Z-M3 STM32F103最小系统板
2) 章节摘自【正点原子】M144Z-M3最小系统板使用指南——STM32F103版
6)正点原子STM32技术交流QQ群:725095144
本章介绍STM32F103实时时钟(RTC)的使用,实时时钟能为系统提供一个准确的时间,即时系统复位或主电源断电,RTC依然能够运行,因此RTC也经常用于各种低功耗场景。通过本章的学习,读者将学习到RTC的使用。 本章分为如下几个小节: 27.1 硬件设计 27.2 程序设计 27.3 下载验证
27.1 硬件设计 27.1.1 例程功能 1. 通过LCD实时显示RTC时间,并可通过USMART设置RTC时间和闹钟 2. RTC闹钟串口提示 3. RTC周期唤醒改变LED1状态,周期的1秒 4. LED0闪烁,指示程序正在运行
27.1.2硬件资源 1. LED LED0 - PB5 LED1 - PE5 2. 正点原子2.8/3.5/4.3/7/10寸TFTLCD模块 3. USART1 USART1_TX - PA9 USART1_RX - PA10 4. TIM4 5. RTC
27.1.3 原理图 本章实验使用的RTC为STM32F103的片上资源,因此没有相应的连接原理图。
27.2 程序设计 27.2.1 HAL库的PWR驱动 本章实验要使用到RTC,因此需要对RTC及其相关的寄存器进行配置,但是为了防止误操作,系统复位后备份区域(指RTC、备份寄存器)是被禁止写访问的,备份区域的写访问是由PWR进行配置的,具体的配置步骤如下: ①:使能访问备份区域 在HAL库中对应的驱动函数如下: ①:使能访问备份区域 该函数用于使能访问备份区域,其函数原型如下所示: - voidHAL_PWR_EnableBkUpAccess(void);
复制代码该函数的形参描述,如下表所示: 表27.2.1.1 函数HAL_PWR_EnableBkUpAccess()形参描述 该函数的返回值描述,如下表所示: 表27.2.1.2 函数HAL_PWR_EnableBkUpAccess()返回值描述 该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- /* 使能访问备份区域 */
- HAL_PWR_EnableBkUpAccess();
- }
复制代码
27.2.2 HAL库的RTC驱动 本章实验使用了RTC,RTC最基本的操作就是设置和获取时间和日期,同时还需要读写RTC备份寄存器保存是否已经在初始化过程中设置过时间的标志,其具体的步骤如下: ①:初始化RTC ②:读取RTC备份寄存器判断是否进行设置过时间 ③:若未设置过时间,则设置RTC的时间 ④:若未设置过时间,则设置RTC的日期 ⑤:将设置过时间的标志写入RTC备份寄存器 ⑥:读取RTC的时间 ⑦:读取RTC的日期 在HAL库中对应的驱动函数如下: ①:初始化RTC 该函数用于初始化RTC,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
复制代码该函数的形参描述,如下表所示: 表27.2.2.1 函数HAL_RTC_Init()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.2 函数HAL_RTC_Init()返回值描述 该函数需要传入RTC的句柄指针,该句柄中就包含了RTC的初始化配置参数结构体,该结构体的定义如下所示: - typedef struct
- {
- uint32_t AsynchPrediv; /* 异步预分频器系数 */
- uint32_t OutPut; /* 信号输出 */
- }RTC_InitTypeDef;
复制代码该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_HandleTypeDef rtc_handle = {0};
-
- /* 初始化RTC */
- rtc_handle.Instance = RTC;
- rtc_handle.Init.AsynchPrediv = 32767;
- rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
- HAL_RTC_Init(&rtc_handle);
- }
复制代码②:读取RTC备份寄存器 该函数用于读取RTC备份寄存器,其函数原型如下所示: - uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister);
复制代码该函数的形参描述,如下表所示: 表27.2.2.3 函数HAL_RTCEx_BKUPRead()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.4 函数HAL_RTCEx_BKUPRead()返回值描述 该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- uint32_t rtc_backup_reg0;
-
- /* 读取RTC备份寄存器0 */
- rtc_backup_reg0 = HAL_RTCEx_BKUPRead(&rtc_handle, RTC_BKP_DR0);
-
- /* Do something. */
- }
复制代码③:配置RTC时间 该函数用于配置RTC的时间,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_SetTime( RTC_HandleTypeDef *hrtc,
- RTC_TimeTypeDef*sTime,
- uint32_t Format);
复制代码该函数的形参描述,如下表所示: 表27.2.2.5 函数HAL_RTC_SetTime()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.6 函数HAL_RTC_SetTime()返回值描述 该函数使用RTC_TimeTypeDef类型的结构体变量传入RTC的时间参数,该结构体的定义如下所示: - typedef struct
- {
- uint8_t Hours; /* 时 */
- uint8_t Minutes; /* 分 */
- uint8_t Seconds; /* 秒 */
- }RTC_TimeTypeDef;
复制代码该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_TimeTypeDef time = {0};
-
- /* 配置RTC时间 */
- RTC_TimeTypeDef.Hours = 0;
- RTC_TimeTypeDef.Minutes = 0;
- RTC_TimeTypeDef.Seconds = 0;
- HAL_RTC_SetTime(&rtc_handle, &time, RTC_FORMAT_BIN);
- }
复制代码④:配置RTC日期 该函数用于配置RTC的日期,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_SetDate( RTC_HandleTypeDef *hrtc,
- RTC_DateTypeDef*sDate,
- uint32_t Format);
复制代码该函数的形参描述,如下表所示: 表27.2.2.7 函数HAL_RTC_SetDate()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.8 函数HAL_RTC_SetDate()返回值描述 该函数使用RTC_DateTypeDef类型的结构体变量传入RTC的日期参数,该结构体的定义如下所示: - typedef struct
- {
- uint8_t WeekDay; /* 星期 */
- uint8_t Month; /* 月 */
- uint8_t Date; /* 日 */
- uint8_t Year; /* 年 */
- }RTC_DateTypeDef;
复制代码该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_DateTypeDef date = {0};
-
- /* 配置RTC日期 */
- date.WeekDay =RTC_WEEKDAY_SATURDAY;
- date.Month = RTC_MONTH_JANUARY;
- date.Date = 1;
- date.Year = 0;
- HAL_RTC_SetDate(&rtc_handle, &date, RTC_FORMAT_BIN);
- }
复制代码⑤:写入RTC备份寄存器 该函数用于写入RTC备份寄存器,其函数原型如下所示: - voidHAL_RTCEx_BKUPWrite( RTC_HandleTypeDef *hrtc,
- uint32_t BackupRegister,
- uint32_t Data);
复制代码该函数的形参描述,如下表所示: 表27.2.2.9 函数HAL_RTCEx_BKUPWrite()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.10 函数HAL_RTCEx_BKUPWrite()返回值描述 该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- /* 往RTC备份寄存器0写入0x50505050 */
- RTC_WriteBackup(&rtc_handle, RTC_BKP_DR0, 0x50505050);
- }
复制代码⑥:读取RTC时间 该函数用于读取RTC的时间,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_GetTime( RTC_HandleTypeDef *hrtc,
- RTC_TimeTypeDef*sTime,
- uint32_t Format);
复制代码该函数的形参描述,如下表所示: 表27.2.2.11 函数HAL_RTC_GetTime()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.12 函数HAL_RTC_GetTime()返回值描述 该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_TimeTypeDef time;
-
- /* 读取RTC的时间 */
- HAL_RTC_GetTime(&rtc_handle, &time, RTC_FORMAT_BIN);
-
- /* Do something. */
- }
复制代码⑦:读取RTC日期 该函数用于读取RTC的日期,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_GetDate( RTC_HandleTypeDef *hrtc,
- RTC_DateTypeDef*sDate,
- uint32_t Format);
复制代码该函数的形参描述,如下表所示: 表27.2.2.13 函数HAL_RTC_GetDate()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.14 函数HAL_RTC_GetDate()返回值描述 该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_DateTypeDef date;
-
- /* 读取RTC的日期 */
- HAL_RTC_GetDate(&rtc_handle, &date, RTC_FORMAT_BIN);
-
- /* Do something. */
- }
复制代码本实验同时也使能了RTC的闹钟功能,闹钟功能可以在RTC时间到达设定值时触发中断,其具体的使用步骤如下: ①:设置RTC中断闹钟 在HAL库中对应的驱动函数如下: ①:设置RTC中断闹钟 该函数用于设置RTC的中断闹钟,其函数原型如下所示: - HAL_StatusTypeDef HAL_RTC_SetAlarm_IT( RTC_HandleTypeDef *hrtc,
- RTC_AlarmTypeDef*sAlarm,
- uint32_t Format);
复制代码该函数的形参描述,如下表所示: 表27.2.2.15 函数HAL_RTC_SetAlarm_IT()形参描述 该函数的返回值描述,如下表所示: 表27.2.2.16 函数HAL_RTC_SetAlarm_IT()返回值描述 该函数使用RTC_AlarmTypeDef类型的结构体变量传入RTC闹钟的配置参数,该结构体的定义如下所示: - typedef struct
- {
- RTC_TimeTypeDef AlarmTime; /* 闹钟时间 */
- uint32_t Alarm; /* 闹钟对象 */
- }RTC_AlarmTypeDef;
复制代码该函数的使用示例,如下所示: - #include "stm32f1xx_hal.h"
- void example_fun(void)
- {
- RTC_AlarmTypeDef alarm = {0};
-
- /* 设置RTC闹钟中断 */
- alarm.AlarmTime.Hours = 0;
- alarm.AlarmTime.Minutes = 0;
- alarm.AlarmTime.Seconds = 0;
- alarm.Alarm = RTC_ALARM_A;
- HAL_RTC_SetAlarm_IT(&rtc_handle, &alarm, RTC_FORMAT_BIN);
- }
复制代码27.2.4 RTC驱动 本章实验的RTC驱动主要负责向应用层提供RTC的初始化和配置自动唤醒及闹钟的函数。本章实验中,RTC的驱动代码包括rtc.c和rtc.h两个文件。 RTC驱动中,RTC的初始化函数,如下所示: - /**
- *@brief 初始化RTC
- *@param 无
- *@retval 初始化结果
- *@arg 0: 初始化成功
- *@arg 1: 初始化失败
- */
- uint8_t rtc_init(void)
- {
- uint16_t flag;
-
- __HAL_RCC_PWR_CLK_ENABLE();
- __HAL_RCC_BKP_CLK_ENABLE();
- HAL_PWR_EnableBkUpAccess();
-
- /* 从后备寄存器读取RTC初始化标志 */
- flag = rtc_read_bkr(1);
-
- /* 初始化RTC */
- g_rtc_handle.Instance = RTC;
- g_rtc_handle.Init.AsynchPrediv = LSE_VALUE - 1;
- g_rtc_handle.Init.OutPut =RTC_OUTPUTSOURCE_NONE;
- if (HAL_RTC_Init(&g_rtc_handle) != HAL_OK)
- {
- return 1;
- }
-
- /* RTC第一次初始化 */
- if ((flag != 0x5051) && (flag != 0x5050))
- {
- /* 设置RTC时间信息 */
- rtc_set_time(2023, 6, 8, 8, 0, 0);
- }
-
- /* 更新时间信息 */
- rtc_get_time();
-
- return 0;
- }
复制代码从上面的代码中可以看出,RTC的初始化函数中,会先读取RTC备份寄存器来判断是否初始化过RTC然后再初始化RTC,RTC初始化完成后,若是第一次初始化RTC,则设置RTC的时间,否则不设置RTC的时间。 RTC驱动中设置、获取RTC时间、日期的两个函数,如下所示: - /**
- *@brief 获取RTC时间信息
- *@param 无
- *@retval 无
- */
- void rtc_get_time(void)
- {
- RTC_DateTypeDef rtc_date_struct = {0};
- RTC_TimeTypeDef rtc_time_struct = {0};
-
- /* 获取并更新RTC日期信息 */
- HAL_RTC_GetDate(&g_rtc_handle, &rtc_date_struct, RTC_FORMAT_BCD);
- g_calendar.year =rtc_date_struct.Year + 2000;
- g_calendar.month =rtc_date_struct.Month;
- g_calendar.date =rtc_date_struct.Date;
- g_calendar.week = rtc_get_week(g_calendar.year,
- g_calendar.month,
- g_calendar.date);
-
- /* 获取并更新RTC时间信息 */
- HAL_RTC_GetTime(&g_rtc_handle, &rtc_time_struct, RTC_FORMAT_BCD);
- g_calendar.hour =rtc_time_struct.Hours;
- g_calendar.minute =rtc_time_struct.Minutes;
- g_calendar.second =rtc_time_struct.Seconds;
- }
- /**
- *@brief 设置RTC时间信息
- *@param year: 年
- *@param month: 月
- *@param date: 日
- *@param hour: 时
- *@param minute: 分
- *@param second: 秒
- *@retval 设置结果
- *@arg 0: 设置成功
- *@arg 1: 设置失败
- */
- uint8_t rtc_set_time( uint16_t year,
- uint8_t month,
- uint8_t date,
- uint8_t hour,
- uint8_t minute,
- uint8_t second)
- {
- RTC_DateTypeDef rtc_date_struct = {0};
- RTC_TimeTypeDef rtc_time_struct = {0};
-
- /* 设置RTC日期信息 */
- rtc_date_struct.WeekDay = rtc_get_week(year, month, date);
- rtc_date_struct.Month = month;
- rtc_date_struct.Date = date;
- rtc_date_struct.Year = year - 2000;
- if (HAL_RTC_SetDate( &g_rtc_handle,
- &rtc_date_struct,
- RTC_FORMAT_BCD) != HAL_OK)
- {
- return 1;
- }
-
- /* 设置RTC时间信息 */
- rtc_time_struct.Hours = hour;
- rtc_time_struct.Minutes = minute;
- rtc_time_struct.Seconds = second;
- if (HAL_RTC_SetTime( &g_rtc_handle,
- &rtc_time_struct,
- RTC_FORMAT_BCD) != HAL_OK)
- {
- return 1;
- }
-
- return 0;
- }
复制代码以上两个获取、设置RTC时间、日期的函数,均是对HAL库中RTC驱动的简单封装。 RTC驱动中,配置RTC闹钟中断及其对应的闹钟事件回调函数,如下所示: - /**
- *@brief 设置RTC闹钟时间信息
- * @param hour:时
- *@param minute: 分
- *@param second: 秒
- *@retval 无
- */
- voidrtc_set_alarm(uint8_t hour, uint8_t minute, uint8_t second)
- {
- RTC_AlarmTypeDef rtc_alarm_struct = {0};
-
- HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
- HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
-
- /* 设置闹钟中断 */
- rtc_alarm_struct.AlarmTime.Hours = hour;
- rtc_alarm_struct.AlarmTime.Minutes = minute;
- rtc_alarm_struct.AlarmTime.Seconds = second;
- rtc_alarm_struct.Alarm = RTC_ALARM_A;
- HAL_RTC_SetAlarm_IT(&g_rtc_handle, &rtc_alarm_struct, RTC_FORMAT_BIN);
- }
- /**
- *@brief HAL库RTC闹钟中断回调函数
- *@param hrtc: RTC句柄
- *@retval 无
- */
- voidHAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
- {
- printf("AlarmA!\r\n");
- }
复制代码从上面的代码中可以看到,在RTC的闹钟中断中通过串口打印了闹钟的提示。
27.2.5 实验应用代码 本章实验的应用代码,如下所示: - int main(void)
- {
- uint8_t t = 0;
- char tbuf[40];
- uint8_t hour, minute, second, ampm;
- uint8_t year, month, date, week;
-
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(RCC_PLL_MUL9); /* 配置时钟,72MHz */
- delay_init(72); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- usmart_dev.init(72); /* 初始化USMART */
- led_init(); /* 初始化LED */
- lcd_init(); /* 初始化LCD */
- rtc_init(); /* 初始化RTC */
-
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "RTCTEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
-
- /* 设置RTC周期性唤醒中断 */
- rtc_set_wakeup(RTC_WAKEUPCLOCK_CK_SPRE_16BITS, 0);
-
- while (1)
- {
- if ((t % 10) == 0)
- {
- /* 获取RTC时间信息 */
- rtc_get_time(&hour, &minute, &second, &m);
- sprintf(tbuf, "Time:%02d:%02d:%02d", hour, minute, second);
- lcd_show_string(30, 120, 210, 16, 16, tbuf, RED);
- /* 获取RTC日期信息 */
- rtc_get_date(&year, &month, &date, &week);
- sprintf(tbuf, "Date:20%02d-%02d-%02d", year, month, date);
- lcd_show_string(30, 140, 210, 16, 16, tbuf, RED);
- sprintf(tbuf, "Week:%d", week);
- lcd_show_string(30, 160, 210, 16, 16, tbuf, RED);
- }
-
- if (++t == 20)
- {
- t = 0;
- LED0_TOGGLE();
- }
-
- delay_ms(10);
- }
- }
复制代码从上面的代码中可以看到,在初始化完RTC后,配置了RTC周期性地唤醒,且唤醒周期为1Hz,因此应该能看到LED1以0.5Hz的频率闪烁,随后便每间隔100毫秒获取一次RTC的时间和日期,并在LCD上进行显示。 本实验同时也使用的USMART调试组件,并在usart_config.c文件中添加了RTC驱动中相关的函数,以便调试。、
27.3 下载验证 在完成编译和烧录操作后,可以看到LCD上实时地显示着RTC的时间,并且可以看到LED1在RTC周期性唤醒的驱动下以0.5Hz的频率闪烁着,此时可以通过串口调试助手调用USMART调试组件的rtc_set_alarma()函数来设置RTC的闹钟,当通过LCD观察到RTC的时间达到设置的闹钟时间后,可以看到串口调试助手上打印了“ALARM A!\r\n”的字符串提示。 |