STM32之BKP和RTC

1. 什么是BKP

• 备份寄存器(BKP)在大容量STM32芯片中通常是42个16位寄存器(共84字节),用于存储用户数据。它们位于备份域中,当VDD电源切断时,可由VBAT引脚连接的备用电池维持供电保证数据不丢失。当系统从待机模式唤醒、或发生系统复位/电源复位时,BKP数据不会被清除(除非主动进行备份域复位或VBAT断电)。

• 注意:BKP特殊的一点就是他们是由两个电源控制的(VDD和VBAT),当VDD不工作了还有VBAT给BKP供电。但是,BKP也不是掉电不丢失数据的。当两个电源都没了,那数据可能也就没了。

• 此外, BKP控制寄存器用来管理侵入检测 (TAMPWE)RTC 校准功能

• 作用:

• 存储RTC的配置参数(如校准值、时钟源选择)。

• 保存关键数据(如时间戳、闹钟设置),避免复位或掉电后丢失。

• 复位后,对备份寄存器 (BKP)RTC 的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器(BKP)和RTC的访问:

• 通过设置寄存器RCC_APB1ENR的PWREN BKPEN位来打开电源和后备接口的时钟,如图:

• 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器(BKP)和RTC的访问,如图:

• 用户数据存储容量:20字节(中容量和小容量)/ 84字节(大容量和互联型)。

2. BKP框图

• 侵入检测的作用:防止别人恶意读取数据,保护数据,如果一旦发生别人读取数据,数据自动销毁。

3. 实操,读写BKP

3.1 在rtc.c

cpp 复制代码
#include "rtc.h"

RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(){

    __HAL_RCC_PWR_CLK_ENABLE();//打开电源时钟
    __HAL_RCC_BKP_CLK_ENABLE();//打开备份接口时钟
    HAL_PWR_EnableBkUpAccess();//使能允许写入RTC和BKP
    
    rtc_handle.Instance = RTC;
    rtc_handle.Init.AsynchPrediv = 32767;
    rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
    HAL_RTC_Init(&rtc_handle);//rtc和bkp是紧密的,都在一个区域(后备域),并且还共用一个VBAT
}

uint16_t rtc_read_bkr(uint8_t bkrx){//读
    uint32_t recv_data = 0;
    recv_data = HAL_RTCEx_BKUPRead(&rtc_handle,bkrx);
    return (uint16_t) recv_data;
}

void rtc_write_bkr(uint8_t bkrx,uint16_t data){//写
    HAL_RTCEx_BKUPWrite(&rtc_handle,bkrx,data);
}

3.2 在main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.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");
    rtc_write_bkr(1,0x11CD);
    printf("读到的数据是:%X\r\n",rtc_read_bkr(1));
    while(1)//流水灯实验
    { 
      
    }
}

4. 什么是RTC

实时时钟 是一个独立的定时器RTC 模块拥有一组连续计数的计数器 (32 位的可编程计数器 ) ,在相应软件配置下,可提供时钟日历的功能 (F1 系列是没有的 )。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统 (RCC_BDCR 寄存器 ) 处于后备区域 ,即在系统复位或从待机模式唤醒后, RTC的设置和时间维持不变。

• 复位后,对备份寄存器和 RTC 的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问:

• 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟。

• 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。

• 32位的可编程计数器(RTC):保存的是可对应的UNIX时间戳

20位的可编程预分频器,可以适配不同速率的输入时钟

• RTC的时钟源有三种:

• HSE的时钟除以128(通常是8MHz/128)。

• LSE振荡器时钟(通常是32.768KHz)(最常用的),可以配置为1hz(经过分频之后)

• LSI振荡器时钟(40KHz)(这个是不精确的)

• 如图:

5. RTC框图

• 工作原理:(在RTC预分频器)

• 假设要生成1hz 的时钟信号(TR_CLK),可以设置RTC_PRL(分频系数)为32768 - 1,然后重装载给RTC_DIV(余数寄存器),RTC模块把把32.768khz的外部时钟分频 32768 得到1hz,然后再将1hz的信号存入RTC_CNT中。

• RTC_DIV在每次计数时自减,当计数到 0时触发TR_CLK,存入RTC_CNT中,然后并重装载RTC_PRL的值继续分频。

6. RTC驱动步骤

• 使能电源及后备域时钟

• 使能后背域访问

• 初始化RTC(设置分频及工作参数)

• 设置msp函数(使能RTC,设置时钟源,中断配置)

• 编写读写RTC时间函数

• 编写闹钟相关函数

• 注意事项:

• 必须设置RTC_CRL寄存器中的CNF ( 配置位 ) ,使 RTC 进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。

• 对RTC任何寄存器的写操作都必须在前一次写操作结束后进行 。可以通过查询 RTC_CR 寄存器中的 RTOFF 状态位 ,判断RTC寄存器是否处于更新中**。仅当** RTOFF 状态位是 1 时,才可以写入 RTC 寄存器。

7. 实操,读写RTC时间实验

7.1 在rtc.c

cpp 复制代码
#include "rtc.h"
#include "stdio.h"
RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(){

    __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;//RTC不会输出任何信号到侵入引脚

    HAL_RTC_Init(&rtc_handle);//rtc和bkp是紧密的,都在一个区域(后备域),并且还共用一个VBAT
    
    //在HAL库中,CNF位的写1操作被封装在RTC初始化、时间设置和预分频配置等函数中,开发者无需直接操作寄存器。
    //通过调用HAL_RTC_Init()、HAL_RTC_SetTime()等函数,库函数会自动管理CNF位的状态,确保寄存器的安全访问。
}

void rtc_set_time(struct tm time_data){
    RTC_DateTypeDef rtc_date = {0};
    RTC_TimeTypeDef rtc_time = {0};
    rtc_date.Year = time_data.tm_year;
    rtc_date.Month = time_data.tm_mon;
    rtc_date.Date = time_data.tm_mday;
    HAL_RTC_SetDate(&rtc_handle,&rtc_date,RTC_FORMAT_BIN);
    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);
    /*两种格式区别
    BIN格式:需要将每个数值从二进制转换为十进制,
    例如将小时的二进制值1100转换为十进制的12。
    
    BCD格式:可以直接将每个4位二进制数组分解并解码为十进制数字,
    例如小时的BCD值0001 0010直接解码为12。
    其中每个部分分别表示十进制数 1 和 2。
    */
    while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle,RTC_FLAG_RTOFF));//等待写入完成。
}

void rtc_get_time(){
    RTC_DateTypeDef rtc_date = {0};
    RTC_TimeTypeDef rtc_time = {0};
    HAL_RTC_GetDate(&rtc_handle,&rtc_date,RTC_FORMAT_BIN);
    HAL_RTC_GetTime(&rtc_handle,&rtc_time,RTC_FORMAT_BIN);
    printf("%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 HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc){
    //HAL_RCC_ClockConfig 是 STM32 微控制器中用于配置系统时钟、
    //AHB(高级高性能总线)、APB1(高级外设总线1)和 APB2(高级外设总线2)总线时钟的函数
    __HAL_RCC_RTC_ENABLE();//开启RTC
    RCC_OscInitTypeDef rcc_osc = {0};//配置时钟振荡器的函数
    RCC_PeriphCLKInitTypeDef rcc_ppc = {0};
    rcc_osc.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    rcc_osc.LSEState = RCC_LSE_ON;
    rcc_osc.PLL.PLLState = RCC_PLL_NONE;//没用到锁相环
    //HAL_RCC_OscConfig 是 STM32 微控制器中用于配置时钟振荡器的函数。它属于 STM32 的硬件抽象层(HAL)库
    HAL_RCC_OscConfig(&rcc_osc);
    
    rcc_ppc.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    rcc_ppc.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    //HAL_RCCEx_PeriphCLKConfig 是 STM32 微控制器中用于配置外设时钟的函数
    HAL_RCCEx_PeriphCLKConfig(&rcc_ppc);
}
uint16_t rtc_read_bkr(uint8_t bkrx){
    uint32_t recv_data = 0;
    recv_data = HAL_RTCEx_BKUPRead(&rtc_handle,bkrx);
    return (uint16_t) recv_data;
}

void rtc_write_bkr(uint8_t bkrx,uint16_t data){
    HAL_RTCEx_BKUPWrite(&rtc_handle,bkrx,data);
}

7.2 在main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.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) != 0x11CD){//防止复位干扰时间
        rtc_write_bkr(1,0x11CD);
        printf("读到的数据是:%X\r\n",rtc_read_bkr(1));
        struct tm time_data;
        time_data.tm_year = 2025;
        time_data.tm_mon = 3;
        time_data.tm_mday = 12;
        time_data.tm_hour = 15;
        time_data.tm_min = 56;
        time_data.tm_sec = 30;
        rtc_set_time(time_data);
    }
    while(1)//流水灯实验
    { 
        rtc_get_time();
        delay_ms(1000);
    }
}

8. 实操 RTC闹钟实验

8.1 在rtc.c

cpp 复制代码
#include "rtc.h"
#include "stdio.h"
RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(){

    __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);//rtc和bkp是紧密的,都在一个区域(后备域),并且还共用一个VBAT
}

void rtc_set_time(struct tm time_data){
    RTC_DateTypeDef rtc_date = {0};
    RTC_TimeTypeDef rtc_time = {0};
    rtc_date.Year = time_data.tm_year;
    rtc_date.Month = time_data.tm_mon;
    rtc_date.Date = time_data.tm_mday;
    HAL_RTC_SetDate(&rtc_handle,&rtc_date,RTC_FORMAT_BIN);
    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);
    
    while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle,RTC_FLAG_RTOFF));
}
void rtc_get_time(){
    RTC_DateTypeDef rtc_date = {0};
    RTC_TimeTypeDef rtc_time = {0};
    HAL_RTC_GetDate(&rtc_handle,&rtc_date,RTC_FORMAT_BIN);
    HAL_RTC_GetTime(&rtc_handle,&rtc_time,RTC_FORMAT_BIN);
    printf("%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_alarm(struct tm alarm_data){
    RTC_AlarmTypeDef rtc_alarm = {0};
    rtc_alarm.AlarmTime.Hours = alarm_data.tm_hour;
    rtc_alarm.AlarmTime.Minutes = alarm_data.tm_min;
    rtc_alarm.AlarmTime.Seconds = alarm_data.tm_sec;
    rtc_alarm.Alarm = RTC_ALARM_A;
    HAL_RTC_SetAlarm_IT(&rtc_handle,&rtc_alarm,RTC_FORMAT_BIN);//启动闹钟中断。
    
     while(!__HAL_RTC_ALARM_GET_FLAG(&rtc_handle,RTC_FLAG_RTOFF));
}

void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc){
    __HAL_RCC_RTC_ENABLE();
    RCC_OscInitTypeDef rcc_osc = {0};
    RCC_PeriphCLKInitTypeDef rcc_ppc = {0};
    rcc_osc.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    rcc_osc.LSEState = RCC_LSE_ON;
    rcc_osc.PLL.PLLState = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&rcc_osc);
    
    rcc_ppc.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    rcc_ppc.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    
    HAL_RCCEx_PeriphCLKConfig(&rcc_ppc);
    
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn,2,0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
void RTC_Alarm_IRQHandler(){
    HAL_RTC_AlarmIRQHandler(&rtc_handle);
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){
    printf("ring ring ring\r\n");
}
uint16_t rtc_read_bkr(uint8_t bkrx){
    uint32_t recv_data = 0;
    recv_data = HAL_RTCEx_BKUPRead(&rtc_handle,bkrx);
    return (uint16_t) recv_data;
}

void rtc_write_bkr(uint8_t bkrx,uint16_t data){
    HAL_RTCEx_BKUPWrite(&rtc_handle,bkrx,data);
}

8.2 在main.c

cpp 复制代码
#include "sys.h"
#include "uart1.h"
#include "delay.h"
#include "led.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) != 0x11CD){
        rtc_write_bkr(1,0x11CD);
        printf("读到的数据是:%X\r\n",rtc_read_bkr(1));
        struct tm time_data;
        struct tm alarm_data;
        time_data.tm_year = 2025;
        time_data.tm_mon = 3;
        time_data.tm_mday = 12;
        time_data.tm_hour = 15;
        time_data.tm_min = 56;
        time_data.tm_sec = 30;
        rtc_set_time(time_data);
        
        alarm_data.tm_hour = 15;
        alarm_data.tm_min = 56;
        alarm_data.tm_sec = 40;
        rtc_set_alarm(alarm_data);
    }
    while(1)//流水灯实验
    { 
        rtc_get_time();
        delay_ms(1000);
    }
}
相关推荐
买辣椒用券2 小时前
STM32F407音频采集与播放实战:INMP441麦克风与MAX98357A扬声器
stm32·嵌入式硬件·音视频
代码游侠2 小时前
学习笔记——Linux内核与嵌入式开发2
linux·运维·arm开发·嵌入式硬件·学习·架构
哎呦 你干嘛~2 小时前
plc仿真来控制单片机
单片机·嵌入式硬件
ℳ๓. Sweet2 小时前
【STM32】关于DMA发送后立刻复位单片机导致无法正确发送的问题
stm32·单片机·嵌入式硬件
恒锐丰小吕2 小时前
屹晶微 EG2136S 600V三相半桥驱动芯片技术解析
嵌入式硬件·硬件工程
三佛科技-134163842122 小时前
多功能奶泡机MCU方案开发设计分析
单片机·嵌入式硬件·物联网·智能家居·pcb工艺
embedded大铭2 小时前
zynq上的裸机lwip网络性能测试iperf使用心得
单片机·嵌入式硬件
DLGXY3 小时前
STM32——DMA数据转换、DMA+AD多通道(十五)
stm32·单片机·嵌入式硬件
爱学嵌入式的小刘3 小时前
小白学UDP编程:从基础代码到优化实战(附完整可运行代码)
单片机·嵌入式硬件