stm32入门学习13-时钟RTC

(一)时钟RTC

stm32内部集成了一个秒计数器RTC,用于显示我们日常的时间,如日期年月日,时分秒等,RTC的主要原理就是进行每秒自增,如果我们知道开始记秒的开始时间,就可以计算现在的日期,但是这不需要我们计算,我们只要调用C语言库函数即可自动完成秒数到日常时间或日常时间到秒数的转换,如果只是实现秒自增的功能,那么内部任何一个计数器都可以实现,但是时钟RTC与其他计数器不同的是其可以使用备用电源保持工作,在最小板供电停止时如果有备用电源其会继续工作;这里的备用电源供电引脚为stm32最小板的1好引脚,标注为VBT;

(二)备份寄存器BKP

和时钟RTC一样,备份寄存器BKP可以在备用电源供电时保持数据不丢失,但是其却不能在备用电源和主电源同时断电时候还保持掉电不丢失,这里使用BKP主要是为了给时钟判断是否需要初始化,如果备用电源没有断开,RTC依旧在工作,那么在主电源重新供电的时候时钟就不需要初始化,直接读取其时钟值即可;

(三)RTC时钟和BKP备份寄存器

这里BKP主要是为了检测备用电源是否掉电的,我们的思路是在上电的时候在备份寄存器BKP上写入一个数据(不要写默认值0),如果备用电源没有断电,这个数据会一直保持,如果断电则会恢复为0,我们只要会初始化BKP和读写BKP即可,主要用到这几个函数

void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
// 写入备份寄存器

uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
// 读出备份寄存器

void RTC_WaitForLastTask(void);
// 等待上次任务完成,内部自带while循环和进入RTC的配置模式

void RTC_WaitForSynchro(void);
// 等待系统同步,自带while循环和进入RTC的配置模式

(1)BKP初始化

BKP初始化要初始BKP的时钟,还有电源管理模块的时钟RCC_APB1Periph_PWR,以便在使用备用电源时stm32可以进行电源管理进入待机模式,这些时钟都是APB1上的外设,因此我们要调动APB1的初始化函数

void rtc_bkp_init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
}

(2)RTC初始化

RTC不想其他外设一样用初始化结构体来进行初始化,我们要对每一步手动初始化,初始化流程图如下

首先要开启RTC的时钟并选择对应通道,然后要写入重装寄存器和预分频值以使得计数器实现每秒自增(即频率为1Hz),当然我们还要打开对应的电源控制

如果在主电源断开但备用电源没有断开时,RTC仍然会工作,但是主电源开始供电时,程序会重启,这时会再次调用RTC的初始化函数,这时不希望的,因此我们在BKP中写入一个标志位,如果该标志位没有清零,表示备用电源和主电源没有同时断电,这时我们就不需要重新对RTC进行初始化,我们在RTC初始化中加入if判断即可

void rtc_init()
{
	PWR_BackupAccessCmd(ENABLE);
	
	if (BKP_ReadBackupRegister(BKP_DR1) != 1)
	{
		RCC_LSEConfig(RCC_LSE_ON);
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();	// wait synchronization
		RTC_WaitForLastTask();	// wait write
		
		RTC_SetPrescaler(32768-1);
		RTC_WaitForLastTask();
		
		rtc_set_time(2024, 8, 9, 16, 7, 0);
	
		BKP_WriteBackupRegister(BKP_DR1, 1);
	}
}

由于RTC的时钟和系统时钟不是一个频率,在开启时钟后要等待时钟同步(synchronization),每次进行写操作时要等待上次任务完成;

这里选择的是外部低速时钟,其晶振为32.768KHz,因此要进行32768分频使其频率为1Hz

(3)设置和读取时间

我们可以从该时钟读取到的只有秒值,且要修改时间也只有输入对应时间的秒数才能修改,但是我们可以借助time库来计算我们常见的时间格式和对应秒数的转换,主要有两个函数和一个结构体

objectivec 复制代码
struct tm {
    int tm_sec;   /* seconds after the minute, 0 to 60
                     (0 - 60 allows for the occasional leap second) */
    int tm_min;   /* minutes after the hour, 0 to 59 */
    int tm_hour;  /* hours since midnight, 0 to 23 */
    int tm_mday;  /* day of the month, 1 to 31 */
    int tm_mon;   /* months since January, 0 to 11 */
    int tm_year;  /* years since 1900 */
    int tm_wday;  /* days since Sunday, 0 to 6 */
    int tm_yday;  /* days since January 1, 0 to 365 */
    int tm_isdst; /* Daylight Savings Time flag */
    union {       /* ABI-required extra fields, in a variety of types */
        struct {
            int __extra_1, __extra_2;
        };
        struct {
            long __extra_1_long, __extra_2_long;
        };
        struct {
            char *__extra_1_cptr, *__extra_2_cptr;
        };
        struct {
            void *__extra_1_vptr, *__extra_2_vptr;
        };
    };
};
// 时间结构体,对应秒、分、时、日、月、年、星期、天

extern _ARMABI time_t mktime(struct tm * /*timeptr*/) __attribute__((__nonnull__(1)));
// 将时间结构体转换为秒数,输入一个时间结构体指针输出其到1990年的秒数,结构体只要年月日即可

extern _ARMABI struct tm *localtime(const time_t * /*timer*/) __attribute__((__nonnull__(1)));
// 输入到1990年的秒数输出一个时间结构体指针

这里有一些规定与日常不合,这里的月是从0开始的,年是如今到1900年的年数,星期从星期天(0)开始

这里转换的是伦敦时间,要计算北京时间还要加上8小时(东八区),对应秒数加8*60*60

objectivec 复制代码
void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
{
	struct tm timer;
	unsigned int counter;
	timer.tm_year = year-1900;
	timer.tm_mon = month-1;
	timer.tm_mday = day;
	timer.tm_hour = hour;
	timer.tm_min = min;
	timer.tm_sec = sec;
	counter = mktime(&timer);
	counter -= 8*60*60;
	RTC_SetCounter(counter);
	RTC_WaitForLastTask();
}
objectivec 复制代码
struct tm* rtc_read_time()
{
	struct tm* timer;
	unsigned int counter = RTC_GetCounter() + 8*60*60;
	timer = localtime(&counter);
	timer->tm_year += 1900;
	timer->tm_mon += 1;
	return timer;
}

(4)封装与声明

最终.c 和 .h文件如下

objectivec 复制代码
#include "stm32f10x.h"                  // Device header
#include <time.h>

void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);

void rtc_bkp_init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
}

void rtc_init()
{
	PWR_BackupAccessCmd(ENABLE);
	
	if (BKP_ReadBackupRegister(BKP_DR1) != 1)
	{
		RCC_LSEConfig(RCC_LSE_ON);
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();	// wait synchronization
		RTC_WaitForLastTask();	// wait write
		
		RTC_SetPrescaler(32768-1);
		RTC_WaitForLastTask();
		
		rtc_set_time(2024, 8, 9, 16, 7, 0);
	
		BKP_WriteBackupRegister(BKP_DR1, 1);
	}
}

void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
{
	struct tm timer;
	unsigned int counter;
	timer.tm_year = year-1900;
	timer.tm_mon = month-1;
	timer.tm_mday = day;
	timer.tm_hour = hour;
	timer.tm_min = min;
	timer.tm_sec = sec;
	counter = mktime(&timer);
	counter -= 8*60*60;
	RTC_SetCounter(counter);
	RTC_WaitForLastTask();
}

struct tm* rtc_read_time()
{
	struct tm* timer;
	unsigned int counter = RTC_GetCounter() + 8*60*60;
	timer = localtime(&counter);
	timer->tm_year += 1900;
	timer->tm_mon += 1;
	return timer;
}
objectivec 复制代码
#ifndef __RTC_H__
#define __RTC_H__

void rtc_bkp_init(void);
void rtc_init(void);
void rtc_set_time(unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
struct tm* rtc_read_time(void);

#endif

(四)主函数

只要在主函数进行对应初始化和调用读取函数即可,初始化已经包含了开始时间的设定,如果要重新设定开始时间,可以在初始化中更改,也可以在主函数中使用rtc_set_time函数来修改,但是要注意上电后stm32复位问题,应该和初始化一样加入备用电源是否断电的判断代码

objectivec 复制代码
#include "stm32f10x.h"                  // Device header
#include "rtc.h"
#include "OLED.h"
#include <time.h>

int main()
{
	struct tm* time_inf;
	OLED_Init();
	rtc_bkp_init();
	rtc_init();
	OLED_ShowString(1, 1, "time:");
	OLED_ShowString(2, 1, "xxxx-xx-xx");
	OLED_ShowString(3, 1, "xx:xx:xx");
	while(1)
	{
		time_inf = rtc_read_time();
		OLED_ShowNum(2, 1, time_inf->tm_year, 4);
		OLED_ShowNum(2, 6, time_inf->tm_mon, 2);
		OLED_ShowNum(2, 9, time_inf->tm_mday, 2);
		OLED_ShowNum(3, 1, time_inf->tm_hour, 2);
		OLED_ShowNum(3, 4, time_inf->tm_min, 2);
		OLED_ShowNum(3, 7, time_inf->tm_sec, 2);
	}
	return 0;
}

(五)总结

通过实现一个时钟,我们学习了RTC和BKP两个可以使用备用电源供电工作的外设,了解了计数器RTC的原理和备份寄存器BKP的原理,通过备份寄存器实现了RTC的掉电继续计时和数据不丢失

相关推荐
Mephisto.java23 分钟前
【大数据学习 | kafka高级部分】kafka中的选举机制
大数据·学习·kafka
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
武子康2 小时前
大数据-212 数据挖掘 机器学习理论 - 无监督学习算法 KMeans 基本原理 簇内误差平方和
大数据·人工智能·学习·算法·机器学习·数据挖掘
使者大牙2 小时前
【大语言模型学习笔记】第一篇:LLM大规模语言模型介绍
笔记·学习·语言模型
As977_2 小时前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
ajsbxi3 小时前
苍穹外卖学习记录
java·笔记·后端·学习·nginx·spring·servlet
Rattenking3 小时前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
m0_739312873 小时前
【STM32】项目实战——OV7725/OV2604摄像头颜色识别检测(开源)
stm32·单片机·嵌入式硬件
嵌入式小章3 小时前
基于STM32的实时时钟(RTC)教学
stm32·嵌入式硬件·实时音视频
TeYiToKu3 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm