RTC的功能有很多,这里主要介绍RTC的周期唤醒和闹钟功能。
本篇博客先阐述RTC的概念以及周期唤醒和闹钟的原理,并对STM32CubeMX的配置参数进行阐述,最后结合LED与BEEP进行效果演示。
最终效果:
每分钟的第5秒翻转LED0状态.
每分钟的第30秒翻转BEEP的状态。
每秒翻转LED1状态。
RTC(Real-Time Clock),实时时钟。它是由时钟信号驱动的日历时钟,提供日期和时间数据。在STM32F407ZET6里面,有一个RTC,由普中的开发板后面的电源VBAT供电,这个的好处在于它可以提供不间断的日期时间数据。不像我们写程序配置的时间,一般我们关闭了开发板的电源就丢失了。这个RTC其实在我使用中觉得最有用的大概就是使用ssh连接linux系统的时候,往里面传文件,可以看到文件的修改日期。如果没有这个RTC,传输进嵌入式系统的文件的修改日期大概率就是系统最初始的时间,比如1970-1-1。
RTC有两个可编程的闹钟,库函数中就是AlarmA和AlarmB。可以设定任意组合和重复性闹钟:有一个周期唤醒单元,可以作为一个普通定时器使用;还具有时间戳和入侵检测功能。这篇博客就写的是前半部分,后面会再写一篇后续的功能。
我们RTC的时钟是哪里来的?打开看STM32CubeMX看一下时钟配置就知道了
通过上图可以看到RTC的时钟来源有三个:
LSI:
MCU内部的32kHz时钟信号。
LSE:
MCU外接的32.768kHz时钟信号。
HSE_RTC:
MCU外接的高速晶振HSE经过2到31分频后的时钟信号。
在实际的时钟源选择上,我们一般选择LSE。因为外接的32.768kHz的时钟,经过多次二分频后就可以得到精确的1Hz的时钟信号,转成时间就是1s一个周期。
STM32CubeMx只给出RTC的时钟来源,RTC的具体框图如下。
要想实现输出不同频率的时钟,就取决于预分频器的系数。对于给定的初始RTC时钟源信号,只有经过预分频器的分频,才可以在指定引脚输出我们想要的频率的时钟。
在上图中,以32.768kHz的LSE时钟源为例,RTC的时钟源经过精密校准后,通过异步分频器128分频,得到ck_apre为256Hz。256Hz的时钟信号经过同步分频256分频,得到1Hz的ck_spre信号。
ck_apre和ck_spre经过一个选择器后,可以选择其中一个时钟信号作为RTC_CALIB时钟信号,这个时钟信号经过输出控制选择,可以输出到复用引脚RTC_AF1,也就是可以向外部提供一个256Hz(异步分频)或1Hz(异步+同步分频)的时钟信号。
上图RTC的总框图中有两个暗部的影子寄存器
RTC_SSR:
亚秒计数器
RTC_TR:
时间计数器
RTC_DR:
日期计数器
系统每隔两个RTCCLK周期就讲当前的日历值复制到影子寄存器中。当程序读取日期时间数据时,读取的是影子寄存器的内容,而不会影响日历计数器的工作。
本质上我认为影子寄存器就是用来做一个数据备份,主要备份的就是亚秒,时间,日期。
我在写定时器TIM的相关博客的时候写到过一个自动重装载值。比如我将该值设定为10,那么计数器到10就触发一次事件,同时计数器从0开始计数。这个周期自动唤醒的功能也类似。
RTC内有一个16位自动重载递减计数器,可以产生周期性的唤醒中断,16位寄存器RTC_WURT存储用于设置定时周期的自动重载值。周期唤醒定时器的输入时钟有两个来源:
1.同步预分频器输出的clk_spre时钟信号,通常是1Hz
2.RTCCLK经过2、4、8、16分频后的时钟信号(结合一下RTC的原理框图)
一般可以在周期性唤醒中断里读取RTC当前时间。例如,设置周期唤醒时钟源为1Hz的ck_spre信号,并且每秒中断一次。唤醒中断产生时间信号WUTF,这个信号可以配置到复用引脚RTC_AF1。
在STM32F407ZET6中有两个可编程闹钟,闹钟A,闹钟B。
闹钟的时间和重复方式可以自定义,后面STM32CubeMX配置的时候阐述。闹钟触发的时候闹钟A,B分别产生事件信号ALRAF和ALRBF。这两个信号和周期唤醒事件WUTF一起经过一个选择器,可以选择一个信号作为输出信号RTC_ALARM,再通过输出控制可以输出到复用引脚RTC_AF1。
我这里只介绍用到的时钟周期唤醒以及闹钟对应的中断名称和ISR。
中断号 | 中断名称 | 说明 | ISR |
---|---|---|---|
3 | RTC_WKUP | 连接到EXTI 22线的RTC唤醒中断 | RTC_WKUP_IRQHandler() |
41 | RTC_Alarm | 连接到EXTI 17线的RTC闹钟(A和B)中断 | RTC_Alarm_IRQHandler() |
两个中断可以触发的中断事件以及对应的回调函数如下表。
中断名称 | 中断事件源 | 中断事件类型 | 输出或输引脚 | 回调函数 |
---|---|---|---|---|
RTC_Alarm | 闹钟A | RTC_IT_ALRA | RTC_AF1 | HAL_RTC_AlarmAEventCallback() |
RTC_Alarm | 闹钟B | RTC_IT_ALRB | RTC_AF1 | HAL_RTCEx_AlarmBEventCallback() |
RTC_WKUP | 周期唤醒 | RTC_IT_WUT | RTC_AF1 | HAL_RTCEx_WakeUpTimerEventCallback() |
对于STM32F407ZET6,复用引脚RTC_AF1是引脚PC13,RTC_AF2是引脚PI8。但是只有178个引脚的MCU才有PI8,所以普中这款STM32F407开发板上面是没有RTC_AF2只有RTC_AF1。
写到这里,其实我们大概已经知道怎么做了。对于最终效果:
每分钟的第5秒翻转LED状态.
每分钟的第30秒翻转BEEP的状态。
每秒翻转LED1状态。
我们只需要在调用中断函数的时候翻转一下电平就可以了。
现在的问题是什么时候调用中断,怎么判定设置闹钟以及周期唤醒。
下面我们用STM32CubeMX来详细阐述一下。
刚才讲到RTC的1Hz的分频,最好选择的是时钟源是外部的低速晶振。
这里我们不涉及到时间戳等RTC其他功能,只考虑我们用到的周期唤醒和闹钟。
对于Timers模块,我们需要启用时钟源以及日历。
Disable:
禁用
Internal Alarm:
内部闹钟功能
Routed to AF1:
闹钟事件信号输出到复用引脚RTC_AF1。
Disable:
禁用
Internal WakeUp:
内部唤醒功能
Routed to AF1:
闹钟事件信号输出到复用引脚RTC_AF1。
这里注意,Routed to AF1这个选项,有且只能有一个支持配置。也就是说AlarmA ,AalarmB,WakeUp三种唤醒方式只能支持一种配置到AF1。一旦AF1被占用了,那么其他的唤醒方式不能配置输出到AF1。
General
Hour Format:
小时格式,可选12h制或者24小时制。
Asynchronous Predivider value:
异步分频器值。设置值为0~127,对应分频系数1-128。当RTCCLK为32.768kHz时,128分频后就是256Hz。
Synchronous Predivider value:
同步预分频器。设置值为0~32767,对应分频系数为1-32768。256Hz分频后就是1Hz。
Output Polarity:
输出极性。闹钟A,B以及周期唤醒中断事件信号有效时的输出极性,可设置为高电平或低电平。
Output Polarity
:输出类型。复用引脚RTC_AF1的输出类型,可选开漏输出或者推挽输出。
Calendar Time
Data Format:
数据格式。可选择二进制格式或者BCD格式。
Hours:
初始化时间数据的时。
Minutes:
初始化时间数据的分。
Seconds:
初始化时间数据的秒。
Day Light Saving: value of hour adjustment:
夏令时设置。这里设置为不使用夏令时。啥叫夏令时?
Store Operation:
存储操作。表示是否已经对夏令时设置做修改。设置为Reset表示未修改夏令时。Set则表示已修改。
Calendar Date
这个分组主要设置的是初始化的日期。
Week Day:
周几
Month:
几月
Date:
几日
Year:
几年
因为AlarmB和AlarmA的设置是一样的,我这里方便截图,就直接用AlarmB做解释了。
AlarmA/B
Hours:
时 0-23
Minutes:
分 0-59
Seconds:
秒 0-59
Sub Seconds:
亚秒 0-59
Alarm Mask Date Week day:
屏蔽日期。设置为Enable表示屏蔽,即闹钟与日期无关。反之有关
Alarm Mask Hours:
屏蔽小时。Enable表示闹钟与小时数据无,反之有关。
Alarm Mask Minutes:
屏蔽分钟。Enable表示闹钟与分钟数据无,反之有关。
Alarm Mask Seconds:
屏蔽秒。Enable表示闹钟与秒数据无,反之有关。
Alarm Sub Second Mask:
屏蔽亚秒。Enable表示闹钟与亚秒数据无,反之有关。
Alarm Date Week Day Sel:
日期形式。有Date和Weekday两种选项。选项Date表示用1-31表示日期。选择后者则表示用Monday到Sunday表示星期几
Alarm Date:
日期。1-31或Monday-Sunday。
这里屏蔽日期什么含义,具体来说就是,我正常设置一个闹钟是哪一天哪一时哪一分哪一秒,如果屏蔽了日期,那也就是说我每天的固定时分秒都会响闹钟。屏蔽日期与小时,则说明,每小时的第几分钟第几秒都会响闹钟。以此类推。
Wake UP
Wake Up Clock:
周期唤醒的时钟源。
Wake Up Counter:
唤醒计数器的重载值。设定范围为0-65535。这个就是计数器到达设定值就触发中断,接着重新计数。如果这个值设置为0,则每个时钟周期中断一次。这里WakeUpClock设置为1Hz,WakeUpCounter设置为0,则表示每秒触发一次中断。
实现我们想要的电平翻转其实就只需要对3个回调函数做一下处理就行。
每次来一个中断就翻转一下电平
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
}
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{
HAL_GPIO_TogglePin(BEEP_GPIO_Port,BEEP_Pin);
}
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
}
这里WakeUp的输出到RTF_AF1的电平是高电平,可以用示波器抓一下看看。我这通过杜邦线引出来看LED,只能看到微弱的闪烁。
这篇博客主要是RTC的一些基本概念以及简单运用,和TIM定时器相似,不过多了很多东西。要对比学习。