32 RTC实时时钟-独立定时器

一、前言

1.RTC简介

RTC(Real Time Clock,实时时钟),是一个掉电 后仍然可以继续运行独立定时器

RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期。RTC还包含用于管理低功耗模式的自动唤醒单元。

2.硬件架构

STM32 RTC 的核心架构分为 3 层,保证独立运行和低功耗:

  1. 备份域:包含 RTC 寄存器、备份寄存器(用户可存少量掉电不丢数据),由 VBAT 供电,主电源掉电后数据不丢失;
  2. 时钟模块:选择 LSE/LSI 作为时钟输入,经分频后驱动 RTC 计数核心;
  3. 中断 / 唤醒模块:闹钟、周期唤醒事件触发中断,可直接唤醒 CPU(停止 / 待机模式),无需 CPU 预运行。

2.1主电源与备用电源的区别

STM32 芯片的供电分为两个核心部分,两者分工明确:

VBAT 的唯一作用是「主电源掉电时给备份域(RTC + 备份寄存器)供电」;如果完全不需要这个 "掉电保持" 功能,VBAT 就失去了 "备用供电" 的意义,只需让它和主电源共用同一路电即可。

3 RTC功能框图

RTC实时时钟会产生两路信号,一个是为日历服务的秒信号,还有一路为闹钟服务,闹钟信号也来自于秒信号

4 RTC时钟源

RTC有3路时钟来源:HSE(8MHz)/128、LSE(32.768KHz)、LSI(40KHz)。其中,如果使用HSE或LSI的话,当主电源掉电的话,这两个始终都会受到影响,RTC就无法正常工作。所以,一般的通用做法是使用LSE 。2个原因,一是LSE不受主电源掉电的影响(在BKP中),二是它的频率是32768Hz,正好是2^15,分频容易实现。

1)APB1接口

用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线时钟驱动,用来与APB1总线连接。

通过APB1接口可以访问RTC的相关寄存器(预分频值、计数器值、闹钟值)。

2)RTC预分频模块

这个模块是RTC预分频模块,属于后备区域,VDD掉电后,可以在VBAT下继续运行。包含了一个20位的可编程分频器(RTC预分频器)。它可编程产生 1 秒的 RTC 时间基准 TR_CLK。

秒信号获取的方式: RTCCLK = 32768 Hz

3)32位可编程计数器

这个模块也属于后备区域,是一个32位的可编程计数器,可被初始化为当前的系统时间。一个32位的时钟计数器,

4)中断

从图中可以看到一共有3个中断:

  1. 秒中断:每计时1s产生一次中断。
  2. 计数器溢出中断。136年才会产生溢出,一般用不上。

RTC闹钟中断。RCT_CN和RTC_ALR会比较相等,如果相等表示闹钟时间到,会产生闹钟中断

二、代码

需求 : 测试STM32芯片进入低功耗 - 待机模式 RTC唤醒芯片

Dri_RTC.c:(1)初始化RTC函数(2)设置闹钟

cpp 复制代码
/**
 * 实时(Real Time)时钟 - 初始化
 */
void Dri_RTC_Init(void) {

    // 1. 开启时钟
    // ● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // ● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
    PWR->CR |= PWR_CR_DBP;

    // 2. 配置RTC
    //    配置RTC时钟源
    
    // Enable LSE
    RCC->BDCR |= RCC_BDCR_LSEON;

    // LSE is Ready
    while ( ( RCC->BDCR & RCC_BDCR_LSERDY ) == 0 );

    //    ● 可以选择以下三种RTC的时钟源:
    //     ─ HSE时钟除以128;
    //     ─ LSE振荡器时钟;
    //     ─ LSI振荡器时钟
    // RCC->BDCR &= ~RCC_BDCR_RTCSEL_1;
    // RCC->BDCR |=  RCC_BDCR_RTCSEL_0;
    RCC->BDCR |= RCC_BDCR_RTCSEL_LSE; // 32.768K Hz
    RCC->BDCR |= RCC_BDCR_RTCEN;


    //    配置秒信号:RTC_PRL
        // 配置过程:
        // 1. 查询RTOFF位,直到RTOFF的值变为'1' 
        while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
        // 2. 置CNF值为1,进入配置模式
        RTC->CRL |= RTC_CRL_CNF;
        // 3. 对一个或多个RTC寄存器进行写操作
        //     32.768K Hz => PRL(32768) => 1HZ
        // 16 bit
        RTC->PRLL = 0x7FFF; // 0x7FFF + 1 = 0x8000 => 1 0000 0000 0000 000 = 65535 + 1
        // 4 bit
        RTC->PRLH = 0;

        // 4. 清除CNF标志位,退出配置模式
        RTC->CRL &= ~RTC_CRL_CNF;
        // 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
        while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}

/**
 * 实时(Real Time)时钟 - 设定闹钟
 */
void Dri_RTC_SetAlarm(uint32_t sec) {
    //    配置闹钟信号
    //    配置RTC_CNT
    //    配置RTC_ALR
        // 1. 查询RTOFF位,直到RTOFF的值变为'1' 
        while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
        // 2. 置CNF值为1,进入配置模式
        RTC->CRL |= RTC_CRL_CNF;
        // 3. 对一个或多个RTC寄存器进行写操作
        RTC->CNTL = 0;
        RTC->CNTH = 0;
        RTC->ALRL = sec; // Low 16 bit
        RTC->ALRH = sec >> 16; // High 16 bit
        // 4. 清除CNF标志位,退出配置模式
        RTC->CRL &= ~RTC_CRL_CNF;
        // 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
        while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}

main函数:待机模式常规代码以及rtc设置闹钟函数

cpp 复制代码
#include "USART.h"
#include "SysTick.h"
#include "stm32f10x.h"
#include "LED.h"
#include <stdio.h>
#include "KEY.h"
#include "Dri_RTC.h"
/**
 * 需求 : 测试STM32芯片进入低功耗 - 待机模式
 */
/**
 * 待机模式
 */
void enter_standby_mode() {

    // 深睡眠
    SCB->SCR |= SCB_SCR_SLEEPDEEP;

    // 掉电
    PWR->CR |= PWR_CR_PDDS;

    // 清除唤醒标志位
    PWR->CR |= PWR_CR_CWUF;

    // 使能唤醒引脚
    //PWR->CSR |= PWR_CSR_EWUP;

    __wfi();
}
/**
 * 停止模式
 */
void enter_stop_mode() {

    // 开启时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // 深睡眠
    SCB->SCR |= SCB_SCR_SLEEPDEEP;

    // 掉电
    PWR->CR &= ~PWR_CR_PDDS;

    // 低功耗
    PWR->CR |= PWR_CR_LPDS;

    __wfi();
}
/**
 * 睡眠模式
 */
void enter_sleep_mode() {

    // 浅睡眠
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP;

    SCB->SCR &= ~SCB_SCR_SLEEPONEXIT; // 立即进入睡眠
    //SCB->SCR |= SCB_SCR_SLEEPONEXIT; // 所有中断程序处理完后再进入睡眠

    __wfi();
}
void system_clock_reset(void) {

    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;

  /* Enable HSE */    
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);

  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    /*  PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);

    /* Enable PLL */
    RCC->CR |= RCC_CR_PLLON;

    /* Wait till PLL is ready */
    while((RCC->CR & RCC_CR_PLLRDY) == 0)
    {
    }

    /* Select PLL as system clock source */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* Wait till PLL is used as system clock source */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
}

int main(void)
{
    USART_Init();
    Dri_RTC_Init();
    KEY_Init();
    LED_Init();
    LED_On(LED_BLUE);

    // 开启时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    // !获取芯片待机状态,判断之前是否曾经进入过待机模式
    if ( (PWR->CSR & PWR_CSR_SBF) != 0 ) {
        printf("STM32芯片从待机模式被");
        // 复位状态
        PWR->CR |= PWR_CR_CSBF;
        if (  (PWR->CSR & PWR_CSR_WUF) == 0 ) {
            printf("Reset复位键唤醒 \n");
        } else {
            // 复位状态
            PWR->CR |= PWR_CR_CWUF;
            printf("RTC闹钟唤醒 \n");
        }
    }

    printf("测试STM32芯片进入低功耗 - 待机模式 \n");
    printf("5s后STM32芯片进入待机模式 \n");
    SysTick_DelayS(2);
    printf("3s后STM32芯片进入待机模式 \n");
    SysTick_DelayS(1);
    printf("2s后STM32芯片进入待机模式 \n");
    SysTick_DelayS(1);
    printf("1s后STM32芯片进入待机模式 \n");
    SysTick_DelayS(1);

    // !在进入待机模式前设定闹钟
    Dri_RTC_SetAlarm(3);

    // !进入待机模式
    enter_standby_mode();

    while(1)
    {

    }
}

三、寄存器

RTC_PRLH/RTC_PRLL 低位16:高位4:
RTC_CNTH/RTC_CNTL 高16位 低16位
RTC_ALRH/RTC_ALRL

四、HAL库

4.1配置RCC\SYS\USART1

4.2配置Timers-RTC-

4.3VSCODE

倒计时3s闹钟唤醒程序

五、拓展延伸

需求:设定当前时间

分析:需要设置当前时间,然后在获得当前时间。

cpp 复制代码
/**
 * 实时(Real Time)时钟 - 设定当前时间(秒)
 */
void Dri_RTC_SetTime(uint32_t sec) {
    // 1. 查询RTOFF位,直到RTOFF的值变为'1' 
    while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
    // 2. 置CNF值为1,进入配置模式
    RTC->CRL |= RTC_CRL_CNF;
    // 3. 对一个或多个RTC寄存器进行写操作
    RTC->CNTL = sec;
    RTC->CNTH = sec >> 16;
    // 4. 清除CNF标志位,退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;
    // 5. 查询RTOFF,直至RTOFF位变为'1'以确认写操作已经完成。
    while ( ( RTC->CRL & RTC_CRL_RTOFF ) == 0 ) ;
}
/**
 * 实时(Real Time)时钟 - 设定时间(秒)
 */
uint32_t Dri_RTC_GetTime() {
    return (RTC->CNTH << 16) | RTC->CNTL;
}

main.c:

(1)获取1900到现在有多少s。将 RTC 输出的 Unix 时间戳(秒级,从 1970-01-01 00:00:00 UTC 起的秒数)转换为「本地时区的年月日时分秒结构化数据

(2)sprintf函数进行拼接字符串, 获取系统的几个函数,放到while里面 每隔1s刷新获取最新的时间

先拆解代码功能

这段代码的核心是:将 RTC 输出的 Unix 时间戳(秒级,从 1970-01-01 00:00:00 UTC 起的秒数)转换为「本地时区的年月日时分秒结构化数据」 ,其中 localtime(&sec) 是实现这一转换的核心函数。

二、localtime() 函数详解

1. 函数基本信息
  • 所属库 :C 标准库 <time.h>(嵌入式系统中需确认编译器 / SDK 是否支持,如 STM32 的 ARMCC/ARMClang 均兼容);
  • 函数原型struct tm *localtime(const time_t *timer);
    • 入参:const time_t *timer ------ 指向 Unix 时间戳的指针(time_t 本质是 long/uint32_t 类型,代表秒级时间戳);
    • 返回值:struct tm * ------ 指向结构化时间数据的指针(内存通常为静态分配,无需手动释放,但会被后续调用覆盖)。
2. 核心作用

将「秒级时间戳」(纯数字)解析为人类可读的「本地时区日历时间」,并填充到 struct tm 结构体中。

  • "本地时区":函数会自动根据系统 / 编译器配置的时区偏移(如东八区 UTC+8),将 UTC 时间戳转换为本地时间;
  • 结构化解析:把单调递增的秒数,拆解为年、月、日、时、分、秒、星期等独立字段。
cpp 复制代码
int main(void)
{
    Dri_RTC_Init();
    Int_LCD_Init();
    Int_LCD_ClearScreen(BLUE);

    uint8_t date_string[20] = {0};

    // 1. 如何获取到当前秒
    Dri_RTC_SetTime(1765966710);

    while(1)
    {

        // 2. 如何根据当前时间生成时间字符串
        //uint8_t *date_string = "2025-01-17 09:58:00";
        uint32_t sec = Dri_RTC_GetTime();
        struct tm *rtc_date = localtime(&sec);

        sprintf((char *)date_string, "%4d-%02d-%02d %02d:%02d:%02d",
            rtc_date->tm_year + 1900,
            rtc_date->tm_mon + 1,
            rtc_date->tm_mday,
            rtc_date->tm_hour,
            rtc_date->tm_min,
            rtc_date->tm_sec
        );

        Int_LCD_DisplayASCIIString(0,0,16,32,date_string,WHITE,BLUE);
        SysTick_DelayS(1);
    }
}

六、归纳总结

RTC和TIM对比

应用场景

  1. 时间戳记录:记录传感器采集数据的时间(如温湿度采集、故障报警时间);
  2. 定时唤醒:低功耗设备周期性唤醒(如每 10 分钟采集一次数据,其余时间休眠);
  3. 闹钟提醒:工业设备的定时任务(如定时开关机、定时上报数据);
  4. 掉电计时:主电源掉电后,备用电池维持 RTC 运行,恢复供电后可追溯掉电时长;
  5. 人机交互:显示屏显示实时日期 / 时间(如智能电表、手持终端)。
相关推荐
三佛科技-187366133972 小时前
智能豆芽机单片机方案开发
单片机·嵌入式硬件
就是蠢啊3 小时前
51单片机——ADC数模转换实验
单片机·嵌入式硬件·51单片机
迅为电子4 小时前
释放多屏潜能:迅为RK3588开发板Android多屏同显开发完全指南
嵌入式硬件·rk3588开发板·多屏同显开发
李斯维4 小时前
MBR 和 GPT 区别
windows·嵌入式硬件·bootstrap·计算机外设
学习路上_write4 小时前
stm32板子mpu6050读取的器件ID一直是0xFF
stm32·单片机·嵌入式硬件
恒锐丰小吕4 小时前
无锡黑锋 HF6010 PWM/PFM控制DC-DC降压稳压器技术解析
嵌入式硬件·硬件工程
亿道电子Emdoor5 小时前
【Arm】MDK查看语句的执行累积时间和次数
stm32·单片机·物联网
Tel199253080045 小时前
CCD相机同步外触发拍照抓拍识别高速脉冲计数器信号采集模块
单片机·数码相机·物联网·自动化·工业自动化·仪器仪表
l3538o675735 小时前
国产POE降压恒压芯片方案选型:48v-52v输入转5v-12v/1-3A电源芯片
人工智能·科技·单片机·嵌入式硬件·电脑·智能家居