STM32RTC实时时钟实验讲解,从入门到放弃。

STM32RTC实时时钟实验讲解,从入门到放弃。


前言

本文参考了网上的博文,并加以归纳总结,帮助新手从入门到放弃


提示:以下是本篇文章正文内容:

一、RTC

RTC是什么?

RTC(Real Time Clock):实时时钟,是指可以像时钟一様输出实际时间的电子设备,一般会是集成电路,因此也称为时钟芯片。总之,RTC只是个能靠电池维持运行的32位定时器,并不像实时时钟芯片,读出来就是年月日。RTC就只一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器(BKP)。因为它掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。 STM32 的实时时钟(RTC)是一个独立的定时器。 STM32 的 RTC 模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC和后备寄存器通过一个开关供电,在VDD有效时该开关选择VDD供电,否则由VBAT引脚供电。后备寄存器(10个16位的寄存器)可以用于在关闭VDD时,保存20个字节的用户应用数据。 RTC和后寄存器不会被系统或电源复位源复位;当从待机模式唤醒时,也不会被复位。实时时钟具有一组连续行的计数器,可以通过适当的软件提供日历时钟功能,还具有闹钟中断和阶段性中断功能。 RTC的驱动时钟可以是一个使用外部晶体的32.768kHz的振荡器、内部低功耗RC振荡器或高速的外部时钟经128分频。内部低功耗RC振荡器的典型频率为40kHz。为补偿天然晶体的偏差,可以通过输出一个512Hz的信号对RTC的时钟进行校准。 RTC具有一个32位的可编程计数器,使用比较寄存器可以进行长时间的测量。有一个20位的预分频器用于时基时钟,默认情况下时钟为32.768kHz时,它将产生一个1秒长的时间基准。

因为RTC 模块和时钟配置系统(RCC_BDCR 寄存器)是在后备区域,即在系统复位或从待机模式唤醒后 RTC 的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和 RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护,我们就先看一下,RTC的工作过程。

RTC工作过程及寄存器

在这里插入图片描述
图中浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行.这部分仅包括RTC的分频器,计数器,和闹钟控制器.若VDD电源有效,RTC可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断).从结构图可以看到到,其中的定时器溢出事件无法被配置为中断.如果STM32原本处于待机状态,可由闹钟事件或WKUP事件(外部唤醒事件,属于EXTI模块,不属于RTC)使它退出待机模式.闹钟事件是在计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发的.

因为RTC的寄存器是属于备份域,所以它的所有寄存器都是16位的.它的计数RTC_CNT的32位由RTC_CNTL和RTC_CNTH两个寄存器组成,分别保存计数值的低16位和高16位.在配置RTC模块的时钟时,把输入的32768Hz的RTCCLK进行32768分频得到实际驱动计数器的时钟TR_CLK = RTCCLK/37768 = 1Hz,计时周期为1秒,计时器在TR_CLK的驱动下计数,即每秒计数器RTC_CNT的值加1(常用)

RTC只是一个时钟,但与RTC相连的有两个系统时钟,一个是APB1接口的PCLK1另一个是RTC时钟。这样,RTC功能也就分为两个部分:第一部分,APB1接口部分,与APB1总线相连,MCU也就是通过这条总线对其进行读写操作。另一部分,RTC核,由一系列可编程计数器组成,这部分又再细分为两个组件:预分频模块与32位可编程计数器。预分频模块用来产生最长为1秒的RTC时间基准,而32位的可编程的计数器可被初始化为当前的系统时间。

RTC核心模块

第一模块:RTC的预分频模块(可编程产生1s的RTC时间基准TR_CLK)20位的预分频器,如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个TR_CLK 周期中 RTC 产生一个中断(秒中断)。

第二模块:32位计数器(可初始化当前系统时间),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记录 4294967296 秒,约合 136 年 左右,作为一般应用,这已经是足够了的。(RTC_CNT是一个32位寄存器,可存储的最大值为(232-1),这样的话就是在232秒之后溢出,大概换算为:Time = 2 32 /365/24/60/60大约等于136年)感兴趣的可以看一下UNIX时间戳

闹钟寄存器RTC_ALR:

RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断 由于RTC内核完全独立与APB1接口,软件只能通过APB1的接口访问RTC的预分频值、计数器值和闹钟值,相关的寄存器值是在APB1时钟进行重新同步的RTC上升沿被更新,所以在读取RTC寄存器曾经被禁止的APB1接口前,必须等待RTC_CRL寄存器的PSF位被置1。

需要了解一下RTC的原理,先来看一下相关的寄存器。
RTC 的控制寄存器

RTC_CRL:

0位:进入秒中断后,可判断该位为1决定发生了中断,必须写0清除

3位:寄存器同步标志位,没有同步之前,不被允许修改RTC_CRT/CRL的值,必须先判断该位为1时,同步了。

4位:在修改RTC_CNT/RTC_ALR/RTC_PRL的值前,必须置该位为1,进入配置模式。

5位:RTC操作位,由硬件操作,软件只读,判断该位为1时,表示上一次操作已经完成,才可进行下一次操作。

RTC_CRH:0-3位置1,允许溢出中断、闹钟中断、秒中断。

RTC 总共有 2 个控制寄存器 RTC_CRH 和 RTC_CRL,且两个都是 16 位的。

在这里插入图片描述
该寄存器用来控制中断的,我们本次实验将要用到秒钟中断,所以在该寄存器必须设置最低位为 1,以允许秒钟中断。

在这里插入图片描述
本次我们用到的是该寄存器的 0、3~5 这几个位。

第 0 位秒钟标志位,我们在进入闹钟中断的时候,通过判断这位来决定是不是发生了秒钟中断。然后必须通过软件将该位清零(写0)。
第 3 位寄存器同步标志位,我们在修改控制寄存器 RTC_CRH/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修改 RTC_CRH/CRL 的值是不行的。
第 4 位配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式。
第 5 位RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。

RTC 预分频装载寄存器

TRC_PRLH:低4位有效,存放PRL的19-16位

TRC_PRLL:存放PRL的前16位

也有 2 个寄存器组成, RTC_PRLHRTC_PRLL。这两个寄存器用来配置 RTC 时钟的分频数的,比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 32767,以得到一秒钟的计数频率。
RTC_PRLH 的各位描述如图 20.1.4 所示:
在这里插入图片描述
从图 20.1.4 可以看出, RTC_PRLH 只有低四位有效,用来存储 PRL 的 19~16 位。而 PRL的前 16 位,存放在 RTC_PRLL 里面,寄存器 RTC_PRLL 的各位描述如图 20.1.5 所示:
在这里插入图片描述
RTC 预分频器余数寄存器

该寄存器也有 2 个寄存器组成 RTC_DIVHRTC_DIVL,这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到 0.1 秒,或者 0.01 秒等。该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。这两个寄存器和 RTC 预分频装载寄存器的各位是一样的。

RTC 计数器寄存器 RTC_CNT

2个16位寄存器组成,共32位RTC_CNTH、RTC_CNTL:用来存放秒钟值

该寄存器由 2 个 16位的寄存器组成 RTC_CNTHRTC_CNTL,总共 32 位,用来记录秒钟值(一般情况下)。此两个计数器也比较简单,我们也不多说了。注意一点,在修改这个寄存器的时候要先进入配置模式。

RTC 闹钟寄存器

RTC_ALRH、RTC_ALRL

用来记录闹钟产生的时间,当RTC_CNTRTC_ALR的值相等时,则产生闹钟中断,条件是使能了中断且进入了寄存器的配置模式了。

该寄存器也是由 2 个 16 为
的寄存器组成 RTC_ALRHRTC_ALRL。总共也是 32 位,用来标记闹钟产生的时间(以秒为单位),如果 RTC_CNT 的值与 RTC_ALR 的值相等,并使能了中断的话,会产生一个闹钟中断。该寄存器的修改也要进入配置模式才能进行。

STM32 的备份寄存器

执行如下操作对备份寄存器和RTC进行访问

1)设置寄存器RCC_APB1ENRPWRENBKPEN位打开电源和后备时钟

2)电源后备寄存器(PWR_CR)的DBP位使能对后备寄存器和RTC的访问。

备份寄存器是 42 个 16 位的寄存器(大容量),可用来存储 84 个字节的用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT (备用电源)维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。此外, BKP 控制寄存器用来管理侵入检测和 RTC 校准功能,这里我们不作介绍。复位后,对备份寄存器和 RTC 的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和 RTC 的访问:
1)通过设置寄存器 RCC_APB1ENRPWRENBKPEN 位来打开电源和后备接口的时钟
2)电源控制寄存器(PWR_CR)的 DBP 位来使能对后备寄存器和 RTC 的访问。我们一般用 BKP 来存储 RTC 的校验值或者记录一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不是真正的 EEPROM,而是需要电池来维持它的数据。关于 BKP 的详细介绍请看《STM32 参考手册》的第 47 页, 5.1 一节。
备份区域控制寄存器RCC_BDCR
在这里插入图片描述
RTC 的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。
提到时钟源,就要讲解一下,RTC的时钟源来源:

1,高速外部时钟的128分频:HSE/128;
2,低速内部时钟LSI;
3,低速外部时钟LSE;

二、实验部分

步骤分解

1. 使能电源时钟和备份区域时钟
2. 取消备份区写保护
3. 复位备份区域,开启外部低速振荡器
4. 选择 RTC 时钟,并使能
5. 设置 RTC 的分频,以及配置 RTC 时钟
6. 更新配置,设置 RTC 中断分组
7. 编写中断服务函数

RTC 相关的库函数在文件 stm32f10x_rtc.c 和 stm32f10x_rtc.h 文件中, BKP 相关的库函数在
文件 stm32f10x_bkp.c 和文件 stm32f10x_bkp.h 文件中


1、RTC时钟源和时钟操作函数;
void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择;
void RCC_RTCCLKCmd(FunctionalState NewState);//时钟使能;
2、RTC初始化函数
ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct);
trypedef struct
{
uint32_t RTC_HourFormat;//小时格式:24/12
uint32_t RTC_AsynchPrediv;//异步分频系数
uint32_t RTC_SynchPrediv;//同步分频系数;
}RTC_InitTypeDef;
3、日历配置相关函数
ErrorStatus RTC_SetTime(uint32_t RTC_Format,RTC_TimeTypeDef* RTC_TimeStruct);
void RTC_GetTime(uint32_t RTC_Format,RTC_TimeTypeDef* RTC_TimeStruct);
ErrorStatus RTC_SetDate(uint32_t RTC_Format,RTC_Dae TypeDef* RTC_DataStruct);
void RTC_GetDate(uint32_t RTC_Format,RTC_Date TypeDef* RTC_DateStruct);
uint32_t RTC_GetSubSecond(void);
4、RTC闹钟相关函数
ErrorStatus RTC_AlarmCmd(uint32_t RTC_Alarm,FunctionalState NewState);
void RTC_SetAlarm();
void RTC_GetAlarm();
void RTC_AlarmSubSecondConfig();
uint32_t RTC_GetAlarmSubSecond(uint32_t RTC_Alarm);
5、RTC周期唤醒相关函数:
void RTC_WakeUpClockConfig();
void RTC_SetWakeUpCounter();
uint32_t RTC_GetWakeUpCounter(void);
RTC_WakeUpCmd(DISABLE);//关闭WAKEUP
6、RTC中断配置以及状态相关函数
void RTC_ITConfig();
FlagStatus RTC_GetFlgStatus(uint32_t RTC_FLAG);
void RTC_ClearFlag(uint32_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint32_t RTC_IT);
void RTC_ClearITPendingBit();
7、RTC相关约束函数
void RTC_WriteProtectionCmd();//取消写保护
ErrorStatus RTC_EnterInitNode();//进入配hi模式,RTC_ISR_INITF位设置位1
void RTC_ExitInitMode(void);//退出初始化模式
8、其他函数
uint32_t RTC_ReadBackupRegister();
void RTC_WriteBackupRegister();
void RTC_ITConfig();


1. 使能电源时钟和备份区域时钟
前面已经介绍了,我们要访问 RTC 和备份区域就必须先使能电源时钟和备份区域时钟。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

2. 取消备份区写保护
要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。 取消备份区域写保护的库函数实现方法是:

PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问

3. 复位备份区域,开启外部低速振荡器
在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个
操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器,注意这里一般要先判断 RCC_BDCR 的 LSERDY位来确定低速振荡器已经就绪了才开始下面的操作。

BKP_DeInit();//复位备份区域
RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器

4.选择 RTC 时钟,并使能。
这里我们将通过 RCC_BDCR 的 RTCSEL 来选择选择外部 LSI 作为 RTC 的时钟。然后通过RTCEN 位使能 RTC 时钟。

RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟

对于 RTC 时钟的选择,还有 RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128两个,顾名思义,前者为 LSI,后者为 HSE 的 128 分频,这在时钟系统章节有讲解过。使能 RTC 时钟的函数是:

RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟

5.设置 RTC 的分频,以及配置 RTC 时钟。
在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置RTC 的允许配置位(RTC_CRH 的 CNF 位), 设置时间(其实就是设置 RTC_CNTH 和 RTC_CNTL两个寄存器)。 下面我们一一这些步骤用到的库函数:在进行 RTC 配置之前首先要打开允许配置位(CNF),库函数是:

RTC_EnterConfigMode();/// 允许配置

在配置完成之后,千万别忘记更新配置同时退出配置模式,函数是:

RTC_ExitConfigMode();//退出配置模式, 更新配置

设置 RTC 时钟分频数, 库函数是:

void RTC_SetPrescaler(uint32_t PrescalerValue);

这个函数只有一个入口参数,就是 RTC 时钟的分频数,很好理解。
然后是设置秒中断允许, RTC 使能中断的函数是:

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState)

这个函数的第一个参数是设置秒中断类型,这些通过宏定义定义的。 对于使能秒中断方法是:

RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断

下一步便是设置时间了,设置时间实际上就是设置 RTC 的计数值,时间与计数值之间是需要换算的。库函数中设置 RTC 计数值的方法是:

void RTC_SetCounter(uint32_t CounterValue)//最后在配置完成之后

6.更新配置,设置 RTC 中断分组。
在设置完时钟之后,我们将配置更新同时退出配置模式,这里还是通过 RTC_CRH 的 CNF
来实现。

RTC_ExitConfigMode();//退出配置模式,更新配置

在退出配置模式更新配置之后我们在备份区域 BKP_DR1 中写入 0X5050 代表我们已经初始化过时钟了,下次开机(或复位)的时候,先读取 BKP_DR1 的值,然后判断是否是 0X5050 来决定是不是要配置。接着我们配置 RTC 的秒钟中断,并进行分组。
往备份区域写用户数据的函数是:

void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data)

这个函数的第一个参数就是寄存器的标号了,这个是通过宏定义定义的。 比如我们要往
BKP_DR1 写入 0x5050,方法是:

BKP_WriteBackupRegister(BKP_DR1, 0X5050);

同时,有写便有读,读取备份区域指定寄存器的用户数据的函数是

uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR)

7. 编写中断服务函数
我们要编写中断服务函数,在秒钟中断产生的时候,读取当前的时间值,并显示到
oled 模块上。

代码部分

rtc.c文件代码
//注意:代码中的乱码,复制在keil5中可以恢复,便于读者编译运行。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h" 		    

_calendar_obj calendar;//ʱÖӽṹÌå 
 
static void RTC_NVIC_Config(void)
{	
  NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;		//RTCÈ«¾ÖÖжÏ
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;	//ÏÈÕ¼ÓÅÏȼ¶1λ,´ÓÓÅÏȼ¶3λ
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	//ÏÈÕ¼ÓÅÏȼ¶0λ,´ÓÓÅÏȼ¶4λ
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//ʹÄܸÃͨµÀÖжÏ
	NVIC_Init(&NVIC_InitStructure);		//¸ù¾ÝNVIC_InitStructÖÐÖ¸¶¨µÄ²ÎÊý³õʼ»¯ÍâÉèNVIC¼Ä´æÆ÷
}

//ʵʱʱÖÓÅäÖÃ
//³õʼ»¯RTCʱÖÓ,ͬʱ¼ì²âʱÖÓÊÇ·ñ¹¤×÷Õý³£
//BKP->DR1ÓÃÓÚ±£´æÊÇ·ñµÚÒ»´ÎÅäÖõÄÉèÖÃ
//·µ»Ø0:Õý³£
//ÆäËû:´íÎó´úÂë

u8 RTC_Init(void)
{
	//¼ì²éÊDz»ÊǵÚÒ»´ÎÅäÖÃʱÖÓ
	u8 temp=0;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//ʹÄÜPWRºÍBKPÍâÉèʱÖÓ   
	PWR_BackupAccessCmd(ENABLE);	//ʹÄܺ󱸼ĴæÆ÷·ÃÎÊ  
	if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)		//´ÓÖ¸¶¨µÄºó±¸¼Ä´æÆ÷ÖжÁ³öÊý¾Ý:¶Á³öÁËÓëдÈëµÄÖ¸¶¨Êý¾Ý²»Ïàºõ
		{	 			
		BKP_DeInit();	//¸´Î»±¸·ÝÇøÓò 	
		RCC_LSEConfig(RCC_LSE_ON);	//ÉèÖÃÍⲿµÍËÙ¾§Õñ(LSE),ʹÓÃÍâÉèµÍËÙ¾§Õñ
		while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)	//¼ì²éÖ¸¶¨µÄRCC±ê־λÉèÖÃÓë·ñ,µÈ´ýµÍËÙ¾§Õñ¾ÍÐ÷
			{
			temp++;
			delay_ms(10);
			}
		if(temp>=250)return 1;//³õʼ»¯Ê±ÖÓʧ°Ü,¾§ÕñÓÐÎÊÌâ	    
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);		//ÉèÖÃRTCʱÖÓ(RTCCLK),Ñ¡ÔñLSE×÷ΪRTCʱÖÓ    
		RCC_RTCCLKCmd(ENABLE);	//ʹÄÜRTCʱÖÓ  
		RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É
		RTC_WaitForSynchro();		//µÈ´ýRTC¼Ä´æÆ÷ͬ²½  
		RTC_ITConfig(RTC_IT_SEC, ENABLE);		//ʹÄÜRTCÃëÖжÏ
		RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É
		RTC_EnterConfigMode();/// ÔÊÐíÅäÖÃ	
		RTC_SetPrescaler(32767); //ÉèÖÃRTCÔ¤·ÖƵµÄÖµ
		RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É
		RTC_Set(2021,2,9,17,40,00);  //ÉèÖÃʱ¼ä	
		RTC_ExitConfigMode(); //Í˳öÅäÖÃģʽ  
		BKP_WriteBackupRegister(BKP_DR1, 0X5050);	//ÏòÖ¸¶¨µÄºó±¸¼Ä´æÆ÷ÖÐдÈëÓû§³ÌÐòÊý¾Ý
		}
	else//ϵͳ¼ÌÐø¼ÆÊ±
		{

		RTC_WaitForSynchro();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É
		RTC_ITConfig(RTC_IT_SEC, ENABLE);	//ʹÄÜRTCÃëÖжÏ
		RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É
		}
	RTC_NVIC_Config();//RCTÖжϷÖ×éÉèÖÃ		    				     
	RTC_Get();//¸üÐÂʱ¼ä	
	return 0; //ok

}		 				    
//RTCʱÖÓÖжÏ
//ÿÃë´¥·¢Ò»´Î  
//extern u16 tcnt; 
void RTC_IRQHandler(void)
{		 
	if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//ÃëÖÓÖжÏ
	{							
		RTC_Get();//¸üÐÂʱ¼ä   
 	}
	if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//ÄÖÖÓÖжÏ
	{
		RTC_ClearITPendingBit(RTC_IT_ALR);		//ÇåÄÖÖÓÖжÏ	  	
	  RTC_Get();				//¸üÐÂʱ¼ä   
  	printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//Êä³öÄÖÁåʱ¼ä	
		
  	} 				  								 
	RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);		//ÇåÄÖÖÓÖжÏ
	RTC_WaitForLastTask();	  	    						 	   	 
}
//ÅжÏÊÇ·ñÊÇÈòÄ꺯Êý
//ÔÂ·Ý   1  2  3  4  5  6  7  8  9  10 11 12
//ÈòÄê   31 29 31 30 31 30 31 31 30 31 30 31
//·ÇÈòÄê 31 28 31 30 31 30 31 31 30 31 30 31
//ÊäÈë:Äê·Ý
//Êä³ö:¸ÃÄê·ÝÊDz»ÊÇÈòÄê.1,ÊÇ.0,²»ÊÇ
u8 Is_Leap_Year(u16 year)
{			  
	if(year%4==0) //±ØÐëÄܱ»4Õû³ý
	{ 
		if(year%100==0) 
		{ 
			if(year%400==0)return 1;//Èç¹ûÒÔ00½áβ,»¹ÒªÄܱ»400Õû³ý 	   
			else return 0;   
		}else return 1;   
	}else return 0;	
}	 			   
//ÉèÖÃʱÖÓ
//°ÑÊäÈëµÄʱÖÓת»»ÎªÃëÖÓ
//ÒÔ1970Äê1ÔÂ1ÈÕΪ»ù×¼
//1970~2099ÄêΪºÏ·¨Äê·Ý
//·µ»ØÖµ:0,³É¹¦;ÆäËû:´íÎó´úÂë.
//Ô·ÝÊý¾Ý±í											 
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //ÔÂÐÞÕýÊý¾Ý±í	  
//ƽÄêµÄÔ·ÝÈÕÆÚ±í
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
	u16 t;
	u32 seccount=0;
	if(syear<1970||syear>2099)return 1;	   
	for(t=1970;t<syear;t++)	//°ÑËùÓÐÄê·ÝµÄÃëÖÓÏà¼Ó
	{
		if(Is_Leap_Year(t))seccount+=31622400;//ÈòÄêµÄÃëÖÓÊý
		else seccount+=31536000;			  //ƽÄêµÄÃëÖÓÊý
	}
	smon-=1;
	for(t=0;t<smon;t++)	   //°ÑÇ°ÃæÔ·ݵÄÃëÖÓÊýÏà¼Ó
	{
		seccount+=(u32)mon_table[t]*86400;//Ô·ÝÃëÖÓÊýÏà¼Ó
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//ÈòÄê2Ô·ÝÔö¼ÓÒ»ÌìµÄÃëÖÓÊý	   
	}
	seccount+=(u32)(sday-1)*86400;//°ÑÇ°ÃæÈÕÆÚµÄÃëÖÓÊýÏà¼Ó 
	seccount+=(u32)hour*3600;//СʱÃëÖÓÊý
    seccount+=(u32)min*60;	 //·ÖÖÓÃëÖÓÊý
	seccount+=sec;//×îºóµÄÃëÖÓ¼ÓÉÏÈ¥

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//ʹÄÜPWRºÍBKPÍâÉèʱÖÓ  
	PWR_BackupAccessCmd(ENABLE);	//ʹÄÜRTCºÍºó±¸¼Ä´æÆ÷·ÃÎÊ 
	RTC_SetCounter(seccount);	//ÉèÖÃRTC¼ÆÊýÆ÷µÄÖµ

	RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É  	
	return 0;	    
}

//³õʼ»¯ÄÖÖÓ		  
//ÒÔ1970Äê1ÔÂ1ÈÕΪ»ù×¼
//1970~2099ÄêΪºÏ·¨Äê·Ý
//syear,smon,sday,hour,min,sec£ºÄÖÖÓµÄÄêÔÂÈÕʱ·ÖÃë   
//·µ»ØÖµ:0,³É¹¦;ÆäËû:´íÎó´úÂë.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
	u16 t;
	u32 seccount=0;
	if(syear<1970||syear>2099)return 1;	   
	for(t=1970;t<syear;t++)	//°ÑËùÓÐÄê·ÝµÄÃëÖÓÏà¼Ó
	{
		if(Is_Leap_Year(t))seccount+=31622400;//ÈòÄêµÄÃëÖÓÊý
		else seccount+=31536000;			  //ƽÄêµÄÃëÖÓÊý
	}
	smon-=1;
	for(t=0;t<smon;t++)	   //°ÑÇ°ÃæÔ·ݵÄÃëÖÓÊýÏà¼Ó
	{
		seccount+=(u32)mon_table[t]*86400;//Ô·ÝÃëÖÓÊýÏà¼Ó
		if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//ÈòÄê2Ô·ÝÔö¼ÓÒ»ÌìµÄÃëÖÓÊý	   
	}
	seccount+=(u32)(sday-1)*86400;//°ÑÇ°ÃæÈÕÆÚµÄÃëÖÓÊýÏà¼Ó 
	seccount+=(u32)hour*3600;//СʱÃëÖÓÊý
    seccount+=(u32)min*60;	 //·ÖÖÓÃëÖÓÊý
	seccount+=sec;//×îºóµÄÃëÖÓ¼ÓÉÏÈ¥ 			    
	//ÉèÖÃʱÖÓ
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);	//ʹÄÜPWRºÍBKPÍâÉèʱÖÓ   
	PWR_BackupAccessCmd(ENABLE);	//ʹÄܺ󱸼ĴæÆ÷·ÃÎÊ  
	//ÉÏÃæÈý²½ÊDZØÐëµÄ!
	
	RTC_SetAlarm(seccount);
 
	RTC_WaitForLastTask();	//µÈ´ý×î½üÒ»´Î¶ÔRTC¼Ä´æÆ÷µÄд²Ù×÷Íê³É  	
	
	return 0;	    
}


//µÃµ½µ±Ç°µÄʱ¼ä
//·µ»ØÖµ:0,³É¹¦;ÆäËû:´íÎó´úÂë.
u8 RTC_Get(void)
{
	static u16 daycnt=0;
	u32 timecount=0; 
	u32 temp=0;
	u16 temp1=0;	  
    timecount=RTC_GetCounter();	 
 	temp=timecount/86400;   //µÃµ½ÌìÊý(ÃëÖÓÊý¶ÔÓ¦µÄ)
	if(daycnt!=temp)//³¬¹ýÒ»ÌìÁË
	{	  
		daycnt=temp;
		temp1=1970;	//´Ó1970Ä꿪ʼ
		while(temp>=365)
		{				 
			if(Is_Leap_Year(temp1))//ÊÇÈòÄê
			{
				if(temp>=366)temp-=366;//ÈòÄêµÄÃëÖÓÊý
				else {temp1++;break;}  
			}
			else temp-=365;	  //ƽÄê 
			temp1++;  
		}   
		calendar.w_year=temp1;//µÃµ½Äê·Ý
		temp1=0;
		while(temp>=28)//³¬¹ýÁËÒ»¸öÔÂ
		{
			if(Is_Leap_Year(calendar.w_year)&&temp1==1)//µ±ÄêÊDz»ÊÇÈòÄê/2Ô·Ý
			{
				if(temp>=29)temp-=29;//ÈòÄêµÄÃëÖÓÊý
				else break; 
			}
			else 
			{
				if(temp>=mon_table[temp1])temp-=mon_table[temp1];//ƽÄê
				else break;
			}
			temp1++;  
		}
		calendar.w_month=temp1+1;	//µÃµ½Ô·Ý
		calendar.w_date=temp+1;  	//µÃµ½ÈÕÆÚ 
	}
	temp=timecount%86400;     		//µÃµ½ÃëÖÓÊý   	   
	calendar.hour=temp/3600;     	//Сʱ
	calendar.min=(temp%3600)/60; 	//·ÖÖÓ	
	calendar.sec=(temp%3600)%60; 	//ÃëÖÓ
	calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//»ñÈ¡ÐÇÆÚ   
	return 0;
}	 
//»ñµÃÏÖÔÚÊÇÐÇÆÚ¼¸
//¹¦ÄÜÃèÊö:ÊäÈ빫ÀúÈÕÆÚµÃµ½ÐÇÆÚ(Ö»ÔÊÐí1901-2099Äê)
//ÊäÈë²ÎÊý£º¹«ÀúÄêÔÂÈÕ 
//·µ»ØÖµ£ºÐÇÆÚºÅ																						 
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{	
	u16 temp2;
	u8 yearH,yearL;
	
	yearH=year/100;	yearL=year%100; 
	// Èç¹ûΪ21ÊÀ¼Í,Äê·ÝÊý¼Ó100  
	if (yearH>19)yearL+=100;
	// Ëù¹ýÈòÄêÊýÖ»Ëã1900ÄêÖ®ºóµÄ  
	temp2=yearL+yearL/4;
	temp2=temp2%7; 
	temp2=temp2+day+table_week[month-1];
	if (yearL%4==0&&month<3)temp2--;
	return(temp2%7);
}	

		  

main.c文件

#include "delay.h"
#include "sys.h"
#include "usart.h"	
#include "rtc.h" 
#include "stm32f10x.h"

int main(void)
{
	u8 t=1;
	delay_init();	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	OLED_Init();
	RTC_Init();
 	while (1)
	{
		if(t!=calendar.sec)
		{
			t=calendar.sec;
			/*OLED_ShowNum(0,0,calendar.w_year,2,24);
			OLED_ShowString(24,0,"-",24);
			OLED_ShowNum(36,0,calendar.w_month,2,24);
			OLED_ShowString(60,0,"-",24);
			OLED_ShowNum(72,0,calendar.w_date,2,24);
			*/
			switch(calendar.week)
			{
				case 0:
					OLED_ShowString(0,24,"Sunday",24);break;
				case 1:
					OLED_ShowString(0,24,"Monday",24);break;
				case 2:
					OLED_ShowString(0,24,"Tuesday",24);break;
				case 3:
					OLED_ShowString(0,24,"Wednesday",24);break;
				case 4:
					OLED_ShowString(0,24,"Thursday",24);break;
				case 5:
					OLED_ShowString(0,24,"friday",24);break;
				case 6:
					OLED_ShowString(0,24,"Saturday",24);break;
			}
			
			OLED_ShowNum(0,0,calendar.hour,2,24);
			OLED_ShowString(24,0,"-",24);
			OLED_ShowNum(36,0,calendar.min,2,24);
			OLED_ShowString(60,0,"-",24);
			OLED_ShowNum(72,0,calendar.sec,2,24);
			OLED_Refresh_Gram();
		}
		delay_ms(10);
	}
	

}






总结

今天的文章就讲到这里,觉得写的还行的小伙伴,点赞加关注。如果还是看不懂的话,点击链接,小破站视频链接直达

rtcSTM32 STM32 STM32的实时钟( 实时钟( 实时钟( RTCRTCRTC)是一个独立的定时器。 )是一个独立的定时器。 )是一个独立的定时器。 )是一个独立的定时器。 )是一个独立的定时器。 STM32STM32 STM32RTCRTCRTC模块拥有一组连续计数 模块拥有一组连续计数 模块拥有一组连续计数 模块拥有一组连续计数 模块拥有一组连续计数 的计数器, 的计数器, 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 在相应软件配置下,可提供时钟日历的功能。修改计数器值以重新设系统当 前的时间和日期。 前的时间和日期。 前的时间和日期。 前的时间和日期。 RTCRTCRTC模块和时钟配置系统 模块和时钟配置系统 模块和时钟配置系统 模块和时钟配置系统 模块和时钟配置系统 (RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR(RCC_BDCR寄存器 寄存器 )是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 是在后备区域,即系统复位或从待机模式 唤醒后 唤醒后 RTCRTCRTC的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器的设置和 时间维持不变。但是在系统复位后,会自动禁止访问备寄存器RTCRTCRTC, 以防止对后备区域 以防止对后备区域 以防止对后备区域 以防止对后备区域 (BKP)(BKP)(BKP)(BKP)(BKP)的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 的意外写操作。所以在要设置时间之前, 先要取消备份区域( 先要取消备份区域( 先要取消备份区域( 先要取消备份区域( 先要取消备份区域( BKPBKPBKP) 写保护。 写保护。 RTCRTCRTC的简化框图,如 的简化框图,如 的简化框图,如 的简化框图,如 图 20.1. 0.1. 1所示: 所
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KING_阿飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值