写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!
本文写于:2025.04.19
STM32开发板学习——第43节: [12-3] 读写备份寄存器&实时时钟
- 前言
- 开发板说明
- 引用
- 解答和科普
- 一、读写BKP备份寄存器
- 二、RTC实时时钟
- 问题
- 总结
前言
本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始32单片机的学习之路。
欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习32单片机了,就跟着B站上的江协科技开始学习了.
在这里会记录下江协科技32单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!
开发板说明
本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。另外我也购买了江科大的学习套间。
原理图如下
1、开发板原理图
2、STM32F103C6和51对比
3、STM32F103C6核心板
视频中的都用这个开发板来实现,如果有资源就利用起来。另外也计划实现江协科技的套件。
下图是实物图
引用
【STM32入门教程-2023版 细致讲解 中文字幕】
还参考了下图中的书籍:
STM32库开发实战指南:基于STM32F103(第2版)
数据手册
解答和科普
一、读写BKP备份寄存器
1、 读写BKP程序
PB1接一个按键,用于控制,VBAT引脚接STLINK的3.3V.
第一步,开启PWR和BKP的时钟,第二步使用PWR的一个函数,使能对BKP和RTC的访问,然后写入数据的话,BKP有个写入的函数,读取数据,BKP也有个读取的函数。
手动清空BKP所有的数据寄存器,这样BKP的数据,都会变为0;
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
这时时钟输出功能的配置,可以选择在RTC引脚上输出时钟信号,输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲。
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
读写寄存器DR;
备份寄存器使能
void PWR_BackupAccessCmd(FunctionalState NewState);
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "OLED.h"
uint8_t KeyNum;
uint16_t ArrayWrite[] = {0x1234, 0x5678};
uint16_t ArrayRead[2];
int main(void)
{
OLED_Init();
Key_Init();
OLED_ShowString(1, 1, "W:");
OLED_ShowString(2, 1, "R:");
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE);
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 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);
}
}
二、RTC实时时钟
2、RTC线路
初始化。第一步,执行注意事项:开启PWR和BKP时钟,使能BKP和RTC的访问。
第二步,启动RTC的时钟,计划使用LSE作为系统时钟,所以要使用RCC模块里的函数,开启LSE时钟,为了省电,默认是关闭的,所以要手动开启;
第三步,配置RTCCLK这个数据选择器,指定LSE为RTCCLK,这一步函数也是在RCC模块里的;
第四步,先不着急,要完成两个等待函数,一个是等待函数,另一个是这里的,等待上一次操作完成;
第五步,配置预分频器,给PRL重装器一个合适的分频值,确保输出给计数器的频率是1Hz;
第六步,配置CNT的值,给这个RTC一个初始时间,如果需要闹钟的话就配置闹钟,需要中断,可以配置中断;
并没有库函数配置RTC,没有Cmd函数,不需要启动一下。
void RCC_LSEConfig(uint8_t RCC_LSE);
启动LSE时钟就调用这个函数。
void RCC_LSICmd(FunctionalState NewState);
配置LSI内部低速时钟,如果出现外部时钟不起振,可以用这个内部时钟来进行实验;
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
这个函数用来选择RTCCLK的时钟源,实际上就是配置数据选择器
void RCC_RTCCLKCmd(FunctionalState NewState);
启动RTCCLK,调用上面函数选择时钟之后,还需要调用这个Cmd函数,使能一下;
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
获取标志位,因为这个LSE时钟,不是说你让它启动,他就能立刻启动的,调用时钟启动后,还需要等待一下标志位,等RCC有个标志位LSERDY置1后,这个时钟才是启动完成,工作稳定。
void RTC_EnterConfigMode(void);
进入配置模式,置CRL的CNF为1,进入配置模式;必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。
void RTC_ExitConfigMode(void);
退出配置模式,就是把CNF清零;
uint32_t RTC_GetCounter(voi
获取,CNT计数器的值;读取时钟,就靠这个函数。
void RTC_SetCounter(uint32_t CounterValue);
写入计数器CNT的值;设置时间,就靠这个函数;
void RTC_SetPrescaler(uint32_t PrescalerValue);
写入预分频器,这个值会写入到预分频器的PRL重装寄存器中,用来配置预分频器的分频系数;
void RTC_SetAlarm(uint32_t AlarmValue);
写入闹钟值;
uint32_t RTC_GetDivider(void);
读取预分频器中的DIV余数寄存器;为了得到更细的时间;
void RTC_WaitForLastTask(void);
等待上次操作完成;对应对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。
void RTC_WaitForSynchro(void);
等待同步,若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1。
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
标志位相关函数。
写入预分频器也要进入配置模式。只是不用我们写,库函数中包含了。
RTC晶振确实起振不了,为了观察到实验现象,可以备选内部低速时钟LSI,
void MyRTC_SetTime(void) //数组的时间转换为秒数到CNT
{
time_t time_cnt;
struct tm time_data;
time_data.tm_year =MyRTC_Time[0]-1900;
time_data.tm_mon =MyRTC_Time[1]-1;
time_data.tm_mday=MyRTC_Time[2];
time_data.tm_hour=MyRTC_Time[3];
time_data.tm_min=MyRTC_Time[4];
time_data.tm_sec=MyRTC_Time[5];
time_cnt= mktime(&time_data); //日期时间到秒数的计数
RTC_SetCounter( time_cnt); //写入计数器CNT
RTC_WaitForLastTask();
}
第一步,把数组指定时间,填充到struct tm结构体,第二步,使用mktime函数,得到秒数,第三步将得到的秒数写入到RTC的CNT中
extern uint16_t MyRTC_Time[];
全局变量传参,数组不加也行,单个变量必须加;
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.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);
}
}
C
#include "stm32f10x.h" // Device header
#include <time.h>
uint16_t MyRTC_Time[]={2023,1 ,1 ,23 ,59,55};
void MyRTC_SetTime(void);
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_LSEConfig(RCC_LSE_ON); // 启动外边LSE晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET); //低速外部时钟源准备就绪(LSE)
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //数据选择器选择LSE
RCC_RTCCLKCmd(ENABLE); //使能时钟
//RTCCLK配置完成
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上次操作完成
RTC_SetPrescaler(32768-1); // 配置分频器,写操作不是立即生效,等待写操作完成
RTC_WaitForLastTask(); //等待上次操作完成
// RTC_SetCounter(1672588795); //设定初始时间
MyRTC_SetTime();
// RTC_WaitForLastTask(); //等待上次操作完成
BKP_WriteBackupRegister(BKP_DR1,0xA5A5);
}
else
{
RTC_WaitForSynchro(); //等待同步
RTC_WaitForLastTask(); //等待上次操作完成
}
}
void MyRTC_SetTime(void) //数组的时间转换为秒数到CNT
{
time_t time_cnt;
struct tm time_data;
time_data.tm_year =MyRTC_Time[0]-1900;
time_data.tm_mon =MyRTC_Time[1]-1;
time_data.tm_mday=MyRTC_Time[2];
time_data.tm_hour=MyRTC_Time[3];
time_data.tm_min=MyRTC_Time[4];
time_data.tm_sec=MyRTC_Time[5];
time_cnt= mktime(&time_data)-8*60*60; //日期时间到秒数的计数
RTC_SetCounter( time_cnt); //写入计数器CNT
RTC_WaitForLastTask();
}
void MyRTC_ReadTime(void)
{
time_t time_cnt;
struct tm time_data;
time_cnt= RTC_GetCounter()+8*60*60;
time_data = *localtime(&time_cnt); //结构体赋值
MyRTC_Time[0]=time_data.tm_year+1900;
MyRTC_Time[1]=time_data.tm_mon+1;
MyRTC_Time[2]=time_data.tm_mday;
MyRTC_Time[3]=time_data.tm_hour;
MyRTC_Time[4]=time_data.tm_min;
MyRTC_Time[5]=time_data.tm_sec;
}
#ifndef __MYRTC_H
#define __MYRTC_H
extern uint16_t MyRTC_Time[];
void MyRTC_SetTime(void);
void MyRTC_Init(void);
void MyRTC_ReadTime(void);
#endif
问题
总结
本节课主要是了解这个BKP备份寄存的硬件在代码下的实现,RTC实时时钟的读取与设置,这用到了上节课时间戳讲的函数,由于STM32不能识别当地时间,所以在这里就是这里获取的时间都是伦敦时间,需要加一个偏移量。