当前位置: 首页 > article >正文

细说STM32F407单片机RTC的基本原理及闹钟和周期唤醒功能的使用方法

目录

一、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或
RTC AF2

HAL_RTCEx_TimeStampEventCallback()

入侵检测1

RTC_IT_TAMP1

RTC AF1


RTC AF2

HAL_RTCEx_Tamper1EventCallback()

入侵检测2

RTC_IT_TAMP2

RTC AF1


RTC AF2

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_
MONDAY到RTC_WEEKDAY_SUNDAY之间

IS_RTC_FORMAT(FORMAT)

宏函数,判断参数FORMAT是否为RTC_FORMAT_BIN或
RTC_FORMAT_BCD

IS_RTC_HOUR_FORMAT(FORMAT)

宏函数,判断参数FORMAT是否为RTC_HOURFORMAT_12
或RTC_HOURFORMAT_24

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,默认值为127
    uint32_t SynchPrediv;		//同步预分频器值,范围0x00~0x7FFFU,默认值为255
    uint32_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~31
    uint8_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~59
    uint8_t Seconds;		    //秒,范围0~59
    uint8_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_DISABLE_IT(&hrtc,RTC_ALARM_A)

__HAL_RTC_ALARM_ENABLE_IT()

允许闹钟A或闹钟B产生硬件中断,例如
__HAL_RTC_ALARM_ENABLE_IT(&hrtc,RTC_ALARM_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_DeactivateAlarm(&hrtc,RTC_ALARM_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的参考时钟源。

2RTC基本参数设置

        在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的初始时间为715分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两种选项。
选项Date表示用1~31日表示日期选择Weekday表示用Monday到Sunday表示星期几

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:ss
		sprintf(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、运行与调试

 

 


http://www.kler.cn/a/543957.html

相关文章:

  • 【Antv G2 5.x】饼图添加点击事件,获取当前坐标数据
  • mysql读写分离与proxysql的结合
  • 3.6 学习UVM中的uvm_sequencer类分为几步?
  • 活动预告 | 为 AI 新纪元做好准备:助力安全的业务转型
  • C++智能指针的使用
  • C# 属性的反射介绍
  • 在Windows 7操作系统,基于llama.cpp本地化部署 deepseek-r1模型的方法 2025-02-08
  • SQL-leetcode—1393. 股票的资本损益
  • 如何使用智能化RFID管控系统,对涉密物品进行安全有效的管理?
  • 计算机网络-MPLS基础概念
  • C++ ——基础进阶
  • Seaweedfs(master volume filer) docker run参数帮助文档
  • 性能优化中的服务器与操作系统优化
  • 华为云kubernetes基于keda自动伸缩deployment副本(监听redis队列长度)
  • 在亚马逊云科技上一键自动部署Falcon3大语言模型
  • 11.推荐系统的安全与隐私保护
  • 对gru的理解
  • 【C++八股】C++内存管理
  • 从360度全景照片到高质量3D场景:介绍SC-Omnigs 3D重建系统
  • redis持久化原理相关面试题剖析
  • 期权帮 | 股指期货交易:规则速览与解读!
  • SpringBoot速成(九)获取用户信息 P9-P10
  • git - 克隆带子模块的git工程的方法
  • windows蓝牙驱动开发-支持蓝牙事件通知
  • 为什么 ARCGIS PRO ArcGISIndexingServer.exe 使用大量计算机内存?
  • UGUI下UI元素的position和localPosition