BKP(备份寄存器)&RTC(实时时钟)

1 BKP基本介绍

  • BKP(backup register,备份寄存器)。备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
  • 此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。

2 BKP案例

2.1 register版本

  • 案例需求:读写备份寄存器的值,验证掉电不丢失。

  • Driver_bkp.c

写数据时,第4.1步需要复位备份域,但读数据时就需要将其注释,否者数据会被清空。

#include "Driver_bkp.h"

void Driver_BKP_Init(void)
{
    /* 1. 打开电源控制时钟PWR */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;

    /* 2. 打开后备寄存器的时钟BKP */
    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;

    /* 3. 打开后备寄存器的写保护 */
    PWR->CR |= PWR_CR_DBP;
    
   /* 4. 设置合理的RTC时钟 */
    // 位于RCC的BDCR中
    // 4.1 复位备份域 -> 复位完成记得清空
    RCC->BDCR |= RCC_BDCR_BDRST;
    Delay_ms(1);
    RCC->BDCR &= ~RCC_BDCR_BDRST;
    // 4.2 RTC时钟使能
    RCC->BDCR |= RCC_BDCR_RTCEN;

    //4.3 LSE外部低速时钟使能
    RCC->BDCR |= RCC_BDCR_LSEON;
    //4.4 等待LSE准备就绪
    while ((RCC->BDCR & RCC_BDCR_LSERDY) == 0)
    ;
    
    //4.5 选择RTC的时钟源为LSE
    RCC->BDCR &= ~RCC_BDCR_RTCSEL_1;
    RCC->BDCR |= RCC_BDCR_RTCSEL_0;

}

  • main.c

写数据,读数据时把BKP->DR1赋值那行代码注释,以观察掉电不丢失。

int main(void)
{
	/*  测试bkp备份域模式 */
	Driver_USART1_Init();
	Driver_Key_Init();
	printf("hello bkp...\n");

	Driver_BKP_Init();
	BKP->DR1 = 9527;
    Delay_ms(100);
	printf("现在DR1中的值为%d\n",BKP->DR1);
	while (1)
	{

	}
}

2.2 HAL库版本

  • CubeMX配置

在这里插入图片描述

在这里插入图片描述

  • main.c
 /* USER CODE BEGIN 2 */
  // 备份域已经初始化完成 => 只需要开启RTC时钟 默认会初始化备份域
  // 直接写入对应的数据

  HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,9527);
  HAL_Delay(1);

  // 直接读取备份域的数据

  uint32_t num = HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1);
    printf("后备域的值DR1为%d\n", num);

  /* USER CODE END 2 */

3 RTC 基本介绍

RTC(Real Time Clock,实时时钟)。是一个掉电后仍然可以继续运行的独立定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期 RTC还包含用于管理低功耗模式的自动唤醒单元。

3.1 RTC功能框图

在这里插入图片描述

3.2 RTC的时钟源

在这里插入图片描述

  • RTC有3路时钟来源:HSE(8MHz)/128,LSE(32.768KHz),LSI(40KHz)。
  • 一般的通用做法是使用LSE。2个原因:
    • 一是LSE不受主电源掉电的影响,
    • 二是它的频率是我们都是选择32768Hz,正好是2^15,分频容易实现。

3.3 APB1接口

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

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

3.4 RTC预分频模块

在这里插入图片描述

RTC预分频模块,属于后备区域,VDD掉电后,可以在VBAT下继续运行。包含了一个20位的可编程分频器(RTC预分频器)。它可编程产生 1 秒的 RTC 时间基准 TR_CLK。如果在RTC_CR寄存器中设置了相应的允许位,则在每个TR_CLK周期中RTC产生一个中断(秒中断)。

3.5 32位可编程计数器

在这里插入图片描述

这个模块也属于后备区域,是一个32位的可编程计数器,可被初始化为当前的系统时间。一个32位的时钟计数器,按秒钟计算,可以记录4294967296秒,约合136年左右。

3.6 中断

在这里插入图片描述

(1)秒中断:每计时1s产生一次中断。

(2)计数器溢出中断。136年才会产生溢出,一般用不上。

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

4 案例

4.1 RTC案例一:实时时钟

需求描述:显示时间。通过串口把时间发送给电脑显示。即使关机很多天,再启动后也能正确显示时间。

4.1.1 项目准备
1. 初始化RTC

(1) 打开电源控制PWR时钟

(2) 打开备份域时钟BKP时钟

(3) 默认清空下 备份域写保护的 -> 打开写保护 PWR_CR

(4) 打开LSE 同时切换对应的RTC时钟 使能

(5) 等待RTOFF为1

(6) 进入到配置模式

(7) 修改预分频值

(8) 退出配置模式

2. 设置时间戳的值

(1) 等待RTOFF为1

(2) 进入到配置模式

(3) 修改CNT的值 => 等待秒标志

(4) 退出配置模式

3. 读取时间戳的值转换为日期时间

(1) 读取RTC中的值需要等待 寄存器同步

(2) 读取CNT的值 拼接为32位数字 + 8 * 3600

(3) localtime方法 转换32位数字为结构体tm

(4) 年份+1900 月份+1 0时区的时间(如果想要修改为东八区的时间)

4.1.2 register版本
  • Driver_RTC.c
#include "Driver_RTC.h"

void Driver_RTC_Init(void)
{
    /* 1. 打开时钟  PWR  BKP */
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    RCC->APB1ENR |= RCC_APB1ENR_BKPEN;
    /* 2. 打开备份区域的写保护 */
    PWR->CR |= PWR_CR_DBP;

    /* 3. 配置LSE外部低速时钟  作为RTC的系统时钟 */
    // 3.1 开启LSE外部低速时钟
    RCC->BDCR |= RCC_BDCR_LSEON;
    while ((RCC->BDCR & RCC_BDCR_LSERDY) == 0)
        ;
    // 3.2 选择外部低速时钟作为RTC系统时钟
    // 01
    RCC->BDCR &= ~RCC_BDCR_RTCSEL_1;
    RCC->BDCR |= RCC_BDCR_RTCSEL_0;

    // 3.3 开启RTC时钟
    RCC->BDCR |= RCC_BDCR_RTCEN;

    /* 4. 配置RTC => 计数器  主要配置预分频系数 */
    // 4.1 等待RTOFF标志位为1  才能进行后续的操作
    while ((RTC->CRL & RTC_CRL_RTOFF) == 0)
        ;

    // 4.2 置为CNF   进入到配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 4.3 修改其他寄存器的值  =>  主要修改预分频系数  PRL
    // 计算频率 = rtcclk(32768) / (prl + 1)
    // RTC->PRLH = 32767 >> 16;
    RTC->PRLL = 0X7FFF;

    // 4.4 清除CNF位  退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;
}

void Driver_RTC_set_timestamp(uint32_t timestamp)
{
    /* 将timestamp存入到CNT中 */
    // 1. 等待RTOFF标志位
    while ((RTC->CRL & RTC_CRL_RTOFF) == 0)
        ;

    // 2. 进入配置模式
    RTC->CRL |= RTC_CRL_CNF;

    // 3. 对其他寄存器进行配置
    // 3.1 等到秒标志
    while ((RTC->CRL & RTC_CRL_SECF) == 0)
        ;

    // 3.2 将timpstamp写入到CNT中
    RTC->CNTH = timestamp >> 16;
    RTC->CNTL = timestamp & 0xffff;

    // 4. 退出配置模式
    RTC->CRL &= ~RTC_CRL_CNF;
}

void Driver_RTC_get_dateTime(DateTimeStruct *datetime)
{
    // 1. 等到寄存器同步
    while ((RTC->CRL & RTC_CRL_RSF) == 0)
        ;

    // 2. 读出CNT的值
    uint32_t timestamp = (RTC->CNTH << 16) | (RTC->CNTL) + 8 * 3600;

    // 3. 将CNT的值转换为年月日时分秒
    struct tm *datetime_tm = localtime(&timestamp);

    datetime->year = datetime_tm->tm_year + 1900;
    datetime->month = datetime_tm->tm_mon + 1;
    datetime->day = datetime_tm->tm_mday;
    datetime->hour = datetime_tm->tm_hour ;
    datetime->minute = datetime_tm->tm_min;
    datetime->second = datetime_tm->tm_sec;
}

  • Driver_RTC.h
#ifndef __DRIVER_RTC_H
#define __DRIVER_RTC_H

#include "stm32f10x.h"
#include "time.h"

typedef struct
{
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
}DateTimeStruct;

void Driver_RTC_Init(void);

void Driver_RTC_set_timestamp(uint32_t timestamp);

void Driver_RTC_get_dateTime(DateTimeStruct *datetime);

#endif

  • main.c

    Driver_RTC_set_timestamp 设置好后第二次读就可以注释掉了,因为RTC掉电不丢失。

int main(void)
{

	/*  实现实时时钟 */
	Driver_USART1_Init();
	printf("hello rtc...\n");
	Driver_RTC_Init();

	// Driver_RTC_set_timestamp(1729234016);
	
	DateTimeStruct datetime;
	while (1)
	{
		Driver_RTC_get_dateTime(&datetime);
		printf("当前时间为%04d-%02d-%02d %02d:%02d:%02d\n",
				datetime.year,
				datetime.month,
				datetime.day,
				datetime.hour,
				datetime.minute,
				datetime.second);
		Delay_ms(1000);
	}
}
4.1.3 实验现象

在这里插入图片描述

4.1.4 HAL库版本
  • main.c
// 开发板备份域存有RTC的值  直接使用即可
  RTC_TimeTypeDef time,pre_time;
  RTC_DateTypeDef date,pre_date;

  pre_time.Hours = 15;
  pre_time.Minutes = 39;
  pre_time.Seconds = 0;
  pre_date.Year = 54;
  pre_date.Month = 10;
  pre_date.Date = 18;
  HAL_RTC_SetTime(&hrtc,&pre_time,RTC_FORMAT_BIN);
  HAL_RTC_SetDate(&hrtc,&pre_date,RTC_FORMAT_BIN);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN);
    printf("现在的时间是%04d-%02d-%02d %02d:%02d:%02d\n",
           (uint16_t)date.Year + 1970, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

4.2 RTC案例2:使用闹钟唤醒待机模式

执行完毕正常代码之后,让MCU进入待机模式,设置闹钟,让MCU在设定时间内从待机模式中被唤醒。

4.2.1 项目准备

1. 初始化RTC

(1) 打开电源控制PWR时钟

(2) 打开备份域时钟BKP时钟

(3) 默认清空下 备份域写保护的 -> 打开写保护 PWR_CR

(4) 打开LSE 同时切换对应的RTC时钟 使能

(5) 等待RTOFF为1

(6) 进入到配置模式

(7) 修改预分频值

(8) 退出配置模式

2. 设置闹钟的值

(0) 清空闹钟标志

(1) 等待RTOFF为1

(2) 进入到配置模式

(3) 等到秒标志

(4) 修改CNT为0

(5) 修改alarm的值为second-1

(6) 退出配置模式

4.2.2 register版本
  • Driver_RTC.c

在上个案例的同名文件加上一个函数即可

void Driver_RTC_set_alarm(uint32_t second)
{
    /* 0. 清理闹钟标志位 */
    RTC->CRL &= ~RTC_CRL_ALRF;

    /* 1. 等待RTOFF */
    while ((RTC->CRL & RTC_CRL_RTOFF) == 0)
        ;

    /* 2. 进入配置模式 */
    RTC->CRL |= RTC_CRL_CNF;

    /* 3. 等待秒标志 */
    while ((RTC->CRL & RTC_CRL_SECF) == 0)
        ;

    /* 4. 先设置CNT为0 */
    RTC->CNTH = 0;
    RTC->CNTL = 0;

    /* 5. 再设置alarm的值 */
    // RTC->ALRH = (second - 1) >> 16;
    RTC->ALRL = (second - 1);

    /* 6. 退出配置模式 */
    RTC->CRL &= ~RTC_CRL_CNF;
}

  • main.c
#include "Driver_USART1.h"
#include "Delay.h"
#include "Driver_RTC.h"

void enter_standby_mode(void)
{
	// 开启PWR的RCC时钟需要提前进行
	/* 1. 选择睡眠模式为深度睡眠  */
	SCB->SCR |= SCB_SCR_SLEEPDEEP;

	/* 2. 深睡眠模式对应的是待机模式 */
	PWR->CR |= PWR_CR_PDDS;

	/* 3. 使用PA0的wake up功能退出待机模式 */
	PWR->CSR |= PWR_CSR_EWUP;

	/* 4. 进入到待机模式 */
	__WFI();
}

int main(void)
{

	/*  实现实时时钟 */
	Driver_USART1_Init();
	printf("hello rtc...\n");
	Driver_RTC_Init();

	// 提前打开RCC中的PWR时钟
	RCC->APB1ENR |= RCC_APB1ENR_PWREN;

	// 每次从main方法的起始位置运行的时候  =>  通过唤醒标志位判断是否从待机模式唤醒
	if (PWR->CSR & PWR_CSR_WUF)
	{
		printf("从待机模式被唤醒\n");
		// 从待机模式唤醒  需要清除对应的标志位
		PWR->CR |= PWR_CR_CSBF;
		PWR->CR |= PWR_CR_CWUF;
	}
	else
	{
		printf("复位键按下运行\n");
	}

	while (1)
	{
		printf("代码运行完成  5s后进入到待机模式\n");
		Delay_ms(5000);
		// 提前设置好闹钟
		Driver_RTC_set_alarm(10);
		Delay_ms(1);
		enter_standby_mode();
	}
}

4.2.3 实验现象

可见唤醒时间如预期一样间隔15s

在这里插入图片描述

4.2.4 HAL库版本
CubeMX配置

在这里插入图片描述

在这里插入图片描述

项目准备

(1) 使用cube 构建代码

(2) 修改系统时钟 => 打开USART1 用于printf输出 => 添加LED2作为正常运行模式的显示

(3) 打开RTC时钟 修改时钟源为LSE 32.768kHz

(4) 修改项目名称 => 创建代码

(5) 修改keil => 勾选lib => 修改debug => 添加对应的文件和目录

设置闹钟的方法

(1) 获取当前的时间

HAL_RTC_GetTime();

(2) 设置闹钟的时间

HAL_RTC_SetAlarm(); alarm.alarmtime.second = time.second + s - 1;

  • main.c
/* USER CODE BEGIN 2 */
  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);

  printf("hello standby...\n");

  // 判断是否从待机模式启动
  if (__HAL_PWR_GET_FLAG(PWR_CSR_WUF))
  {
    // 从待机模式被唤醒
    printf("从待机模式被唤醒\n");
    __HAL_PWR_CLEAR_FLAG(PWR_CSR_WUF);
    __HAL_PWR_CLEAR_FLAG(PWR_CSR_SBF);
  }
  else
  {
    printf("从复位启动\n");
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

    printf("代码执行完成 5s后进入待机模式\n");
    HAL_Delay(5000);
    
    // 使用封装后的设置闹钟方法
    User_set_alarm(5);
    HAL_Delay(1);
    HAL_SuspendTick();
    HAL_PWR_EnterSTANDBYMode();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
  • rtc.c
/* USER CODE BEGIN 1 */
void User_set_alarm(uint32_t second)
{
  // 获取当前的时间
  RTC_TimeTypeDef time;
  HAL_RTC_GetTime(&hrtc,&time,RTC_FORMAT_BIN);

  // 加填写的参数的秒数  再写入
  RTC_AlarmTypeDef alarm;
  alarm.AlarmTime.Hours = time.Hours;
  alarm.AlarmTime.Minutes = time.Minutes;
  alarm.AlarmTime.Seconds = time.Seconds + second - 1;
  HAL_RTC_SetAlarm(&hrtc,&alarm,RTC_FORMAT_BIN);
}
/* USER CODE END 1 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值