STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解

news2024/11/13 16:04:47

目录

STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解

1 什么是STM32的后备区域

分割线*

2.1 BKP备份寄存器简介

2.2 BKP备份寄存器基本结构

2.3 BKP外设头文件 bkp.h介绍

2.4 读写 BKP备份寄存器 操作步骤

2.5 编写 读写备份寄存器

5.1 文件介绍

main.c

分割线*

3.1 什么是Unix时间戳

3.2 C语言中时间戳转换函数

2.1 C语言提供的time.h函数介绍

2.2 时间戳转换图

3.3 RTC简介

3.4 RTC时钟选择

3.5 RTC框图

3.6 RTC基本框图

3.7 RTC硬件电路

3.8 RTC头文件介绍

3.9 使用 RTC实时时钟 操作步骤

3.10 编写RTC实时时钟显示年月日时分秒

3.10.1 文件介绍

MyRTC.c

MyRTC.h

main.c


STM32后备区域:读写BKP备份寄存器与使用RTC实时时钟详解

1 什么是STM32的后备区域

STM32 的后备区域是芯片内部的一个特殊区域。

特点和作用

  • 后备区域可以在主电源VCC断电之后由备用电源VBA提供供电。
  • 后备区域存储的数据不会因为复位而重置
  • 但是如果包括主电源和VBAT都断电了。那么备用区域也会清除,因为他们的存储器本质是RAM存储器,掉电丢失。

后备区域有什么:

  • BKP备份寄存器
  • RTC实时时钟

分割线*

下面开始BKP部分

2.1 BKP备份寄存器简介

BKP(Backup Registers)备份寄存器(后备寄存器)

它位于后备区域。

  • BKP可用于存储数据。

  • 存储特性:

    1. 当VDD(2.0~3.6V)电源(主电源)被切断,后备区域仍然由VBAT备用电源(1.8~3.6V)维持供电。
    2. 并且系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
    3. 但是如果备用电源VBAT和主电源VCC都断电了,就会清除数据,因为BKP本质是RAM存储器。掉电丢失数据
  • STM32的TAMPER引脚

    他可以产生的侵入事件可以将所有备份寄存器内容清除

    1. TAMPER是用于引入检测信号(可以是或上升沿/下降沿)的,当发生入侵时,将清除BKP所有内容,并申请中断。
    2. 并且他是由备用电源供电,主电源断电后侵入检测仍然有效,以保证数据安全
  • BKP的RTC引脚可以输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲

  • BKP存储RTC时钟校准寄存器

  • STM32后备区域的供电特性:

    1. 当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电。
    2. 当VDD主电源上电时,后备区域供电会自动从VBAT切换到VDD。
  • BKP数据存储容量:

    1. 20字节(中容量和小容量)/ 84字节(大容量和互联型)
  • 手册建议

    1. 如果没有外部电池,建议VBAT引脚接到VDD,就是VBAT和主电源接到一起,并且再连接一个100nf的滤波电容

2.2 BKP备份寄存器基本结构

其中橙色部分就是后备区域,BKP处于后备区域。但后备区域不止有BKP。

STM32后备区域的特性:

  • 当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电。
  • 当VDD主电源上电时,后备区域供电会自动从VBAT切换到VDD。

数据寄存器:

  • 每个寄存器有16位(可存储2个字节)。
  • 中小容量设备有DR1~DR10,总共10个数据寄存器,每个寄存器存储2个字节,总容量为20字节。
  • 大容量和互联型设备有42个数据寄存器(DR)。

TAMPER引脚:

  • 用于引入检测信号(上升沿/下降沿),清除BKP所有内容以保证数据安全。

时钟输出:

  • 可以将RTC相关时钟从PC13位置的RTC引脚输出出去,供外部使用。
  • 输出校准时钟时,可以配合校准寄存器对RTC的误差进行校准。

2.3 BKP外设头文件 bkp.h介绍

void BKP_DeInit(void);

  • 恢复缺省配置(用于清空BKP所有BKP寄存器)

void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);

  • 配置TAMPER引脚的有效电平

void BKP_TamperPinCmd(FunctionalState NewState);

  • 是否开启侵入检测功能

void BKP_ITConfig(FunctionalState NewState);

  • 是否开启中断

void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);

  • 时钟输出功能配置(在RTC引脚上输出时钟信号、RTC校准时钟、RTC闹钟脉冲、秒脉冲)

void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);

  • 设置RTC校准值

void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);

  • 写BKP备份寄存器

uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);

  • 读BKP寄存器

FlagStatus BKP_GetFlagStatus(void);

查看标志位

void BKP_ClearFlag(void);

清除标志位

ITStatus BKP_GetITStatus(void);

查看中断标志位

void BKP_ClearITPendingBit(void);

清除中断标志位

pwr.h中也有一个函数是需要用到的 void PWR_BackupAccessCmd(FunctionalState NewState);

  • 备份寄存器访问使能(其实是设置BDP位)

2.4 读写 BKP备份寄存器 操作步骤

  1. 启PWR和BKP的时钟
    • PWR的开启函数为RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 它的目的是开启后备电源的时钟(可以理解为开启后备电源VBAT)。
    • BKP的开启函数RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); 他的目的是开启BKP外设的时钟,可以看到他们都是APB1总线下的。
  2. 使能备份区域的访问
    • 因为RTC实时时钟和BKP备份寄存器,都处于备份区域中。在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。它在PwR.h中 函数:PWR_BackupAccessCmd(ENABLE);
  3. 读写操作
    • 写入:BKP_WriteBackupRegister(BKP_DR1,Data);
    • 读出:Data = BKP_ReadBackupRegister(BKP_DR1);

2.5 编写 读写备份寄存器

5.1 文件介绍

  • main.c : 读写测试BKP备份寄存器是否在单主电源掉电时丢失数据。是否受到复位影响…

main.c

#include "stm32f10x.h"                  // Device header
#include "oled.h"
#include "Delay.h"
#include "Key.h"

/**
  * 函    数:STM32 BKP备份寄存器的读写测试
  * 参    数:无
  * 返 回 值:无
  * 注意事项:无
  */
  
uint16_t WriteArr[] = {0x0000, 0x0001};
uint16_t ReadArr[2] = { 0 };
  
int main()
{
    OLED_Init();//初始化OLED;
    KEY_Init();//初始化按键
    
    //使能时钟电源和后备接口时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
    
    //使能备份访问控制
    PWR_BackupAccessCmd(ENABLE);
    
    OLED_ShowString(1,1,"W:");
    OLED_ShowString(2,1,"R:");
    while(1)
    {
        if(KEY_Get() == 1)
        {
            //写入BKP
            BKP_WriteBackupRegister(BKP_DR1,WriteArr[0]);
            BKP_WriteBackupRegister(BKP_DR2,WriteArr[1]);
            
            //显示写入的值
            OLED_ShowHexNum(1,3,WriteArr[0],4);
            OLED_ShowHexNum(1,8,WriteArr[1],4);
            
            WriteArr[0]++;
            WriteArr[1]++;
        }
        //读取BKP
        ReadArr[0] = BKP_ReadBackupRegister(BKP_DR1);
        ReadArr[1] = BKP_ReadBackupRegister(BKP_DR2);
        //显示读取的值
        OLED_ShowHexNum(2,3,ReadArr[0],4);
        OLED_ShowHexNum(2,8,ReadArr[1],4);
        
    }
}

分割线*

下面开始RTC部分

3.1 什么是Unix时间戳

  • Unix时间戳,最早是Unix系统使用的。所以叫Unix时间戳。
  • 目前Windows、linux、安卓等系统都是实用的Unix时间戳。
  • Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒。
    • GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准 (不过他不精准,因为地球自转是越来越慢的。所以又有了如下的规定)

      (格林尼治是伦敦的一个区)

    • UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致

    • 我们时间戳所说的1970年1月1日0时0分0秒。指的是伦敦伦敦时间的0时0秒。 其他的位置可以分为24个时区。每偏差一个时区,相应的时间就要加或减一个小时

  • 时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间 比如Unix时间戳为0,代表伦敦的0时0分,那么北京就是+8得到8时0分
  • C语言官方为我们提供了函数,可以直接把Unix时间戳转换为时间。
  • 可以在百度直接搜索unix在线时间戳。

3.2 C语言中时间戳转换函数

2.1 C语言提供的time.h函数介绍

  • C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换

  • 一些常用的函数如下:(粗细为更重要)

    time_t time(time_t*);获取系统时钟
    struct tm* gmtime(const time_t*);秒计数器转换为日期时间(格林尼治时间)
    struct tm* localtime(const time_t*);秒计数器转换为日期时间(当地时间)
    time_t mktime(struct tm*);日期时间转换为秒计数器(当地时间)
    char* ctime(const time_t*);秒计数器转换为字符串(默认格式)
    char* asctime(const struct tm*);日期时间转换为字符串(默认格式)
    size_t strftime(char*, size_t, const char*, const struct tm*);日期时间转换为字符串(自定义格式)

2.2 时间戳转换图

这张图就清晰了显示了各个函数的作用:其实就是在各种数据类型之间进行转换。

  • 首先先了解一下各个数据类型是什么
    1. 秒计数器数据类型:time_t,其实就是一个32或64位的有符号的整形数据。也就是64位的秒计数器(如果不特别声明,默认为64。)

    2. 日期时间数据类型:struct tm,这时一个结构体类型。成员如下:

      • 秒、
      • 分、
      • 时、
      • 月的几日、
      • 月份、(需要+1偏移量)
      • 年份(需要+1900偏移量)、
      • 周某开始的星期几、从1月1日开始的第几天、
      • 是否使用夏令时 (是为了鼓励夏天时早睡早起节约用电设计的,目前个别国家在使用)
    3. 字符串型数据类型:char*,用来指向一个表示时间的字符串

所以,在转换时,要根据函数的返回值,来进行相应赋值。比如struct tm* gmtime(const time_t*);的返回值为Struct tm的指针类型。那么在赋值给自己定义的Struct tm xxx 结构体时,就要先用*取值,才能正确赋值

等等以此类推,需要根据不同的类型进行转换,

(time_t 整形、Struct tm 结构体 Char* 字符指针..)

3.3 RTC简介

RTC (Real Time Clock):实时时钟。

  • RTC也位于STM32的后备区域中。可以由VBAT备用电源供电
  • RTC是一个独立的定时器,可为系统提供时钟和日历的功能。
  • RTC和时钟配置系统处于后备区域,系统复位时数据不清零,因为在VDD(2.0 - 3.6V)断电后可借助VBAT(1.8 - 3.6V)供电继续走时。
  • RTCCLK可选择三种RTC时钟源:
    1. HSE时钟除以128(通常为8MHz/128)
    2. LSE振荡器时钟(通常为32.768KHz)
    3. LSI振荡器时钟(40KHz)

3.4 RTC时钟选择

时钟信号解释

  • HSE = 高速外部时钟信号

  • HSI = 高速内部时钟信号

  • LSI = 低速内部时钟信号

  • LSE = 低速外部时钟信号

  • H (High):高速,L (Low):低速,E (External):外部,I (Internal):内部

时钟选择

  • 外部高速时钟:一般作为系统主时钟。供内部程序运行和主要外设使用。
  • 内部低速时钟:主要用做看门狗等的使用。
  • 外部低速时钟:这个时钟一般都是为了给RTC提供时钟的。
    1. 因为LSE外部低速时钟,才可以通过VBAT备用电池供电
    2. 所以在主电源断电情况下LSE可以继续震荡,实现RTC主电源掉电继续走时的功能。
    3. 并且他的频率为32.768KHZ。经过2^15分频之后每次自然溢出时刚好为1s,也就是1HZ。

3.5 RTC框图

灰色填充区域均是后备区域。

可编程预分频器:

  • RTC_CNT每秒自增,因此驱动计数器的时钟 TR_CLK 需要是1Hz的信号。 实际提供RTC模块的时钟(RTCCLK)频率较高,因此RTCCLK经过20位RTC预分频器(1~2^20分频),保证输出给计数器的频率为1Hz。

分频和计数:

  • 输入时钟RTCCLK,经过RTC预分频器(由重装载寄存器RTC_PRL和余数寄存器RTC_DIV控制),计数器重装值ARR和CNT进行分频。

RTC_CNT:

  • 可以作为Unix时间戳的秒计数器,再借用time.h的函数可以方便地得到年月日时分秒。

闹钟寄存器RTC_ALR:

  • 32位寄存器,用来设置闹钟。设置闹钟时,将ALR写入一个秒数,当CNT的值等于ALR设定的闹钟值时,就会产生RTC_Alarm闹钟信号。 通过中断系统,在闹钟中断里执行相应操作。
  • 同时,闹钟信号可以让STM32退出待机模式。 此外,这个闹钟值是一个定值,只能响一次。若想实现周期闹钟,在每次闹钟响过后,都需要重新设置下一次闹钟时间。

中断信号:

  • RTC_Second(秒中断):来自于CNT的输入时钟。开启此中断后,程序会每秒进入一次RTC中断。
  • RTC_Overflow(溢出中断):来自CNT右边,表示CNT的32位计数器计满溢出时触发一次中断。
  • RTC_Alarm(闹钟中断):当计数器的值和闹钟值相等时触发中断,同时可以唤醒设备退出待机模式。

中断标志位和中断输出控制

  • F(Flag) 结尾的是对应的中断标志位。
  • IE(Interrupt Enable) 结尾的是中断使能。
  • 最后三个信号通过一个或汇聚到NVIC中断控制器。

APB1总线和APB1接口:

  • 程序读写寄存器的地方可以通过APB1总线完成,RTC位于APB1总线上的设备。 退出待机模式:唤醒机制
  • 闹钟信号和WKUP引脚都可以唤醒设备,退出待机模式。

3.6 RTC基本框图

时钟来源配置:

  • 最左边的RTCCLK时钟来源在RCC中配置,可以从三个时钟中选择一个作为RTCCLK。

时钟预分频:

  • 选择的RTCCLK经过预分频器对时钟进行分频。
  • 余数计数器是一个自减计数器,存储当前的计数值。
  • 重装寄存器决定计数目标和分频值。
  • 分频后得到1Hz的秒计数信号,传递给32位计数器,每秒自增一次。

闹钟设定:

  • 32位计数器下有一个32位的闹钟值,可以设定闹钟时间。

中断信号触发:

  • 右侧有三个信号可以触发中断:秒信号、计数器溢出信号和闹钟信号。
  • 这三个信号通过中断输出控制,进行中断使能。
  • 启用的中断信号才能传递到NVIC,然后向CPU申请中断。

程序配置步骤:

**配置数据选择器:**选择RTCCLK时钟来源。

**配置重装寄存器:**选择分频系数。

配置32位计数器:

  • 进行日期时间的读写。
  • 如果需要闹钟,配置32位闹钟值。

配置中断:

  • 启用中断,再配置NVIC。
  • 最后,编写对应的中断函数。

3.7 RTC硬件电路

  1. 备用电池供电
    • **简单连接(左侧):**使用一个3V的电池B1直接连接到VBAT和GND。这样设计简单,但是电源冗余不高。
    • **推荐连接(中间):**使用两个3V的电池B2和B3通过两个二极管D1和D2连接到VBAT和GND。这样设计增加了电源的可靠性,因为如果一个电池失效,另一个电池还能提供电源。电容C3(0.1uF)用于滤波,稳定电压。
  2. 外部低速晶振
    • **晶振部分(中间):**使用一个32.768kHz的晶振(X1)连接到两个10pF的电容(C1和C2),并接地。这部分电路提供了一个稳定的时钟信号,通常用于RTC(实时时钟)功能。
    • **连接到STM32单片机(右侧):**OSC32_IN和OSC32_OUT分别连接到STM32单片机的PC14和PC15引脚。
  3. STM32单片机连接
    • **供电和地(右侧):**VDD和VSS分别是电源和地,VDD连接到电源正极,VSS连接到地。VBAT连接到备用电池供电部分的输出。
    • **时钟信号(右侧):**PC14和PC15分别连接到外部低速晶振的OSC32_IN和OSC32_OUT。
    • **其他引脚(右侧):**图中列出了STM32F103C8T6单片机的引脚配置,包括PA0到PA15,PB0到PB15等。这些引脚可以根据具体应用进行配置。

3.8 RTC头文件介绍

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);

  • RTC 中断使能:通过传入指定的中断类型 RTC_IT 和状态 NewState,实现对 RTC 中断的使能或失能控制。

void RTC_EnterConfigMode(void);

  • 进入 RTC 配置模式:用于进入 RTC 的配置状态,以便进行相关参数的修改。

void RTC_ExitConfigMode(void);

  • 退出 RTC 配置模式:在完成 RTC 配置操作后,使用此函数退出配置模式。

uint32_t RTC_GetCounter(void);

  • 获取 RTC 计数器的值:返回 RTC 计数器的当前数值。

void RTC_SetCounter(uint32_t CounterValue);

  • 设置 RTC 计数器的值:将 RTC 计数器设置为指定的数值 CounterValue

void RTC_SetPrescaler(uint32_t PrescalerValue);

  • 设置 RTC 预分频的值:为 RTC 预分频设置特定的值 PrescalerValue

void RTC_SetAlarm(uint32_t AlarmValue);

  • 设置 RTC 闹钟的值:设定 RTC 闹钟的触发值为 AlarmValue

uint32_t RTC_GetDivider(void);

  • 获取 RTC 预分频分频因子的值:获取当前 RTC 预分频分频因子的数值。

void RTC_WaitForLastTask(void);

  • 等待最近一次对 RTC 寄存器的写操作完成:确保之前对 RTC 寄存器的写入操作已经完成。

void RTC_WaitForSynchro(void);

  • 等待 RTC 寄存器与 RTC 的 APB 时钟同步:等待 RTC 相关寄存器(如 RTC_CNTRTC_ALR 和 RTC_PRL)与 APB 时钟完成同步。

FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);

  • 检查指定的 RTC 标志位设置与否:通过传入标志位 RTC_FLAG,返回其状态。

void RTC_ClearFlag(uint16_t RTC_FLAG);

  • 清除 RTC 的待处理标志位:清除指定的 RTC 标志位。

ITStatus RTC_GetITStatus(uint16_t RTC_IT);

  • 检查指定的 RTC 中断发生与否:根据传入的中断类型 RTC_IT,判断中断是否发生。

void RTC_ClearITPendingBit(uint16_t RTC_IT);

  • 清除 RTC 的中断待处理位:清除指定 RTC 中断的待处理位。

3.9 使用 RTC实时时钟 操作步骤

RTC使用时,一般都是和BKP一起使用的:

因为RTC在初始化时,需要配置其32位的时间戳计数器。

但每次复位或者主电源上电后,在执行主程序时,会重新初始化RTC的时间戳计数器。这就导致了RTC实时时钟本来能在备份电源的供给下计时,但是上电后又给我重新初始化覆盖了。导致不实时了!

所以需要在初始化程序中判断是否需要设置32位的时间戳计数器。

一般的方法是,在第一次初始化RTC时,顺便在BKP备份寄存器中写入特定值。在下一次初始化RTC时,对BKP备份寄存器的值进行判断。如果之前没有写入,那么就初始化。否则不初始化。

所以使用RTC实时时钟操作步骤如下:

  • 执行以下操作将使能对BKP和RTC的访问:

    1. 启PWR和BKP的时钟

      • PWR的开启函数为RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 开启后备电源的时钟
      • BKP的开启函数RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); 开启BKP外设的时钟
    2. 使能备份区域的访问

      • 在STM32中,想访问RTC和BKP,就要先开启备份区域的访问权限。

      函数:PWR_BackupAccessCmd(ENABLE);

  • 判断是否需要初始化RTC见代码部分

  • 开启LSE时钟

    1. 开启LSE时钟 RCC_LSEConfig(RCC_LSE_ON);
    2. 等待LSE时钟开启完毕 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
    3. 选择RTCCLK时钟为LSERCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    4. 使能RTCCLK时钟 RCC_RTCCLKCmd(ENABLE);
  • 等待时钟同步

    1. 因为可能在恢复主电源之后,APB1总线刚刚恢复震荡频率,但是RTCCLK需要经过外部震荡源分频后才能有一次输出。如果直接读取,会导致读取不准确。 所以需要等待RTCCLK产生上升沿来激活更新一下时间戳计数器,这时APB1直接读取。才是准确的。 所以软件读取时必须等待RTCCLK来一个上升沿,使RTC_CRL寄存器中的**RSF位(寄存器同步标志)被硬件置1。**这时再读取RTC_CRL寄存器。才能正确读取。

      函数:RTC_WaitForSynchro(); (等待时钟同步)

  • 等待写入完成

    1. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。

    2. 可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。 当RTOFF状态位是1时,才可以写入RTC寄存器

      函数:RTC_WaitForLastTask();

  • 设置RTC预分频器

    1. 设置预分频器RTC_SetPrescaler(32768 - 1);
    2. 等待写入完成RTC_WaitForLastTask();
  • 写入前其实是需要设置RTC_CRL寄存器中的CNF位 (函数会帮我们自动完成,所以不需要)

    1. 在写入时,必须设置RTC_CRL寄存器中的CNF位, 使RTC进入配置模式后,才能写入RTC_PRL(预分频器)、RTC_CNT(时间戳计数器)、RTC_ALR(闹钟寄存器)寄存器
  • 设置CNT时间戳计数器时间

    (利用C语言中time.h来转换时间戳并写入,详见代码)

  • 在BKP备份寄存器中写入特定数据。为下次复位或仅主电源断电后上电时判断是否初始化打下基础

3.10 编写RTC实时时钟显示年月日时分秒

3.10.1 文件介绍

  • MyRTC.c 初始化RTC、通过C语言time.h函数来编写 时间转换时间戳、时间戳转换时间的函数
  • MyRTC.h 函数声明
  • main.c 测试MyRTC显示

MyRTC.c

#include "stm32f10x.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[] = {2024, 8, 18, 23, 46, 0};   //定义全局的时间数组,数组内容分别为年、月、日、时、分、秒

void MyRTC_SetTime(void);           //函数声明

/**
  * 函    数:RTC初始化
  * 参    数:无
  * 返 回 值:无
  */
void MyRTC_Init(void)
{
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);     //开启PWR的时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);     //开启BKP的时钟
    
    /*备份寄存器、RTC访问使能*/
    PWR_BackupAccessCmd(ENABLE);                            //使用PWR开启对备份寄存器和RTC的访问
    
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)          //通过写入备份寄存器的标志位,判断RTC是否是第一次配置
                                                            //if成立则执行第一次的RTC初始化
    {
        RCC_LSEConfig(RCC_LSE_ON);                          //开启LSE时钟
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);  //等待LSE准备就绪
        
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);             //选择RTCCLK来源为LSE
        RCC_RTCCLKCmd(ENABLE);                              //RTCCLK使能
                                        
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        RTC_SetPrescaler(32768 - 1);                        //设置RTC预分频器,预分频后的计数频率为1Hz
        RTC_WaitForLastTask();                              //等待上一次操作完成
        
        MyRTC_SetTime();                                    //设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路
        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);           //在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置
    }
    else                                                    //RTC不是第一次配置
    {
        RTC_WaitForSynchro();                               //等待同步
        RTC_WaitForLastTask();                              //等待上一次操作完成
    }
}
/**
  * 函    数:RTC设置时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路
  */
void MyRTC_SetTime(void)
{
    time_t time_cnt = 0;    //定义秒计数器数据类型
    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;    //调用mktime函数,将日期时间转换为秒计数器格式
                                                    //- 8 * 60 * 60为东八区的时区调整
    
    RTC_SetCounter(time_cnt);                       //将秒计数器写入到RTC的CNT中
    RTC_WaitForLastTask();                          //等待上一次操作完成
}

/**
  * 函    数:RTC读取时间
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组
  */
void MyRTC_ReadTime(void)
{
    time_t time_cnt;        //定义秒计数器数据类型
    struct tm time_date;    //定义日期时间数据类型
    
    time_cnt = RTC_GetCounter() + 8 * 60 * 60;      //读取RTC的CNT,获取当前的秒计数器
                                                    //+ 8 * 60 * 60为东八区的时区调整
    
    time_date = *localtime(&time_cnt);              //使用localtime函数,将秒计数器转换为日期时间格式
    
    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;
}

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);

#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"

int main(void)
{
    /*模块初始化*/
    OLED_Init();        //OLED初始化
    MyRTC_Init();       //RTC初始化
    
    /*显示静态字符串*/
    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();                           //RTC读取时间,最新的时间存储到MyRTC_Time数组中
        
        OLED_ShowNum(1, 6, MyRTC_Time[0], 4);       //显示MyRTC_Time数组中的时间值,年
        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);   //显示32位的秒计数器
        OLED_ShowNum(4, 6, RTC_GetDivider(), 10);   //显示余数寄存器
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Centos7 系统下安装go语言开发环境

该文章简述在Centos7 amd64 系统中安装go开发环境的方法。 一、golang官网查看对应平台最新的golang版本 Golang 官网地址&#xff1a;All releases - The Go Programming Language 二、 安装GO的过程及相关命令 # 1、下载go&#xff0c;这里使用 go1.22.5 版本&#xff0c;可…

【ACL2024】基于长尾检索知识增强的大语言模型

近日&#xff0c;阿里云人工智能平台PAI与阿里集团安全部内容安全算法团队、华东师范大学何晓丰教授团队合作&#xff0c;在自然语言处理顶级会议ACL2024上发表论文《On the Role of Long-tail Knowledge in Retrieval Augmented Large Language Models》&#xff0c;论文主题为…

爆火游戏《黑神话:悟空》研发背后有哪些故事?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

美团的测试面试题,真的很难吗?

年前&#xff0c;我的一个粉丝留言给我说&#xff0c;他在面试美团的自动化测试岗的时候&#xff0c;不幸挂掉了。 越想越可惜&#xff0c;回想面试经过&#xff0c;好好总结了几个点&#xff0c;发现面试没过的主要原因是在几个关键的问题没有给到面试官想要的答案 美团的面…

寻访中国100家.NET中大企业 —— 第二站:苏州行

一&#xff1a;事情起因 在.NET圈里混了十多年&#xff0c;相信有不少人知道我专注于玩 .NET高级调试&#xff0c;如今技术上的硬实力还是能够解决市面上的一些疑难杂症&#xff0c;但软实力却在另一个极端&#xff0c;如&#xff08;人际交往&#xff0c;人情事故&#xff09…

[RCTF2015]EasySQL1

打开题目 点进去看看 注册个admin账户&#xff0c;登陆进去 一个个点开看&#xff0c;没发现flag 我们也可以由此得出结论&#xff0c;页面存在二次注入的漏洞&#xff0c;并可以大胆猜测修改密码的源代码 resoponse为invalid string的关键字是被过滤的关键字&#xff0c;Le…

氟化工特氟龙精馏装置:PFA氟化氢反应装置的应用

精馏装置是进行精馏的一种塔式气液接触装置。利用混合物中各组分具有不同的挥发度&#xff0c;即在同一温度下各组分的蒸气压不同这一性质&#xff0c;使液相中的轻组分&#xff08;低沸物&#xff09;转移到气相中。 实验精馏装置的组成 实验精馏装置通常由以下几部分组成&am…

Linux2.6设备驱动开发

一&#xff1a;Linux2.6驱动设备开发的特点 1&#xff1a;首先是属于字符型设备注册的方法之一 这种开发接口是在Linux2.6引入的&#xff0c;之前的版本不支持这种开发方式&#xff0c;也是目前最标准的开发方式。 2&#xff1a;Linux2.6的设备开发 不再去限制设备号&#xf…

(javaweb)SpringBootWeb案例(毕业设计)案例--部门管理

目录 1.准备工作 2.部门管理--查询功能 3.前后端联调 3.部门管理--新增功能 1.准备工作 mapper数据访问层相当于dao层 根据页面原型和需求分析出接口文档--前后端必须遵循这种规范 大部分情况下 接口文档由后端人员来编写 前后端进行交互基于restful风格接口 http的请求方式…

TypeScript学习笔记(二)——TypeScript 高级类型

目录 1. class 类 1.1 class 关键字的基本使用 1.2 类继承 1.3 类成员可见性 1.4 类成员只读修饰符 2. 类型兼容性 2.1 类型兼容性 2.2 接口兼容性 2.3 函数兼容性 3. 交叉类型 4. 泛型 4.1 创建泛型函数 4.2 泛型约束 4.3 多个泛型的类型变量约束 4.4 泛型接口…

【深度学习入门项目】基于支持向量机的手写数字识别

目录 导入必要的包1. 数据集2. 数据处理3. 训练过程4. 输出结果完整代码 本项目使用SVM训练模型&#xff0c;用于预测手写数字图片。 导入必要的包 numpy: 这个库是Python中常用的数学计算库。在这个项目中&#xff0c;我使用numpy来处理图像数据&#xff0c;将图像数据转换为…

FPGA开发——DS18B20读取温度并且在数码管上显示

一、简介 在上一篇文章中我们对于DS18B20的相关理论进行了详细的解释&#xff0c;同时也对怎样使用DS18B20进行了一个简单的叙述。在这篇文章我们通过工程来实现DS18B20的温度读取并且实现在数码管伤显示。 1、基本实现思路 根据不同时刻的操作&#xff0c;我们可以使用一个状…

基于vue框架的班级网站的设计与实现vg66m(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;班级,学生,班级活动,班级相册,班级开支,活动记录 开题报告内容 基于Vue框架的班级网站设计与实现 开题报告 一、引言 随着互联网技术的飞速发展&#xff0c;网络已经成为人们日常生活中不可或缺的一部分。在教育领域&#xff0c;班级…

大白话解析:深入浅出大模型RAG模块全解析

文章目录 什么是 RAG&#xff1f; 技术交流&资料通俗易懂讲解大模型系列 RAG模块化 什么是模块化RAG&#xff1f; 索引模块 块优化 滑动窗口从小到大元数据附加 结构化组织 层次化索引知识图谱文档组织 预检索模块 查询扩展 多查询子查询CoVe 查询转换 重写HyDE 查询路由…

TON链上游戏项目开发基本要求及模式创建与海外宣发策略

TON&#xff08;The Open Network&#xff09;是由Telegram开发的区块链平台&#xff0c;以其高速、低延迟、和高扩展性吸引了大量开发者和项目方。TON链上游戏项目作为一个新兴领域&#xff0c;结合了区块链技术和游戏产业&#xff0c;为用户提供了全新的游戏体验和经济激励。…

精益生产咨询:为企业量身定制的高效能蜕变计划!——张驰咨询

在当今这个快速变化、竞争激烈的市场环境中&#xff0c;企业如何保持持续的竞争优势&#xff0c;提高生产效率&#xff0c;降低成本&#xff0c;同时又能快速响应市场需求&#xff0c;成为了每一个企业家必须面对的重大课题。精益生产&#xff08;Lean Production&#xff09;作…

第5节:Elasticsearch核心概念

我的后端学习笔记大纲 我的ElasticSearch学习大纲 1.Lucene和Elasticsearch的关系: 1.Lucene&#xff1a;最先进、功能最强大的搜索库&#xff0c;直接基于lucene开发&#xff0c;非常复杂&#xff0c;api复杂2.Elasticsearch&#xff1a;基于lucene&#xff0c;封装了许多luc…

跳槽?面试软件测试需要掌握的知识你Get了吗

想从事软件测试相关的工作&#xff0c;立志成为一名优秀的软件测试工程师。 一名优秀的软件测试工程师&#xff0c;需要扎实的专业基础&#xff0c;包括测试相关技术、编程技能、数据库知识、计算机网络、以及操作系统等等。对于没有测试经验的应届生求职者来说&#xff0c;面…

SpringBoot项目部署时application.yml文件的加载优先级和启动脚本

文章目录 application.yml文件的加载优先级(由高到低)第一级命令行参数第二级Jar包同级目录 /config第三级Jar包同级目录第四级classpath 下的/config第五级classpath 根路径/总结&#xff1a; logback.xml 文件加载顺序当application.yml 和 bootstrap.yml 同时存在时java jar…

淘宝天猫详情接口API:实现轻松购物,探索最具性价比的商品

随着电子商务的蓬勃发展&#xff0c;网络购物已经成为现代人日常生活中的重要部分。在这个浩瀚的电商海洋中&#xff0c;淘宝和天猫无疑是最为耀眼的两大平台。然而&#xff0c;如何在众多的商品中挑选出性价比最高的产品&#xff1f;淘宝天猫详情接口API为您提供了解决方案。 …