BKP和RTC实时时钟
BKP
BKP简介
- BKP(Backup Registers) 备份寄存器
- BKP可用于存储用户应用程序数据。当VDD(2.0-3.6V) 电源被切断时,仍然由VBAT(1.8-3.6V) 维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,也不会被复位
- TAMPER引脚产生的侵入事件将所有备份寄存器内容清除(可做防拆卸设计)
- RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
- 存储RTC时钟校准寄存器
- 用户数据存储容量:20字节(中容量和小容量)/ 84字节(大容量和互联型)
BKP基本结构
- 每个数据寄存器存2个字节数据,中容量和小容量共十个DR,大容量和互联型共42个DR
RTC
RTC简介
- RTC(real time clock) 实时时钟
- RTC是一个独立的定时器,可为系统提供时钟和日历的功能
- RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0-3.6V)断电后可借助VBAT(1.8-3.6V)供电继续走时
- 32位的可编程计数器,可对应Unix时间戳的秒计数器
- 20位的可编程预分频器,可适配不同频率的输入时钟
- 可选择三种RTC时钟源:( 通常使用LSE)
- HSE时钟除以128(通常为8MHz/128)
- LSE振荡器时钟(通常为32.768KHz)
- LSI振荡器时钟(40KHz)
-
RTCCLK 一般信号频率一般为32.768kHz
-
RTC预分频器通过计数实现对输入信号的分频,RTC_DIV实际是一个计数器,RTC_PRL用于设置预分频值( 分频倍数 = RTC_PRL+1 )
- 为得到每秒的计时(RTC_PRL一般为32767)
-
RTC_CNT中存放的是UNIX时间戳的秒数
-
RTC_ALR是RTC的闹钟功能,当RTC_CNT=RTC_ALR时,触发RTC_Alarm中断。若配置了RTC_Alarm中断服务,可在中断服务函数中执行需要的操作
-
RTC_Overflow但RCT_CNT溢出时会触发该中断,一般不会触发
-
RTC_Second, 每秒中断(具体时间由输入信号频率与预分频系数配置决定)
RTC框图
- 一般情况信号源为LSE,且外部一般接32.768khz的石英晶振 (制作工艺和使用方便)
硬件电路
- C1和C2的参数教程是参考手册,但若有硬件设计需求,建议参考所选用晶振的负载电容大小
-
值得注意的是: 晶振本身就有百万分之几十的误差(ppm), 因此电容相差不太的情况, 所造成的误差可能也就几百万分之一
-
电容对频率的影响: 电容越大频率越低,反之越高
-
这里是一个UP对晶振电容大小和频率的测试视频 【晶振的负载电容到底怎么选择?】
RTC操作注意事项
-
执行以下操作将使能对BKP和RTC的访问:
-
设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟
-
设置PWR_CR的DBP,使能对BKP和RTC的访问
-
-
若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1 (等待同步)
-
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器 (进入配置模式, 无需软件操作 )
-
对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器 (等待写入)
程序设计
读写BKP
#include "stm32f10x.h"
#include "delay.h"
#include "OLED.h"
int main(void)
{
OLED_Init();
// RCC
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); // 使能BKP
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); // 使能PWR
PWR_BackupAccessCmd(ENABLE); // 配置DBP
BKP_WriteBackupRegister(BKP_DR1, 0x01); // 写入数据 中容量 DR1-DR10 第二次注释该行
OLED_ShowHexNum(1, 1, BKP_ReadBackupRegister(BKP_DR1), 4); // 读BKP DR寄存器
// 在提供备用电源的情况下,断电和复位不会情况BKP里的数据
while(1)
{
}
}
PWR_BackupAccessCmd(ENABLE)
实际配置了CR寄存器的DBP位
/**
* @brief Enables or disables access to the RTC and backup registers.
* @param NewState: new state of the access to the RTC and backup registers.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void PWR_BackupAccessCmd(FunctionalState NewState)
{
/* Check the parameters */
assert_param(IS_FUNCTIONAL_STATE(NewState));
*(__IO uint32_t *) CR_DBP_BB = (uint32_t)NewState;
}
读写RTC
MyRTC.c
#include "stm32f10x.h"
#include <time.h>
uint16_t MyRTC_Time[] = {2023, 9, 18, 20, 40, 58};
/**
* @brief 设置时间
*/
void MyRTC_SetTime(void)
{
time_t time_cnt;
struct tm time_date;
time_date.tm_year = MyRTC_Time[0] - 1900; // 年
time_date.tm_mon = MyRTC_Time[1] - 1; // 月
time_date.tm_mday = MyRTC_Time[2]; // 日
time_date.tm_hour = MyRTC_Time[3]; // 时
time_date.tm_min = MyRTC_Time[4]; // 分
time_date.tm_sec = MyRTC_Time[5]; // 秒
time_cnt = mktime(&time_date) - 8 * 60 * 60; // 转换为时间戳 减去东八偏移
RTC_SetCounter(time_cnt); // 将数据写入RTC的CNT
RTC_WaitForLastTask(); // 等待写入完成
}
/**
* @brief 读取时间
*/
void MyRTC_ReadTime(void)
{
time_t time_cnt;
struct tm time_date;
time_cnt = RTC_GetCounter() + 8 * 60 * 60; // 加上东八区偏移
time_date = *localtime(&time_cnt);
MyRTC_Time[0] = time_date.tm_year + 1900; // 年
MyRTC_Time[1] = time_date.tm_mon + 1;// 月
MyRTC_Time[2] = time_date.tm_mday; // 日
MyRTC_Time[3] = time_date.tm_hour; // 时
MyRTC_Time[4] = time_date.tm_min ; // 分
MyRTC_Time[5] = time_date.tm_sec ; // 秒
}
/**
* @brief 初始化RTC
*/
void MyRTC_Init(void)
{
// RCC
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 设置CR寄存器的DBP位
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) // 避免时间重复写入
{
RCC_LSEConfig(RCC_LSE_ON); // 开启LSE时钟 外接32.768khz晶振 LSE默认不上电
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET); // 等待LSE起振
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 设置RTC时钟源为LSE
RCC_RTCCLKCmd(ENABLE); // 使能时钟
RTC_WaitForSynchro(); // 等待同步
RTC_WaitForLastTask(); // 等待写入完成
RTC_SetPrescaler(37628-1); // 设置分频系数
RTC_WaitForLastTask();
MyRTC_SetTime(); // 设置时间
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
RTC_WaitForSynchro(); // 等待同步
RTC_WaitForLastTask(); // 等待写入完成
}
}
MyRTC.h
#ifndef __MYRTC_H
#define __MYRTC_H
extern uint16_t MyRTC_Time[];
void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);
void MyRTC_Init(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"
int main(void)
{
OLED_Init();
MyRTC_Init();
OLED_ShowString(1, 1, "Date:XXXX-XX-XX");
OLED_ShowString(2, 1, "Time:XX:XX:XX");
OLED_ShowString(3, 1, "CNT :");
OLED_ShowString(4, 1, "DIV :");
while (1)
{
MyRTC_ReadTime();
OLED_ShowNum(1, 6, MyRTC_Time[0], 4);
OLED_ShowNum(1, 11, MyRTC_Time[1], 2);
OLED_ShowNum(1, 14, MyRTC_Time[2], 2);
OLED_ShowNum(2, 6, MyRTC_Time[3], 2);
OLED_ShowNum(2, 9, MyRTC_Time[4], 2);
OLED_ShowNum(2, 12, MyRTC_Time[5], 2);
OLED_ShowNum(3, 6, RTC_GetCounter(), 10);
OLED_ShowNum(4, 6, RTC_GetDivider(), 10); // 余数寄存器
}
}
资料
【晶振的负载电容到底怎么选择?】
【STM32入门教程-2023持续更新中】
STM32F10xxx参考手册(中文).pdf