STM32读写备份寄存器和实时时钟

2024-06-23 07:36

本文主要是介绍STM32读写备份寄存器和实时时钟,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

1. 硬件电路

2. RTC操作注意事项

操作步骤

3. 代码实现

3.1 读写备份寄存器

3.1.1 main.c

3.2 实时时钟

3.2.1 MyRTC.c

3.2.2 MyRTC.h

3.2.3 main.c


1. 硬件电路

对于BKP备份寄存器和RTC实时时钟的详细解析可以看下面这篇文章:

STM32单片机BKP备份寄存器和RTC实时时钟详解-CSDN博客

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等。这些引脚可以根据具体应用进行配置。

2. RTC操作注意事项

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

使能PWR和BKP时钟

  • 设置RCC_APB1ENR寄存器中的PWREN和BKPEN位,开启PWR和BKP的时钟。

使能对BKP和RTC的访问

  • 设置PWR_CR寄存器中的DBP位,使能对BKP和RTC的访问。

读取RTC寄存器时的注意事项

  • 如果RTC的APB1接口曾经处于禁止状态,则在读取RTC寄存器之前,软件必须首先等待RTC_CRL寄存器中的RSF(寄存器同步标志)位被硬件置1。

进入配置模式

  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。

写操作的顺序

  • 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CRL寄存器中的RTOFF(状态位)来判断RTC寄存器是否处于更新中。仅当RTOFF状态位为1时,才可以写入RTC寄存器。

操作步骤

开启PWR和BKP的时钟

  • 正常情况下,外设第一步是开启时钟即可使用,但对于BKP和RTC这两个外设,必须首先设置RCC_APB1ENR以开启APB1外设时钟,并同时开启PWR和BKP的时钟。对于RTC来说,没有单独开启时钟的选项,还需要设置PWR_CR的DBP位。

同步RTC寄存器

  • 刚上电时,需要调用RTC等待同步函数。因为RTC的寄存器在RTCCLK的同步下变更,当用PCLK1(36MHz)驱动的总线读取RTCCLK(32KHz)驱动的寄存器时,会有时钟不同步的问题。RTC寄存器只有在RTCCLK上升沿更新,所以需要等待同步。

进入配置模式

  • RTC进入配置模式的标志位需要被置1,才能设置时间。在操作寄存器的库函数中,已经包含了这个操作,所以不需要单独调用函数进入配置模式。

写入操作

  • 写入之前,需要等待RTOFF状态位为1后才能写入,这是因为PCLK1和RTCCLK频率不同。

3. 代码实现

3.1 读写备份寄存器

功能:先初始化->写DR->读DR->写入和读出是否一致

BKP初始化步骤

  • 开启PWR和BKP时钟
  • 使用PWR的一个函数,使能对BKP和RTC的访问
  • BKP写入数据函数
  • BKP读出数据函数

3.1.1 main.c

关于按键和oled的程序,参考专栏中之前的教程。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"uint8_t KeyNum;					//定义用于接收按键键码的变量uint16_t ArrayWrite[] = {0x1234, 0x5678};	//定义要写入数据的测试数组
uint16_t ArrayRead[2];						//定义要读取数据的测试数组int main(void)
{/*模块初始化*/OLED_Init();				//OLED初始化Key_Init();					//按键初始化/*显示静态字符串*/OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问while (1){KeyNum = Key_GetNum();		//获取按键键码if (KeyNum == 1)			//按键1按下{ArrayWrite[0] ++;		//测试数据自增ArrayWrite[1] ++;BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);	//写入测试数据到备份寄存器BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);OLED_ShowHexNum(1, 3, ArrayWrite[0], 4);		//显示写入的测试数据OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);}ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);		//读取备份寄存器的数据ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 3, ArrayRead[0], 4);				//显示读取的备份寄存器数据OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}

3.2 实时时钟

RTC配置步骤

开启PWR和BKP的时钟:使能对BKP和RTC的访问。

启动RTC时钟:使用RCC模块函数设置LSE(低功耗模式下使用,默认是关闭的)作为系统时钟。

配置RTCCLK数据选择器:使用RCC模块函数,指定LSE为RTCCLK。

调用等待函数:等待同步以及等待上一次操作完成。

配置预分频器:设置PRL重装寄存器为一个合适的分频值,确保输出给计数器的频率是1Hz。

配置CNT值:给RTC设置一个初始时间。如果需要闹钟,可以配置闹钟值。

配置中断:如果需要中断,可以进行相关中断部分的配置。

3.2.1 MyRTC.c

#include "stm32f10x.h"                  // Device header
#include <time.h>uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};	//定义全局的时间数组,数组内容分别为年、月、日、时、分、秒void MyRTC_SetTime(void);				//函数声明//RTC初始化
void MyRTC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问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来源为LSERCC_RTCCLKCmd(ENABLE);								//RTCCLK使能RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成RTC_SetPrescaler(32768 - 1);						//设置RTC预分频器,预分频后的计数频率为1HzRTC_WaitForLastTask();								//等待上一次操作完成MyRTC_SetTime();									//设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);			//在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置}else													//RTC不是第一次配置{RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成}
}//如果LSE无法起振导致程序卡死在初始化函数中
//可将初始化函数替换为下述代码,使用LSI当作RTCCLK
//LSI无法由备用电源供电,故主电源掉电时,RTC走时会暂停
/* 
void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){RCC_LSICmd(ENABLE);while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();RTC_SetPrescaler(40000 - 1);RTC_WaitForLastTask();MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{RCC_LSICmd(ENABLE);				//即使不是第一次配置,也需要再次开启LSI时钟while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();}
}*//*** 函    数:RTC设置时间* 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路*/
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;	//调用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;
}

3.2.2 MyRTC.h

#ifndef __MYRTC_H
#define __MYRTC_Hextern uint16_t MyRTC_Time[];void MyRTC_Init(void);
void MyRTC_SetTime(void);
void MyRTC_ReadTime(void);#endif

3.2.3 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);	//显示余数寄存器}
}

这篇关于STM32读写备份寄存器和实时时钟的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1086527

相关文章

零基础STM32单片机编程入门(一)初识STM32单片机

文章目录 一.概要二.单片机型号命名规则三.STM32F103系统架构四.STM32F103C8T6单片机启动流程五.STM32F103C8T6单片机主要外设资源六.编程过程中芯片数据手册的作用1.单片机外设资源情况2.STM32单片机内部框图3.STM32单片机管脚图4.STM32单片机每个管脚可配功能5.单片机功耗数据6.FALSH编程时间,擦写次数7.I/O高低电平电压表格8.外设接口

[FPGA][基础模块]跨时钟域传播脉冲信号

clk_a 周期为10ns clk_b 周期为34ns 代码: module pulse(input clk_a,input clk_b,input signal_a,output reg signal_b);reg [4:0] signal_a_widen_maker = 0;reg signal_a_widen;always @(posedge clk_a)if(signal_a)

Modbus初学者教程,第三章:modbus寄存器说明

第三章:modbus寄存器说明 寄存器种类 Modbus协议中一个重要的概念是寄存器,所有的数据均存放于寄存器中。Modbus寄存器是指一块内存区域。Modbus寄存器根据存放的数据类型以及各自读写特性,将寄存器分为4个部分,这4个部分可以连续也可以不连续,由开发者决定。寄存器的意义如下表所示。 Modbus协议定义了设备间的数据传输方式,包括数据格式和通信规则。Modbus寄存器是协议中用

大型网站架构演化(五)——数据库读写分离

网站在使用缓存后,使绝大部分数据读操作访问都可以不通过数据库就能完成,但是仍有一部分读操作(缓存访问不命中、缓存过期)和全部的写操作需要访问数据库,在网站的用户达到一定规模后,数据库因为负载压力过大而成为网站的瓶颈。      目前豆粉的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。网站利用数据库的这一功能,

Android 10.0 系统开机重启桌面时钟小部件widget加载慢解决方案

1.前言 在10.0的系统rom产品定制化开发中,在Launcher3桌面系统默认会有时钟widget小部件显示在首屏的,但是发现在开机过程 中会显示的好慢,等进入桌面了 还没显示,所以接下来分析下相关的源码流程,来实现相应的功能 2.系统开机重启桌面时钟小部件widget加载慢解决方案的核心类 frameworks\base\services\appwidget\java\com\andr

STM32单片机PWR电源控制详解

文章目录 1. PWR概述 2. 电源结构框图 3. 上电复位和掉电复位 4. 可编程电压监测器 5. 低功耗模式 6. 模式选择 6.1 睡眠模式 6.2 停止模式 6.3 待机模式 7. 代码示例 1. PWR概述 PWR(Power Control)电源控制,负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能。 可编程电压监测器

YOLOv9摄像头或视频实时检测

1、下载yolov9的项目 地址:YOLOv9 2、使用下面代码进行检测 import torchimport cv2from models.experimental import attempt_loadfrom utils.general import non_max_suppression, scale_boxesfrom utils.plots import plot_o

【续2】linux C语言 文件描述符 读写地址分析

记录时间:2014-10-24 10:35 记录原因:一直对文件指针读写存在疑惑,导致经常性的操作文件时,结果不理想。原来一直是对文件被打开后,对文件流指针的读写位置没有弄清楚。 文件在打开后,任何一个对文件的操作都会改变文件流指针的位置,所以在对文件进行操作时,应从如下两点考虑: 1、文件是不是第一次打开:可以确认文件指针的确却位置; 2、检测文件打开方式,特殊注

linux C语言 文件描述符 读写地址分析

1、fwrite 和 fread对文件操作之后,文件位置指针已经移动到被操作的位置; 2、如果不是在fread或fwrite操作后的位置读、写文件内容,那么必须重新定位文件指针位置,此时可以使用 rewind、fseek函数; 3、rewind(FILE *stream):将文件指针直接移动到文件起始位置; 4、fseek(FILE *stream, long offset

使用obdumper对oceanbase进行备份,指定2881端口

1.安装obdumper (1)下载软件 OceanBase分布式数据库-海量数据 笔笔算数https://www.oceanbase.com/softwarecenter (2)安装软件 参考:https://www.oceanbase.com/docs/common-oceanbase-dumper-loader-1000000000628759https://www.oceanb