1. 什么是RTC?
RTC(Real-Time Clock) 是嵌入式系统中用于提供独立计时功能的硬件模块,具有以下特点:
- 独立于主系统时钟(即使MCU进入低功耗模式仍可运行)
- 提供日历功能(年/月/日/时/分/秒/亚秒)
- 支持闹钟中断和周期性唤醒
- 由备用电池供电(VBAT引脚),主电源断开后仍可保持计时
20位的可编程预分频器,可适配不同频率的输入时钟。
可选择三种RTC时钟源:
HSE时钟除以128(通常为8MHz/128)
LSE振荡器时钟(通常为32.768KHz)
LSI振荡器时钟(40KHz)
2. RTC驱动步骤
简化的RTC框图
RTC驱动步骤
- 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
- 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器
3. 常用HAL库函数
函数 | 说明 |
---|---|
HAL_RTC_Init() |
初始化RTC |
HAL_RTC_SetTime() |
设置时间 |
HAL_RTC_GetTime() |
获取时间 |
HAL_RTC_SetAlarm() |
设置闹钟 |
HAL_RTC_DeactivateAlarm() |
禁用闹钟 |
HAL_RTC_GetDate() |
获取日期(需配合使用) |
4. 关键寄存器说明
RTC控制寄存器
寄存器 | 说明 |
---|---|
CRH | 中断使能:OWIE (溢出中断), ALRIE (闹钟中断), SECIE (秒中断) |
CRL | 状态标志:RTOFF (寄存器操作完成), RSF (寄存器同步标志), OW (溢出标志), ALRF (闹钟标志) |
PRLH/PRLL | 预分频器高位/低位寄存器 |
CNTH/CNTL | 计数器高位/低位寄存器(32位计数器) |
ALRH/ALRL | 闹钟寄存器高位/低位 |
5. 读写RTC时间实验
RTC初始化
RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(void)
{
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
rtc_handle.Instance = RTC;
rtc_handle.Init.AsynchPrediv = 32767;
rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
HAL_RTC_Init(&rtc_handle);
}
设置msp函数
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
__HAL_RCC_RTC_ENABLE();
RCC_OscInitTypeDef osc_initstruct = {0};
RCC_PeriphCLKInitTypeDef periphclk_initstruct = {0};
osc_initstruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
osc_initstruct.LSEState = RCC_LSE_ON;
osc_initstruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&osc_initstruct);
periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
periphclk_initstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
}
获取时间
void rtc_get_time(void)
{
RTC_TimeTypeDef rtc_time = {0};
RTC_DateTypeDef rtc_date = {0};
HAL_RTC_GetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
printf("rtc time: %d-%02d-%02d %02d:%02d:%02d\r\n", rtc_date.Year + 2000, rtc_date.Month, rtc_date.Date,
rtc_time.Hours, rtc_time.Minutes, rtc_time.Seconds);
}
设置时间
void rtc_set_time(struct tm time_data)
{
RTC_TimeTypeDef rtc_time = {0};
RTC_DateTypeDef rtc_date = {0};
rtc_time.Hours = time_data.tm_hour;
rtc_time.Minutes = time_data.tm_min;
rtc_time.Seconds = time_data.tm_sec;
HAL_RTC_SetTime(&rtc_handle, &rtc_time, RTC_FORMAT_BIN);
rtc_date.Year = time_data.tm_year - 2000;
rtc_date.Month = time_data.tm_mon;
rtc_date.Date = time_data.tm_mday;
HAL_RTC_SetDate(&rtc_handle, &rtc_date, RTC_FORMAT_BIN);
while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle, RTC_FLAG_RTOFF));
}
主函数
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "rtc.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
rtc_init();
printf("hello world!\r\n");
if(rtc_read_bkr(1) != 0xA5A5)
{
rtc_write_bkr(1, 0xA5A5);
printf("读出来的值为:%X\r\n", rtc_read_bkr(1));
struct tm time_data;
time_data.tm_year = 2024;
time_data.tm_mon = 7;
time_data.tm_mday = 1;
time_data.tm_hour = 16;
time_data.tm_min = 50;
time_data.tm_sec = 30;
rtc_set_time(time_data);
}
while(1)
{
rtc_get_time();
delay_ms(1000);
}
}
6. RTC闹钟实验
使用RTC闹钟中断,可使用闹钟功能。
初始化RTC时钟
RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(void)
{
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
rtc_handle.Instance = RTC;
rtc_handle.Init.AsynchPrediv = 32767;
rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
HAL_RTC_Init(&rtc_handle);
}
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
__HAL_RCC_RTC_ENABLE();
RCC_OscInitTypeDef osc_initstruct = {0};
RCC_PeriphCLKInitTypeDef periphclk_initstruct = {0};
osc_initstruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
osc_initstruct.LSEState = RCC_LSE_ON;
osc_initstruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&osc_initstruct);
periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
periphclk_initstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
RTC中断函数
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(&rtc_handle);
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
printf("ring ring ring...\r\n");
}
设置闹钟函数
void rtc_set_alarm(struct tm alarm_data)
{
RTC_AlarmTypeDef alarm = {0};
alarm.Alarm = RTC_ALARM_A;
alarm.AlarmTime.Hours = alarm_data.tm_hour;
alarm.AlarmTime.Minutes = alarm_data.tm_min;
alarm.AlarmTime.Seconds = alarm_data.tm_sec;
HAL_RTC_SetAlarm_IT(&rtc_handle, &alarm, RTC_FORMAT_BIN);
}
主函数
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "rtc.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
rtc_init();
printf("hello world!\r\n");
if(rtc_read_bkr(1) != 0xA5A5)
{
rtc_write_bkr(1, 0xA5A5);
printf("读出来的值为:%X\r\n", rtc_read_bkr(1));
struct tm time_data, alarm_data;
time_data.tm_year = 2024;
time_data.tm_mon = 7;
time_data.tm_mday = 1;
time_data.tm_hour = 16;
time_data.tm_min = 50;
time_data.tm_sec = 30;
rtc_set_time(time_data);
alarm_data.tm_hour = 16;
alarm_data.tm_min = 50;
alarm_data.tm_sec = 40;
rtc_set_alarm(alarm_data);
}
while(1)
{
rtc_get_time();
delay_ms(1000);
}
}
7. 注意事项
- 备份寄存器操作:修改RTC配置前需先禁用写保护(
PWR->CR |= PWR_CR_DBP
) - 时钟源选择:LSE精度高但需要外部晶振,LSI无需外部元件但精度较低
- 中断处理:必须及时清除中断标志,否则会持续触发
- 低功耗模式:RTC在Standby模式下仍可正常工作
- 计数器溢出:约136年后自动归零(32位计数器@1Hz)
使用RTC时钟,我们发现并没有实现实时时钟的功能,如果我们需要准确的时间,我们可以使用EPS8266链接WIFI,然后通过HTTP来获取最新时间。
本笔记基于STM32标准外设库编写,适用于需要理解RTC工作原理的开发者。实际开发时需结合具体硬件设计和需求调整参数。