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

    在这里插入图片描述

    • 每个数据寄存器存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)
    	{
    		
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    读写RTC

    在这里插入图片描述

    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(32768-1);  // 设置分频系数
    		RTC_WaitForLastTask();
    		
    		MyRTC_SetTime();  // 设置时间 
    		
    		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
    	}
    	else
    	{
    		RTC_WaitForSynchro();  // 等待同步
    		RTC_WaitForLastTask(); // 等待写入完成 
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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);  // 余数寄存器
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    资料

    【晶振的负载电容到底怎么选择?】

    【STM32入门教程-2023持续更新中】

    STM32F10xxx参考手册(中文).pdf

    错误纠正

    之前代码在RTC配置分配系数用的37268-1(敲错了),正确分频应该是32768-1。自己焊了个RTC的电路,发现比正常一分慢了8s,一直觉得是硬件问题,换了3组电容,都是稳定慢八秒,然后发现是程序分频这里敲错了。2023/10/13

  • 相关阅读:
    iVX低代码平台系列详解 -- 概述篇(一)
    XYNUOJ 1256 喷水装置(一)—贪心算法
    设计模式之观察者模式
    轻松实现H5页面下拉刷新:滑动触发、高度提示与数据刷新全攻略
    REST framework serializer 数据data校验失败返回状态码
    word实用小技巧
    【竞品分析】竞品分析报告的基本模板
    jquery源码
    注解详解系列 - @EnableAspectJAutoProxy:启用AspectJ自动代理
    从零开始Blazor Server(12)--编辑菜单
  • 原文地址:https://blog.csdn.net/nanxl1/article/details/133679240