新北市网站建设_网站建设公司_门户网站_seo优化
2025/12/18 20:38:38 网站建设 项目流程

一、前言

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)设置闹钟

/** * 实时(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设置闹钟函数

#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闹钟唤醒程序

五、拓展延伸

需求:设定当前时间

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

/** * 实时(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 时间戳转换为本地时间;
  • 结构化解析:把单调递增的秒数,拆解为年、月、日、时、分、秒、星期等独立字段。
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. 人机交互:显示屏显示实时日期 / 时间(如智能电表、手持终端)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询