第二十章 RTC实时时钟实验
1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0
前面我们介绍了两款液晶模块,这一章我们将介绍
STM32F4的内部实时时钟(
RTC)。在本章中,我们将使用
TFTLCD模块来显示日期和时间,实现一个简单的实时时钟,并可以设置闹铃。另外,本章将顺带向大家介绍
BKP的使用。本章分为如下几个部分:
20.1 STM32F4 RTC时钟简介
20.2 硬件设计
20.3 软件设计
20.4 下载验证
20.1 STM32F4 RTC时钟简介
STM32F4的实时时钟(
RTC)相对于
STM32F1来说,改进了不少,带了日历功能了,
STM32F4的
RTC,是一个独立的
BCD 定时器
/计数器。
RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(
ALARM A和
ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。
RTC 还包含用于管理低功耗模
式的自动唤醒单元。
两个
32位寄存器(
TR和
DR)包含二进码十进数格式
(BCD) 的秒、分钟、小时(
12或
24小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。
STM32F4的
RTC可以自动将月份的天数补偿为
28、
29(闰年)、
30 和
31 天。并且还可以进行夏令时
补偿。
RTC模块和时钟配置是在后备区域,即在系统复位或从待机模式唤醒后
RTC的设置和时间维持不变,只要后备区域供电正常,那么
RTC将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和
RTC,以防止对后备区域
(BKP)的意外写操作。所以在要设置时间之前,
先要取消备份区域(
BKP)写保护。
RTC的简化框图,如图
20.1.1所示:
图
20.1.1 RTC框图
此图可以从
STM32F4中文参考手册
RTC章节中找到(图
222:
RTC框图)。本章我们用到
RTC时钟和日历,并且用到闹钟功能。接下来简单介绍下
STM32F4 RTC时钟的使用。
1,时钟和分频
首先,我们看
STM32F4的
RTC时钟分频。
STM32F4的
RTC时钟源(
RTCCLK)通过时钟控制器,可以从
LSE时钟、
LSI时钟以及
HSE时钟三者中选择(通过
RCC_BDCR寄存器选择)。一般我们选择
LSE,即外部
32.768Khz晶振作为时钟源
(RTCCLK),而
RTC时钟核心,要求提供
1Hz的时钟,所以,我们要设置
RTC的可编程预分配器。
STM32F4的可编程预分配器(
RTC_PRER)分为
2个部分:
1, 一个通过
RTC_PRER寄存器的
PREDIV_A位配置的
7位异步预分频器。
2, 一个通过
RTC_PRER寄存器的
PREDIV_S位配置的
15位同步预分频器。
图
20.1.1中,
ck_spre的时钟可由如下计算公式计算:
Fck_spre=Frtcclk/[(PREDIV_S+1)*( PREDIV_A+1)]
其中,
Fck_spre即可用于更新日历时间等信息。
PREDIV_A和
PREDIV_S为
RTC的异步和同步分频器。且推荐设置
7位异步预分频器(
PREDIV_A)的值较大,以最大程度降低功耗。要设置为
32768分频,我们只需要设置:
PREDIV_A=0X7F,即
128分频;
PREDIV_S=0XFF,即
256分频,即可得到
1Hz的
Fck_spre。
另外,图
20.1.1中,
ck_apre可作为
RTC亚秒递减计数器(
RTC_SSR)的时钟,
Fck_apre的计算公式如下:
Fck_apre=Frtcclk/( PREDIV_A+1)
当
RTC_SSR寄存器递减到
0的时候,会使用
PREDIV_S的值重新装载
PREDIV_S。而
PREDIV_S一般为
255,这样,我们得到亚秒时间的精度是:
1/256秒,即
3.9ms左右,有了这个亚秒寄存器
RTC_SSR,就可以得到更加精确的时间数据。
2,日历时间(RTC_TR)和日期(RTC_DR)寄存器
STM32F4的
RTC日历时间(
RTC_TR)和日期(
RTC_DR)寄存器,用于存储时间和日期(也可以用于设置时间和日期),可以通过与
PCLK1(
APB1 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。
每隔
2个
RTCCLK周期,当前日历值便会复制到影子寄存器,并置位
RTC_ISR寄存器的
RSF位。我们可以读取
RTC_TR和
RTC_DR来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以
BCD码的格式存储的,读出来要转换一下,才可以得到十进制的数据。
3,可编程闹钟
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即可。
4,周期性自动唤醒
STM32F4的
RTC不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个
16位可编程自动重载递减计数器(
RTC_WUTR)生成,可用于周期性中断
/唤醒。
我们可以通过
RTC_CR寄存器中的
WUTE位设置使能此唤醒功能。
唤醒定时器的时钟输入可以是:
2、
4、
8或
16分频的
RTC时钟
(RTCCLK),也可以是
ck_spre时钟(一般为
1Hz)。
当选择
RTCCLK(假定
LSE是:
32.768 kHz)作为输入时钟时,可配置的唤醒中断周期介于
122us(因为
RTCCLK/2时,
RTC_WUTR不能设置为
0)和
32 s之间,分辨率最低为:
61us。
当选择
ck_spre(
1Hz)作为输入时钟时,可得到的唤醒时间为
1s到
36h左右,分辨率为
1 秒。并且这个
1s~36h的可编程时间范围分为两部分:
当
WUCKSEL[2:1]=10时为:
1s到
18h。
当
WUCKSEL[2:1]=11时约为:
18h到
36h。
在后一种情况下,会将
2^16添加到
16位计数器当前值(即扩展到
17位,相当于最高位用
WUCKSEL [1]代替)。
初始化完成后,定时器开始递减计数。在低功耗模式下使能唤醒功能时,递减计数保持有效。此外,当计数器计数到
0时,
RTC_ISR寄存器的
WUTF标志会置
1,并且唤醒寄存器会使用其重载值(
RTC_WUTR寄存器值)动重载,之后必须用软件清零
WUTF标志。
通过将
RTC_CR寄存器中的
WUTIE位置
1来使能周期性唤醒中断时,可以使
STM32F4退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒
STM32F4。
接下来,我们看看本章我们要用到的
RTC部分寄存器,首先是
RTC时间寄存器:
RTC_TR,该寄存器各位描述如图
20.1.2所示:
图
20.1.2 RTC_TR寄存器各位描述
这个寄存器比较简单,注意数据保存是
BCD格式的,读取之后需要稍加转换,才是十进制的时分秒等数据,在初始化模式下,对该寄存器进行写操作,可以设置时间。
然后看
RTC日期寄存器:
RTC_DR,该寄存器各位描述如图
20.1.3所示:
图
20.1.3 RTC_DR寄存器各位描述
同样,该寄存器的的数据采用
BCD码格式(如不熟悉
BCD,百度即可),其他的就比较简单了。同样,在初始化模式下,对该寄存器进行写操作,可以设置日期。
接下来,看
RTC亚秒寄存器:
RTC_SSR,该寄存器各位描述如图:
20.1.4所示:
图
20.1.4 RTC_SSR寄存器各位描述
该寄存器可用于获取更加精确的
RTC时间。不过,在本章没有用到,如果需要精确时间的地方,大家可以使用该寄存器。
接下来看
RTC控制寄存器:
RTC_CR,该寄存器各位描述如图
20.1.5所示:
图
20.1.5 RTC_CR寄存器各位描述
该寄存器我们不详细介绍每个位了,重点介绍几个要用到的:
WUTIE,
ALRAIE是唤醒定时器中断和闹钟
A中断使能位,本章要用到,设置为
1即可。
WUTE和
ALRAE,则是唤醒定时器和闹钟
A定时器使能位,同样设置为
1,开启。
FMT为小时格式选择位,我们设置为
0,选择
24小时制。最后
WUCKSEL[2:0],用于唤醒时钟选择,这个前面已经有介绍了,我们这里就不多说了,
RTC_CR寄存器的详细介绍,请看《
STM32F4xx中文参考手册》第
23.6.3节。
接下来看
RTC初始化和状态寄存器:
RTC_ISR,该寄存器各位描述如图
20.1.6所示:
图
20.1.6 RTC_ISR寄存器各位描述
该寄存器中,
WUTF、
ALRBF和
ALRAF,分别是唤醒定时器闹钟
B和闹钟
A的中断标志位,当对应事件产生时,这些标志位被置
1,如果设置了中断,则会进入中断服务函数,这些位通过软件写
0清除;
INIT为初始化模式控制位,要初始化
RTC时,必须先设置
INIT=1;
INITF为初始化标志位,当设置
INIT为
1以后,要等待
INITF为
1,才可以更新时间、日期和预分频寄存器等;
RSF位为寄存器同步标志,仅在该位为
1时,表示日历影子寄存器已同步,可以正确读取
RTC_TR/RTC_TR寄存器的值了;
WUTWF、
ALRBWF和
ALRAWF分别是唤醒定时器、闹钟
B和闹钟
A的写标志,只有在这些位为
1的时候,才可以更新对应的内容,比如:要设置闹钟
A的
ALRMAR和
ALRMASSR,则必须先等待
ALRAWF为
1,才可以设置。
接下来看
RTC预分频寄存器:
RTC_PRER,该寄存器各位描述如图
20.1.7所示:
图
20.1.7 RTC_PRER寄存器各位描述
该寄存器用于
RTC的分频,我们在之前也有讲过,这里就不多说了。该寄存器的配置,必须在初始化模式(
INITF=1)下,才可以进行。
接下来看
RTC唤醒定时器寄存器:
RTC_WUTR,该寄存器各位描述如图
20.1.8所示:
图
20.1.8 RTC_WUTR寄存器各位描述
该寄存器用于设置自动唤醒重装载值,可用于设置唤醒周期。该寄存器的配置,必须等待
RTC_ISR的
WUTWF为
1才可以进行。
接下来看
RTC闹钟
A器寄存器:
RTC_ALRMAR,该寄存器各位描述如图
20.1.9所示:
图
20.1.9 RTC_ALRMAR寄存器各位描述
该寄存器用于设置闹铃
A,当
WDSEL选择
1时,使用星期制闹铃,本章我们选择星期制闹铃。该寄存器的配置,必须等待
RTC_ISR的
ALRAWF为
1才可以进行。另外,还有
RTC_ALRMASSR寄存器,该寄存器我们这里就不再介绍了,大家参考《
STM32F4xx中文数据手册》第
23.6.19节。
接下来看
RTC写保护寄存器:
RTC_WPR,该寄存器比较简单,低八位有效。上电后,所有
RTC寄存器都受到写保护(
RTC_ISR[13:8]、
RTC_TAFCR和
RTC_BKPxR除外),必须依次写入:
0XCA、
0X53两关键字到
RTC_WPR寄存器,才可以解锁。写一个错误的关键字将再次激活
RTC的寄存器写保护。
接下来,我们介绍下
RTC备份寄存器:
RTC_BKPxR,该寄存器组总共有
20个,每个寄存器是
32位的,可以存储
80个字节的用户数据,这些寄存器在备份域中实现,可在
VDD电源关闭时通过
VBAT保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在
MCU从待机模式唤醒时复位。
复位后,对
RTC和
RTC备份寄存器的写访问被禁止,执行以下操作可以使能对
RTC及
RTC备份寄存器的写访问:
1)通过设置寄存器
RCC_APB1ENR的
PWREN位来打开电源接口时钟
2)电源控制寄存器
(PWR_CR)的
DBP位来使能对
RTC及
RTC备份寄存器的访问。
我们可以用
BKP来存储一些重要的数据,相当于一个
EEPROM,不过这个
EEPROM并不是真正的
EEPROM,而是需要电池来维持它的数据。
最后,我们还要介绍一下备份区域控制寄存器
RCC_BDCR。该寄存器的个位描述如图
20.1.10所示:
图
20.1.10 RCC_ BDCR寄存器各位描述
RTC的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在
RTC操作之前先要通过这个寄存器选择
RTC的时钟源,然后才能开始其他的操作。
RTC寄存器介绍就给大家介绍到这里了,我们下面来看看要经过哪几个步骤的配置才能使
RTC正常工作。接下来我们来看看通过库函数配置
RTC一般配置步骤。
RTC相关的库函数文件为
stm32f4xx_rtc.c以及头文件
stm32f4xx_rtc.h中:
1)使能电源时钟,并使能RTC及RTC后备寄存器写访问。
前面已经介绍了,我们要访问
RTC和
RTC备份区域就必须先使能电源时钟,然后使能
RTC即后备区域访问。电源时钟使能,通过
RCC_APB1ENR寄存器来设置;
RTC及
RTC备份寄存器的写访问,通过
PWR_CR寄存器的
DBP位设置。库函数设置方法为:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,
ENABLE);//使能
PWR时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
2)开启外部低速振荡器,选择RTC时钟,并使能。
这个步骤,只需要在
RTC初始化的时候执行一次即可,不需要每次上电都执行,这些操作都是通过
RCC_BDCR寄存器来实现的。
开启
LSE的库函数为:
RCC_LSEConfig(RCC_LSE_ON);//LSE 开启
同时,选择
RTC时钟源以及使能时钟函数为:
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设选择
LSE作为
RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能
RTC时钟
3)初始化RTC,设置RTC的分频,以及配置RTC参数。
在库函数中,初始化
RTC是通过函数
RTC_Init实现的:
ErrorStatus RTC_Init(RTC_InitTypeDef*
RTC_InitStruct);
同样按照以前的方式,我们来看看
RTC初始化参数结构体
RTC_InitTypeDef定义:
typedef struct
{
uint32_t
RTC_HourFormat;
uint32_t
RTC_AsynchPrediv;
uint32_t
RTC_SynchPrediv;
}RTC_InitTypeDef;
结构体一共只有三个成员变量,我们逐一来看看:
参数
RTC_HourFormat 用来设置
RTC的时间格式,也就是我们前面寄存器讲解的设置
CR寄存器的
FMT位。如果设置为
24小时格式参数值可选择
RTC_HourFormat_24,
12小时格式,参数值可以选择
RTC_HourFormat_24。
参数
RTC_AsynchPrediv用来设置
RTC的异步预分频系数,也就是设置
RTC_PRER寄存器的
PREDIV_A相关位。同时,因为异步预分频系数是
7位,所以最大值为
0x7F,不能超过这个值。
参数
RTC_SynchPrediv用来设置
RTC的同步预分频系数,也就是设置
RTC_PRER寄存器的
PREDIV_S相关位。同时,因为同步预分频系数也是
15位,所以最大值为
0x7FFF,不能超过这个值。
最后关于
RTC_Init函数我们还要指出,在设置
RTC相关参数之前,会先取消
RTC写保护,这个操作通过向寄存器
RTC_WPR写入
0XCA和
0X53两个数据实现。所以
RTC_Init函数体开头会有下面两行代码用来取消
RTC写保护:
RTC->WPR = 0xCA;
RTC->WPR = 0x53;
在取消写保护之后,我们要对
RTC_PRER、
RTC_TR和
RTC_DR等寄存器的写操作,必须先进入
RTC初始化模式,才可以进行,库函数中进入初始化模式的函数为:
ErrorStatus RTC_EnterInitMode(void);
进入初始化模式之后,
RTC_init函数才去设置
RTC->CR以及
RTC->RER寄存器的值。在设置完值之后,我们还要退出初始化模式,函数为:
void RTC_ExitInitMode(void)
最后再开启
RTC写保护,往
RTC_WPR寄存器写入值
0xFF即可。
4)设置RTC的时间。
库函数中,设置
RTC时间的函数为:
ErrorStatus RTC_SetTime(uint32_t RTC_Format,
RTC_TimeTypeDef* RTC_TimeStruct);
实际上,根据我们前面寄存器的讲解,
RTC_SetTime函数是用来设置时间寄存器
RTC_TR的相关位的值。
RTC_SetTime函数的第一个参数
RTC_Format,用来设置输入的时间格式为
BIN格式还是
BCD格式,可选值为
RTC_Format_BIN和
RTC_Format_BCD。因为
RTC_DR的数据必须是
BCD格式,所以如果您设置为
RTC_Format_BIN,那么在函数体内部会调用函数
RTC_ByteToBcd2将参数转换为
BCD格式。这里还是比较好理解的。
我们接下来看看第二个初始化参数结构体
RTC_TimeTypeDef的定义:
typedef struct
{
uint8_t
RTC_Hours;
uint8_t
RTC_Minutes;
uint8_t
RTC_Seconds;
uint8_t
RTC_H12;
}RTC_TimeTypeDef;
这四个的参数真的就比较好理解了,分别用来设置
RTC时间参数的小时,分钟,秒钟,以及
AM/PM符号,大家参考前面讲解的
RTC_TR的位描述即可。
5)设置RTC的日期。
设置
RTC的日期函数为:
ErrorStatus RTC_SetDate(uint32_t RTC_Format,
RTC_DateTypeDef* RTC_DateStruct);
实际上,根据我们前面寄存器的讲解,
RTC_SetDate设置日期函数是用来设置日期寄存器
RTC_DR的相关位的值。
第一个参数
RTC_Format,跟函数
RTC_SetTime的第一个入口参数是一样的,用来设置输入日期格式。
接下来我们看看第二个日期初始化参数结构体
RTC_DateTypeDef的定义:
typedef struct
{
uint8_t
RTC_WeekDay;
uint8_t
RTC_Month;
uint8_t
RTC_Date;
uint8_t
RTC_Year;
}RTC_DateTypeDef;
这四个参数也很好理解,分别用来设置日期的星期几,月份,日期,年份。这个大家可以参考我们前面讲解的
RTC_DR寄存器的位描述来理解。
1) 获取RTC当前日期和时间。
获取当前
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_TR寄存器和
RTC_DR寄存器的时间和日期的值,然后将值存放到相应的结构体中。
通过以上
6个步骤,我们就完成了对
RTC的配置,
RTC即可正常工作,而且这些操作不是每次上电都必须执行的,可以视情况而定。当然,我们还需要设置时间、日期、唤醒中断、闹钟等,这些将在后面介绍。
20.2 硬件设计
本实验用到的硬件资源有:
1) 指示灯
DS0
2) 串口
3) TFTLCD模块
4) RTC
前面
3个都介绍过了,而
RTC属于
STM32F4内部资源,其配置也是通过软件设置好就可以了。不过
RTC不能断电,否则数据就丢失了,我们如果想让时间在断电后还可以继续走,那么必须确保开发板的电池有电(
ALIENTEK探索者
STM32F4开发板标配是有电池的)。
20.3 软件设计
打开本章实验工程可以看到,我们先在
FWLIB下面引入了
RTC支持的库函数文件
stm32f4xx_rtc.c。然后我们在
HARDWARE文件夹下新建了一个
rtc.c的文件和
rtc.h的头文件,同时将这两个文件引入我们的工程
HARDWARE分组下。
由于篇幅所限,
rtc.c中的代码,我们不全部贴出了,这里针对几个重要的函数,进行简要说明,首先是
My_RTC_Init,其代码如下:
u8 My_RTC_Init(void)
{
RTC_InitTypeDef
RTC_InitStructure;
u16
retry=0X1FFF;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);//使能
PWR时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x5050)//是否第一次配置
?
{
RCC_LSEConfig(RCC_LSE_ON);//LSE 开启
while
(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
//检查指定的
RCC标志位设置与否
,等待低速晶振就绪
{ retry++;
delay_ms(10);
}
if(retry==0)return
1; //LSE 开启失败
.
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择
LSE作为
RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能
RTC时钟
RTC_InitStructure.RTC_AsynchPrediv = 0x7F;//RTC异步分频系数
(1~0X7F)
RTC_InitStructure.RTC_SynchPrediv
= 0xFF;//RTC同步分频系数
(0~7FFF)
RTC_InitStructure.RTC_HourFormat
= RTC_HourFormat_24;//24小时格式
RTC_Init(&RTC_InitStructure);//初始化
RTC参数
RTC_Set_Time(23,59,56,RTC_H12_AM); //设置时间
RTC_Set_Date(14,5,5,1); //设置日期
RTC_WriteBackupRegister(RTC_BKP_DR0,0x5050); //标记已经初始化过了
}
return
0;
}
该函数用来初始化
RTC配置以及日期和时钟,但是只在第一次的时候设置时间,以后如果重新上电
/复位都不会再进行时间设置了(前提是备份电池有电)。在第一次配置的时候,我们是按照上面介绍的
RTC初始化步骤来做的,这里就不在多说了。
这里设置时间和日期,分别是通过
RTC_Set_Time和
RTC_Set_Date函数来实现的,这两个函数实际就是调用库函数里面的
RTC_SetTime函数和
RTC_SetDate函数来实现,这里我们之所以要写两个这样的函数,目的是为了我们的
USMART来调用,方便直接通过
USMART来设置时间和日期。
这里默认将时间设置为
14年
5月
5日星期
1,
23点
59分
56秒。在设置好时间之后,我们调用函数
RTC_WriteBackupRegister向
RTC的
BKR寄存器(地址
0)写入标志字
0X5050,用于标记时间已经被设置了。这样,再次发生复位的时候,该函数通过调用函数
RTC_ReadBackupRegister判断
RTC对应
BKR地址的值,来决定是不是需要重新设置时间,如果不需要设置,则跳过时间设置,这样不会重复设置时间,使得我们设置的时间不会因复位或者断电而丢失。
这里我们来看看读备份区域和写备份区域寄存器的两个函数为:
uint32_t RTC_ReadBackupRegister(uint32_t
RTC_BKP_DR);
void RTC_WriteBackupRegister(uint32_t RTC_BKP_DR,
uint32_t Data);
这两个函数的使用方法就非常简单,分别用来读和写
BKR寄存器的值。这里我们只是略微点到为止。
接着,我们介绍一下
RTC_Set_AlarmA函数,该函数代码如下:
//设置闹钟时间
(按星期闹铃
,24小时制
)
//week:星期几
(1~7) @ref RTC_Alarm_Definitions
//hour,min,sec:小时
,分钟
,秒钟
void RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)
{
EXTI_InitTypeDef EXTI_InitStructure;
RTC_AlarmTypeDef
RTC_AlarmTypeInitStructure;
RTC_TimeTypeDef
RTC_TimeTypeInitStructure;
RTC_AlarmCmd(RTC_Alarm_A,DISABLE);//关闭闹钟
A
RTC_TimeTypeInitStructure.RTC_Hours=hour;//小时
RTC_TimeTypeInitStructure.RTC_Minutes=min;//分钟
RTC_TimeTypeInitStructure.RTC_Seconds=sec;//秒
RTC_TimeTypeInitStructure.RTC_H12=RTC_H12_AM;
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_InitStructure.EXTI_Line
= EXTI_Line17;//LINE17
EXTI_InitStructure.EXTI_Mode
= EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger =
EXTI_Trigger_Rising; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
LINE17
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel
= RTC_Alarm_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
= 0x02;//抢占优先级
1
NVIC_InitStructure.NVIC_IRQChannelSubPriority
= 0x02;//响应优先级
2
NVIC_InitStructure.NVIC_IRQChannelCmd =
ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
该函数用于设置闹钟
A,也就是设置
ALRMAR和
ALRMASSR寄存器的值,来设置闹钟时间,这里库函数中用来设置闹钟的函数为:
void RTC_SetAlarm(uint32_t RTC_Format, uint32_t
RTC_Alarm, RTC_AlarmTypeDef* RTC_AlarmStruct);
第一个参数
RTC_Format用来设置格式,这里前面我们讲解过,就不做过多讲解。
第二个参数
RTC_Alarm用来设置是闹钟
A还是闹钟
B,我们使用的是闹钟
A,所以值为
RTC_Alarm_A。
第三个参数就是我们用来设置闹钟参数的结构体指针。接下来我们看看
RTC_AlarmTypeDef结构体的定义:
typedef struct
{
RTC_TimeTypeDef RTC_AlarmTime;
uint32_t
RTC_AlarmMask;
uint32_t
RTC_AlarmDateWeekDaySel;
uint8_t
RTC_AlarmDateWeekDay;
}RTC_AlarmTypeDef;
结构体的第一个成员变量为
RTC_TimeTypeDef类型的成员变量
RTC_AlarmTime,这个是用来设置闹钟时间的,
RTC_TimeTypeDef结构体成员变量的含义我们在之前已经讲解,这里我们就不做过多讲解。
第二个参数
RTC_AlarmMask,使用来设置闹钟时间掩码,也就是在我们第一个参数设置的时间中(包括后面参数
RTC_AlarmDateWeekDay设置的星期几
/哪一天),哪些是无关的。 比如我们设置闹钟时间为每天的
10点
10分
10秒,那么我们可以选择值
RTC_AlarmMask_DateWeekDay,也就是我们不关心是星期几
/每月哪一天。这里我们选择为
RTC_AlarmMask_None,也就是精确匹配时间,所有的时分秒以及星期几
/(或者每月哪一天
)都要精确匹配。
第三个参数
RTC_AlarmDateWeekDaySel,用来选择是闹钟是按日期还是按星期。比如我们选择
RTC_AlarmDateWeekDaySel_WeekDay那么闹钟就是按星期。如果我们选择
RTC_AlarmDateWeekDaySel_Date那么闹钟就是按日期。这与后面第四个参数是有关联的,我们在后面第四个参数讲解。
第四个参数
RTC_AlarmDateWeekDay用来设置闹钟的日期或者星期几。比如我们第三个参数
RTC_AlarmDateWeekDaySel设置了值为
RTC_AlarmDateWeekDaySel_WeekDay,也就是按星期,那么参数
RTC_AlarmDateWeekDay的取值范围就为星期一
~星期天,也就是
RTC_Weekday_Monday~RTC_Weekday_Sunday。如果第三个参数
RTC_AlarmDateWeekDaySel设置值为
RTC_AlarmDateWeekDaySel_Date,那么它的取值范围就为日期值,
0~31。
调用函数
RTC_SetAlarm设置闹钟
A的参数之后,最后,开启闹钟
A中断(连接在外部中断线
17),并设置中断分组。当
RTC的时间和闹钟
A设置的时间完全匹配时,将产生闹钟中断。
接着,我们介绍一下
RTC_Set_WakeUp函数,该函数代码如下:
//周期性唤醒定时器设置
//wksel: @ref RTC_Wakeup_Timer_Definitions
//cnt:自动重装载值
.减到
0,产生中断
.
void RTC_Set_WakeUp(u32 wksel,u16 cnt)
{
EXTI_InitTypeDef EXTI_InitStructure;
RTC_WakeUpCmd(DISABLE);//关闭
WAKE UP
RTC_WakeUpClockConfig(wksel);//唤醒时钟选择
RTC_SetWakeUpCounter(cnt);//设置
WAKE UP自动重装载寄存器
RTC_ClearITPendingBit(RTC_IT_WUT);
//清除
RTC WAKE UP的标志
EXTI_ClearITPendingBit(EXTI_Line22);//清除
LINE22上的中断标志位
RTC_ITConfig(RTC_IT_WUT,ENABLE);//开启
WAKE UP 定时器中断
RTC_WakeUpCmd(
ENABLE);//开启
WAKE UP 定时器
EXTI_InitStructure.EXTI_Line
= EXTI_Line22;//LINE22
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
LINE22
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel
= RTC_WKUP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;//抢占优先级
1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//响应优先级
2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}
该函数用于设置
RTC周期性唤醒定时器,步骤同
RTC_Set_AlarmA级别一样,只是周期性唤醒中断,连接在外部中断线
22。
有了中断设置函数,就必定有中断服务函数,接下来看这两个中断的中断服务函数,代码如下:
//RTC闹钟中断服务函数
void RTC_Alarm_IRQHandler(void)
{
if(RTC_GetFlagStatus(RTC_FLAG_ALRAF)==SET)//ALARM
A中断
?
{ RTC_ClearFlag(RTC_FLAG_ALRAF);//清除中断标志
printf("ALARM
A!
");
}
EXTI_ClearITPendingBit(EXTI_Line17); //清除中断线
17的中断标志
}
//RTC WAKE UP中断服务函数
void RTC_WKUP_IRQHandler(void)
{
if(RTC_GetFlagStatus(RTC_FLAG_WUTF)==SET)//WK_UP中断
?
{
RTC_ClearFlag(RTC_FLAG_WUTF); //清除中断标志
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line22);//清除中断线
22的中断标志
}
其中,
RTC_Alarm_IRQHandler函数用于闹钟中断,该函数先判断中断类型,然后执行对应操作,每当闹钟
A闹铃时,会从串口打印一个:
ALARM A!的字符串。
RTC_WKUP_IRQHandler函数用于
RTC自动唤醒定时器中断,先判断中断类型,然后对
LED1取反操作,可以通过观察
LED1的状态来查看
RTC自动唤醒中断的情况。
rtc.c的其他程序,这里就不再介绍了,请大家直接看光盘的源码。
rtc.h头文件中主要是一些函数声明,我们就不多说了,有些函数在这里没有介绍,请大家参考本例程源码。
最后我们看看
main函数源码如下:
int main(void)
{
RTC_TimeTypeDef
RTC_TimeStruct;
RTC_DateTypeDef
RTC_DateStruct;
u8
tbuf[40];
u8
t=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组
2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为
115200
usmart_dev.init(84);
//初始化
USMART
LED_Init(); //初始化
LED
LCD_Init(); //初始化
LCD
My_RTC_Init(); //初始化
RTC
RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0);//WAKE
UP每秒一次中
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer
STM32F4");
LCD_ShowString(30,70,200,16,16,"RTC
TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/5/5");
while(1)
{
t++;
if((t%10)==0) //每
100ms更新一次显示数据
{
RTC_GetTime(RTC_Format_BIN,&RTC_TimeStruct);
sprintf((char*)tbuf,"Time:%02d:%02d:%02d",RTC_TimeStruct.RTC_Hours,
RTC_TimeStruct.RTC_Minutes,RTC_TimeStruct.RTC_Seconds);
LCD_ShowString(30,140,210,16,16,tbuf);
RTC_GetDate(RTC_Format_BIN,
&RTC_DateStruct);
sprintf((char*)tbuf,"Date:20%02d-%02d-%02d",RTC_DateStruct.RTC_Year,
RTC_DateStruct.RTC_Month,RTC_DateStruct.RTC_Date);
LCD_ShowString(30,160,210,16,16,tbuf);
sprintf((char*)tbuf,"Week:%d",RTC_DateStruct.RTC_WeekDay);
LCD_ShowString(30,180,210,16,16,tbuf);
}
if((t%20)==0)LED0=!LED0; //每
200ms,翻转一次
LED0
delay_ms(10);
}
}
这部分代码,也比较简单,注意,我们通过
RTC_Set_WakeUp(RTC_WakeUpClock_CK_SPRE_16bits,0);设置
RTC周期性自动唤醒周期为
1秒钟,类似于
STM32F1的秒钟中断。然后,在
main函数不断的读取
RTC的时间和日期(每
100ms一次),并显示在
LCD上面。
为了方便设置时间,我们在
usmart_config.c里面,修改
usmart_nametab如下:
struct _m_usmart_nametab usmart_nametab[]=
{
#if USMART_USE_WRFUNS==1 //如果使能了读写操作
(void*)read_addr,"u32
read_addr(u32 addr)",
(void*)write_addr,"void
write_addr(u32 addr,u32 val)",
#endif
(void*)RTC_Set_Time,"u8
RTC_Set_Time(u8 hour,u8 min,u8 sec,u8 ampm)", (void*)RTC_Set_Date,"u8
RTC_Set_Date(u8 year,u8 month,u8 date,u8 week)", (void*)RTC_Set_AlarmA,"void
RTC_Set_AlarmA(u8 week,u8 hour,u8 min,u8 sec)", (void*)RTC_Set_WakeUp,"void
RTC_Set_WakeUp(u8 wksel,u16 cnt)",
};
将
RTC的一些相关函数加入了
usmart,这样通过串口就可以直接设置
RTC时间、日期、闹钟
A、周期性唤醒和备份寄存器读写等操作。
至此,
RTC实时时钟的软件设计就完成了,接下来就让我们来检验一下,我们的程序是否正确了。
20.4 下载验证
将程序下载到探索者
STM32F4开发板后,可以看到
DS0不停的闪烁,提示程序已经在运行了。同时可以看到
TFTLCD模块开始显示时间,实际显示效果如图
20.4.1所示:
图
20.4.1 RTC实验测试图
如果时间和日期不正确,可以利用上一章介绍的
usmart工具,通过串口来设置,并且可以设置闹钟时间等,如图
20.4.2所示:
图
20.4.2 通过
USMART设置时间和日期并测试闹钟
A
可以看到,设置闹钟
A后,串口返回了
ALARM A!字符串,说明我们的闹钟
A代码正常运行了!
实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
/* Get the current Time */
RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure0);
/* Get the current Date */
RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
即可,
我以为在这两个函数中间可能会发生值的变化,所以放在了一个循环中,然后连续两次时间一样才算读出的数据是正确的并退出循环
RTC_DateTypeDef RTC_DateStructure;
RTC_TimeTypeDef RTC_TimeStructure0, RTC_TimeStructure1;
while(1){
/* Get the current Time */
RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure0);
/* Get the current Date */
RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
/* Get the current Time */
RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure1);
if(memcmp(&RTC_TimeStructure0, &RTC_TimeStructure1, sizeof(RTC_TimeTypeDef))==0)
break;
}
在网上找到:
因此STM32设计:用户读取“时分秒”寄存器时,会硬件锁定“年月日”寄存器的值。这样保持时刻的一致性。
看来我想多了
//检查指定的RCC标志位设置与否,等待低速晶振就绪
{ retry++;
delay_ms(10);
}
if(retry==0)return 1; //LSE 开启失败.
z这里他怎么跳出来循环 如果外部rtc不起振的话?
一周热门 更多>