STM32F4_RTC实时时钟

目录

1. RTC实时时钟简介

2. RTC框图

3. RTC初始化和配置

3.1 RTC和低功耗模式

3.2 RTC中断

4. RTC相关寄存器

4.1 时间寄存器:RTC_TR

4.2 日期寄存器:RTC_DR

4.3 亚秒寄存器:RTC_SSR

4.4 控制寄存器:RTC_CR

4.5 RTC初始化和状态寄存器:RTC_ISR

4.6 预分频寄存器:RTC_PRER

4.7 唤醒定时器寄存器:RTC_WUTR

4.8 闹钟A寄存器:RTC_ALRMAR

4.9 写保护寄存器:RTC_WPR

4.10 等同于EEPROM记忆芯片的备份寄存器:RTC_BKPxR

4.11 备份区域控制寄存器:RCC_BDCR

5. 库函数配置RTC

6. 实验程序

6.1 日历配置

6.2 闹钟配置

6.3 周期性自动唤醒配置

6.4 实验完整程序

6.4.1 main.c

6.4.2 RTC.c

6.4.3 RTC.h

6.4.4 总结


1. RTC实时时钟简介

        实时时钟RTC是一个独立的BCD定时器 / 计数器。RTC提供一个日历时钟、两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。

        两个32位寄存器包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。同时系统可以自动将月份的天数补偿为28、29(闰年)、30和31天。并且还可以进行夏令时补偿。其他32位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。

        此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。

        注意:无论器件是处于运行模式、低功耗模式或者复位模式,只要电源电压保持在正常工作范围,RTC就不会停止工作。

2. RTC框图

STM32F4_RTC实时时钟_第1张图片

时钟和预分频器:

STM32F4_RTC实时时钟_第2张图片

实时时钟和日历:

STM32F4_RTC实时时钟_第3张图片

        STM32F4的RTC日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期,也可以用于设置时间和日期,可以通过与PCLK1同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问。

        每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前的时间和日期信息。

        注意:时间和日期都是以BCD码的格式存储的,读出来时需要转换为10进制数据,中间存在一个转换关系。

可编程闹钟:

STM32F4_RTC实时时钟_第4张图片

STM32F4提供两个可编程闹钟:闹钟A(ALARM_A)和闹钟B (ALARM_B)。通过RTC_CR寄存器的ALRAE和ALRBE位置1来使能可编程闹钟功能。当日历的亚秒、秒、分、小时、日期分别与闹钟寄存器RTC_ALRMASSR/RTC_ALRMAR和RTC_ALRMBSSR/RTC_ALRMBR中的值匹配时,则可以产生闹钟。

如果配置A闹钟,则配置RTC_ALRMASSR/RTC_ALRMAR即可。

如果配置B闹钟,则配置RTC_ALRMBSSR/RTC_ALRMBR即可。

周期性自动唤醒:

STM32F4_RTC实时时钟_第5张图片

周期性唤醒功能,由一个16位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒

唤醒定时器的时钟输入可以是:2 4 6 8或者16分频的RTC时钟(RTCCLK),也可以是ck_spre时钟。

当选择RTCCLK作为输入时钟时,可配置的唤醒中断周期介于122us和32s之间,分辨率最低为61us。

当选择ck_spre作为输入时钟时,可得到的唤醒时间为1s到36h左右,分辨率为1秒。 

        初始化完成后,定时器开始递减计数。在低功耗模式下,使能唤醒功能时,递减计数保持有效。当计数器计数到0时,RTC_ISR寄存器的WUTF标志会置1,并且唤醒寄存器会使用其重载值(RTC_WUTR寄存器值)重载,之后必须用软件清0  WUTF 标志;

3. RTC初始化和配置

RTC寄存器访问

        RTC寄存器为32位寄存器。除了当BYPSHAD=0时对日历影子寄存器执行的读访问之外,APB接口会在访问RTC寄存器时引入两个等待周期。

RTC寄存器写保护

        系统复位后,可通过PWR电源控制寄存器(PWR_CR)的DBP位保护RTC寄存器以防止非正常的写访问。必须将DBP位置1才能使能RTC寄存器写访问。在上电复位后,所有的RTC寄存器均受到写保护。通过向写保护寄存器(RTC_WPR)写入一个密钥来使能对RTC寄存器的写操作

日历初始化和配置

1. 将 RTC_ISR 寄存器中的 INIT 位置 1 进入初始化模式。在此模式下,日历寄存器停止工作并且这时候的值是可以更新的;

2. 等待RTC_ISR寄存器的INITF位置1,当该位置1时,进入初始化阶段模式,大约需要2个RTCLCK时钟周期。

3. 要为日历计数器生成 1 Hz 时钟,应首先编程 RTC_PRER 寄存器中的同步预分频系数, 然后编程异步预分频系数

4. 在影子寄存器(RTC_TR 和 RTC_DR)中加载初始时间和日期值,然后通过 RTC_CR 寄存器中的 FMT 位配置时间格式(12 或 24 小时制)

5. 通过清零 INIT 位退出初始化模式。随后,自动加载实际日历计数器值,在 4 个 RTCCLK 时钟周期后重新开始计数

注意:初始化序列结束以后,日历开始计数。

夏令时

可通过RTC_CR寄存器的SUB1H、ADD1H、BKP位管理夏令时。

利用SUB1H和ADD1H,软件只需要单次操作便可在日历中减去或增加一个小时,无需执行整个初始化步骤。

编译闹钟

要对可编程的闹钟(闹钟 A 或闹钟 B)进行编程或更新

1. 将 RTC_CR 寄存器中的 ALRAE 或 ALRBE 位清零以禁止闹钟 A 或闹钟 B

2. 轮询 RTC_ISR 寄存器中的 ALRAWF 或 ALRBWF 位,直到其中一个置 1,以确保闹钟 寄存器可以访问。大约需要 2 个 RTCCLK 时钟周期(由于时钟同步)。

3. 编程闹钟 A 或闹钟 B 寄存器(RTC_ALRMASSR/RTC_ALRMAR 或 RTC_ALRMBSSR/RTC_ALRMBR)

4. 将 RTC_CR 寄存器中的 ALRAE 或 ALRBE 位置 1 以再次使能闹钟 A 或闹钟 B

编程唤醒寄存器

1.清零 RTC_CR 中的 WUTE 以禁止唤醒定时器。

2. 轮询 RTC_ISR 中的 WUTWF,直到该位置 1,以确保可以访问唤醒自动重载定时器和 WUCKSEL[2:0] 位。大约需要 2 个 RTCCLK 时钟周期(由于时钟同步)。

3. 编程唤醒自动重载值 WUT[15:0],并选择唤醒时钟(RTC_CR 中的 WUCKSEL[2:0] 位)。 将 RTC_CR 寄存器中的 WUTE 位置 1 以再次使能定时器。唤醒定时器重新开始递减 计数。

3.1 RTC和低功耗模式

STM32F4_RTC实时时钟_第6张图片

3.2 RTC中断

所有的RTC中断均与EXTI控制器相连:

要使能RTC闹钟中断,须

1. 将EXTI线17配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的RTC_Alarm IRQ通道并将其使能。

3. 配置RTC以生成RTC闹钟(闹钟A或者闹钟B)。

要使能RTC唤醒中断,须

1. 将EXTI线22配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的RTC_WKUP IRQ通道并将其使能。

3. 配置RTC以生成RTC唤醒定时器事件。

要使能RTC入侵中断,须

1. 将EXTI线21配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的TAMP_STAMP IRQ通道并将其使能。

3. 配置RTC以检测RTC入侵事件。

要使能RTC时间戳中断,须

1. 将EXTI线21配置为中断模式并将其使能,然后选择上升沿有效。

2. 配置NVIC中的TAMP_STAMP IRQ通道并将其使能。

3. 配置RTC以检测RTC时间戳事件。

STM32F4_RTC实时时钟_第7张图片

4. RTC相关寄存器

4.1 时间寄存器:RTC_TR

时间寄存器:RTC_TR(RTC time register)   RTC_TR是日历时间影子寄存器。只能在初始化模式下对该寄存器执行写操作。

STM32F4_RTC实时时钟_第8张图片

STM32F4_RTC实时时钟_第9张图片

注意该寄存器下数据保存是BCD格式的,读取之后需要进行转换(这也是STM32的优越性,51单片机必须手动转换成BCD码;而STM32单片机只需要通过该寄存器就可以完成二进制到BCD码的转换),才是10进制的时分秒数据,在初始化模式下,对该寄存器进行写操作,可以设置时间。

4.2 日期寄存器:RTC_DR

日期寄存器:RTC_DR(RTC data register)RTC_DR是日历日期影子寄存器。只能在初始化模式下对该寄存器执行写操作。

STM32F4_RTC实时时钟_第10张图片

注意该寄存器的数据同样采用BCD码格式,在初始化模式下,对该寄存器进行写操作,可以设置日期。

4.3 亚秒寄存器:RTC_SSR

亚秒寄存器:RTC_SSR(RTC sub second register)

STM32F4_RTC实时时钟_第11张图片

很显然,该寄存器用于设置更精确的时间。

4.4 控制寄存器:RTC_CR

控制寄存器:RTC_CR(RTC control register)  下图为配置闹钟A,配置闹钟B原理一样,只需要配置相应的位A-->B即可。

STM32F4_RTC实时时钟_第12张图片

4.5 RTC初始化和状态寄存器:RTC_ISR

RTC初始化和状态寄存器:RTC_ISR(RTC initialization and status register)

STM32F4_RTC实时时钟_第13张图片

4.6 预分频寄存器:RTC_PRER

预分频寄存器:RTC_PRER(RTC prescaler register)

STM32F4_RTC实时时钟_第14张图片

注意: 该寄存器必须在初始化模式下INITF=1下,才可以进行。

4.7 唤醒定时器寄存器:RTC_WUTR

唤醒定时器寄存器:RTC_WUTR(RTC wakeup timer register)

STM32F4_RTC实时时钟_第15张图片

注意: 该寄存器必须在RTC_ISR的WUTWF=1下,才可以进行。

4.8 闹钟A寄存器:RTC_ALRMAR

闹钟A寄存器:RTC_ALRMAR(RTC alarm A register)

STM32F4_RTC实时时钟_第16张图片

该寄存器用于设置闹钟A,当位30 WDSEL选择1时,使用星期制闹钟;

该寄存器的配置,必须等待RTC_ISR的ALRAWF为 1 才可以进行。

4.9 写保护寄存器:RTC_WPR

写保护寄存器:RTC_WPR(RTC write protection register)

STM32F4_RTC实时时钟_第17张图片

写保护寄存器的低八位有效。上电后,除了RTC_ISR[13:8]、RTC_TAFCR和RTC_BKPxR寄存器,必须依次写入:0xCA、0x53两个关键字到写保护寄存器RTC_WPR,才可以解锁。写一个错误的关键字将再次激活RTC的寄存器写保护。

4.10 等同于EEPROM记忆芯片的备份寄存器:RTC_BKPxR

备份寄存器:RTC_BKPxR(RTC backup registers)(很重要)

STM32F4_RTC实时时钟_第18张图片

        该寄存器组总共有20个,每个寄存器都是32位,可以存储80个字节的用户数据,这些寄存器在备份域中实现,可在VDD电源关闭时通过VBAT保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在MCU从待机模式唤醒时复位。

我们可以用BKP来存储一些重要信息,相当于一个EEPROM(类似于51上的记忆存储芯片),不过这个EEPROM需要电池来维持他的数据。

4.11 备份区域控制寄存器:RCC_BDCR

STM32F4_RTC实时时钟_第19张图片

STM32F4_RTC实时时钟_第20张图片

RTC的时钟源选择及使能设置是通过这个寄存器来实现的,所以我们在RTC操作之前先要通过这个寄存器选择RTC时钟源,然后才能开始其他操作。

5. 库函数配置RTC

1. 使能电源时钟,并使能RTC及RTC后备寄存器写访问

电源时钟使能,通过RCC_APB1ENR寄存器来设置;RTC及RTC备份寄存器的写访问,通过PWR_CR寄存器的DBP位设置;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);    //使能PWR时钟         -----------前面的标志就是对应需要使用的寄存器

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

2. 开启外部低速振荡器,选择RTC时钟,并使能

这一步骤,只需要在RTC初始化的时候执行一次即可,不需要每次上电都执行,这些操作都是通过 4.11 介绍的备份区域控制寄存器RCC_BDCR 来实现的。

RCC_LSEConfig(RCC_LSE_ON);     //LSE 开启 

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

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

3. 初始化RTC,设置RTC的分频,以及配置RTC函数

该步骤同以往学习的初始化结构体一样,设置结构体变量,分别去配置结构体成员变量即可。

ErrorStatus RTC_Init(RTC_InitTypeDef* RTC_InitStruct);       //RTC初始化函数,RTC初始化参数结构体为RTC_InitTypeDef定义;

typedef struct 
{  
uint32_t RTC_HourFormat;  //设置RTC时间格式
//也就是控制寄存器CR的FMT位。可以选择为24小时/12小时格式;RTC_HourFormat_24/RTC_HourFormat_12;
uint32_t RTC_AsynchPrediv;  //设置RTC的异步分频系数
//也就是设置RTC_PRER寄存器的PREDIV_A位的值。
//注意:异步预分频系数为7位,所以最大值为0x7F,不能超过这个值
uint32_t RTC_SynchPrediv;    //设置RTC同步预分频系数
//也就是设置RTC_PRER寄存器的PREDIV_S位的值
//注意:同步预分频系数为15位,所以最大值为0x7FFF,不能超过这个值
}RTC_InitTypeDef; 
//初始化函数时:
//RTC_Init函数在设置RTC相关参数之前,会先取消RTC写保护,这个操作通过向寄存器RTC_WPR写入
//0xCA和0X53两个数据来实现


RTC->WPR=0XCA;
RTC->WPR=0X53;



//先取消写保护之后,通过上述寄存器的学习,我们知道想要对寄存器进行写操作
//必须先进入RTC初始化模式,才可以进行

ErrorStatus RTC_EnterInitMode(void); //进入RTC初始化模式的函数

//当进去初始化模式之后,RTC_Init函数才去设置RTC->CR和RTC->PRER寄存器的值。在设置完成以后
//我们还需要通过程序退出初始化模式

void RTC_ExitInitMode(void)  //退出RTC初始化模式的函数

//最后开启RTC写保护

RTC->WPR=0XFF;

4. 设置RTC时间

ErrorStatus RTC_SetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);   //设置RTC时间的函数 , 其实质上是设置RTC_TR      寄存器相关位的值

该函数第一个参数RTC_Format用来设置输入的时间格式;可选择为BIN和BCD,RTC_Format_BIN或者RTC_Format_BCD;

//第二个参数为初始化结构体RTC_TimeTypeDef

typedef struct 
{  
uint8_t RTC_Hours;  //时间参数的小时
uint8_t RTC_Minutes;  //分钟
uint8_t RTC_Seconds;  //秒
uint8_t RTC_H12;   //AM/PM符号
}RTC_TimeTypeDef; 

5. 设置RTC日期

ErrorStatus RTC_SetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);    //设置RTC日期函数 其实质是访问RTC_DR寄存器来设置相关位;

第一个参数也是设置时间格式,BCD还是BIN;

//第二个初始化参数为结构体RTC_DateTypeDef

typedef struct 
{  
uint8_t RTC_WeekDay; //设置日期为星期几
 uint8_t RTC_Month;  //月份
uint8_t RTC_Date;  //日期
uint8_t RTC_Year;   //年份
}RTC_DateTypeDef; 

6. 获取RTC当前日期和时间

void RTC_GetTime(uint32_t RTC_Format, RTC_TimeTypeDef* RTC_TimeStruct);    //获取当前RTC时间的函数

void RTC_GetDate(uint32_t RTC_Format, RTC_DateTypeDef* RTC_DateStruct);     //获取当前RTC日期的函数

这两个函数的实质就是读取RTC_TR和RTC_DR寄存器的时间和日期的值,然后将值存放到相应的结构体中。

//RTC初始化
//返回值:0初始化成功
//		  1 LSE开启失败
u8 My_RTC_Init(void)
{
	u16 retry=0x1FFF;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能PWR时钟
	PWR_BackupAccessCmd(ENABLE);//使能后备寄存器
	
	if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5051)//判断是否为第一次配置,这里的0x5051完全是自己设置的一个标志位
		//RTC_ReadBackupRegister叫做读取后备寄存器,RTC_BKP_DR0为寄存器的标志位,如果读取标志位不为我们设置的0x5051
    //(0x5051是我们随便构想的一个32位值,如果是第一次配置该寄存器,不可能第一次的值就是我们随便构想的值)
	//则表示是第一次配置,执行下述程序;如果不是第一次配置,断电从启后会跳过该代码
	{
		RCC_LSEConfig(RCC_LSE_ON);//LSE开始,打开时钟
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)//while循环判断LSE开启的状态位是否置1
			//显然while循环离开的标志是获取的状态位RCC_FLAG_LSERDY等于1,离开也就代表这LSE开启成功
		{
			retry++;
			delay_ms(10);
		}
		if(retry==0)
			return 1;//LSE开启失败
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//既然程序没有返回1能来到这,一定表示LSE开启成功了,那么设置LSE作为RTC时钟
		RCC_RTCCLKCmd(ENABLE);//设置完RTC时钟以后,使能该时钟
		
		RTC_InitTypeDef RTC_InitStructure;
		
		RTC_InitStructure.RTC_AsynchPrediv=0x7F;//异步预分频系数 1~0x7F
		RTC_InitStructure.RTC_HourFormat=RTC_HourFormat_24; //24小时格式
		RTC_InitStructure.RTC_SynchPrediv=0xFF;//同步预分频系数 0~7FFF
		
		RTC_Init(&RTC_InitStructure);  //RTC_Init是库函数定义好的结构体,直接调用即可,也正因为如此,我们的初始化函数不可以设置为RTC_Init
		
		RTC_Set_Time(23,59,59,RTC_H12_AM);//根据上面自己设置的函数,设置时间,因为这个是第一次配置才会调用该程序,所以SetTime会供主函数GetTime调用
		RTC_Set_Date(23,1,21,6);  	
		
		RTC_WriteBackupRegister(RTC_BKP_DR0,0x5051);//程序能来到这里,表示上面的初始化已经完成了,并且是第一次配置的,这里将后备寄存器的状态位设置为0x5051
		//断电重启以后,不在重复执行该程序;0x5051对应if判断中设置的状态位;
		//在这里,如果想要每次重启以后都能执行上述时间,可以通过每次都设置新的状态位值来实现
	}
	return 0;//初始化成功
}

6. 实验程序

该实验的实验现象是在LCD屏上呈现日历;

仔细分析RTC框图,任何配置都是基于该概况图的;

STM32F4_RTC实时时钟_第21张图片

6.1 日历配置

//时间设置函数
//hour,min,sec 小时 分钟 秒
//ampm:可供选择的范围为 RTC_H12_AM/RTC_H12_PM
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)
{
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	
	RTC_TimeTypeInitStructure.RTC_H12=ampm;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	return RTC_SetTime(RTC_Format_BIN,&RTC_TimeTypeInitStructure);
	//参数设置相关时间,把建立的时间SetTime返回,供主函数GetTime获取
}

//日期设置函数
//year,month,date:年月日
//week:星期1~7有效 0非法
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)
{
	RTC_DateTypeDef RTC_DateTypeInitStructure;
	
	RTC_DateTypeInitStructure.RTC_Date=date;
	RTC_DateTypeInitStructure.RTC_Month=month;
	RTC_DateTypeInitStructure.RTC_WeekDay=week;
	RTC_DateTypeInitStructure.RTC_Year=year;
	
	return RTC_SetDate(RTC_Format_BIN,&RTC_DateTypeInitStructure);
}

6.2 闹钟配置

//设置闹钟时间
//week:星期1~7有效 
//hour,min,sec 小时 分钟秒
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)//STM32实时时钟具有2个可编程闹钟A和B,这里使用闹钟A,闹钟B只要设置相应寄存器的相关位即可
{
	RTC_AlarmCmd(RTC_Alarm_A,DISABLE);//关闭闹钟A
	
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	RTC_TimeTypeInitStructure.RTC_H12=RTC_H12_AM;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	RTC_AlarmTypeDef RTC_AlarmTypeInitStructure;
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDay=week;//星期
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDaySel=RTC_AlarmDateWeekDaySel_WeekDay;//按星期闹
	RTC_AlarmTypeInitStructure.RTC_AlarmMask=RTC_AlarmMask_None;//精确匹配到星期 时分秒
	RTC_AlarmTypeInitStructure.RTC_AlarmTime=RTC_TimeTypeInitStructure;
	RTC_SetAlarm(RTC_Format_BIN,RTC_Alarm_A,&RTC_AlarmTypeInitStructure);//设置闹钟参数
	
	RTC_ClearITPendingBit(RTC_IT_ALRA);//清除RTC闹钟A的标志
	EXTI_ClearITPendingBit(EXTI_Line17);//清除LINE17上的中断标志位
	
	RTC_ITConfig(RTC_IT_ALRA,ENABLE);//开启闹钟A中断
	RTC_AlarmCmd(RTC_Alarm_A,ENABLE);//开启闹钟A
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line17;//LINE17
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_Alarm_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//子优先级
	NVIC_Init(&NVIC_InitStructure);//配置中断优先级
}

//RTC闹钟中断
void RTC_Alarm_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_ALRAF)==SET)//闹钟A中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_ALRAF);//清除中断标志
		printf("helloworld!\r\n"); //一旦中断来了,在串口上打印helloworld
	}
	EXTI_ClearITPendingBit(EXTI_Line17);//清除中断线17的中断标志
}

6.3 周期性自动唤醒配置

//周期性唤醒定时器设备
//psr:预分频值,库函数配置的预分频值如下
//arr:自动重装载值,计数器CNT值减到0产生中断
//#define RTC_WakeUpClock_RTCCLK_Div16        ((uint32_t)0x00000000)
//#define RTC_WakeUpClock_RTCCLK_Div8         ((uint32_t)0x00000001)
//#define RTC_WakeUpClock_RTCCLK_Div4         ((uint32_t)0x00000002)
//#define RTC_WakeUpClock_RTCCLK_Div2         ((uint32_t)0x00000003)
//#define RTC_WakeUpClock_CK_SPRE_16bits      ((uint32_t)0x00000004)
//#define RTC_WakeUpClock_CK_SPRE_17bits      ((uint32_t)0x00000006)
void RTC_Set_WakeUp(u32 psr,u16 arr)
{
	RTC_WakeUpCmd(DISABLE);//关闭唤醒设备WakeUp
	
	RTC_WakeUpClockConfig(psr);//配置时钟分频系数
	RTC_SetWakeUpCounter(arr);//设置Wake Up自动重装载寄存器
	
	RTC_ClearITPendingBit(RTC_IT_WUT);//清除RTC Wake Up中断标志位
	EXTI_ClearITPendingBit(EXTI_Line22);//清除LINE22上中断标志位
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line22;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置相应的中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_WKUP_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;
	NVIC_Init(&NVIC_InitStructure);
}

//RTC WAKE UP中断服务函数
void RTC_WKUP_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//判断WK_UP中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_WUTF);//清空中断标志
		LED1=!LED1; //一旦进入中断,LED1闪烁
	}
	EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线22的中断标志 	
}

6.4 实验完整程序

6.4.1 main.c

#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "RTC.h"
#include "usmart.h"

//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
	LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
	led_set(sta);
}
int main(void)
{
	RTC_TimeTypeDef RTC_TimeStructure;
	RTC_DateTypeDef RTC_DateStructure;
	
	u8 tbuf[40];//设置一个数组存储时间,日期,打印数组即可
	u8 t=0;//作为时间计数作用,类似于Count
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级
	uart_init(115200); //设置串口
	usmart_dev.init(84);
	LED_Init();
	delay_init(168);
	LCD_Init();
	My_RTC_Init();
	
	RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0); //配置WAKE UP中断,1秒钟中断一次
	
	POINT_COLOR=RED;
	LCD_ShowString(30,50,200,16,16,"Hello");
	LCD_ShowString(30,70,200,16,16,"RTC TEST");
	LCD_ShowString(30,90,200,16,16,"Strive");
	LCD_ShowString(30,110,200,16,16,"2023/20/23");
	while(1)
	{
		t++;
		if((t%10)==0)//每100ms更新一次数据
		{
			RTC_GetTime(RTC_Format_BIN,&RTC_TimeStructure);//获取时间
			sprintf((char*)tbuf,"Time:%02d:%02d:%02d",RTC_TimeStructure.RTC_Hours,RTC_TimeStructure.RTC_Minutes,RTC_TimeStructure.RTC_Seconds);//打印时分秒
			LCD_ShowString(30,140,210,16,16,tbuf);//LCD显示出来,打印的结果
			
			RTC_GetDate(RTC_Format_BIN,&RTC_DateStructure);//获取日期
			sprintf((char*)tbuf,"Date:20%02d-%02d-%02d",RTC_DateStructure.RTC_Year,RTC_DateStructure.RTC_Month,RTC_DateStructure.RTC_Date);//打印年月日
			LCD_ShowString(30,160,210,16,16,tbuf);//LCD显示出来,打印的结果
			
			sprintf((char*)tbuf,"Week:%d",RTC_DateStructure.RTC_WeekDay);//打印日期
			LCD_ShowString(30,180,210,16,16,tbuf);//LCD显示出来,打印的结果
		}
		if((t%20)==0)//每200ms,LED0翻转一次
			LED0=!LED0;
		delay_ms(10);
	}
}


6.4.2 RTC.c

#include "stm32f4xx.h"                  
#include "RTC.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"

//时间设置函数
//hour,min,sec 小时 分钟 秒
//ampm:可供选择的范围为 RTC_H12_AM/RTC_H12_PM
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)
{
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	
	RTC_TimeTypeInitStructure.RTC_H12=ampm;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	return RTC_SetTime(RTC_Format_BIN,&RTC_TimeTypeInitStructure);
	//参数设置相关时间,把建立的时间SetTime返回,供主函数GetTime获取
}

//日期设置函数
//year,month,date:年月日
//week:星期1~7有效 0非法
//返回值:SUCCED(1)成功
//		  ERROR(0)失败
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)
{
	RTC_DateTypeDef RTC_DateTypeInitStructure;
	
	RTC_DateTypeInitStructure.RTC_Date=date;
	RTC_DateTypeInitStructure.RTC_Month=month;
	RTC_DateTypeInitStructure.RTC_WeekDay=week;
	RTC_DateTypeInitStructure.RTC_Year=year;
	
	return RTC_SetDate(RTC_Format_BIN,&RTC_DateTypeInitStructure);
}

//RTC初始化
//返回值:0初始化成功
//		  1 LSE开启失败
u8 My_RTC_Init(void)
{
	u16 retry=0x1FFF;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能PWR时钟
	PWR_BackupAccessCmd(ENABLE);//使能后备寄存器
	
	if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5050)//判断是否为第一次配置,这里的0x5051完全是自己设置的一个标志位
		//RTC_ReadBackupRegister叫做读取后备寄存器,RTC_BKP_DR0为寄存器的标志位,如果读取标志位不为我们设置的0x5051
	//则表示是第一次配置,执行下述程序;如果不是第一次配置,断电从启后会跳过该代码
	{
		RCC_LSEConfig(RCC_LSE_ON);//LSE开始,打开时钟
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)//while循环判断LSE开启的状态位是否置1
			//显然while循环离开的标志是获取的状态位RCC_FLAG_LSERDY等于1,离开也就代表这LSE开启成功
		{
			retry++;
			delay_ms(10);
		}
		if(retry==0)
			return 1;//LSE开启失败
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//既然程序没有返回1能来到这,一定表示LSE开启成功了,那么设置LSE作为RTC时钟
		RCC_RTCCLKCmd(ENABLE);//设置完RTC时钟以后,使能该时钟
		
		RTC_InitTypeDef RTC_InitStructure;
		
		RTC_InitStructure.RTC_AsynchPrediv=0x7F;//异步预分频系数 1~0x7F
		RTC_InitStructure.RTC_HourFormat=RTC_HourFormat_24; //24小时格式
		RTC_InitStructure.RTC_SynchPrediv=0xFF;//同步预分频系数 0~7FFF
		
		RTC_Init(&RTC_InitStructure);  //RTC_Init是库函数定义好的结构体,直接调用即可,也正因为如此,我们的初始化函数不可以设置为RTC_Init
		
		RTC_Set_Time(23,59,59,RTC_H12_AM);//根据上面自己设置的函数,设置时间,因为这个是第一次配置才会调用该程序,所以SetTime会供主函数GetTime调用
		RTC_Set_Date(23,1,21,7);  	
		
		RTC_WriteBackupRegister(RTC_BKP_DR0,0x5050);//程序能来到这里,表示上面的初始化已经完成了,并且是第一次配置的,这里将后备寄存器的状态位设置为0x5051
		//断电重启以后,不在重复执行该程序;0x5051对应if判断中设置的状态位;
		//在这里,如果想要每次重启以后都能执行上述时间,可以通过每次都设置新的状态位值来实现
	}
	return 0;//初始化成功
}

//设置闹钟时间
//week:星期1~7有效 
//hour,min,sec 小时 分钟秒
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)//STM32实时时钟具有2个可编程闹钟A和B,这里使用闹钟A,闹钟B只要设置相应寄存器的相关位即可
{
	RTC_AlarmCmd(RTC_Alarm_A,DISABLE);//关闭闹钟A
	
	RTC_TimeTypeDef RTC_TimeTypeInitStructure;
	RTC_TimeTypeInitStructure.RTC_H12=RTC_H12_AM;
	RTC_TimeTypeInitStructure.RTC_Hours=hour;
	RTC_TimeTypeInitStructure.RTC_Minutes=min;
	RTC_TimeTypeInitStructure.RTC_Seconds=sec;
	
	RTC_AlarmTypeDef RTC_AlarmTypeInitStructure;
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDay=week;//星期
	RTC_AlarmTypeInitStructure.RTC_AlarmDateWeekDaySel=RTC_AlarmDateWeekDaySel_WeekDay;//按星期闹
	RTC_AlarmTypeInitStructure.RTC_AlarmMask=RTC_AlarmMask_None;//精确匹配到星期 时分秒
	RTC_AlarmTypeInitStructure.RTC_AlarmTime=RTC_TimeTypeInitStructure;
	RTC_SetAlarm(RTC_Format_BIN,RTC_Alarm_A,&RTC_AlarmTypeInitStructure);//设置闹钟参数
	
	RTC_ClearITPendingBit(RTC_IT_ALRA);//清除RTC闹钟A的标志
	EXTI_ClearITPendingBit(EXTI_Line17);//清除LINE17上的中断标志位
	
	RTC_ITConfig(RTC_IT_ALRA,ENABLE);//开启闹钟A中断
	RTC_AlarmCmd(RTC_Alarm_A,ENABLE);//开启闹钟A
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line17;//LINE17
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;//使能
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_Alarm_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;//子优先级
	NVIC_Init(&NVIC_InitStructure);//配置中断优先级
}

//周期性唤醒定时器设备
//psr:预分频值,库函数配置的预分频值如下
//arr:自动重装载值,计数器CNT值减到0产生中断
//#define RTC_WakeUpClock_RTCCLK_Div16        ((uint32_t)0x00000000)
//#define RTC_WakeUpClock_RTCCLK_Div8         ((uint32_t)0x00000001)
//#define RTC_WakeUpClock_RTCCLK_Div4         ((uint32_t)0x00000002)
//#define RTC_WakeUpClock_RTCCLK_Div2         ((uint32_t)0x00000003)
//#define RTC_WakeUpClock_CK_SPRE_16bits      ((uint32_t)0x00000004)
//#define RTC_WakeUpClock_CK_SPRE_17bits      ((uint32_t)0x00000006)
void RTC_Set_WakeUp(u32 psr,u16 arr)
{
	RTC_WakeUpCmd(DISABLE);//关闭唤醒设备WakeUp
	
	RTC_WakeUpClockConfig(psr);//配置时钟分频系数
	RTC_SetWakeUpCounter(arr);//设置Wake Up自动重装载寄存器
	
	RTC_ClearITPendingBit(RTC_IT_WUT);//清除RTC Wake Up中断标志位
	EXTI_ClearITPendingBit(EXTI_Line22);//清除LINE22上中断标志位
	
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line22;
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;//中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);//配置相应的中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=RTC_WKUP_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x02;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x02;
	NVIC_Init(&NVIC_InitStructure);
}

//RTC闹钟中断
void RTC_Alarm_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_ALRAF)==SET)//闹钟A中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_ALRAF);//清除中断标志
		printf("helloworld!\r\n");
	}
	EXTI_ClearITPendingBit(EXTI_Line17);//清除中断线17的中断标志
}

//RTC WAKE UP中断服务函数
void RTC_WKUP_IRQHandler(void)
{
	if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//判断WK_UP中断状态位是否为1
	{
		RTC_ClearFlag(RTC_FLAG_WUTF);//清空中断标志
		LED1=!LED1;
	}
	EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线22的中断标志 	
}

6.4.3 RTC.h

#ifndef _RTC__H_
#define _RTC__H_


ErrorStatus RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm);
ErrorStatus RTC_Set_Date(u8 year,u8 month,u8 date,u8 week);
u8 My_RTC_Init(void);
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec);
void RTC_Set_WakeUp(u32 psr,u16 arr);
void RTC_Alarm_IRQHandler(void);
void RTC_WKUP_IRQHandler(void);

#endif

6.4.4 总结

        1. RTC.c中外部中断的闹钟事件和唤醒事件之所以要使用外部中断线17和22,是因为STM32支持22个外部中断/事件;每个中断都有状态位;具体可以看:STM32F4_外部中断详解(EXTI)_light_2025的博客-CSDN博客

        STM32F4的每个IO口都可以作为外部中断的中断输入口,这点也是STM32F4的强大之处(相比于51只有5个中断,2个外部中断、2个定时器中断、1个串口中断)STM32F4的中断控制器支持22个外部中断/事件请求。每个中断都有状态位,每个中断/事件都有独立的触发和屏蔽设置。

STM32F4的22个外部中断为:

EXTI线0-15:对应外部IO口的输入中断。

EXTI线16:连接到PVD输出。

EXTI线17:连接到RTC闹钟事件。

EXTI线18:连接到USB OTG FS唤醒事件。

EXTI线19:连接到以太网唤醒事件。

EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件。

EXTI线21:连接到RTC入侵和时间戳事件。

EXTI线22:连接到RTC唤醒事件。

        2. 如果发现download上述程序之后,开发板上只是显示了Time、Date、Week,而没有像时钟一样变化;那么可能是RTC的驱动文件的晶振与开发板对应的晶振不一致导致的;我在实验的过程中也遇到类似的问题;可以通过改变驱动文件中对应的晶振去改变;

        3. 上述实验程序只是通过RTC实时时钟显示了日历,闹钟和唤醒事件基本一致,只需要结合外部中断线在上述程序中相应的做出修改即可;通过外部中断服务函数即可证实闹钟和唤醒事件是否发生,有兴趣者可自行实验,这里不再给出相应的程序!

        4. 关于上述程序,如果还有其他问题,欢迎留言!一起学习,共同进步!

你可能感兴趣的:(STM32,stm32,单片机,嵌入式硬件)