目录
一、RTC基础知识
1、 RTC的功能
2、RTC工作原理
(1)RTC的时钟信号源
(2)预分频器
(3)实时时钟和日历数据
(4)周期性自动唤醒
(5)可编程闹钟
(6)时间戳
(7)入侵检测
(8)数字校准
(9)RTC参考时钟检测
3、RTC的中断和复用引脚
二、RTC的HAL基础驱动
1、RTC初始化
2、读取和设置日期
3、读取和设置时间
4、二进制数与BCD码之间的转换
5、判断函数
三、周期唤醒和闹钟
1、周期唤醒相关HAL函数
(1)宏函数
(2)周期唤醒定时器
(3)周期唤醒中断回调函数
2、闹钟相关HAL函数
四、案例:闹钟及周期唤醒功能的用法
1、工程简介
2、项目配置
(1)时钟、DEBUG、CodeGenerator
(2) RTC
1)RTC模式设置
2)RTC基本参数设置
3)闹钟定时设置
4)周期唤醒设置
(3)USART6
(4) NVIC
3、软件设计
(1)main.c
(2)rtc.c
4、运行与调试
实时时钟(Real-Time Clock,RTC)是由时钟信号驱动的日历时钟,提供日期和时间数据。STM32F407上有一个RTC,可以由备用电源VBAT供电,从而提供不间断的日期时间数据。它还有两个可编程闹钟,一个周期唤醒单元,使用RTC可以实现一些时间日历相关的应用。
一、RTC基础知识
1、 RTC的功能
STM32F407有一个片上RTC,它可以由内部或外部时钟信号驱动,提供日历时间数据,它内部维护一个日历,能自动确定每个月的天数,能自动处理闰年情况,还可以设定夏令时补偿。RTC能提供BCD或二进制的秒、分钟、小时(12或24小时制)、星期几、曰期、月份、年份数据,还可以提供二进制的亚秒数据。
RTC及其时钟都使用备用存储区域,而备用存储区域使用VBAT备用电源(一般用纽扣电池作为VBAT电源),所以即使主电源断电或系统复位也不影响RTC的工作。
RTC有两个可编程闹钟,可以设定任意组合和重复性的闹钟;有一个周期唤醒单元,可以作为一个普通定时器使用;还具有时间戳和入侵检测功能。
2、RTC工作原理
RTC的结构如下图所示,从几个方面对RTC的工作原理和特性进行说明。
(1)RTC的时钟信号源
从上图,RTC可以从3个时钟信号中选择一个作为RTC的时钟信号源。
- LSI,MCU内部的32kHz时钟信号。
- LSE,MCU外接的32.768kHz时钟信号。
- HSE_RTC,HSE经过2到31分频后的时钟信号。
如果MCU有外接的32.768kHz晶振,一般选择LSE作为RTC的时钟源,因为32.768kHz经过多次2分频后,可以得到精确的1Hz时钟信号。
(2)预分频器
RTC的时钟源信号经过精密校准后就是时钟信号RTCCLK,RTCCLK依次要经过一个7位的异步预分频器(最高为128分频)和一个15位的同步预分频器(默认为256分频)。
如果选用32.768kHz的LSE时钟源作为RTCCLK,经过异步预分频器128分频后的信号ck_apre是256Hz。256Hz的时钟信号再经过同步预分频器256分频后得到1Hz时钟信号ck_spre。这个1Hz信号可以用于更新日历,也可以作为周期唤醒单元的时钟源。
ck_apre和ck_spre经过一个选择器后,可以选择其中一个时钟信号作为RTC_CALIB时钟信号,这个时钟信号再经过输出控制选择,可以输出到复用引脚RTC_AF1,也就是可以向外部提供一个256Hz或1Hz的时钟信号。
(3)实时时钟和日历数据
上图中,有3个影子寄存器,RTC_SSR对应亚秒数据,RTC_TR对应于时间,RTC_DR对应于日期。影子寄存器就是内部亚秒计数器、日历时间计数器的数值暂存寄存器,系统每隔两个RTCCLK周期就将当前的日历值复制到影子寄存器。当程序读取日期时间数据时,读取的是影子寄存器的内容,而不会影响日历计数器的工作。
(4)周期性自动唤醒
RTC内有一个16位自动重载递减计数器,可以产生周期性的唤醒中断,16位寄存器RTC_WUTR存储用于设置定时周期的自动重载值。周期唤醒定时器的输入时钟有如下两个来源。
- 同步预分频器输出的ck_spre时钟信号,通常是1Hz。
- RTCCLK经过2、4、8或16分频后的时钟信号。
一般可以在周期性唤醒中断里读取RTC当前时间,例如,设置周期唤醒时钟源为1Hz的ck_spre信号,且每秒中断一次。唤醒中断产生事件信号WUTF,这个信号可以配置输出到复用引脚RTC_AF1。
(5)可编程闹钟
RTC有2个可编程闹钟,即闹钟A和闹钟B。闹钟的时间和重复方式是可以设置的,闹钟触发时可以产生事件信号ALRAF和ALRBF。这两个信号和周期唤醒事件信号WUTF一起经过一个选择器,可以选择其中一个信号作为输出信号RTC_ALARM,再通过输出控制可以输出到复用引脚RTC_AF1。
(6)时间戳
时间戳(timestamp)就是某个外部事件(上跳沿或下跳沿变化)发生时刻的日历时间,例如,行车记录仪在发生碰撞时保存的发生碰撞时刻的RTC日期时间数据就是时间戳。
启用RTC的时间戳功能,可以选择复用引脚RTC_AF1或RTC_AF2作为事件源RTC_TS,监测其上跳沿或下跳沿变化。当复用引脚上发生事件时,RTC就将当前的日期时间数据记录到时间戳寄存器,还会产生时间戳事件信号TSF,响应此事件中断就可以读取出时间戳寄存器的委据。如果检测到入侵事件,也可以记录时间戳数据。
(7)入侵检测
入侵检测(tamper[ˈtæmpər] detection)输入信号源有两个,即RTC_TAMP1和RTC_TAMP2,这两个信号源可以映射到复用引脚RTC_AF1和RTC_AF2。可以配置为边沿检测或带滤波的电平检测。STM32F407上有20个32位备份寄存器,这些备份寄存器是在备份域中的,由备用电源VBAT供电。在系统主电源关闭或复位时,备份寄存器的数据不会丢失,所以可以用于保存用户定义数据。当检测到入侵事件发生时,MCU就会复位这20个备份寄存器的内容。检测到入侵事件时,MCU会产生中断事件信号,同时还会记录时间戳数据。
(8)数字校准
RTC内部有粗略数字校准和精密数字校准。粗略数字校准需要使用异步预分频器的256Hz时钟信号,校准周期为64min。精密数字校准需要使用同步预分频器输出的1Hz时钟信号,默误模式下校准周期为32s。
用户可以选择256Hz或1Hz数字校准时钟信号作为校准时钟输出信号RTC_CALIB,通过输出控制可以输出到复用引脚RTC_AF1。
(9)RTC参考时钟检测
RTC的日历更新可以与一个参考时钟信号RTC_REFIN(通常为50Hz或60Hz)同步,RTC_REFIN使用引脚PB15。参考时钟信号RTC_REFIN的精度应该高于32.768kHz的LSE时钟。启用RTC_REFIN检测时,日历仍然由LSE提供时钟,而RTC_REFIN用于补偿不准确的日历更新频率。
3、RTC的中断和复用引脚
一般的外设只有一个中断号,一个中断号有多个中断事件源,例如,通用定时器,虽然有多个中断事件源,但只有一个中断号。但是RTC有3个中断号,每个中断号有对应的ISR,如下表所示:
中断号 | 中断名称 | 说明 | ISR |
2 | TAMP_STAMP | 连接到EXTI 21线的RTC入侵和时间戳中断 | TAMP_STAMP_IRQHandler() |
3 | RTC_WKUP | 连接到EXTI22线的RTC唤醒中断 | RTC_WKUP_IRQHandler() |
41 | RTC_Alarm | 连接到EXTI17线的RTC闹钟(A和B)中断 | RTC_Alarm_IRQHandler() |
RTC的这3个中断号各对应1个到3个中断事件源,例如,RTC_WKUP中断只有1个中断事件源,即周期唤醒中断事件,RTC_Alarm中断有2个中断事件源,即闹钟A中断事件和闹钟B中断事件,而TAMP_STAMP有3个中断事件源。
在HAL驱动程序中,每个中断事件都对应有表示中断事件类型的宏,每个中断事件对应一个回调函数。中断名称、中断事件类型和回调函数的对应关系如下表所示。基于这个表,在处理某个中断事件时,就只需重新实现其回调函数即可。
中断名称 | 中断事件源 | 中断事件类型 | 输出或输入 | 回调函数 |
RTC_Alarm | 闹钟A | RTC_IT_ALRA | RTC AF1 | HAL_RTC_AlarmAEventCallback() |
闹钟B | RTC_IT_ALRB | RTC AF1 | HAL_RTCEx_AlarmBEventCallback() | |
RTC_WKUP | 周期唤醒 | RTC_IT_WUT | RTC AF1 | HAL_RTCEx_WakeUpTimerEventCallback() |
TAMP_STAMP | 时间戳 | RTC_IT_TS | RTC_AF1或 | HAL_RTCEx_TimeStampEventCallback() |
入侵检测1 | RTC_IT_TAMP1 | RTC AF1 或 | HAL_RTCEx_Tamper1EventCallback() | |
入侵检测2 | RTC_IT_TAMP2 | RTC AF1 或 | HAL_RTCEx_Tamper2EventCallback() |
其中,“中断事件类型”一列中是HAL库中定义的宏,实际上是各中断事件在RTC控制寄存器(RTC_CR)中的中断使能控制位的掩码。这些中断事件类型的宏定义如下:
#define RTC_IT_TS 0x00008000U
#define RTC_IT_WUT 0x00004000U
#define RTC_IT_ALRB 0x00002000U
#define RTC_IT_ALRA 0x00001000U
#define RTC_IT_TAMP 0x00000004U /*仅用于使能Tamper中断*/
#define RTC_IT_TAMP1 0x00020000U
#define RTC_IT_TAMP2 0x00040000U
某些中断事件产生的信号可以选择输出到RTC的复用引脚,某些事件需要外部输入信号。其中,闹钟A、闹钟B和周期唤醒中断的信号可以选择输出到复用引脚RTC_AF1,时间戳事件检测一般使用RTC_AF1作为输入引脚,入侵检测可以使用RTC_AF1或RTC_AF2作为输入引脚。对于STM32F407xx,复用引脚RTC_AF1是引脚PC13,RTC_AF2是引脚PI8。只有176个引脚的MCU上才有PI8,所以,STM32F407ZG上没有RTC_AF2,只有RTC_AF1。
复用引脚除了可以作为闹钟A、闹钟B和周期唤醒中断信号的输出引脚外,还可以作为两个预分频器时钟的输出引脚,用于输出256Hz或1Hz的时钟信号。
二、RTC的HAL基础驱动
RTC的HAL驱动程序头文件有两个,即stm32f4xx_hal_rtc.h和stm32f4xx_hal_rtc_ex.h针对RTC、闹钟、周期唤醒、入侵检测分别有一组函数和定义。
RTC的一些通用基础函数如下表所示,包括RTC初始化函数、读取日期和时间的函数设置日期和时间的函数、BCD码与二进制之间转换的函数,以及一些判断函数。
函数名 | 功能描述 |
HAL_RTC_Init() | RTC初始化 |
HAL_RTC_MspInit() | RTC初始化的MSP弱函数,在HAL RTC_Init()中被调用。重新实现的这个函数一般用于RTC中断的设置 |
HAL_RTC_GetDate() | 获取RTC当前日期,返回的日期数据是RTC_DateTypeDef类型结构体 |
HAL_RTC_SetDate() | 设置RTC日期 |
HAL_RTC_GetTime() | 获取RTC当前时间,返回的时间数据是RTC_TimeTypeDef类型结构体 |
HAL_RTC_SetTime() | 设置RTC时间 |
HAL_RTC_GetState() | 返回RTC当前状态,状态是枚举类型HAL_RTCStateTypeDef |
RTC_Bcd2ToByte() | 2位BCD码转换为二进制数 |
RTC_ByteToBcd2() | 将二进制数转换为2位BCD码 |
IS_RTC_YEAR(YEAR) | 宏函数,判断参数YEAR是否小于100 |
IS_RTC_MONTH(MONTH) | 宏函数,判断参数MONTH是否在1和12之间 |
IS_RTC_DATE(DATE) | 宏函数,判断参数DATE是否在1和31之间 |
IS_RTC_WEEKDAY(WEEKDAY) | 宏函数,判断参数WEEKDAY是否在宏定义常量RTC_WEEKDAY_ |
IS_RTC_FORMAT(FORMAT) | 宏函数,判断参数FORMAT是否为RTC_FORMAT_BIN或 |
IS_RTC_HOUR_FORMAT(FORMAT) | 宏函数,判断参数FORMAT是否为RTC_HOURFORMAT_12 |
1、RTC初始化
RTC初始化函数进行RTC初始化的函数是HAL_RTC_Init(),其原型定义如下:
HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
其中,参数hrtc是RTC外设对象指针,是RTC_HandleTypeDef结构体类型指针。结构体RTC_HandleTypeDef的定义如下:
typedef struct
{RTC_TypeDef *Instance; //RTC寄存器基地址RTC_InitTypeDef Init; //RTC的参数HAL_LockTypeDef Lock; //RTC锁定对象__IO HAL_RTCStateTypeDef State; //时间通信状态
}RTC_HandleTypeDef;
其中的成员变量Init存储了RTC的各种参数,是RTC_InitTypeDef结构体类型,其原型定义如下:
typedef struct
{uint32_t HourFormat; //小时数据格式,12小时制或24小时制uint32_t AsynchPrediv; //异步预分频器值,范围0x00~0x7E,默认值为127uint32_t SynchPrediv; //同步预分频器值,范围0x00~0x7FFFU,默认值为255uint32_t OutPut; //哪个信号被作为RTC输出信号uint32_t OutPutPolarity; //输出信号的极性,信号有效时的电平uint32_t OutPutType; //输出引脚模式,开漏输出或推挽输出
}RTC_InitTypeDef;
其中,小时数据的格式取值可以用如下的宏定义常量:
#define RTC_HOURFORMAT_240x00000000U //24小时制
#define RTC_HOURFORMAT_120x00000040U //12小时制
2、读取和设置日期
读取RTC当前日期的函数是HAL_RTC_GetDate(),其原型定义如下:
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc,RTC_DateTypeDef *sDate,uint32_t Format);
返回的日期数据保存在RTC_DateTypeDef类型指针sDate指向的变量里,参数Format表示返回日期数据类型是BCD码或二进制码,可以用下面的宏定义常量作为Format的值。
#define RTC_FORMAT_BIN 0x00000000U //二进制格式
#define RTC_FORMAT_BCD 0x00000001U //BCD码格式
日期数据结构体RTC_DateTypeDef的定义如下:
typedef struct
{uint8_t WeekDay; //星期几,有表示星期几的宏定义uint8_t Month; //月份,有表示月份的宏定义uint8_t Date; //日期,范围1~31uint8_t Year; //年,范围0~99,表示2000~2099
}RTC_DateTypeDef;
设置日期的函数是HAL_RTC_SetDate(),其原型定义如下:
HAL_statusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc,RTC_DateTypeDef *sDate.uint32_t Format);
参数sDate是需要设置的日期数据指针,参数Format表示数据的格式是BCD码或二进制码。
3、读取和设置时间
读取时间的函数是HAL_RTC_GetTime(),其原型定义如下:
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime.uint32_t Format);
返回的时间数据保存在RTC_TimeTypeDef类型指针sTime指向的变量里,参数Format表示返回日期数据类型是BCD码或二进制码。
时间数据结构体RTC_TimeTypeDef的定义如下:
typedef struct
{uint8_t Hours; //小时,范围0~12(12小时制),或0~23(24小时制)uint8_t Minutes; //分钟,范围0~59uint8_t Seconds; //秒,范围0~59uint8_t TimeFormat; //时间格式,AM或PM显示uint32_t SubSeconds; //亚秒数据uint32_t SecondFraction; //秒的小数部分数据uint32_t DayLightSaving; //夏令时设置uint32_t StoreOperation; //存储操作定义
}RTC_TimeTypeDef;
一般情况下,只需关心时间的小时、分钟和秒数据,如果是12小时制,还需要看TimeFormat的值。AM/PM的取值使用如下的宏定义:
#define RTC_HOURFORMAT12_AM ((uint8_t)0x00) //表示AM
#define RTC_HOURFORMAT12_PM ((uint8_t)0x40) //表示PM
设置时间的函数是HAL_RTC_SetTime(),其原型定义如下:
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,RTC_TimeTypeDef *sTime,int32_t Format);
任何时候读取日期和读取时间的函数都必须成对使用,即使读出的日期或时间数据用不上。也就是说,调用HAL_RTC_GetTime()之后,必须调用HAL_RTC_GetDate(),否则不能连续更新日期和时间。因为调用HAL_RTC_GetTime()时会锁定日历影子寄存器的当前值,直到日期数据被读出后才会被解锁。
4、二进制数与BCD码之间的转换
读取和设置RTC的日期或时间数据时,可以指定数据格式为二进制或BCD码,二进制就是常规的数。BCD(Binary Coded Decimal)码是为便于BCD数码管显示用的编码,它用4位二进制数表示十进制中一个位的数,表示的范围是0~9,百位、十位、个位连续排列。
读取日期或时间的函数中有个Format参数,可以指定为二进制格式(RTC_FORMAT_BIN)或BCD码格式(RTC_FORMAT_BCD),这两种编码的数据之间可以通过HAL提供的两个函数进行转换。这两个函数的原型定义如下,这两个函数只能转换两位数字的数据。
uint8_t RTC_Bcd2TOByte(uint8_t Value) //两位BCD码转换为二进制数
uint8_t RTC_ByteTOBcd2(uint8_t Value) //二进制数转换为两位BCD码
5、判断函数
在文件stm32f4xx_hal_rtc.h中有一些以“IS_RTC_”为前缀的宏函数,这些宏函数主要用于判断参数是否在合理范围之内。部分典型的宏函数定义如下,全部的此类函数定义见文件m32f4xx_hal_rtc.h。
#define IS_RTC_YEAR(YEAR) ((YEAR)<=99U)
#define IS_RTC_MONTH(MONTH) (((MONTH)>=1U)&&((MONTH)<=12U))
#define IS_RTC_DATE(DATE) (((DATE)>=1U)&&((DATE)<=31U))
define IS_RTC_HOUR12(HOUR) (((HOUR)>0U)&&((HOUR)<=12U))
#define IS_RTC_HOUR24(HOUR) ((HOUR)<=23U)
#define IS_RTC_ASYNCH_PREDIV(PREDIV) ((PREDIV)<=0x7FU)
#define IS_RTC_SYNCH_PREDIV(PREDIV) ((PREDIV)<=0x7FFFU)
#define IS_RTC_MINUTES(MINUTES) ((MINUTES)<=59U)
#define IS_RTC_SECONDS(SECONDS) ((SECONDS)<=59U)
三、周期唤醒和闹钟
1、周期唤醒相关HAL函数
周期唤醒就是RTC的一种定时功能,一般为周期唤醒定时器设置1Hz时钟源,每秒或每隔几秒中断一次。使用RTC的周期唤醒功能,可以很方便地设置1s定时中断,与系统时钟频率无关,比用定时器设置1s中断要简单得多。
RTC周期唤醒中断的相关函数在文件stm32f4xx_hal_rtc_ex.h中定义,常用的函数如下表所示。
函数名 | 功能描述 |
__HAL_RTC_WAKEUPTIMER_ENABLE() | 开启RTC的周期唤醒单元 |
__HAL_RTC_WAKEUPTIMER_DISABLE() | 停止RTC的周期唤醒单元 |
__HAL_RTC_WAKEUPTIMER_ENABLE_IT() | 允许RTC周期唤醒事件产生硬件中断 |
__HAL_RTC_WAKEUPTIMER_DISABLE_IT() | 禁止RTC周期唤醒事件产生硬件中断 |
HAL_RTCEx_GetWakeUpTimer() | 获取周期唤醒计数器的当前计数值,返回值类型uint32_t |
HAL_RTCEx_SetWakeUpTimer() | 设置周期唤醒单元的计数周期和时钟信号源,不开启 |
HAL_RTCEx_SetWakeUpTimer_IT() | 设置周期唤醒单元的计数周期和时钟信号源,开启中断 |
HAL_RTCEx_DeactivateWakeUpTimer() | 停止RTC周期唤醒单元及其中断,停止后可用两个宏函数重新启动RTC周期唤醒单元及其中断 |
HAL_RTCEx_WakeUpTimerIRQHandler() | RTC周期唤醒中断的ISR里调用的通用处理函数 |
HAL_RTCEx_WakeUpTimerEventCallback() | RTC周期唤醒事件的回调函数 |
这些函数都要用到RTC外设对象指针。RTC初始化程序文件rtc.c定义了表示RTC的外设对象变量hrtc。
RTC_HandleTypeDef hrtc; //RTC外设对象变量
(1)宏函数
周期唤醒中断事件类型的定义是宏定义RTC_IT_WUT,定义如下:
#define RTC_IT_WUT 0x00004000U //周期唤醒中断事件类型
在允许或禁止RTC周期唤醒事件产生硬件中断的宏函数里,会用到这个宏定义,示例如下:
__HAL_RTC_WAKEUPTIMER_ENABLE_IT(&hrtc,RTC_IT_WUT); //允许RTC周期唤醒事件产生中断
__HAL_RTC_WAKEUPTIMER_DISABLE_IT(&hrtc,RTC_IT_WUT); //禁止RTC周期唤醒事件产生中断
(2)周期唤醒定时器
函数HAL_RTCEx_SetWakeUpTimer()设置周期唤醒定时器的定时周期数和时钟信号源,不开启周期唤醒中断,其原型定义如下:
HAL_StatusTypeDef HAL_RTCEx_SetWakeUpTimer(RTC_HandleTypeDef *hrtc,uint32_t WakeUpCounter,uint32_t WakeUpClock)
其中,参数WakeUpCounter是计数周期值,参数WakeUpClock是时钟信号源,可以使用一组宏定义表示的时钟信号源。
函数HAL_RTCEx_SetWakeUpTimer_IT()设置周期唤醒定时器的定时周期数和时钟信号源,并开启周期唤醒中断,函数参数形式与HAL_RTCEx_SetWakeUpTimer()的一样。这两个函数在CubeMX生成的RTC初始化函数代码里会被调用。
函数HAL_RTCEx_DeactivateWakeUpTimer()用于停止RTC周期唤醒单元及其中断,内部调用__HAL_RTC_WAKEUPTIMER_DISABLE()和__HAL_RTC_WAKEUPTIMER_DISABLE_IT()。
(3)周期唤醒中断回调函数
RTC的周期唤醒中断有独立的中断号,ISR是RTC_WKUP_IRQHandler()。在CubeMX中开启RTC的周期唤醒中断后,在文件stm32f4xx_t.c中自动生成周期唤醒中断的ISR,代码如下:
void RTC_WKUP_IRQHandler(void)
{HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}
其中,函数HAL_RTCEx_WakeUpTimerIRQHandler()是周期唤醒中断的通用处理函数,它内部会调用周期唤醒事件的回调函数HAL_RTCEx_WakeUpTimerEventCallback()。所以,用户要对周期唤醒中断进行处理,只需重新实现这个回调函数即可。
2、闹钟相关HAL函数
RTC有两个闹钟(某些型号MCU上只有一个),闹钟相关的函数和定义在文件stm32f4xx_rtc.h和stm32f4xx_hal_rtc_ex.h中定义。有些函数需要用一个变量区分是哪个闹钟,文件32f4xx_hal_rtc.h中有如下的宏定义区分闹钟A和闹钟B:
#define RTC_ALARM_ARTC_CR_ALRAE //表示闹钟A
#define RTC_ALARM_BRTC_CR_ALRBE //表示闹钟B
同样,假设在RTC初始化程序文件rtc.c中定义了表示RTC的外设对象变量hrtc。闹钟的相关常用函数如下表所示。
函数名 | 功能 |
__HAL_RTC_ALARM_DISABLE_IT() | 禁止闹钟A或闹钟B产生硬件中断,例如 |
__HAL_RTC_ALARM_ENABLE_IT() | 允许闹钟A或闹钟B产生硬件中断,例如 |
__HAL_RTC_ALARMA_DISABLE() | 关闭闹钟A模块,例如__HAL_RTC_ALARMA_DISABLE(&hrtc) |
__HAL_RTC_ALARMA_ENABLE() | 开启闹钟A模块,例如__HAL_RTC_ALARMA_ENABLE(&hrtc) |
__HAL_RTC_ALARMB_DISABLE() | 关闭闹钟B模块,例如__HAL_RTC_ALARMB_DISABLE(&hrtc) |
__HAL_RTC_ALARMB_ENABLE() | 开启闹钟B模块,例如__HAL_RTC_ALARMB_ENABLE(&hrtc) |
HAL_RTC_SetAlarm() | 设置闹钟A或闹钟B的闹钟参数,不开启闹钟中断 |
HAL_RTC_SetAlarm_IT() | 设置闹钟A或闹钟B的闹钟参数,开启闹钟中断 |
HAL_RTC_DeactivateAlarm() | 停止闹钟A或闹钟B,例如 |
HAL_RTC_GetAlarm() | 获取闹钟A或闹钟B的设定时间和掩码 |
HAL_RTC_AlarmIRQHandler() | 闹钟硬件中断ISR里调用的通用处理函数 |
HAL_RTC_AlarmAEventCallback() | 闹钟A中断事件的回调函数 |
HAL_RTCEx_AlarmBEventCallback() | 闹钟B中断事件的回调函数 |
函数HAL_RTC_SetAlarm()用于设置闹钟时间和掩码,函数HAL_RTC_GetAlarm()用于获取设置的闹钟时间和掩码,其参数类型与HAL_RTC_SetAlarm()的相同。
闹钟有一个中断号,ISR是RTC_Alarm_IRQHandler()。函数HAL_RTC_AlarmIRQHandler()是闹钟中断ISR里调用的通用处理函数,文件stm32f4xx_it.c中闹钟中断的ISR代码如下:
void RTC_Alarm_IRQHandler(void)
{HAL_RTC_AlarmIRQHandler(&hrtc);
}
函数HAL_RTC_AlarmIRQHandler()会根据闹钟事件来源,分别调用闹钟A的中断事件回调函数HAL_RTC_AlarmAEventCallback()或闹钟B的中断事件回调函数HAL_RTCEx_AlarmBEventCallback()。
四、案例:闹钟及周期唤醒功能的用法
1、工程简介
本实例使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0,使用闹钟A、闹钟B和周期唤醒功能,具有如下功能。
- 使用32.768kHz的LSE时钟作为RTC的时钟源。
- 系统复位时初始化RTC日期为2022-2-12,时间为10:10:10。
- 每秒唤醒一次,在周期唤醒中断里读取当前日期和时间,并在串口助手上显示。
- 将周期唤醒中断信号WUTF输出到复用引脚RTC_AF1(PC13),用杜邦线连接PC13和LED1的引脚PA6(在CubeMX中不要配置PA6引脚),使其为初姓复位状态,这样,LED1的亮灭由PC13的输出状态控制。
- 闹钟A设置为在时间xx:16:05触发,其中xx表示任意数字,即在每个小时的16分5秒时刻触发闹钟A。对闹钟A中断次数计数,并在串口助手上显示。
- 闹钟B设置为在时间xx:x:30触发,即在每分钟的30秒时刻触发闹钟B。对闹钟B中断次数计数,并在串口助手上显示。
2、项目配置
(1)时钟、DEBUG、CodeGenerator
HSE,外部晶振,HCLK=168MHz;
LSE,外部晶振,32.768kHz,LSE to RTC;
DEBUG,serial wire;
选中,CodeGenerator;
(2) RTC
1)RTC模式设置
启用时钟源和日历,Alarm A和Alarm B是闹钟A和闹钟B,它们旁边的下拉列表框里都有3个选项。
- Disable,禁用闹钟。
- Internal Alarm,内部闹钟功能。
- Routed to AF1,闹钟事件信号输出到复用引脚RTC_AF1,也就是引脚PC13。
WakeUp是周期唤醒功能,它旁边的下拉列表框里有3个选项。
- Disable,禁用周期唤醒功能。
- Internal WakeUp,内部周期唤醒。
- Routed to AF1,周期唤醒事件信号输出到复用引脚RTC_AF1。
本例,将闹钟A和闹钟B都设置为Internal Alarm,将周期唤醒WakeUp设置为Routed to AF1,也就是将周期唤醒事件信号输出到复用引脚RTC_AF1。
闹钟A、闹钟B和周期唤醒都可产生中断,且中断事件信号都可以输出到复用引脚RTC_AF1,但是只能选择其中一个信号输出到RTC_AF1。有一个信号占用RTC_AF1引脚后,其他使用RTC_AF1引脚的功能就不能使用了。(Timestamp Routed to AF1(时间戳)、Tamper1 Routed to AF1(入侵检测)、Calibration(校准时钟)等功能都用紫色底色标识,并且不能配置了。)
特别地,Reference clock detection是参考时钟检测功能,如果勾选此项,就会使引脚PB15作为RTC_REFIN引脚,这个引脚需要接一个50Hz或60Hz的精密时钟信号,用于对RTC日历的1Hz更新频率进行精确补偿。某些GPS模块可以配置输出025Hz至10MHz的时钟脉冲信号,在使用GPS模块的应用中,就可以配置GPS模块输出50Hz信号,作为RTC的参考时钟源。
2)RTC基本参数设置
在RTC的配置界面对RTC的参数进行设置。设置预分频器、初始日期和时间等基本参数。
General分组里有以下通用参数。
- Hour Format,小时格式,可以选择12小时制或24小时制。
- Asynchronous Predivider value,异步预分频器值。设置范围为0~127,对应分频系数是1~128。当RTCCLK为32.768kHz时,128分频后就是256Hz。
- Synchronous Predivider value,同步预分频器值。设置范围为0~32767,对应分频系数是1~32768。256分频后就是1Hz。
- Output Polarity,输出极性。闹钟A、闹钟B、周期唤醒中断事件信号有效时的输出极性,可设置为高电平或低电平。这里设置为低电平,因为配置周期唤醒模块的事件信号WUTF输出到复用引脚RTC_AF1,而RTC_AF1连接LED1的引脚PA6,当RTC_AF1输出为低电平时LED1点亮。
- Output Type,输出类型。复用引脚RTC_AF1的输出类型,可选开漏(Opendrain)输出或推挽(Pushpull)输出,这里设置为开漏输出。因为RTC_AF1和PA6都连接到LED1的同一个引脚上,设置为开漏输出更安全。
Calendar Time分组里有用于设置日历的时间参数和初始化数据的如下参数。
- Data Format,数据格式,可选择二进制格式或BCD格式,这里选择Binary data format。
- 初始化时间数据,包括时、分、秒的数据,这里设置为7时15分10秒。
- Day Light Saving:value of hour adjustment,夏令时设置,这里设置为DaylightsavingNone,即不使用夏令时。
- Store Operation,存储操作,表示是否已经对夏令时设置做修改。设置为StoreoperatioReset表示未修改夏令时,设置为Storeoperation Set表示已修改。用户可以在Calendar Date分组里设置日历初始化日期数据,包括年、月、日、星期几,其中,年的设置范围是0~99,表示2000~2099年。这里设置初始化日期为2025年2月12日,星期三。
3)闹钟定时设置
在RTC的模式设置里启用闹钟A和闹钟B后,就会在参数配置部分看到闹钟的设置。闹钟A和闹钟B的设置方法是完全一样的,闹钟的触发时间可以设置为日期(天或星期几)、时、分、秒、亚秒的任意组合,只需设置相应的日期时间和屏蔽即可。本例中,设置RTC的初始时间为7时15分10秒,闹钟A设置为在时间xx:16:05触发,即每个小时的16分5秒时刻触发。闹钟A的设置主要是闹钟日期时间设置和屏蔽设置,对应的闹钟参数的意义和设置如下表所示。
参数 | 意义 | 取值示例 | 数据范围 |
Hours | 时 | 8 | 0~23 |
Minutes | 分 | 16 | 0~59 |
Seconds | 秒 | 5 | 0~59 |
Sub Seconds | 亚秒 | 0 | 0~59 |
Alarm Mask Date Week day | 屏蔽日期 | Enable | 设置为Enable表示屏蔽,即闹钟与日期数据无关,设置为Disable表示日期数据参与比对 |
Alarm Mask Hours | 屏蔽小时 | Enable | 设置为Enable表示屏蔽,即闹钟与小时数据无关,设置为Disable表示小时数据参与比对 |
Alarm Mask Minutes | 屏蔽分钟 | Disable | 设置为Enable表示屏蔽,即闹钟与分钟数据无关,设置为Disable表示分钟数据参与比对 |
Alarm Mask Seconds | 屏蔽秒 | Disable | 设置为Enable表示屏蔽,即闹钟与秒数据无关,设置为Disable表示秒数据参与比对 |
Alarm Sub Second Mask | 屏蔽亚秒 | All Alarm SS fields are masked | 设置为All Alarm SS fields are masked表示屏蔽,闹钟与亚秒数据无关,设置为其他选项时,用于亚秒数据比对 |
Alarm Date Week Day Sel | 日期形式 | Date | 有Date和Weekday两种选项。 |
Alarm Date | 日 期 | 3 | 1~31或Monday到Sunday |
对于闹钟A,只有Alarm Mask Minutes和Alarm Mask Seconds设置为Disable,所以闹钟A的定时是xx:16:05,与小时、日期数据无关。
同样,对于闹钟B,只有Alarm Mask Seconds设置为Disable,所以闹钟B的定时是xx:xx:30,即每分钟的第30秒触发闹钟B。
4)周期唤醒设置
周期唤醒的参数设置如图11-7所示,只有两个参数需要设置。
Wake Up Clock,周期唤醒的时钟源。周期唤醒的时钟源可以来自同步预分频器的1Hz信号,也可以来自RTCCLK经过2、4、8、16分频的信号。若RTCCLK是32.768kHz,则这个参数各选项的意义如下。
- RTCCLK/16,16分频信号,即2.048kHz。
- RTCCLK/8,8分频信号,即4.096kHz。
- RTCCLK/4,4分频信号,即8.192kHz。
- RTCCLK/2,2分频信号,即16.384kHz。
- 1 Hz,来自ck_spre的1Hz信号。
- 1Hz with 1 bit added to Wake Up Counter,来自ck_spre的1Hz信号,将Wake Up Counter(唤醒计数器)的值加2¹⁶。
Wake Up Counter,唤醒计数器的重载值,设定值的范围是0~65535。表示周期唤醒计数器的计数值达到这个值时,就触发一次WakeUp中断。如果这个值设置为0,则每个时钟周期中断1次。例如,选择周期唤醒时钟源为1Hz信号时,若设置此值为0,则每1秒发生一次唤醒中断;若设置为1,则每2秒发生一次唤醒中断。
本例选择周期唤醒单元的时钟源为1Hz信号,唤醒计数器的重载值为0,所以每1秒会发生一次唤醒中断。在此中断处理程序里,读取RTC当前时间并在串口助手上显示,就可以看到时间是每秒刷新一次。
(3)USART6
使用管脚PG9、PG14,所有参数默认;
(4) NVIC
在NVIC组件的配置界面里设置RTC的中断。
3、软件设计
(1)main.c
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
// 菜单设计
/* USER CODE BEGIN 2 */printf("Demo11_1_RTC_Alarm:RTC and Alarm.\r\n");printf("Alarm A(xx:16:05) trigger: 0.\r\n");printf("Alarm B(xx:xx:30) trigger: 0.\r\n\r\n");
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
//串口打印
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END 4 */
main()函数在外设初始化部分调用了MX_RTC_Init(对RTC进行初始化,包括周期唤醒和钟的初始化,为RTC选择LSE作为时钟源是在函数SystemClock_Config()里实现的。RTC初始化完成后,就自动开启周期唤醒和闹钟功能。
在串口助手上显示几条菜单信息后,程序就进入了while死循环,此后,程序的运行由中断驱动。
(2)rtc.c
/* USER CODE BEGIN 0 */
#include <stdio.h>uint16_t triggerCntA=0; //闹钟A触发次数
uint16_t triggerCntB=0; //闹钟B触发次数
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
/* 周期唤醒中断回调函数 */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{RTC_TimeTypeDef sTime;RTC_DateTypeDef sDate;if (HAL_RTC_GetTime(hrtc,&sTime,RTC_FORMAT_BIN) == HAL_OK){HAL_RTC_GetDate(hrtc,&sDate,RTC_FORMAT_BIN);//调用HAL_RTC_GetTime()之后必须调用HAL_RTC_GetDate()以解锁数据,才能连续更新日期和时间//调用HAL_RTC_GetTime()时会将日历影子寄存器的当前值锁定,直到日期数据被读出//所以,即使不使用日期数据,也需要调用HAL_RTC_GetDate()读取//* @note You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values//* in the higher-order calendar shadow registers to ensure consistency between the time and date values.//* Reading RTC current time locks the values in calendar shadow registers until current date is read.//显示日期(年月日)char str[40];sprintf(str,"RTC Date= %4d-%2d-%2d",2000+sDate.Year,sDate.Month,sDate.Date);printf(" %s.\r\n",str);//显示时间hh:mm:sssprintf(str,"RTC Time = %2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);printf(" %s.\r\n",str);}
}/* 闹钟A中断的回调函数 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{triggerCntA++; //闹钟A触发计数printf("Alarm A(xx:16:05) trigger: %d\r\n",triggerCntA);
}/* 闹钟B中断的回调函数 */
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{triggerCntB++; //闹钟B触发计数printf("Alarm B(xx:xx:30) trigger: %d\r\n",triggerCntB);
}
/* USER CODE END 1 */
闹钟A和闹钟B共用EXTI线17中断,周期唤醒使用EXTI线22中断,在中断响应程序文件stm32f4xx_it.c自动生成了相应的ISR代码。要实现闹钟A、闹钟B、周期唤醒中断的处理,只需重新实现3个回调函数即可。
文件rtc.c里包含重新实现了这3个回调函数,重新实现的回调函数无须在头文件里声明函数原型。
在文件rtc.c中定义了两个uint16_t类型的全局变量,即triggerCntA和triggerCntB,用于记录闹钟A和闹钟B发生中断的次数。
周期唤醒中断的回调函数是HAL_RTCEx_WakeUpTimerEventCallback(),唤醒中断每秒触发一次。中断回调函数里用HAL_RTC_GetTime()读取RTC的当前时间,再使用HAL_RTC_GetDate()读取当前日期,然后在串口助手上显示。
调用HAL_RTC_GetTime()之后必须调用HAL_RTC_GetDate()以解锁数据,才能连续更新日期和时间。因为调用HAL_RTC_GetTime()时会锁定日历影子寄存器的当前值,直到日期数据被读出,所以即使不使用日期数据,也需要调用HAL_RTC_GetDate()读取日期数据。另外,因为还设置了将周期唤醒中断信号输出到复用引脚RTC_AF1(引脚PC13),并且用杜邦线连接了PC13与LED1的引脚PA6,所以程序运行时,可以看到LED1每秒会闪亮一下,这是因为RTC_AF1输出了一个低电平信号,只是这个信号持续时间比较短,LED1只是一闪即灭。
闹钟A的中断回调函数是HAL_RTC_AlarmAEventCallback()。闹钟A在xx:16:05时刻触发,中断回调函数在串口助手上显示信息,并且显示中断发生的次数。因RTC的初始时间设为7:15:10,因此很快就会到7:16:05,就会触发一次闹钟A中断,在串口助手上就会看到显示的闹钟A的中断信息。闹钟A实际上每隔一小时触发一次。
闹钟B的中断回调函数是HAL_RTCEx_AlarmBEventCallback()。闹钟B在xx:xx:30时刻触发,中断回调函数在串口助手上显示信息并且显示中断发生的次数。闹钟B触发周期是1分钟,可以在串口助手上看到闹钟B的中断次数每分钟增加一次。
4、运行与调试