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寄存器复位值表

stm32死区怎么配置(自学STM32-05)(1)

图1 RTC寄存器地址映射与复位值表

任何外设的功能配置都是通过配置相关寄存器实现的,RTC也不例外。为便于接下来RTC原理和功能的学习,可以先浏览一下RTC寄存器的地址映射与复位值表,如图5.1所示(黄色背景的标志位仅可由硬件设置)。RTC共占用10个32bit寄存器,两两配对,分为5组:

  1. CRH/CRL为控制寄存器,分管中断使能和状态位;
  2. DIVH/DIVL为预分频寄存器,用来对时钟源RTCCLK进行分频,得到RTC的基础时钟TR_CLK(通常为1Hz);
  3. PRLH/PRLL为预分频重载寄存器,用来存储预分频系数,每个TR_CLK周期向分频计数器重装载;
  4. CNTH/CNTL为计数寄存器,从初始值开始在TR_CLK控制下递增;
  5. ALRH/ALRL为闹钟寄存器,用来设置闹钟,当CNT寄存器与ALR寄存器中的值相等时,产生闹钟事件,如果CRH寄存器在的ALRIE=1,则会产生闹钟中断。
5.3 RTC的功能与原理5.3.1 结构框图

stm32死区怎么配置(自学STM32-05)(2)

图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的置位情况与此类似,只是发生在计数器溢出前。

stm32死区怎么配置(自学STM32-05)(3)

图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的关系为:

stm32死区怎么配置(自学STM32-05)(4)

该寄存器受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基本步骤如下:

  1. 使能RTC区访问:RCC_APB1ENR中的PWREN=1和BKPEN=1,PWR_CR中的DBP=1;
  2. 准备配置RTC:清除RSF并待其硬件复位,确保寄存器已同步;每次写入前都要确认RTOFF=1,即没有进行中的写入;
  3. 配置RTC预分频系数。
  4. 设置日期和时间:将日期和时间折算为秒,写入RTC_CNT寄存器。CNT寄存器仅是一个计数器,时间和日期需要在程序中约定一个起点,然后根据计数器的值进行折算。时间起点的约定可以是任意的。计数寄存器中并不会存储年月日等信息,只是相对于参考时间起点的计数次数(秒数),具体的日期和时间需要在程序中折算。
  5. 设置闹钟:写入RTC_ALRH/L寄存器。
  6. 使能所需中断。
5.4.2 RTC初始化/复位函数
  • 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()中相反的操作
5.4.3 事件和日期设置函数
  • 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(); 查询日期。返回句柄中存储的年月日等字段的值。
5.4.4 闹钟设置函数
  • 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函数,需要用户编写代码。
5.4.5 状态控制函数
  • HAL_RTC_GetState(); 查询RTC状态。返回状态机 hrtc->State。
  • HAL_RTC_WaitForSynchro(); 等待RTC寄存器同步
5.5 BKP寄存器5.5.1 BKP简介

备份区(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所示。

stm32死区怎么配置(自学STM32-05)(5)

图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。此寄存器为只写寄存器。
5.5.3 HAL库BKP函数

不同型号的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();
6. 示例-半点报时闹钟

/* 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

    分享
    投诉
    首页