从0到1:STM32 RTC定时器配置全流程

发布于:2025-04-09 ⋅ 阅读:(27) ⋅ 点赞:(0)

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. 注意事项

  1. 备份寄存器操作:修改RTC配置前需先禁用写保护(PWR->CR |= PWR_CR_DBP
  2. 时钟源选择:LSE精度高但需要外部晶振,LSI无需外部元件但精度较低
  3. 中断处理:必须及时清除中断标志,否则会持续触发
  4. 低功耗模式:RTC在Standby模式下仍可正常工作
  5. 计数器溢出:约136年后自动归零(32位计数器@1Hz)

使用RTC时钟,我们发现并没有实现实时时钟的功能,如果我们需要准确的时间,我们可以使用EPS8266链接WIFI,然后通过HTTP来获取最新时间。


本笔记基于STM32标准外设库编写,适用于需要理解RTC工作原理的开发者。实际开发时需结合具体硬件设计和需求调整参数。