文章目录
- 前言
- 一、BKP【备份寄存器】
-
- [1. 什么是BKP?](#1. 什么是BKP?)
- [2. BKP框图](#2. BKP框图)
- [3. 小实验:读写BKP](#3. 小实验:读写BKP)
-
- [3.1 实验目的](#3.1 实验目的)
- [3.2 硬件清单](#3.2 硬件清单)
- [3.3 构思环节](#3.3 构思环节)
- [3.4 出现的问题](#3.4 出现的问题)
- [3.5 代码展示](#3.5 代码展示)
- [3.6 实验结果](#3.6 实验结果)
- 二、RTC【实时时钟】
-
- [1. 什么是RTC?](#1. 什么是RTC?)
- [2. RTC框图](#2. RTC框图)
- [3. RTC寄存器](#3. RTC寄存器)
-
- [3.1 备份域控制寄存器(RCC_BDCR)](#3.1 备份域控制寄存器(RCC_BDCR))
- [3.2 RTC控制寄存器高位(RTC_CRH)](#3.2 RTC控制寄存器高位(RTC_CRH))
- [3.3 RTC控制寄存器低位(RTC_CRL)](#3.3 RTC控制寄存器低位(RTC_CRL))
- [3.4 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)](#3.4 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL))
- [3.5 RTC预分频器余数寄存器(RTC_DIVH / RTC_DIVL)](#3.5 RTC预分频器余数寄存器(RTC_DIVH / RTC_DIVL))
- [3.6 RTC计数器寄存器 (RTC_CNTH / RTC_CNTL)](#3.6 RTC计数器寄存器 (RTC_CNTH / RTC_CNTL))
- [3.7 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)](#3.7 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL))
- [4. RTC库函数](#4. RTC库函数)
- [5. RTC驱动步骤](#5. RTC驱动步骤)
- [6. 小实验](#6. 小实验)
前言
本文主要讲述BKP,42个16位的备份寄存器,用来存储84个字节的用户应用程序数据。其次,讲述RTC,一个独立的实时定时器,可提供时钟日历的功能,以及它相关的寄存器和库函数介绍。最后通过两个小实验帮助我们更实际的了解BKP和RTC。
一、BKP【备份寄存器】
1. 什么是BKP?
BKP,备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域Backup Domain里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
此外,BKP控制寄存器用来管理侵入检测 和RTC校准功能。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问:
- 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
- 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。
用户数据存储容量:
20字节(中容量和小容量)/
84字节(大容量和互联型)
备份域:VDD【主电源】和VBAT两种供电方式。掉电依然是丢失的。
2. BKP框图

3. 小实验:读写BKP
3.1 实验目的
读写BKP
3.2 硬件清单
- 上官二号
- ST-Link
- USB转TTL
3.3 构思环节
- 建立rtc文件夹:因为bkp内容一般是写在rtc中,所以直接建立rtc文件夹;建立.c和.h文件
- rtc.c
c
void rtc_init(void)
{
// 打开PWR和BKP时钟
// 使能后备寄存器访问
HAL_PWR_EnableBkUpAccess();
// RTC初始化
// 3个参数配置
RTC
32767
NONE
HAL_RTC_Init(&rtc_handle);
}
// 读取数据
uint16_t rtc_read_bkp(uint8_t bkrx)
{
uint16_t data = 0;
data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);
return (uint32_t)data;
}
// 写数据
void rtc_write_bkp(uint8_t bkrx, uint8_t data)
{
HAL_RTCEx_BKUPWrite();
}
- main.c
rtc_init();
rtc_write_bkr(1, 0xA5A5);
printf("读出来的值:%X\r\n", rtc_read_bkr(1));
3.4 出现的问题
写入的数据未读出来
只打开了PWR时钟,未把BKP时钟打开
__HAL_RCC_BKP_CLK_ENABLE();
3.5 代码展示
rtc.c
c
#include "rtc.h"
RTC_HandleTypeDef hrtc_handle = {0};
void rtc_init(void)
{
// 打开PWR和BKP时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
// 使能后备寄存器访问
HAL_PWR_EnableBkUpAccess();
hrtc_handle.Instance = RTC;
hrtc_handle.Init.AsynchPrediv = 32767;
hrtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
HAL_RTC_Init(&hrtc_handle);
}
// 读数据
uint16_t rtc_read_bkp(uint8_t bkr)
{
uint32_t data = 0;
data = HAL_RTCEx_BKUPRead(&hrtc_handle, bkr);
return (uint16_t)data;
}
// 写数据
void rtc_write_bkp(uint8_t bkr, uint16_t data)
{
HAL_RTCEx_BKUPWrite(&hrtc_handle, bkr, data);
}
rtc.h
c
#ifndef __RTC_H__
#define __RTC_H__
#include "sys.h"
void rtc_init(void);
uint16_t rtc_read_bkp(uint8_t bkr);
void rtc_write_bkp(uint8_t bkr, uint16_t data);
#endif
main.c
c
#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();
uart1_init(115200);
rtc_init();
printf("hello world! \r\n");
rtc_write_bkp(1, 0xA7A7);
printf("读出来的值:%X\r\n", rtc_read_bkp(1));
while(1)
{
}
}
3.6 实验结果
读出来的值:A5A5
按复位键,读出来的值不受影响

二、RTC【实时时钟】
1. 什么是RTC?
实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问:
- 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
- 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。
32位的可编程计数器,可对应Unix时间戳的秒计数器
Unix时间戳是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒。
20位的可编程预分频器,可适配不同频率的输入时钟。
可选择三种RTC时钟源:
- HSE时钟除以128(通常为8MHz/128)
- LSE振荡器时钟(通常为32.768KHz)
- LSI振荡器时钟(40KHz)
2. RTC框图


3. RTC寄存器
3.1 备份域控制寄存器(RCC_BDCR)
备份域控制寄存器中(RCC_BDCR)的LSEON、LSEBYP、RTCSEL和RTCEN位处于备份域。因
此,这些位在复位后处于写保护状态,只有在电源控制寄存器(PWR_CR)中的DBP位置'1'后才能对这些位进行改动。
关注:
RTCEN:RTC时钟使能
RTCSEL1:0:RTC时钟源选择
LSEON:外部低速振荡器使能 。1:外部32kHz振荡器开启。
3.2 RTC控制寄存器高位(RTC_CRH)

OWIE:允许溢出中断位。1------允许溢出中断
ALRIE:允许闹钟中断。1------允许闹钟中断
SECIE:允许秒中断。1------允许秒中断
这些位用来屏蔽中断请求。
注意:系统复位后,所有的中断被屏蔽,因此可通过写RTC寄存器来确保在初始化后没有挂起的中断请求。
当外设正在完成前一次写操作时(标志位RTOFF=0),不能对RTC_CRH寄存器进行写操作。
RTC功能由这个控制寄存器控制,一些位的写操作必须经过一个特殊的配置过程来完成。
3.3 RTC控制寄存器低位(RTC_CRL)

RTOFF :RTC操作关闭。
RTC模块利用这位来指示对其寄存器进行的最后一次操作的状态,指示操作是否完成。若此位为'0',则表示无法对任何的RTC寄存器进行写操作。此位为只读位。
1------上一次对RTC寄存器的写操作已经完成。
CNF :配置标志。
此位必须由软件置'1'以进入配置模式,从而允许向RTC_CNT、RTC_ALR或RTC_PRL寄存器写入数据。只有当此位在被置'1'并重新由软件清'0'后,才会执行写操作。
1------进入配置模式
3.4 RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)
预分频装载寄存器用来保存RTC预分频器的周期计数值。它们受RTC_CR寄存器的RTOFF位保护,仅当RTOFF值为'1'时允许进行写操作。
RTC预分频装载寄存器高位(RTC_PRLH)
0~3位
RTC预分频装载寄存器低位(RTC_PRLL)
0~15位
3.5 RTC预分频器余数寄存器(RTC_DIVH / RTC_DIVL)
RTC预分频器余数寄存器高位(RTC_DIVH)
0~3位
RTC预分频器余数寄存器低位(RTC_DIVL)
0~15位
3.6 RTC计数器寄存器 (RTC_CNTH / RTC_CNTL)
RTC核有一个32位可编程的计数器,可通过两个16位的寄存器访问。
RTC计数器寄存器高位(RTC_CNTH)
RTC计数器寄存器低位(RTC_CNTL)
3.7 RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)
当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟中断。此寄存器受RTC_CR寄存器里的RTOFF位写保护,仅当RTOFF值为'1'时,允许写操作。
RTC闹钟寄存器高位(RTC_ALRH)
RTC闹钟寄存器低位(RTC_ALRL)
4. RTC库函数
c
HAL_RTC_Init();
// 获取RTC当前时间
HAL_RTC_GetTime();
// 获取RTC当前日期
HAL_RTC_GetDate();
// 设置RTC当前时间
HAL_RTC_SetTime(); // 将CNT的值读出来(时间戳)
// 设置RTC当前日期
HAL_RTC_SetDate();
// 闹钟到点不会响
HAL_RTC_SetAlarm();
// 设置闹钟,到点后会响
HAL_RTC_SetAlarm_IT();
// 标志位是RTOFF位,置1才能写操作
__HAL_RTC_ALARM_GET_FLAG();
5. RTC驱动步骤
注意事项:
- 必须设置RTC_CRL寄存器中的CNF位,是RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
- 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1 时,才可以写入RTC寄存器。

6. 小实验
6.1 实验1:读写RTC时间实验
实验目的
读写RTC时间实验
构思环节
- 在rtc初始化函数中
c
void rtc_init(void)
{
打开PWR和BKP时钟
使能后备寄存器访问
HAL_PWR_EnableBkUpAccess();
// 配置RTC参数
对象选择RTC
分频系数32767 进入之后会自动+1
侵入检测
HAL_RTC_Init();
}
- msp函数
c
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
// 使能RTC
// 设置时钟源,两个函数,并对结构体参数进行配置
振荡器
LSE状态
PLL
HAL_RCC_OscConfig(&osc_initstruct);
外设RTC
时钟选择LSE :32767
HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
}
- 读取时间
c
void rtc_get_time(void)
{
两个函数,获取时间和获取日期
HAL_RTC_GetTime();
HAL_RTC_GetDate();
// 打印输出rtc时间
printf();
}
- 设置时间
struct tm time_data:该结构体是系统自带的,在#include "time.h"文件中
c
void rtc_set_time(struct tm time_data)
{
// 对时间的时分秒进行配置
// 对日期的年月日进行配置
需注意:年份系统给的是从1~99,所以年份表示为:`time_data.tm_year - 2000`
// 仅当RTOFF状态位是1时,才可以写入RTC寄存器,所以要做判断,一直获取RTOFF值
while(!__HAL_RTC_ALARM_GET_FLAG(&hrtc_handle, RTC_FLAG_RTOFF));
}
代码展示
rtc.c
c
#include "rtc.h"
#include "stdio.h"
RTC_HandleTypeDef hrtc_handle = {0};
void rtc_init(void)
{
// 打开PWR和BKP时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
// 使能后备寄存器访问
HAL_PWR_EnableBkUpAccess();
hrtc_handle.Instance = RTC;
hrtc_handle.Init.AsynchPrediv = 32767; // 分频系数
hrtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE; //侵入角 侵入检测
HAL_RTC_Init(&hrtc_handle);
}
// 设置Msp函数
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
// 使能RTC
__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; // LSE状态
osc_initstruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&osc_initstruct);
periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC ; // 外设RTC:32.768
periphclk_initstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; //时钟选择
HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
}
// 读数据
uint16_t rtc_read_bkp(uint8_t bkr)
{
uint32_t data = 0;
data = HAL_RTCEx_BKUPRead(&hrtc_handle, bkr);
return (uint16_t)data;
}
// 写数据
void rtc_write_bkp(uint8_t bkr, uint16_t data)
{
HAL_RTCEx_BKUPWrite(&hrtc_handle, bkr, data);
}
// 读取时间
void rtc_get_time(void)
{
RTC_TimeTypeDef rtc_time = {0};
RTC_DateTypeDef rtc_date = {0};
HAL_RTC_GetTime(&hrtc_handle, &rtc_time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc_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(&hrtc_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(&hrtc_handle, &rtc_date, RTC_FORMAT_BIN);
// 一直获取RTOFF值,0:一直在循环内
while(!__HAL_RTC_ALARM_GET_FLAG(&hrtc_handle, RTC_FLAG_RTOFF));
}
rtc.h
c
#ifndef __RTC_H__
#define __RTC_H__
#include "sys.h"
#include "time.h"
void rtc_init(void);
uint16_t rtc_read_bkp(uint8_t bkr);
void rtc_write_bkp(uint8_t bkr, uint16_t data);
void rtc_get_time(void);
void rtc_set_time(struct tm time_data);
#endif
main.c
c
#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();
uart1_init(115200);
rtc_init();
printf("hello world! \r\n");
if(rtc_read_bkp(1) != 0xA7A7)
{
rtc_write_bkp(1, 0xA7A7);
printf("读出来的值:%X\r\n", rtc_read_bkp(1));
struct tm time_data;
time_data.tm_year = 2026;
time_data.tm_mon = 7;
time_data.tm_mday = 7;
time_data.tm_hour = 9;
time_data.tm_min = 42;
time_data.tm_sec = 30;
rtc_set_time(time_data);
}
while(1)
{
rtc_get_time();
delay_ms(1000);
}
}
实验结果

但是按下复位键,还是从刚开始设置的值开始,思考如何按下复位键,时间不受影响?
解决:写之前判断一下,设置一次值,后面就不会改变。
c
if(rtc_read_bkp(1) != 0xA7A7)
{
rtc_write_bkp(1, 0xA7A7);
printf("读出来的值:%X\r\n", rtc_read_bkp(1));
struct tm time_data;
time_data.tm_year = 2026;
time_data.tm_mon = 7;
time_data.tm_mday = 7;
time_data.tm_hour = 9;
time_data.tm_min = 42;
time_data.tm_sec = 30;
rtc_set_time(time_data);
}

为什么不是2024,而是2000,因为HAL库目前有bug,有需求再去解决。
6.2 实验2:RTC闹钟实验
在实验1基础上做RTC闹钟实验
实验目的
实现RTC闹钟功能
构思环节
c
增加中断配置
HAL_NVIC_SetPriority(闹钟中断, 2, 2);
HAL_NVIC_EnableIRQ(闹钟中断);
闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler
}
闹钟回调函数
printf("ring ring ring...");
// 设置闹钟
void rtc_set_alarm(struct tm alarm_data)
{
// 定义闹钟结构体
// 闹钟参数配置(闹钟选择、时、分、秒)
// HAL库设置闹钟
}
main.c
c
//设置闹钟
struct tm alarm_data;
// 设置闹钟的时分秒
alarm_data.tm_hour = 16;
rtc_set_alarm(alarm_data);
c
// 设置的闹钟会响
HAL_RTC_SetAlarm_IT(&hrtc_handle, &alarm, RTC_FORMAT_BIN);
部分代码展示
rtc.c
c
// 设置Msp函数
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
// 使能RTC
__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; // LSE状态
osc_initstruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&osc_initstruct);
periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC ; // 外设RTC:32.768
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);
}
c
// 闹钟中断函数
void RTC_Alarm_IRQHandler(void)
{
HAL_RTC_AlarmIRQHandler(&hrtc_handle);
}
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
printf("ring ring ring...\r\n");
}
c
// 设置闹钟
void rtc_alarm_time(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(&hrtc_handle, &alarm, RTC_FORMAT_BIN);
}
rtc.h
c
#ifndef __RTC_H__
#define __RTC_H__
#include "sys.h"
#include "time.h"
void rtc_init(void);
uint16_t rtc_read_bkp(uint8_t bkr);
void rtc_write_bkp(uint8_t bkr, uint16_t data);
void rtc_get_time(void);
void rtc_set_time(struct tm time_data);
void rtc_alarm_time(struct tm alarm_data);
#endif
main.c
c
#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();
uart1_init(115200);
rtc_init();
printf("hello world! \r\n");
if(rtc_read_bkp(1) != 0xA7A7)
{
rtc_write_bkp(1, 0xA7A7);
printf("读出来的值:%X\r\n", rtc_read_bkp(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_alarm_time(alarm_data);
}
while(1)
{
rtc_get_time();
delay_ms(1000);
}
}
实验结果
