stm32死区怎么配置(自学STM32-05)
作者:junziyang
(注:如非特別声明,以下笔记内容均针对stm32F103ZET6而言。不同型号,细节可能存在差别)
5.1 RTC简介RTC全称为Real-Time Clock,即实时时钟,是芯片内部一个独立的计时器。类似于电脑上的时钟,在合适软件的支持下,可以为嵌入式系统提供时间、日历和闹钟等功能。例如,显示x年x月x日xx时xx分xx秒,利用闹钟中断定时唤醒等。RTC位于备份区,即使系统断电关机,仍可由电池供电,维持RTC的计时。
RTC的计数器为32bit,可由LSI/LSE或HSE的128分频来提供时钟。如果以秒为单位,理论上来说单向最大计数时长可达136年。RTC可以产生3个中断,即闹钟中断,秒中断和溢出中断。RTC闹钟与外部中断线EXTI 17相连,且在NVIC中有专门的闹钟中断向量RTC_Alarm_RIQn(编号41)与之对应。通过闹钟中断,可以对程序运行进行日程和时间管理,比如节假日自动关机等。
5.2 RTC寄存器复位值表
图1 RTC寄存器地址映射与复位值表
任何外设的功能配置都是通过配置相关寄存器实现的,RTC也不例外。为便于接下来RTC原理和功能的学习,可以先浏览一下RTC寄存器的地址映射与复位值表,如图5.1所示(黄色背景的标志位仅可由硬件设置)。RTC共占用10个32bit寄存器,两两配对,分为5组:
- CRH/CRL为控制寄存器,分管中断使能和状态位;
- DIVH/DIVL为预分频寄存器,用来对时钟源RTCCLK进行分频,得到RTC的基础时钟TR_CLK(通常为1Hz);
- PRLH/PRLL为预分频重载寄存器,用来存储预分频系数,每个TR_CLK周期向分频计数器重装载;
- CNTH/CNTL为计数寄存器,从初始值开始在TR_CLK控制下递增;
- ALRH/ALRL为闹钟寄存器,用来设置闹钟,当CNT寄存器与ALR寄存器中的值相等时,产生闹钟事件,如果CRH寄存器在的ALRIE=1,则会产生闹钟中断。
图2. RTC原理框图
RTC的原理框图如图2所示。主要有两个单元构成:APB1接口(橙色部分)和RTC核心(绿色部分)。APB1接口与一组16bit的RTC的寄存器相连(见图1),共享APB1总线的时钟(PCLK1),这样通过APB1总线可以对这些寄存器进行读写操作从而实现对寄存器的配置。APB1接口在待机状态是不供电的。
RTC核心单元又可分为两个模块:RTC预分频器和计数器。左侧的RTC prescaler主要是对RTCCLK进行分频,以产生RTC的基础时钟TR_CLK。其中包含两组功能寄存器(参考图2),RTC_DIV(共32bit)用来对RTCLK分频,而RTC_PRL(共20bit),用来存储预分频系数,每个TR_CLK周期向分频计数器装载1次。右侧的模块中是一个计数器,也包括2组功能寄存器,RTC_CNT(共32bit)用来计↑,RTC_ALR(共32bit)用来设置闹钟。通常将RTC_CNT初始化为当前时间(对表),然后它会按TR_CLK时钟递增计数。如果不初始化,默认的时间起点是1969年12月31日23:59:58(STM32F103ZET6实测,可以用c语言的time和localtime函数获取该时间)。RTC核心部分在备份区中,在待机模式甚至主电源断开情况下该区域都是有供电的(如果安装了电池)。因此RTC初始化以后,只要不主动关闭,或电池耗尽,可以持续工作。
在待机模式下APB1接口以及右侧的RTC_CR寄存器是不供电的。RTC的3个中断中,溢出中断(RTC_Overflow)和秒中断(RTC_Second)在待机模式下无效。而闹钟中断(RTC_Alarm)有独立的中断向量,在待机状态下可以触发中断并将MCU从待机状态唤醒。
5.3.2 RTC设置系统复位或电源复位后,除RTC_PRL/DIV/CNT/ALR不会复位外,RTC_CR寄存器与备份区外的所有寄存器一起被复位。这几个受保护的RTC寄存器,只有在对备份区进行专门复位(RCC_APB1RSTR寄存器BKPRST位置1)时才会被清除。
与其他外设一样,RTC的设置也是通过配置相关寄存器实现的。但由于这些寄存器位于“保护区”,读写操作略有不同。
1. 读取RTC寄存器
RTC核心与RTC APB1接口是完全独立的。RTC寄存器的更新受控于自己内部的时钟,在TR-CLK的每个上升沿被更新,即使外部时钟关闭的情况下,也是如此。而软件读取RTC的可读寄存器需要通过APB1接口。如果APB1的时钟被关闭,再次重新开启后,RTC的时钟要与之重新同步。如果APB1时钟重新开启后马上就去读RTC寄存器(例如更新时间显示),由于时钟不同步,第一次读取的数据有可能是不正确的(通常会读到0)。例如,系统复位或电源复位后,或者MCU刚从停止模式(Stop mode)或待机模式(Standby mode)被唤醒后,马上去读RTC寄存器就有可能会出现这种情况。
为了避免这种情况,在APB1接口重启后,第一次读RTC寄存器前,要等待硬件将RTC_CRL中的RSF(Register Synchronized Flag)位置1。RSF=1表明,TR_CLK与PCLK1同步完成。
普通睡眠模式不会受此影响,因为这种情况下APB1接口时钟不会关闭。
2. 配置RTC寄存器
RTC位于备份区,系统复位后备份区默认是写保护的。如果需要设置或修改RTC,需要执行如下操作:
1)置RCC_APB1ENR寄存器的PWREN=1和BKPEN=1,开启电源接口和备份区接口的时钟。
2)置电源控制寄存器PWR_CR中的DBP=1,使能对备份区和RTC的访问。
RTC寄存器必须在前一次写入结束后才能进行下一次写入。执行完上述操作,放开写入权限后,还必须确认没有正在写入RTC寄存器的操作才可以执行新的写入。确认的方法是:读取RTOFF(RTC operation OFF),写操作结束后,硬件会将该位置1。
由于RTC_PRL/CNT/ALR寄存器是在TR_CLK同步下不断更新的,要写入这些寄存器,必须先主动停止这些寄存器的自动更新。方法是:在确认RTOFF=1的情况下,将CNF(Configuration Flag)位置1。可以认为CNF=1后,这些寄存器被暂时与TR_CLK断开,进入配置模式(Configuration mode)。配置完毕再置CNF=0,退出配置模式,配置才会生效。为了保险期间,最后可以再确认写一下RTOFF=1。概况起来,写入这些寄存器的步骤如下:
1)查询并等待RTOFF=1;
2)设置CNF=1;
3)写入RTC寄存器;
4)清除CNF,即写入CNF=0;
5)查询并等待RTOFF=1。
由于内外时钟的不同频率,CNF的写入至少需要等待3个RTCCLK周期。
3. RTC的标志位
RTC相关状态标志位由RTC_CRL寄存器管理,除了前面提到过的RTOFF(写入结束)、CNF(配置模式)和RSF(寄存器同步)外,还有SECF(秒标志)、OWF(溢出标志)和ALRF(闹钟标志)3个标志位:
SECF(Second Flag)置位发生在RTC计数器更新前,提前量为1个RTCCLK周期,计数器在每个TR_CLK周期都会更新。因TR_CLK通常设为1秒,所以称为秒标志。
OWF(Overflow Flag)置位发生在计数器数器溢出前,提前量为1个RTCCLK周期。RTC的计数器是32bit递增的,达到最大数后会自动回到0,这一事件称为计数器溢出。
ALRF(Alarm Flag)置位发生在计数器数值达到ALR 1前,提前量为1个RTCCLK。图3所示为PR=0003(即TR_CLK为RTCCLK4分频),ALR=0004(即第5秒)时,RTC时钟、秒时钟、计数器以及ALRF置位的时序关系。OWF的置位情况与此类似,只是发生在计数器溢出前。
图3. ALRF与计数器、RTC时钟以及秒时钟的时序关系
5.3.3 RTC寄存器RTC共有10个寄存器,按功能分为5组,可以按半字或字访问。
1. RTC_CRH/L
RTC_CRH和RTC_CRL为RTC控制寄存器(RTC Control Register High/Low )。二者均占32bit,但大部分为预留空间。
RTC_CRH用来管理RTC相关的中断,只有3个功能位:
- OWIE[2]:Overflow interrupt enable,溢出中断使能。可读写。1-开启溢出中断;0-屏蔽溢出中断。
- ALRIE[1]:Alarm interrupt enable,闹钟中断使能。可读写。1-开启闹钟中断;0-屏蔽闹钟中断。
- SECIE[0]:Second interrupt enable,秒中断使能。可读写。1-开启秒中断;0-屏蔽秒中断。
RTC_CRL用来管理RTC相关的一些标志位。共有6个功能位:
- RTOFF[5]:RTC Operation OFF,RTC写入结束标志。只读。1-写入结束;0-写入进行中。RTC寄存器不允许同时写入,上一次写入完成,硬件会将此位置1。写入前检查RTOFF是否为1,以避免写入失败。
- CNF[4]:Configuration flag,配置模式设置和标志位。可读写。1-进入配置模式;0-退出配置模式(开始更新寄存器)。配置RTC_PRL/CNT/ALR三个寄存器前要先向该位写入1,进入配置模式。配置结束再向该位写入0,更新寄存器使配置生效。通过读取该寄存器也可以判断是否处于配置模式。
- RSF[3]:Registers syncronized flag,寄存器同步标志位。1-寄存器已同步;0-寄存器未同步。每次RTC_CNT和RTC_DIV寄存器被更新后,硬件将此位置1。如果APB1复位或APB1时钟关闭,重启后,必须软件清除该位,待硬件置1后再去读取RTC_PRL/CNT/ALR寄存器,否则会由于时钟不同步导致读取错误。
- OWF[2]:Overflow flag,溢出标志位。可读写。硬件设置,软件清除。1-计数器溢出;0-计数器未溢出。
- ALRF[1]:Alarm flag,闹钟标志位。可读写。硬件设置,软件清除。1-有闹钟事件;0-无闹钟事件。
- SECF[0]:Second flag,秒标志位。可读写。硬件设置,软件清除。每个TR_CLK周期设置1次。
说明:
- RTOFF=0时,所有RTC寄存器不可写入。
- 所有挂起的标志位都需要主动软件复位。
- APB1时钟关闭后,OWF/ALRF/SECF/RSF位不会被更新,这些位只能由硬件设置且只能由软件清除。
- 若ALRF=1且ALRIE=1,RTC全局中断使能。如果EXTI控制器也使能了EXTI-17,则RTC全局中断和闹钟中断均被开启。
- 若ALRF=1,如果EXTI-17开启的是中断模式,则RTC闹钟中断开启。如果EXTI-17开启的是事件模式,该线上会产生事件脉冲(不会产生RTC闹钟中断)。
2. RTC_PRLH/L
RTC的本地时钟TR_CLK是通过对RTCCLK进行分频得到的。分频器的工作原理是,通过一个计数器,每隔N个输入时钟脉冲,产生1个输出时钟脉冲,即实现了对时钟源的N分频。分频器中用一个寄存器来存储分频系数,每次计数器计数归零,自动将分频系数寄存器中的值重新装载到计数器,开始下一个周期的计数。
RTC_PRLH/L为RTC预分频装载寄存器(RTC Prescaler Load Register High/Low),是用来存储分频系数的,每个RTCCLK周期递减,归零时产生TR_CLK时钟脉冲。在该脉冲触发下,RTC_PRL将其中存储的分频系数重新装载到计数器,开始下一个TR_CLK周期的计数(参见图3)。
RTC_PRL寄存器由两个32位寄存器组成。RTC_PRLH仅使用低4位,存储PRL[19-16]。RTC_PRLL使用低16位,存储PRL[15-0]。RTCCLK的频率一般为32.768kHz(215),将RPLL设为0x7FFF,即可得到1s的周期。设置PRLH主要是为了应对更高的RTC时钟频率,例如HSE的128分频为562.5kHz,得到一秒则需要用到PRLH(0x89544)。
TR_CLK的与RTCCLK的关系为:
该寄存器受RTOFF位的保护。
3. RTC_DIVH/L
RTC_DIVH/L为RTC预分频器计数寄存器(RTC Prescaler Divider Register High/Low),用来存储预分频计数器当前值。有了该寄存器,可以在不干扰计数器工作的情况下,通过获取当前计数值实现更精确的时间度量。例如,TR_CLK通常为1秒,为32768个RTCCLK周期,通过读取RTC_DIV中的值,可以实现比秒更短的时间分辨,比如秒表上的1/100s。
此寄存器为只读。RTC_PRL或CNT寄存器修改后,硬件会重新装载该寄存器的值。
4. RTC_CNTH/L
RTC_CNTH/L是RTC计数寄存器(RTC Counter Register High/Low),是用来计数的。由两个32位寄存器组成,每个寄存器仅用低16位,每个TR_CLK周期数值递增1。可连续计数范围0-232(约136年)。该寄存器受RTOFF位的保护。
5. RTC_ALRH/L
RTC_ALRH/L为RTC闹钟寄存器(RTC Alarm Register High/Low),用来设置闹钟。当此寄存器中的值与CNT寄存器中的值相等时,会设置ALRF位,产生闹钟中断/事件。
该寄存器受RTOFF位的保护。
5.4 HAL库RTC函数5.4.1 RTC配置步骤在HAL库中,RTC的API在STM32f1xx_hal_rtc.h中声明,在stm32f1xx_hal_rtc.c中定义。配置RTC基本步骤如下:
- 使能RTC区访问:RCC_APB1ENR中的PWREN=1和BKPEN=1,PWR_CR中的DBP=1;
- 准备配置RTC:清除RSF并待其硬件复位,确保寄存器已同步;每次写入前都要确认RTOFF=1,即没有进行中的写入;
- 配置RTC预分频系数。
- 设置日期和时间:将日期和时间折算为秒,写入RTC_CNT寄存器。CNT寄存器仅是一个计数器,时间和日期需要在程序中约定一个起点,然后根据计数器的值进行折算。时间起点的约定可以是任意的。计数寄存器中并不会存储年月日等信息,只是相对于参考时间起点的计数次数(秒数),具体的日期和时间需要在程序中折算。
- 设置闹钟:写入RTC_ALRH/L寄存器。
- 使能所需中断。
- HAL_RTC_Init(); RTC初始化。根据句柄中参数初始化RTC。包括清除中断标志位,设置备份区控制寄存器(BKP->RTCCR,见BKP相关部分),设置RPL寄存器。该函数还可以自动根据PCLK1的频率计算RTC预分频系数,使得TR_CLK时钟周期为1s(设置方法:hrtc->Init.AsynchPrediv = RTC_AUTO_1_SECOND)。句柄中年月日等参数会被初始化为2000年1月1日。
- HAL_RTC_MspInit(); MCU相关外设初始化。在HAL_RTC_Init()中先调用该函数,然后才会进行参数设置。需要用户编写,一般用来使能RTC区访问,开启RTC中断等。
- HAL_RTC_DeInit(); 复位RTC所有寄存器。
- HAL_RTC_MspDeInit(); 在HAL_RTC_DeInit()中,最后调用该函数来复位MCU相关外设。这个函数需要用户编写。执行HAL_RTC_MspInit()中相反的操作
- HAL_RTC_SetTime(); 设置CNT寄存器。将按时分秒输入的时间折算为计数器值,写入CNTH/L寄存器。函数中,先调用RTC_EnterInitMode()关闭写保护,写入完毕后,调用RTC_ExitInitMode()开启写保护。这两个私有函数实际设置的是CNF位,并等待RTOFF=1。如果CNT寄存器设置成功,该函数还会管理闹钟设置,先读取hrtc中的ALRH/L的值,如果闹钟已过期(ALR中的值小于新设置的值),则会增加24h的计数次数,并更新到ALRH/L。
- HAL_RTC_GetTime(); 获取RTC当前时间。读取CNT的值,折算为时分秒并更新到时间结构体的对应字段中。
- HAL_RTC_SetDate(); 设置日期。日期按年月日存储于hrtc句柄的DateToUpdate字段。这个新设的日期的00:00:00会被作为计数器的新起点。DateToUpdate的意思就是上一次的更新日期,查询这个字段可以确定计时起点。私有函数RTC_DateUpdate可以根据CNT中的天数自动更新句柄中的Year/Mongth/Date/WeekDay字段。日历的计算方法挺有意思,考虑了闰年(leep year)等因素(详见stm32f1xx_hal_rtc.c)。
- HAL_RTC_GetDate(); 查询日期。返回句柄中存储的年月日等字段的值。
- HAL_RTC_SetAlarm(); 设置闹钟。按24小时制的时分秒提供参数,换算成秒数,写入ALRH/L寄存器。如果所设时间小于当前时间,则自动增加24小时。Stm32F1系列只能设置1个闹钟。
- HAL_RTC_SetAlarm_IT(); 时钟闹钟中断,即设置闹钟的同时开启闹钟中断。
- HAL_RTC_DeactivateAlarm(); 关闭闹钟。复位ALRH/L寄存器(0xFFFF)。
- HAL_RTC_GetAlarm(); 查询当前设置的闹钟。
- HAL_RTC_AlarmIRQHandler(); 闹钟中断请求处理函数。调用需要用户定义的回调函数HAL_RTC_AlarmAEventCallback处理中断。
- HAL_RTC_PollForAlarmAEvent(); 轮询等待闹钟事件。while循环,直至ALRAF=1或超时。
- HAL_RTC_AlarmAEventCallback(); 闹钟中断回调函数。这是一个weak函数,需要用户编写代码。
- HAL_RTC_GetState(); 查询RTC状态。返回状态机 hrtc->State。
- HAL_RTC_WaitForSynchro(); 等待RTC寄存器同步
备份区(BacKuP domain)中除了RTC,还有一组寄存器,用来备份应用程序中的用户数据。由于关机或待机后备份区可以由电池供电,因此把一些重要数据存储到备份区的寄存器中,MCU复位或唤醒后可以读取这些数据,执行一些初始化的工作。例如,MCU从停机被唤醒后,默认会使用HSI时钟,往往会造成一些问题。可以设置一个睡眠方式标志,停机前写入备份区寄存器,待被重新激活后,可以读取这个数据,判断是否从停机状态恢复,以便重新初始化时钟。(好像读PWR_CR的PDDS位也可以)。
5.5.2 BKP寄存器STM32系列大容量产品在备份区设置了42个16bit的寄存器(32bit寄存器仅开放低16位),可以存储84个字节的数据。由于备份区在复位后默认是写保护的,与前述设置RTC一样,必须先去掉写保护(RCC_APB1ENR:PWREN=1,BKPEN=1;PWR_CR:DBP=1)才可以写入BKP寄存器。
除了42个数据寄存器,BKP还有3个专用寄存器,来管理RTC的校准、防侵入引脚(TAMPER)的设置,以及BKP状态和中断。这个专用寄存器的地址映射即复位值如图4所示。
图4. BKP寄存器地址映射与复位值表
1. BKP_RTCCR寄存器
BKP_RTCCR是RTC校准寄存器(RCC Clock Calibration Register ),用来管理RTC脉冲输出和校正。该寄存器共有4个功能位:
- ASOS[9]:Alarm or second output selection,0 - 输出RTC闹钟脉冲;1 - 输出RTC秒脉冲。需要先开启ASOE=1。 备份区复位时此位被复位。
- ASOE[8]:Alarm or second output enable,0 - 禁止信号输出;1 - 使能信号输出。该位必须在TAMPER引脚使能前,即TPE=0时设置。备份区复位时此位被复位。
- CCO[7]:Calibration clock output,0 - 无效;1 - 在TAMPER引脚输出RTC时钟的64分频。测量此频率,可用来校准时钟。该位必须在TAMPER引脚使能前,即TPE=0时设置。VDD关闭时此位复位。
- CAL[6:0]:Calibration value,RTC时钟(TR_CLK)校准值。如果需要精确计数,需要选择32.768kHz的LSE晶振时钟。但晶振的频率会受温度的影响而发生漂移,ST提供了这种软件校准的方法。CAL值的意义为:每2^20(1048576)个时钟脉冲忽略的脉冲个数。以每百万时钟计,调整步长为10^6/2^20 ppm,调整范围为0-121ppm。折算成频率为4,即将可校正[32768,32772]Hz范围内的晶振频率。这种方法只能调慢不能调快,显然是不符合实际情况的。一种解决方案是,人为调低RTC_PRLH/L中的预分频系数,这样相当于调高了RTC时钟源的频率。例如,不是设置为0x7FFF(32768),而是设置为0x7FFE(32766),这样相当于把CAL的校正频率范围拉到了[32766-32770]。详细的RTC时钟校正方法可查阅《AN2604 STM32F101xx and STM32F103xx RTC calibration》
2. BKP_CR寄存器
BKP_CR是备份区控制寄存器(Backup control register),用来管理防侵入Tamper引脚。该寄存器只有两个功能位:
- TPAL[1]:Tamper pin active level,TAMPER引脚生效电平。开启TPE后,TAMPER引脚可以触发数据备份寄存器的复位。此位设置起作用的复位电平。0 - 高电平复位所有数据备份寄存器;1 - 低电平复位所有数据备份寄存器。
- TPE[0]:Tamper pin enable,TAMPER引脚使能。0 - TAMPER引脚作为普通I/O;1 - TAMPER引脚使能(AFIO)。TPAL和TPE一起置1没问题,但最好不要一起置0。建议TPE先置0,再复位TPAL。防止产生虚假Tamper事件。
3. BKP_CSR寄存器
BKP_CSR是备份区控制/状态寄存器(Backup control/status register),用来管理TAMPER引脚上的中断/事件及标志位。该寄存器共5个功能位:
- TIF[9]:Tamper interrupt flag,入侵中断标志位。0 - 无侵入中断;1 - 出现侵入中断。TPIE=1且发生侵入事件时,由硬件置1。写入CTI=0或TPIE=0可清除。
- TEF[8]:Tamper event flag,入侵事件标志位。0 - 无侵入事件;1 - 发生侵入事件;Tamper事件会复位所有数据备份寄存器。在TEF=1时,写入备份寄存器的数据不会被保存。侵入事件发生时由硬件置1。写入CTE=0可清除。
- TPIE[2]:TAMPER pin interrupt enable,防入侵引脚中断使能。0 - 禁用;1-使能。如果系统处于低功耗模式,Tamper中断不会将其唤醒。此位仅在系统复位或待机唤醒后才会被复位。
- CTI[1]:Clear tamper interrupt,清除TAMPER中断。0 - 无效;1 - 清除Taper中断且置TIF=0。此寄存器为只写寄存器。
- CTE[0]:Clear tamper event,清除TAMPER事件。0 - 无效;1 - 清除Taper事件且置TEIF=0。此寄存器为只写寄存器。
不同型号的MCU,BKP不尽相同。因此HAL库将相关函数纳入扩展API。在stm32f1xx_hal_rtc_ex.h中声明,在stm32f1xx_hal_rtc_ex.c中定义。
1. TAMPER管理函数
- HAL_RTCEx_SetTamper(); 使能TPE设置TPAL。TPE只有在CCO=0且ASOE=0时才可使能TPE。
- HAL_RTCEx_SetTamper_IT(); 使能TPE设置TPAL并设置TPIE=1。同样,TPE只有在CCO=0且ASOE=0时才可使能TPE。
- HAL_RTCEx_DeactivateTamper(); 复位TPE,清除中断/事件及标志位
- HAL_RTCEx_TamperIRQHandler();
- HAL_RTCEx_Tamper1EventCallback();
- HAL_RTCEx_PollForTamper1Event();
2. 秒中断/事件管理函数
- HAL_RTCEx_SetSecond_IT();
- HAL_RTCEx_DeactivateSecond();
- HAL_RTCEx_RTCIRQHandler();
- HAL_RTCEx_RTCEventCallback();
- HAL_RTCEx_RTCEventErrorCallback();
3.扩展控制函数
- HAL_RTCEx_BKUPWrite();
- HAL_RTCEx_BKUPRead();
- HAL_RTCEx_SetSmoothCalib();
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"
/* USER CODE BEGIN 0 */
#include "time.h"
/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;
/* RTC init function */
void MX_RTC_Init(void){
/* USER CODE BEGIN RTC_Init 0 */
RTC_AlarmTypeDef sAlarm = {0};
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 10;
sTime.Minutes = 0;
sTime.Seconds = 0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_FRIDAY;
DateToUpdate.Month = RTC_MONTH_OCTOBER;
DateToUpdate.Date = 1;
DateToUpdate.Year = 21;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK) {
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
sTime.Hours = 1;
sTime.Minutes = 0;
sAlarm.AlarmTime = sTime;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN); //Alarm in 30min later
/* USER CODE END RTC_Init 2 */
}
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle){
if(rtcHandle->Instance==RTC) {
/* USER CODE BEGIN RTC_MspInit 0 */
/* USER CODE END RTC_MspInit 0 */
HAL_PWR_EnableBkUpAccess();
/* Enable BKP CLK enable for backup registers */
__HAL_RCC_BKP_CLK_ENABLE();
/* RTC clock enable */
__HAL_RCC_RTC_ENABLE();
/* RTC interrupt Init */
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 2, 1);
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
/* USER CODE BEGIN RTC_MspInit 1 */
/* USER CODE END RTC_MspInit 1 */
}
}
/*----------------------------------------------------------------------
*
* Alarm Event Callback
*
-----------------------------------------------------------------------*/
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc){
uint8_t n=0u;
HQ_RTC_UpdateAlarm(hrtc,30);
printf("Current Time is:");
HQ_RTC_DisplayTime();
if((PWR->CSR&(0x1<<1))!=0){ //Wakeup from Stop mode
SystemClock_Config(); //Restore system clock
}
for (n=0;n<4;n ) { //Ring the bell
BEEP = !BEEP;
delay_ms(300);
}
}
//-----UpdateAlarm to ring bell after INTERVAL minutes -----------------
void HQ_RTC_UpdateAlarm(RTC_HandleTypeDef *hrtc,uint8_t interval){
uint16_t high1 = 0U, low = 0U;
uint32_t AlarmCounter = 0U,tickstart = 0U;
// 01. RTC_ReadAlarmCounter
high1 = READ_REG(hrtc->Instance->ALRH & RTC_CNTH_RTC_CNT);
low = READ_REG(hrtc->Instance->ALRL & RTC_CNTL_RTC_CNT);
AlarmCounter = (((uint32_t) high1 << 16U) | low);
// 02. Update the value
AlarmCounter = ((uint32_t) interval)*60U;
// 03. RTC_WriteAlarmCounter
while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);//Wait till RTC is in INIT state
__HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);/* Disable the write protection for RTC registers */
WRITE_REG(hrtc->Instance->ALRH, (AlarmCounter >> 16U));/* Set RTC COUNTER MSB word */
WRITE_REG(hrtc->Instance->ALRL, (AlarmCounter & RTC_ALRL_RTC_ALR));/* Set RTC COUNTER LSB word */
__HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);/* Enable the write protection for RTC registers */
tickstart = HAL_GetTick();
while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET){ //Wait till RTC is in INIT state
if ((HAL_GetTick() - tickstart) > RTC_TIMEOUT_VALUE){
Error_Handler();
}
}
}
// ------ DisplayTime ------------------------------------------------
void HQ_RTC_DisplayTime(){
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
printf("20d-d-d d:d:d \r\n",
sDate.Year,sDate.Month,sDate.Date,
sTime.Hours,sTime.Minutes,sTime.Seconds);
}
学习心得:
嵌入式系统开发过程中最繁琐的部分可能就是配置寄存器,先后顺序、延时、相互间的逻辑联系.....非常容易出错。库函数对常用的寄存器进行了功能性封装。基于库函数开发可以事半功倍,而且HAL库函数自带超时和容错机制,不容易出错。学习过程中可以读一下库函数,看一下其中是如何操作寄存器的。对学习大有裨益。还可以先基于库函数实现所需的功能,然后按其中的思路自己再用配置寄存器的方法实现一遍。调试的过程中可以学到很多细节的东西。毕竟配置寄存器是与硬件对话的更直接途径。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com