目录
一、备份寄存器的功能
二、示例功能
三、项目设置
1、晶振、DEBUG、CodeGenerator、USART6
2、RTC
3、NVIC
4、GPIO 及KEYLED
四、软件设计
1、main.h
2、main.c
3、rtc.c
4、keyled.c、keyled.h
五、运行调试
本实例旨在介绍备份寄存器的作用。本实例继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。本实例将引用本文作者写的其他文章作为参考文献。
参考项目:细说STM32F407单片机RTC的基本原理及闹钟和周期唤醒功能的使用方法-CSDN博客 https://wenchm.blog.csdn.net/article/details/145575366
一、备份寄存器的功能
STM32F407的RTC有20个32位的备份寄存器,寄存器名称为RTC_BKP0R~RTC_BKP19R。这些备份寄存器由备用电源VBAT供电,在系统复位或主电源关闭时,只要VBAT有电,备份寄存器的内容就不会丢失。所以,备份寄存器可以用来存储一些用户数据。
文件stm32f4xx_hal_rtc_ex.h中有读写备份寄存器的功能函数,其原型定义如下:
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc,uint32_t BackupReg);
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc,uint32_t BackupReg,uint32_t Data);
其中,参数BackupReg是备份寄存器编号,文stm32f4xx_hal_rtc_ex.h定义了20个备份寄存器编号的宏,定义如下:
#define RTC_BKP_DR0 0x00000000U
#define RTC_BKP_DR1 0x00000001U
/*省略了中间的定义代码*/
#define RTC_BKP_DR18 0x00000012U
#define RTC_BKP_DR19 0x00000013U
RTC也可以由VBAT供电,在系统复位或主电源关闭时,RTC的日历不受影响。但是在参考项目中,因为在系统复位时调用了RTC初始化函数MX_RTC_Init(),而这个函数总是设置RTC的日期和时间,所以复位后,RTC的日期时间又从CubeMX里设置的初始日期时间开始了。
可以使用备份寄存器对参考项目进行修改,使得系统在复位时不再自动设置RTC的初始日期时间,而是由备份寄存器RTC_BKP_DR0的内容决定,而且可以把RTC当前时间保存到备份寄存器。
二、示例功能
本实例,将设计一个示例,演示备份寄存器的使用。示例具有如下的功能和操作流程。
- 在RTC初始化函数MX_RTC_Init()中增加代码,读取备份寄存器RTC_BKP_DR0的内容,如果值为0,就设置RTC为初始时间和日期,如果值为1,就不设置RTC的日期和时间。
- 在程序运行时,可以修改保存到备份寄存器RTC_BKP_DR0的值,可以保存0或1。
- 将RTC当前时间的时、分、秒数据保存到RTC_BKP_DR2到RTC_BKP_DR4的3个寄存器里。RTC_BKP_DR1里的值为1时,表示保存了RTC时间。
- 读取备份寄存器RTC_BKP_DR0到RTC_BKP_DR4的内容,显示到串口助手上。
- 使用RTC周期唤醒中断,唤醒中断周期1s,在唤醒中断里读取时间后在串口助手上显示。本示例还用到4个按键和2个LED,所以继续引用作者发布的其它参考项目中的KEYLED文件中的程序文件。
- 菜单设计:
[S2]KeyUp = Reset RTC time on startup. //按下S2键,用初始时间重置RTC
[S3]KeyDown = Read BKUP registers. //按下S3键,读取备份寄存器
[S4]KeyLeft = Save current time to BKUP. //按下S4键,把当前时间保存到RTC备份寄存器
[S5]KeyRight= Change RTC time from BKUP. //按下S5键,用备份寄存器存储的时间重置RTC时间
三、项目设置
1、晶振、DEBUG、CodeGenerator、USART6
与参考文章相同。
2、RTC
在RTC的模式设置中,启用时钟源和日历,设置WakeUp模式为Internal WakeUp。在参数设置部分,设置数据格式为Binary data format,设置唤醒时钟为1Hz信号,唤醒周期数为0,这样就会每秒产生一次唤醒中断。
3、NVIC
在NVIC中开启周期唤醒中断,设置RTC抢占优先级为1。
4、GPIO 及KEYLED
4个按键和2个LED。引用文件夹KEYLED的方法请参考本文作者发布的其它文章。
程序标签 | 板上名称 | 引脚名称 | 引脚功能 | GPIO模式 | 默认电平 | 上拉或下拉 |
LED1 | D1 | PA6 | GPIO_Output | 推挽输出 | High | 上拉 |
LED2 | D2 | PA4 | GPIO_Output | 推挽输出 | High | 上拉 |
KeyRight | S5 | PF6 | GPIO_Input | 输入 | 上拉 | |
KeyDown | S3 | PD3 | GPIO_Input | 输入 | 上拉 | |
KeyLeft | S4 | PF7 | GPIO_Input | 输入 | 上拉 | |
KeyUp | S2 | PA0 | GPIO Input | 输入 | 上拉 |
四、软件设计
1、main.h
/* USER CODE BEGIN Private defines */
void RTC_ToggleReset();
void RTC_SaveToBKUP();
void RTC_LoadFromBKUP();
void RTC_ReadBKUP();
/* USER CODE END Private defines */
2、main.c
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime; //RTC读取时间和日期的结果数据
RTC_DateTypeDef sDate;
uint8_t RTC_isReading=0; //RTC是否正在读日期时间数据
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
__HAL_RTC_WAKEUPTIMER_DISABLE(&hrtc); //禁止RTC周期唤醒
printf("Demo11_2_RTC_Backup:Using Backup Registers.\r\n");
uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); //读取备份寄存DR0
printf("Reset RTC time on startup: ");
if ((iniRTC & 0x01)==0)
printf("Yes.\r\n");
else
printf("No.\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
//显示菜单,也可以放在USER CODE BEGIN 2里
printf("[S2]KeyUp = Reset RTC time on startup(LED2_ON).\r\n");
printf("[S3]KeyDown = Read BKUP registers.\r\n");
printf("[S4]KeyLeft = Save current time to BKUP.\r\n");
printf("[S5]KeyRight= Change RTC time from BKUP.\r\n\r\n");
__HAL_RTC_WAKEUPTIMER_ENABLE(&hrtc); //重启RTC周期唤醒
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
switch(curKey)
{
case KEY_UP: //[S2]KeyUp,改变保存到备份寄存器DR0的数值0 or 1
RTC_ToggleReset();
break;
case KEY_DOWN: //[S3]KeyDown,读取5个备份寄存器的数据并显示
RTC_ReadBKUP();
case KEY_LEFT: //[S4]KeyLeft,将RTC当前时间保存到备份寄存器
RTC_SaveToBKUP();
break;
case KEY_RIGHT: //[S5]KeyRight,从备份寄存器读取时间,改变RTC时间
RTC_LoadFromBKUP();
break;
default:
break;
}
HAL_Delay(300); //消抖
}
/* USER CODE END 3 */
main()函数里在完成了外设初始化之后,执行__HAL_RTC_WAKEUPTIMER_DISABLE (&hrtc),禁止了RTC周期唤醒单元。在进入while死循环之前,再开启RTC的周期唤醒单元。
调用函数HAL_RTCEx_BKUPRead()读取备份寄存器RTC_BKP_DR0的内容,如果寄存器的值为0,就会在MX_RTC_Init()里用CubeMX设置的初始日期和时间设置RTC的日期时间;如果寄存器的值不为0,在MX_RTC_Init()里就不会设置RTC的日期和时间。后一种情况下,在按复位键[S6]使系统复位时,RTC仍然保持连续的时间,因为RTC工作在备份域,不会在系统复位时复位。
然后,在串口助手上显示了一个模拟菜单。
在while循环里读取按键输入,用4个按键模拟菜单项的选择,按下某个按键后就执行与菜单项对应的功能。按键的响应代码封装为4个函数。
/* USER CODE BEGIN 4 */
//周期唤醒中断回调函数
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
LED1_Toggle();
RTC_isReading = 1; //RTC正在读取数据,该变量的作用类似于RTOS中的二值信号量
if (HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
{
//调用HAL_RTC_GetTime()之后必须调用HAL_RTC_GetDate()以解锁数据,才能连续更新日期和时间
HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
RTC_isReading = 0; //RTC结束了读取数据
//显示时间hh:mm:ss
char str[40];
sprintf(str,"RTC Time = %2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);
printf(" %s\r\n",str);
}
}
/* 翻转保存备份寄存器RTC_BKP_DR0的值 */
void RTC_ToggleReset() //S2
{
uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
iniRTC = !iniRTC;
if ((iniRTC & 0x01)==0){ //存储值为0时,用CubeMX中的值复位日期时间
printf("Reset RTC time on startup.\r\n");
LED2_ON();
}
else{
printf("Not reset RTC time on startup.\r\n");
LED2_OFF();
}
//__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); //取消写保护
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0, iniRTC); //写入备份寄存器DR0
//__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); //开启写保护
}
/* RTC时间保存到备份寄存器 */
void RTC_SaveToBKUP() //S4
{
while (RTC_isReading) //如果RTC的WakeUp中断里正在读取数据,就等待其读取完
HAL_Delay(1);
//__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); //取消写保护
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1, 0x01); //0x01=保存了时间的标志
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR2, sTime.Hours); //使用全局变量sTime,不再读取当前时间
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR3, sTime.Minutes);
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR4, sTime.Seconds);
//__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); //开启写保护
char timeStr[30];
sprintf(timeStr,"%2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds); //转换为字符串,自动添加"\0"
printf("Current time %s is saved in BKUP.\r\n",timeStr);
}
/* 用保存的时间设置为RTC时间 */
void RTC_LoadFromBKUP() //S5
{
uint32_t isTimeSaved = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); //读取备份寄存DR1
if (isTimeSaved)
{
RTC_TimeTypeDef time;
time.Hours = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
time.Minutes = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
time.Seconds = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; //这两个参数还是有用的,如果不设置这2个参数,设置的小时数会自动减1
time.StoreOperation = RTC_STOREOPERATION_SET;
//HAL_Delay(10); //必须加这个延时,否则设置的时间会错乱
while(RTC_isReading) //如果RTC正在WakeUp中断里读取数据,就等待其读完
HAL_Delay(1);
/*函数HAL_RTC_SetTime()内部在修改RTC的寄存器之前,会调用 __HAL_RTC_WRITEPROTECTION_DISABLE()取消写保护,
写完之后,会调用 __HAL_RTC_WRITEPROTECTION_ENABLE() 重新开启写保护 */
if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) == HAL_OK)
printf("Load and set BKUP time, success.\r\n");
else
printf("Load and set BKUP time, failure.\r\n");
}
else
printf("No BKUP time is saved.\r\n");
}
/* 读取5个备份寄存器内容并显示 */
void RTC_ReadBKUP() //S3
{
uint32_t regValue;
char regStr[40];
regValue = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); //0=系统复位时复位RTC时间
sprintf(regStr,"Reset RTC ,BKP_DR0= %lu",regValue); //转换为字符串,自动添加"\0"
printf(" %s\r\n",regStr);
regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); //1=有时间数值
sprintf(regStr,"Time is saved,BKP_DR1= %lu",regValue);
printf(" %s\r\n",regStr);
regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2); //Hour
sprintf(regStr,"Saved time(Hour) ,BKP_DR2= %lu",regValue);
printf(" %s\r\n",regStr);
regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3); //Minute
sprintf(regStr,"Saved time(Min) , BKP_DR3= %lu",regValue);
printf(" %s\r\n",regStr);
regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4); //Second
sprintf(regStr,"Saved time(Sec) , BKP_DR4= %lu",regValue);
printf(" %s\r\n",regStr);
}
//串口打印
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
回调函数HAL_RTCEx_WakeUpTimerEventCallback()处理RTC周期唤醒中断。
其余的4个函数,是对4个菜单项的响应代码的封装。
3、rtc.c
/* USER CODE BEGIN Check_RTC_BKUP */
uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0); //读取备份寄存DR0
if ((iniRTC & 0x01)) //非零,无需初始化RTC日期时间
{
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,0,RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
Error_Handler();
return; //提前退出函数
}
/* USER CODE END Check_RTC_BKUP */
沙箱中添加的代码就是用函数HAL_RTCEx_BKUPRead()读取备份寄存器RTC_BKP_DR0的内容,如果寄存器的值不为0,就在执行完HAL_RTCEx_SetWakeUpTimer_IT()设置和启动周期唤醒后,退出函数;如果寄存器的值为0,就继续执行函数内后面的代码。
这样,就在函数MX_RTC_Init()中加入了用户功能代码,使得在系统复位时,RTC不必设置初始日期和时间。
4、keyled.c、keyled.h
引用KEYLED文件夹下的 keyled.c、keyled.h,使用方法请参考本文作者发布的其他文章。
五、运行调试
首次下载,或按S6键复位,显示系统菜单。重要的信息是显示当前从哪里重置RTC时间的信息。如果YES(LED2亮)则从RTC初始化值(15:30:0)重置RTC时间,否则不是;
按S2键切换从哪里重置RTC时间。比如,下图,每次按下S6复位键,系统都从RTC初始化值重置时间,否则不重置时间,即当LED2灯不亮(DR0=1)时,按下复位键S6,不改变RTC时间,连续显示RTC时间。
按S4键,存储当前时间到备份寄存器;按S3键,读取备份存储器内容并保存当前最新值到备份寄存器;按S5键,用备份寄存器内容重置RTC时间;