目录
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 总结
实时时钟RTC是一个独立的BCD定时器 / 计数器。RTC提供一个日历时钟、两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。
两个32位寄存器包含二进码十进数格式(BCD)的秒、分钟、小时(12或24小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。同时系统可以自动将月份的天数补偿为28、29(闰年)、30和31天。并且还可以进行夏令时补偿。其他32位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。
此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。
注意:无论器件是处于运行模式、低功耗模式或者复位模式,只要电源电压保持在正常工作范围,RTC就不会停止工作。
时钟和预分频器:
实时时钟和日历:
STM32F4的RTC日历时间(RTC_TR)和日期(RTC_DR)寄存器,用于存储时间和日期,也可以用于设置时间和日期,可以通过与PCLK1同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问。
每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前的时间和日期信息。
注意:时间和日期都是以BCD码的格式存储的,读出来时需要转换为10进制数据,中间存在一个转换关系。
可编程闹钟:
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即可。
周期性自动唤醒:
周期性唤醒功能,由一个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 标志;
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 以再次使能定时器。唤醒定时器重新开始递减 计数。
所有的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时间戳事件。
时间寄存器:RTC_TR(RTC time register) RTC_TR是日历时间影子寄存器。只能在初始化模式下对该寄存器执行写操作。
注意:该寄存器下数据保存是BCD格式的,读取之后需要进行转换(这也是STM32的优越性,51单片机必须手动转换成BCD码;而STM32单片机只需要通过该寄存器就可以完成二进制到BCD码的转换),才是10进制的时分秒数据,在初始化模式下,对该寄存器进行写操作,可以设置时间。
日期寄存器:RTC_DR(RTC data register)RTC_DR是日历日期影子寄存器。只能在初始化模式下对该寄存器执行写操作。
注意:该寄存器的数据同样采用BCD码格式,在初始化模式下,对该寄存器进行写操作,可以设置日期。
亚秒寄存器:RTC_SSR(RTC sub second register)
很显然,该寄存器用于设置更精确的时间。
控制寄存器:RTC_CR(RTC control register) 下图为配置闹钟A,配置闹钟B原理一样,只需要配置相应的位A-->B即可。
RTC初始化和状态寄存器:RTC_ISR(RTC initialization and status register)
预分频寄存器:RTC_PRER(RTC prescaler register)
注意: 该寄存器必须在初始化模式下INITF=1下,才可以进行。
唤醒定时器寄存器:RTC_WUTR(RTC wakeup timer register)
注意: 该寄存器必须在RTC_ISR的WUTWF=1下,才可以进行。
闹钟A寄存器:RTC_ALRMAR(RTC alarm A register)
该寄存器用于设置闹钟A,当位30 WDSEL选择1时,使用星期制闹钟;
该寄存器的配置,必须等待RTC_ISR的ALRAWF为 1 才可以进行。
写保护寄存器:RTC_WPR(RTC write protection register)
写保护寄存器的低八位有效。上电后,除了RTC_ISR[13:8]、RTC_TAFCR和RTC_BKPxR寄存器,必须依次写入:0xCA、0x53两个关键字到写保护寄存器RTC_WPR,才可以解锁。写一个错误的关键字将再次激活RTC的寄存器写保护。
备份寄存器:RTC_BKPxR(RTC backup registers)(很重要)
该寄存器组总共有20个,每个寄存器都是32位,可以存储80个字节的用户数据,这些寄存器在备份域中实现,可在VDD电源关闭时通过VBAT保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在MCU从待机模式唤醒时复位。
我们可以用BKP来存储一些重要信息,相当于一个EEPROM(类似于51上的记忆存储芯片),不过这个EEPROM需要电池来维持他的数据。
RTC的时钟源选择及使能设置是通过这个寄存器来实现的,所以我们在RTC操作之前先要通过这个寄存器选择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;//初始化成功
}
该实验的实验现象是在LCD屏上呈现日历;
仔细分析RTC框图,任何配置都是基于该概况图的;
//时间设置函数
//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);
}
//设置闹钟时间
//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的中断标志
}
//周期性唤醒定时器设备
//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的中断标志
}
#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);
}
}
#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的中断标志
}
#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
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. 关于上述程序,如果还有其他问题,欢迎留言!一起学习,共同进步!