你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32开发 -- RTC详解

[复制链接]
STMCU-管管 发布时间:2021-7-2 13:11
一、RTC实时时钟特征与原理
查看STM32中文手册 16 实时时钟(RTC)(308页)


RTC (Real Time Clock):实时时钟
实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后, RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:
● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。


RTC特征
可编程的预分频系数:分频系数最高为220。
● 32位的可编程计数器,可用于较长时间段的测量。
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟
频率的四分之一以上)。
● 可以选择以下三种RTC的时钟源:
— HSE时钟除以128;
— LSE振荡器时钟;
— LSI振荡器时钟(详见6.2.8节RTC时钟)。
● 2个独立的复位类型:
— APB1接口由系统复位;
— RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)。
● 3个专门的可屏蔽中断:
— 闹钟中断,用来产生一个软件可编程的闹钟中断。
— 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
— 溢出中断,指示内部可编程计数器溢出并回转为0的状态。


二、RTC由两部分组成
**APB1接口:**用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
**RTC核心:**由一组可编程计数器组成。分两个主要模块。
第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断。
第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断。
11.png
RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。


三、RTC时钟源
首先讲一下时钟源:


三种不同的时钟源可被用来驱动系统时钟(SYSCLK):
● HSI振荡器时钟
● HSE振荡器时钟
● PLL时钟
这些设备有以下2种二级时钟源:
● 40kHz低速内部RC,可以用于驱动独立看门狗和通过程序选择驱动RTC。RTC用于从停机/待机模式下自动唤醒系统。
● 32.768kHz低速外部晶体也可用来通过程序选择驱动RTC(RTCCLK)。


参看:STM32时钟系统
12.png


当不被使用时,任一个时钟源都可被独立地启动或关闭,由此优化系统功耗。
用户可通过多个预分频器配置AHB、高速APB(APB2)和低速APB(APB1)域的频率。AHB和APB2域的最大频率是72MHz。APB1域的最大允许频率是36MHz。SDIO接口的时钟频率固定为HCLK/2。
RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。ADC时钟由高速APB2时钟经2、 4、 6或8分频后获得。
定时器时钟频率分配由硬件按以下2种情况自动设置:


如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率一致。
否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍。
如上图,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。
接下来我们一一看一下:


HSE时钟
13.png
**高速外部时钟信号(HSE)**由以下两种时钟源产生:
● HSE外部晶体/陶瓷谐振器
● HSE用户外部时钟
为了减少时钟输出的失真和缩短启动稳定时间,晶体/陶瓷谐振器和负载电容器必须尽可能地靠
近振荡器引脚。负载电容值必须根据所选择的振荡器来调整。
14.png
外部时钟源(HSE旁路)
在这个模式里,必须提供外部时钟。它的频率最高可达25MHz。用户可通过设置在时钟控制寄存器中的HSEBYP和HSEON位来选择这一模式。外部时钟信号(50%占空比的方波、正弦波或三角波)必须连到SOC_IN引脚,同时保证OSC_OUT引脚悬空。见图9。
外部晶体/陶瓷谐振器(HSE晶体)
**4~16Mz外部振荡器可为系统提供更为精确的主时钟。**相关的硬件配置可参考图9,进一步信息可参考数据手册的电气特性部分。
在时钟控制寄存器RCC_CR中的HSERDY位用来指示高速外部振荡器是否稳定。在启动时,直到这一位被硬件置’1’,时钟才被释放出来。如果在时钟中断寄存器RCC_CIR中允许产生中断,将会产生相应中断。
HSE晶体可以通过设置时钟控制寄存器里RCC_CR中的HSEON位被启动和关闭。


HSI时钟
15.png
HSI时钟信号由内部8MHz的RC振荡器产生,可直接作为系统时钟或在2分频后作为PLL输入。
HSI RC振荡器能够在不需要任何外部器件的条件下提供系统时钟。它的启动时间比HSE晶体振荡器短。然而,即使在校准之后它的时钟频率精度仍较差。
校准
制造工艺决定了不同芯片的RC振荡器频率会不同,这就是为什么每个芯片的HSI时钟频率在出厂前已经被ST校准到1%(25°C)的原因。系统复位时,工厂校准值被装载到时钟控制寄存器的HSICAL[7:0]位。
如果用户的应用基于不同的电压或环境温度,这将会影响RC振荡器的精度。可以通过时钟控制寄存器里的HSITRIM[4:0]位来调整HSI频率。
时钟控制寄存器中的HSIRDY位用来指示HSI RC振荡器是否稳定。在时钟启动过程中,直到这一位被硬件置’1’, HSI RC输出时钟才被释放。HSI RC可由时钟控制寄存器中的HSION位来启动和关闭。
如果HSE晶体振荡器失效, HSI时钟会被作为备用时钟源。


PLL


16.png
内部PLL可以用来倍频HSI RC的输出时钟或HSE晶体输出时钟。
PLL的设置(选择HIS振荡器除2或HSE振荡器为PLL的输入时钟,和选择倍频因子)必须在其被激活前完成。一旦PLL被激活,这些参数就不能被改动。
如果PLL中断在时钟中断寄存器里被允许,当PLL准备就绪时,可产生中断申请。
如果需要在应用中使用USB接口, PLL必须被设置为输出48或72MHZ时钟,用于提供48MHz的USBCLK时钟。


LSE时钟
17.png


**LSE晶体是一个32.768kHz的低速外部晶体或陶瓷谐振器。**它为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
LSE晶体通过在备份域控制寄存器(RCC_BDCR)里的LSEON位启动和关闭。
在备份域控制寄存器(RCC_BDCR)里的LSERDY指示LSE晶体振荡是否稳定。在启动阶段,直到这个位被硬件置’1’后, LSE时钟信号才被释放出来。如果在时钟中断寄存器里被允许,可产生中断申请。
外部时钟源(LSE旁路)
在这个模式里必须提供一个32.768kHz频率的外部时钟源。你可以通过设置在备份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位来选择这个模式。具有50%占空比的外部时钟信号(方波、正弦波或三角波)必须连到OSC32_IN引脚,同时保证OSC32_OUT引脚悬空,见图9。


LSI时钟
18.png


LSI RC担当一个低功耗时钟源的角色,它可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。**LSI时钟频率大约40kHz(在30kHz和60kHz之间)。**进一步信息请参考数据手册中有关电气特性部分。
LSI RC可以通过控制/状态寄存器(RCC_CSR)里的LSION位来启动或关闭。
在控制/状态寄存器(RCC_CSR)里的LSIRDY位指示低速内部振荡器是否稳定。在启动阶段,直到这个位被硬件设置为’1’后,此时钟才被释放。如果在时钟中断寄存器(RCC_CIR)里被允许,将产生LSI中断申请。
注意:只有大容量和互联型产品可以进行LSI校准
LSI校准
可以通过校准内部低速振荡器LSI来补偿其频率偏移,从而获得精度可接受的RTC时间基数,以及独立看门狗(IWDG)的超时时间(当这些外设以LSI为时钟源)。
**校准可以通过使用TIM5的输入时钟(TIM5_CLK)测量LSI时钟频率实现。**测量以HSE的精度为保证,软件可以通过调整RTC的20位预分频器来获得精确的RTC时钟基数,以及通过计算得到精确的独立看门狗(IWDG)的超时时间。
LSI校准步骤如下:


打开TIM5,设置通道4为输入捕获模式;
设置AFIO_MAPR的TIM5_CH4_IREMAP位为’1’,在内部把LSI连接到TIM5的通道4;
通过TIM5的捕获/比较4事件或者中断来测量LSI时钟频率;
根据测量结果和期望的RTC时间基数和独立看门狗的超时时间,设置20位预分频器。
四、RTC时钟
**通 过 设 置 备 份 域 控 制 寄 存 器 (RCC_BDCR) 里 的 RTCSEL[1:0] 位 , RTCCLK 时钟源可以由HSE/128、LSE或LSI时钟提供。**除非备份域复位,此选择不能被改变。
LSE时钟在备份域里,但HSE和LSI时钟不是。因此:
● 如果LSE被选为RTC时钟:
— 只要VBAT维持供电,尽管VDD供电被切断, RTC仍继续工作。
● 如果LSI被选为自动唤醒单元(AWU)时钟:
— 如果VDD供电被切断, AWU状态不能被保证。有关LSI校准,详见6.2.5节LSI时钟。
● 如果HSE时钟128分频后作为RTC时钟:
— 如果VDD供电被切断或内部电压调压器被关闭(1.8V域的供电被切断),则RTC状态不确定。
— 必须设置电源控制寄存器(见4.4.1节)的DPB位(取消后备区域的写保护)为’1’。


五、RTC寄存器
上面都是从STM32中文手册里摘取的。大概了解一下RTC和时钟。
不过讲的有点扯,里面有多好寄存器,不知道是干啥的。接下来重点看一下这些寄存器。


RTC控制寄存器高位(RTC_CRH)
19.png
RTC控制寄存器低位(RTC_CRL)
20.png


①修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步。
②修改CNT,ALR,PRL的时候,必须先配置CNF位进入配置模式,修改完之后,设置CNF位为0退出配置模式
③同时在对RTC相关寄存器写操作之前,必须判断上一次写操作已经结束,也就是判断RTOFF位是否置位。


RTC预分频装载寄存器(RTC_PRLH/RTC_PRLL)
预分频装载寄存器用来保存RTC预分频器的周期计数值。它们受RTC_CR寄存器的RTOFF位保护,仅当RTOFF值为’1’时允许进行写操作。
21.png
RTC预分频器余数寄存器(RTC_DIVH / RTC_DIVL)
22.png
RTC计数器寄存器 (RTC_CNTH / RTC_CNTL)
23.png
RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)
24.png
配置RTC寄存器
必须设置RTC_CRL 寄 存 器 中 的CNF位 , 使RTC进入配置模式后 , 才能写 入RTC_PRL、RTC_CNT、 RTC_ALR寄存器。
另外,对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是’1’时,才可以写入RTC寄存器。
配置过程:


查询RTOFF位,直到RTOFF的值变为’1’
置CNF值为1,进入配置模式
对一个或多个RTC寄存器进行写操作
清除CNF标志位,退出配置模式
查询RTOFF,直至RTOFF位变为’1’以确认写操作已经完成。
仅当CNF标志位被清除时,写操作才能进行,这个过程至少需要3个RTCCLK周期。
读RTC寄存器
RTC核完全独立于RTC APB1接口。
软件通过APB1接口访问RTC的预分频值、 计数器值和闹钟值。但是,相关的可读寄存器只在与RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。RTC标志也是如此的。
这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。下述几种
情况下能够发生这种情形:
● 发生系统复位或电源复位
● 系统刚从待机模式唤醒(参见第4.3节:低功耗模式)。
● 系统刚从停机模式唤醒(参见第4.3节:低功耗模式)。
所有以上情况中, APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时, RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。
注:RTC的 APB1接口不受WFI和WFE等低功耗模式的影响


六、RTC相关库函数讲解
库函数所在文件:stm32f10x_rtc.c / stm32f10x_rtc.h


RTC时钟源和时钟操作函数:
  1. void RCC_RTCCLKConfig(uint32_t  CLKSource);//时钟源选择
  2. void RCC_RTCCLKCmd(FunctionalState NewState)//时钟使能
复制代码
RTC配置函数(预分频,计数值:
  1. void RTC_SetPrescaler(uint32_t PrescalerValue);//预分频配置:PRLH/PRLL
  2. void RTC_SetCounter(uint32_t CounterValue);//设置计数器值:CNTH/CNTL
  3. void RTC_SetAlarm(uint32_t AlarmValue);//闹钟设置:ALRH/ALRL
复制代码
RTC中断设置函数:
  1. void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
复制代码
RTC允许配置和退出配置函数:
  1. void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
复制代码
同步函数:
  1. void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
  2. void RTC_WaitForSynchro(void);//等待时钟同步:CRL位RSF
复制代码
相关状态位获取清除函数:
  1. FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
  2. void RTC_ClearFlag(uint16_t RTC_FLAG);
  3. ITStatus RTC_GetITStatus(uint16_t RTC_IT);
  4. void RTC_ClearITPendingBit(uint16_t RTC_IT);
复制代码
其他相关函数(BKP等)
  1. PWR_BackupAccessCmd();//BKP后备区域访问使能
  2. RCC_APB1PeriphClockCmd();//使能PWR和BKP时钟
  3. RCC_LSEConfig();//开启LSE,RTC选择LSE作为时钟源
  4. PWR_BackupAccessCmd();//BKP后备区域访问使能
  5. uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读BKP寄存器
  6. void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t
复制代码
七、RTC配置一般步骤
1) 使能电源时钟和备份区域时钟。
前面已经介绍了,我们要访问 RTC 和备份区域就必须先使能电源时钟和备份区域时钟。
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
复制代码
2) 取消备份区写保护。
要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标记时钟已经配置过了,这样避免每次复位之后重新配置时钟。取消备份区域写保护的库函数实现方法是:
  1. PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
复制代码
3) 复位备份区域,开启外部低速振荡器。
在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器,注意这里一般要先判断 RCC_BDCR 的 LSERDY位来确定低速振荡器已经就绪了才开始下面的操作。
备份区域复位的函数是:
  1. BKP_DeInit();//复位备份区域
复制代码
开启外部低速振荡器的函数是:
  1. RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器
复制代码
4) 选择 RTC 时钟,并使能。
这里我们将通过 RCC_BDCR 的 RTCSEL 来选择选择外部 LSI 作为 RTC 的时钟。然后通过RTCEN 位使能 RTC 时钟。库函数中,选择 RTC 时钟的函数是:
  1. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟
复制代码
对于 RTC 时钟的选择,还有 RCC_RTCCLKSource_LSI 和RCC_RTCCLKSource_HSE_Div128 这两个,顾名思义,前者为 LSI,后者为 HSE 的 128 分频,这在时钟系统章节有讲解过。
使能 RTC 时钟的函数是:
  1. RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
复制代码
5) 设置 RTC 的分频,以及配置 RTC 时钟。
在开启了 RTC 时钟之后,我们要做的就是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置RTC 的允许配置位(RTC_CRH 的 CNF 位),设置时间(其实就是设置 RTC_CNTH 和 RTC_CNTL两个寄存器)。下面我们一一这些步骤用到的库函数:在进行 RTC 配置之前首先要打开允许配置位(CNF),库函数是:
  1. RTC_EnterConfigMode();/// 允许配置
复制代码
在配置完成之后,千万别忘记更新配置同时退出配置模式,函数是:
  1. RTC_ExitConfigMode();//退出配置模式, 更新配置
复制代码
设置 RTC 时钟分频数, 库函数是:
  1. void RTC_SetPrescaler(uint32_t PrescalerValue);
复制代码
这个函数只有一个入口参数,就是 RTC 时钟的分频数,很好理解。
然后是设置秒中断允许, RTC 使能中断的函数是:
  1. void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
复制代码
这个函数的第一个参数是设置秒中断类型,这些通过宏定义定义的。对于使能秒中断方法是:
  1. RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断
复制代码
八、RTC程序
这篇文章复制粘贴了这么多,感觉不到一丝有用的东西。算了,还是看一下,程序是怎么写的吧。


RTC_Init
  1. //实时时钟配置

  2. //初始化 RTC 时钟,同时检测时钟是否工作正常

  3. //BKP->DR1 用于保存是否第一次配置的设置

  4. //返回 0:正常

  5. //其他:错误代码

  6. u8 RTC_Init(void)

  7. {

  8. u8 temp=0;

  9. //检查是不是第一次配置时钟

  10. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |

  11. RCC_APB1Periph_BKP, ENABLE); //①使能 PWR 和 BKP 外设时钟

  12. PWR_BackupAccessCmd(ENABLE); //②使能后备寄存器访问

  13. if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中

  14. //读出数据:读出了与写入的指定数据不相乎

  15. {

  16. BKP_DeInit(); //③复位备份区域

  17. RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE)

  18. while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)

  19. //检查指定的 RCC 标志位设置与否,等待低速晶振就绪

  20. {

  21. temp++;

  22. delay_ms(10);

  23. }

  24. if(temp>=250)return 1;//初始化时钟失败,晶振有问题

  25. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置 RTC 时钟

  26. //(RTCCLK),选择 LSE 作为 RTC 时钟

  27. RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟

  28. RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

  29. RTC_WaitForSynchro(); //等待 RTC 寄存器同步

  30. RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断

  31. RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

  32. RTC_EnterConfigMode(); // 允许配置

  33. RTC_SetPrescaler(32767); //设置 RTC 预分频的值

  34. RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

  35. RTC_Set(2015,1,14,17,42,55); //设置时间

  36. RTC_ExitConfigMode(); //退出配置模式

  37. BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中

  38. //写入用户程序数据 0x5050

  39. }

  40. else//系统继续计时

  41. {

  42. RTC_WaitForSynchro(); //等待最近一次对 RTC 寄存器的写操作完成

  43. RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断

  44. RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

  45. }

  46. RTC_NVIC_Config(); //RCT 中断分组设置

  47. RTC_Get(); //更新时间

  48. return 0; //ok

  49. }
复制代码
RTC_Set
  1. //设置时钟

  2. //把输入的时钟转换为秒钟

  3. //以 1970 年 1 月 1 日为基准

  4. //1970~2099 年为合法年份

  5. //返回值:0,成功;其他:错误代码.

  6. //月份数据表

  7. u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表

  8. //平年的月份日期表

  9. const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};

  10. u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)

  11. {

  12. u16 t;

  13. u32 seccount=0;

  14. if(syear<1970||syear>2099)return 1;

  15. for(t=1970;t<syear;t++) //把所有年份的秒钟相加

  16. { if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数

  17. else seccount+=31536000; //平年的秒钟数

  18. }

  19. smon-=1;

  20. for(t=0;t<smon;t++) //把前面月份的秒钟数相加

  21. { seccount+=(u32)mon_table[t]*86400; //月份秒钟数相加

  22. if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年 2 月份增加一天的秒钟数

  23. }

  24. seccount+=(u32)(sday-1)*86400; //把前面日期的秒钟数相加

  25. seccount+=(u32)hour*3600; //小时秒钟数

  26. seccount+=(u32)min*60; //分钟秒钟数

  27. seccount+=sec; //最后的秒钟加上去

  28. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |

  29. RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外设时钟

  30. PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问

  31. RTC_SetCounter(seccount); //设置 RTC 计数器的值

  32. RTC_WaitForLastTask(); //等待最近一次对 RTC 寄存器的写操作完成

  33. return 0;

  34. }
复制代码
RTC_Get
  1. //得到当前的时间,结果保存在 calendar 结构体里面

  2. //返回值:0,成功;其他:错误代码.

  3. u8 RTC_Get(void)

  4. { static u16 daycnt=0;

  5. u32 timecount=0;

  6. u32 temp=0;

  7. u16 temp1=0;

  8. timecount=RTC->CNTH; //得到计数器中的值(秒钟数)

  9. timecount<<=16;

  10. timecount+=RTC->CNTL;

  11. temp=timecount/86400; //得到天数(秒钟数对应的)

  12. if(daycnt!=temp) //超过一天了

  13. {

  14. daycnt=temp;

  15. temp1=1970; //从 1970 年开始

  16. while(temp>=365)

  17. {

  18. if(Is_Leap_Year(temp1)) //是闰年

  19. {

  20. if(temp>=366)temp-=366; //闰年的秒钟数

  21. else break;

  22. }

  23. else temp-=365; //平年

  24. temp1++;

  25. }

  26. calendar.w_year=temp1; //得到年份

  27. temp1=0;

  28. while(temp>=28) //超过了一个月

  29. {

  30. if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2 月份

  31. {

  32. if(temp>=29)temp-=29;//闰年的秒钟数

  33. else break;

  34. }

  35. else

  36. { if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年

  37. else break;

  38. }

  39. temp1++;

  40. }

  41. calendar.w_month=temp1+1; //得到月份

  42. calendar.w_date=temp+1; //得到日期

  43. }

  44. temp=timecount%86400; //得到秒钟数

  45. calendar.hour=temp/3600; //小时

  46. calendar.min=(temp%3600)/60; //分钟

  47. calendar.sec=(temp%3600)%60; //秒钟

  48. calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);

  49. //获取星期

  50. return 0;

  51. }
复制代码
RTC_NVIC_Config
  1. static void RTC_NVIC_Config(void)

  2. {       

  3. NVIC_InitTypeDef NVIC_InitStructure;

  4.         NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;                //RTC全局中断

  5.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;        //先占优先级1位,从优先级3位

  6.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //先占优先级0位,从优先级4位

  7.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //使能该通道中断

  8.         NVIC_Init(&NVIC_InitStructure);                //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

  9. }
复制代码
RTC_IRQHandler
  1. //RTC 时钟中断

  2. //每秒触发一次

  3. void RTC_IRQHandler(void)

  4. {

  5. if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒钟中断

  6. {

  7. RTC_Get(); //更新时间

  8. }

  9. if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) //闹钟中断

  10. {

  11. RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断

  12. RTC_Get(); //更新时间

  13. printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,

  14. calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间

  15. }

  16. RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断

  17. RTC_WaitForLastTask();

  18. }
复制代码

九、项目代码
  1. void BSP_RTC_Init(void)

  2. {

  3.         u32 i = 0;

  4.         #if(INFO_OUT_RTC_INIT_EN > 0)

  5.         u8        tmpBuf[60]="";

  6.         #endif



  7.         /* Clear reset flags */

  8.         RCC_ClearFlag();



  9.         // 这里标志必须跟测试程序一致否则时间被复位成默认

  10.         if (BKP_ReadBackupRegister(BKP_DR1) != RTC_SAVE_FLAG)

  11.         {

  12.                 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  13.                 /* Allow access to BKP Domain */

  14.                 PWR_BackupAccessCmd(ENABLE);



  15.                 /* Backup data register value is not correct or not yet programmed (when

  16.                 the first time the program is executed) */



  17.                 /* RTC Configuration */

  18.                 BSP_RTC_Config();



  19.                 #if(DEF_RTCINFO_OUTPUTEN > 0)

  20.                 if(dbgInfoSwt & DBG_INFO_RTC)

  21.                         myPrintf("[RTC]: RTC finish configured....\r\n");

  22.                 #endif



  23.                 /* Set default time */

  24.                 SYS_RTC.year                =                Default_year;

  25.                 SYS_RTC.month                =                Default_month;

  26.                 SYS_RTC.day                        =                Default_day;       

  27.                 SYS_RTC.hour                =                Default_hour;

  28.                 SYS_RTC.minute            =                Default_minute;

  29.                 SYS_RTC.second            =                Default_second;



  30.                 /* Adjust time by values entred by the user on the hyperterminal */

  31.                 BSP_RTC_Set_Current(&SYS_RTC);



  32.                 BKP_WriteBackupRegister(BKP_DR1, RTC_SAVE_FLAG);

  33.         }

  34.         else

  35.         {

  36.                 /* Enable PWR and BKP clocks */

  37.                 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);



  38.                 /* Allow access to BKP Domain */

  39.                 PWR_BackupAccessCmd(ENABLE);



  40.                 /* Wait for RTC registers synchronization */

  41.                 RTC_WaitForSynchro();



  42.                 /* Wait until last write operation on RTC registers has finished */

  43.                 RTC_WaitForLastTask();



  44.                 /* Enable the RTC Second */

  45.                 //RTC_ITConfig(RTC_IT_SEC, ENABLE);                                                                // 不能在系统运行前使能中断

  46.                 //RTC_ITConfig(RTC_IT_ALR, ENABLE);                                                                // 系统闹钟中断

  47.                 /* Wait until last write operation on RTC registers has finished */

  48.                 //RTC_WaitForLastTask();                                                                                                        // 不能在系统运行前使能中断



  49.                 /* Initialize Date structure */

  50.                 SYS_RTC.year         = BKP_ReadBackupRegister(BKP_DR4);

  51.                 SYS_RTC.month        = BKP_ReadBackupRegister(BKP_DR3);

  52.                 SYS_RTC.day         = BKP_ReadBackupRegister(BKP_DR2);



  53.                 if(RTC_GetCounter() / 86399 != 0)

  54.                 {

  55.                         for(i = 0; i < (RTC_GetCounter() / 86399); i++)

  56.                         {

  57.                                 BSP_Date_Update(&SYS_RTC);

  58.                         }



  59.                         /* Wait until last write operation on RTC registers has finished */

  60.                         RTC_WaitForLastTask();

  61.                         RTC_SetCounter(RTC_GetCounter() % 86399);

  62.                         /* Wait until last write operation on RTC registers has finished */

  63.                         RTC_WaitForLastTask();



  64.                         BKP_WriteBackupRegister(BKP_DR4, SYS_RTC.year);

  65.                         BKP_WriteBackupRegister(BKP_DR3, SYS_RTC.month);

  66.                         BKP_WriteBackupRegister(BKP_DR2, SYS_RTC.day);

  67.                 }

  68.         }

  69.         /* Clear the RTC Second Interrupt pending bit */

  70.         RTC_ClearITPendingBit(RTC_IT_SEC);                                                                                // 防止系统初始化未完成前进入中断程序

  71.         RTC_ClearFlag(RTC_IT_SEC);



  72.         /* Enable  one second interrupe */

  73.         //RTC_ITConfig(RTC_IT_SEC,        ENABLE);                                                                        // 不能在系统运行前使能中断

  74.         rtcInitFinish        =1;                // 设置初始化完成标志

  75. }
复制代码
  1. void BSP_RTC_Config(void)

  2. {

  3.         //u32 counter = 0;

  4.           uint32_t tmp = 0;

  5.           RCC_ClocksTypeDef RCC_Clocks;

  6.           TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;

  7.           TIM_ICInitTypeDef  TIM_ICInitStructure;



  8.         /* Enable PWR and BKP clocks */

  9.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  10.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

  11.         /* Allow access to BKP Domain */

  12.         PWR_BackupAccessCmd(ENABLE);

  13.         /* Reset Backup Domain */

  14.         BKP_DeInit();

  15.         RCC_LSICmd(ENABLE); //启用LSI       

  16.         while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)

  17.           {}

  18.           RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);

  19.         RCC_RTCCLKCmd(ENABLE);  //  Enable RTC Clock

  20.         RTC_WaitForSynchro();

  21.         RTC_WaitForLastTask();

  22.         RTC_SetPrescaler(40000); // RTC period = RTCCLK/RTC_PR = (4 KHz)/(4000+1) LSI

  23.         RTC_WaitForLastTask();

  24.         BKP_TamperPinCmd(DISABLE);

  25.         BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);

  26.         RCC_GetClocksFreq(&RCC_Clocks);

  27.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);

  28.         GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);

  29.           TIM_TimeBaseStructure.TIM_Prescaler = 0;

  30.           TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;

  31.           TIM_TimeBaseStructure.TIM_Period = 0xFFFF;

  32.           TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;

  33.           TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);

  34.           TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;

  35.           TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;

  36.           TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

  37.           TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;

  38.           TIM_ICInitStructure.TIM_ICFilter = 0;

  39.           TIM_ICInit(TIM5, &TIM_ICInitStructure);

  40.           OperationComplete = 0;

  41.           TIM_Cmd(TIM5, ENABLE);

  42.           TIM5->SR = 0;

  43.         //TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);



  44.           while (OperationComplete != 2)

  45.           {

  46.                   if (TIM_GetFlagStatus(TIM5, TIM_FLAG_CC4) == SET)

  47.             {

  48.                       tmpCC4[IncrementVar_OperationComplete()] = (uint16_t)(TIM5->CCR4);

  49.                       TIM_ClearFlag(TIM5, TIM_FLAG_CC4);

  50.                       if (GetVar_OperationComplete() >= 2)

  51.                       {

  52.                               tmp = (uint16_t)(tmpCC4[1] - tmpCC4[0] + 1);

  53.                               SetVar_PeriodValue(tmp);

  54.                       }

  55.             }

  56.         }

  57.           if (PeriodValue != 0)

  58.           {

  59. #if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)

  60.             LsiFreq = (uint32_t)((uint32_t)(RCC_Clocks.PCLK1_Frequency) / (uint32_t)PeriodValue);

  61. #else

  62.             LsiFreq = (uint32_t)((uint32_t)(RCC_Clocks.PCLK1_Frequency * 2) / (uint32_t)PeriodValue);

  63. #endif

  64.           }

  65.           RTC_SetPrescaler(LsiFreq - 1);

  66.           RTC_WaitForLastTask();



  67.         TIM_DeInit( TIM5 );       

  68. }

复制代码
十、HSE作为RTC时钟源
  1. void RTC_Configuration(void)

  2. {

  3. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

  4. PWR_BackupAccessCmd(ENABLE);

  5. /* Reset Backup Domain */

  6. BKP_DeInit();

  7. //使用外部高速晶振8M/128 = 62.5K

  8. RCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128);

  9. //允许RTC

  10. RCC_RTCCLKCmd(ENABLE);

  11. //等待RTC寄存器同步

  12. RTC_WaitForSynchro();



  13. RTC_WaitForLastTask();

  14. //允许RTC的秒中断(还有闹钟中断和溢出中断可设置)

  15. RTC_ITConfig(RTC_IT_SEC, ENABLE);



  16. RTC_WaitForLastTask();

  17. //62500晶振预分频值是62500,不过一般来说晶振都不那么准

  18. RTC_SetPrescaler(62498); //如果需要校准晶振,可修改此分频值

  19. RTC_WaitForLastTask();

  20. //清除标志

  21. RCC_ClearFlag();

  22. }
复制代码






收藏 评论1 发布时间:2021-7-2 13:11

举报

1个回答
回答时间:2021-7-3 10:25:13
走时准吗?
计数时钟不准 走时也跟着不准吧

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版