STM32入门笔记14_RTC实时时钟

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基本结构

STM32入门笔记14_RTC实时时钟_第1张图片

  • 每个数据寄存器存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)

STM32入门笔记14_RTC实时时钟_第2张图片

  • 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框图

STM32入门笔记14_RTC实时时钟_第3张图片

  • 一般情况信号源为LSE,且外部一般接32.768khz的石英晶振 (制作工艺和使用方便)

硬件电路

STM32入门笔记14_RTC实时时钟_第4张图片

  • C1和C2的参数教程是参考手册,但若有硬件设计需求,建议参考所选用晶振的负载电容大小

STM32入门笔记14_RTC实时时钟_第5张图片

STM32入门笔记14_RTC实时时钟_第6张图片

  • 值得注意的是: 晶振本身就有百万分之几十的误差(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

STM32入门笔记14_RTC实时时钟_第7张图片

#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

STM32入门笔记14_RTC实时时钟_第8张图片

MyRTC.c

#include "stm32f10x.h"
#include 
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

你可能感兴趣的:(单片机学习笔记,stm32,笔记,单片机,c语言,嵌入式硬件)