STM32中RTC的总结

参考

http://t.csdnimg.cn/Hq1pa
正点原子开发指南

一、简介

在这里插入图片描述
RTC(Real Time Clock,实时时钟)是一种专门用于记录时间的设备或模块,通常作为计算机系统中的一部分存在。其本质是一个计数器,以秒为单位进行计数,可以提供精确的时间信息,并且具有以下特性:

1.提供时间信息: RTC能够提供当前的时间,通常以秒钟数的形式表示,但也可以提供更精细的时间分辨率,如毫秒或微秒级别。
2.持久性: RTC具有持久性,即在MCU(Microcontroller Unit,微控制器单元)掉电后仍然能够继续运行,因此能够确保时间信息的连续性和准确性。
3.低功耗: RTC通常具有低功耗特性,以确保在长时间内使用时消耗的能量较少,这对于依靠电池供电的应用尤为重要,因为它可以延长电池的使用寿命。

总的来说,RTC在许多应用中扮演着关键的角色,特别是在需要准确记录时间并且需要在掉电后继续运行的场景下,如数据记录、日志记录、定时任务等。

(1)常用的RTC方案

在这里插入图片描述
常用的RTC方案包括片上RTC外设方案和独立RTC芯片方案。下面是它们的对比:
片上RTC外设方案:
信息差异: 提供秒/亚秒信号,通常只提供基本的时间信息,如秒、亚秒等。
功耗: 功耗较高,因为RTC外设通常与MCU的其他功能集成在一起,共享同一个电源。
体积: 不占用额外体积,因为RTC外设已经集成在MCU芯片内部。
成本: 成本较低,因为不需要额外购买RTC芯片,只需在选择MCU时考虑是否需要RTC外设。

独立RTC芯片方案:
信息差异: 提供秒信号和日历功能,能够记录日期、月份、年份等日历信息。
功耗: 功耗较低,因为独立RTC芯片通常设计为低功耗模式,能够长时间运行。
体积: 体积较大,因为独立RTC芯片需要额外的空间来安装。
成本: 成本较高,因为需要购买额外的RTC芯片,并且可能需要设计额外的外围电路。
综合来看,选择片上RTC外设方案还是独立RTC芯片方案取决于具体的应用需求和设计考虑:

如果需要基本的时间信息记录,对功耗和成本有较低要求,并且希望节省空间,可以选择片上RTC外设方案。如果需要更丰富的时间信息记录,对功耗和成本有更高要求,并且可以接受额外的空间占用,可以选择独立RTC芯片方案。

(2)BCD码(https://baike.baidu.com/item/BCD%E7%A0%81/826461?fr=aladdin)

BCD码(Binary-Coded Decimal‎)也称二进码十进数,BCD码可分为有权码和无权码两类。
BCD码(Binary-Coded Decimal)是一种用二进制数表示十进制数的编码方式。在BCD码中,每个十进制数位用4位二进制数表示,其取值范围为0000到1001(即0到9的二进制表示),每个十进制数位占据一个字节(8位),方便直接转换为十进制数。

BCD码的优点在于它可以直观地表示十进制数,并且每个十进制数位都可以独立处理,便于进行数字的加减运算和其他数学运算。因此,在实时时钟(RTC)等需要精确记录时间的应用中,常常使用BCD码来存储时间信息。

举例来说,如果要表示十进制数 25,它的BCD码表示为 0010 0101。其中,0010表示十位数 2,0101表示个位数 5。

BCD码的主要缺点是它对数字的表示范围有限,无法直接表示大于9的十进制数,因此在一些高性能、大数据量的应用中可能不够灵活。
在这里插入图片描述

(3)时间戳

时间戳(Timestamp)是指一种表示日期和时间的方式,通常是一个数字或字符串,表示自某个特定起点(通常是某个固定的起始时间,如1970年1月1日午夜格林尼治时间)经过了多长的时间间隔,单位可以是秒、毫秒、微秒等。

常见的时间戳是指 Unix 时间戳,它表示自1970年1月1日午夜格林尼治时间(也称为 Unix 纪元时间)以来经过的秒数。Unix 时间戳是计算机领域中最常用的时间表示方式之一,因为它简单、清晰,并且在不同系统之间具有良好的可移植性。

时间戳的优点在于它不受时区的影响,能够统一地表示全球范围内的时间,便于进行时间的比较和计算。在软件开发、数据库管理、日志记录等领域广泛应用。

举例来说,当前时间戳(以秒为单位)可以通过编程语言或操作系统提供的相关函数获取,例如在 Unix/Linux 系统中可以使用 time() 函数获取当前时间戳。

(4)总结

在这里插入图片描述

二、RTC框图介绍

1. STM32 F1 RTC结构框图

在这里插入图片描述
STM32 F1系列的RTC(Real-Time Clock,实时时钟)模块是一个功能强大的硬件模块,用于提供精确的时间和日期信息,并在设备掉电后保持时间信息。下面是对其结构的详细解释:

1.RTC预分频器:
RTC模块包含一个预分频器,用于将外部时钟源(HSE、LSI、LSE)的频率分频为RTC模块所需的时钟频率。预分频器允许通过配置来调整RTC模块的时钟频率,以满足不同的应用需求。

2. 32位可编程计数器:
RTC模块内部包含一个32位的可编程计数器,用于计算秒数、分钟数、小时数等。这个计数器可以通过配置进行初始化,并在每个时钟周期更新。它允许RTC模块跟踪时间的流逝,以及提供精确的日期和时间信息。

3.待机唤醒:
RTC模块具有待机唤醒功能,可以在设备进入低功耗待机模式时继续运行,以提供持续的时间跟踪和唤醒功能。这使得设备可以在低功耗模式下保持时间信息,并在需要时快速唤醒。

4.RTC控制寄存器与APB1接口:
RTC模块与APB1总线相连,通过RTC控制寄存器可以对RTC模块进行配置和控制。这些寄存器允许软件对RTC模块的各个功能进行设置,包括时钟源选择、预分频器设置、计数器初始化等。

5.三个时钟源:
RTC模块可以使用三种不同的时钟源来提供时钟信号:外部高速时钟(HSE)/ 128、低速内部时钟(LSI,频率为40kHz)和低速外部时钟(LSE,频率为32.768kHz)。这些时钟源中的任何一个都可以被选择,以提供RTC模块所需的时钟信号。

6.RTC工作在后备区域:
RTC模块通常工作在设备的后备区域,这意味着即使设备的主要电源VDD掉电,RTC模块仍然可以正常工作,并且可以继续提供时间跟踪和唤醒功能。这种特性对于需要持续跟踪时间的应用非常有用,如实时时钟、定时器和报警系统等。

2. STM32 F4 / F7 / H7 RTC结构框图

在这里插入图片描述
STM32 F4、F7和H7系列的RTC(Real-Time Clock,实时时钟)模块提供了更多的功能和灵活性,用于提供精确的时间和日期信息,并在设备掉电后保持时间信息。以下是对其结构的详细解释:

1.RTC时钟源:
RTC模块可以使用多种时钟源来提供时钟信号,包括外部低速时钟(LSE,频率为32.768kHz)、外部高速时钟(HSE_RTC)和低速内部时钟(LSI)。这些时钟源中的任何一个都可以被选择,以提供RTC模块所需的时钟信号。

2.预分频器:
RTC模块包含异步预分频器和同步预分频器,用于将时钟源的频率分频为RTC模块所需的时钟频率。预分频器允许通过配置来调整RTC模块的时钟频率,以满足不同的应用需求。

3.亚秒寄存器:
RTC模块包含亚秒寄存器,用于提供更精确的时间跟踪。亚秒寄存器可以在每秒钟产生多个时钟周期,以提供更高的时间分辨率。

4.唤醒预分频器和自动重载寄存器:
RTC模块具有唤醒预分频器和唤醒自动重载寄存器,用于配置唤醒功能并在设备进入低功耗模式时继续运行。唤醒预分频器和自动重载寄存器可以设置唤醒周期,并在达到指定的唤醒时间时产生中断。

5.RTC和日历:
RTC模块不仅提供了时间跟踪功能,还提供了日历功能,可以跟踪年、月、日、小时、分钟和秒等日期信息。这些信息可以在RTC模块的寄存器中存储,并可以通过读取和写入操作进行访问和更新。

6.时间戳:
RTC模块还支持时间戳功能,可以用来记录事件发生的时间。时间戳可以记录时间和日期信息,并在需要时读取和处理。

7.闹钟和入侵检测:
RTC模块具有闹钟功能,可以设置闹钟触发时间,并在达到指定的闹钟时间时产生中断。此外,RTC模块还支持入侵检测功能,可以检测外部事件的发生,并在检测到入侵事件时产生中断。

8.备份和RTC入侵控制寄存器:
RTC模块具有备份寄存器和RTC入侵控制寄存器,用于存储和配置RTC模块的备份数据和入侵检测功能。这些寄存器可以用来存储设备的配置信息、用户数据等,并在需要时进行读取和更新。

3.H7

在这里插入图片描述
① 时钟源
STM32H743 的 RTC 时钟源(RTCCLK)通过时钟控制器,可以从 LSE 时钟、LSI 时钟以及 HSE 时钟三者中选择其一(通过设置 RCC_BDCR 寄存器选择)。一般我们选择 LSE,即外部 32.768Khz 晶振作为时钟源(RTCCLK)。外部晶振具有精度高的优点。LSI 是 STM32 芯片内部的低速 RC 振荡器,频率约 32 kHz,缺点是精度较低,可能会有温漂,所以一般不建议使用。比如当没有外部低速晶振(32.768Khz)的时候,分频后的 HSE 可以作为备选使用的时钟源。

② 预分频器
在这里插入图片描述
在这里插入图片描述
③ 时间和日期相关寄存器
该部分包括三个影子寄存器,RTC_SSR(亚秒)、RTC_TR(时间)、RTC_DR(日期)。实时时钟一般表示为:时/分/秒/亚秒。RTC_TR 寄存器用于存储时/分/秒时间数据,可读可写(即可设置或者获取时间)。RTC_DR 寄存器用于存储日期数据,包括年/月/日/星期,可读可写(即可设置或者获取日期)。RTC_SSR 寄存器用于存储亚秒级的时间,这样我们可以获取更加精确的时间数据。

这三个影子寄存器可以通过与 PCLK4(APB4 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。
每隔 2 个 RTCCLK 周期,当前日历值便会复制到影子寄存器,并置位 RTC_ISR 寄存器的RSF位。我们可以读取 RTC_TR和 RTC_DR来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以 BCD 码的格式存储的,读出来要转换一下,才可以得到十进制的数据。

④ 可编程闹钟
STM32H743 提供两个可编程闹钟:闹钟 A(ALARM_A)和闹钟 B(ALARM_B)。通过RTC_CR 寄存器的 ALRAE 和 ALRBE 位置 1 来使能闹钟。当亚秒、秒、分、小时、日期分别与闹钟寄存器 RTC_ALRMASSR/RTC_ALRMAR 和 RTC_ALRMBSSR/RTC_ALRMBR中的值匹配时,则可以产生闹钟(需要适当配置)。本章我们将利用闹钟 A 产生闹铃,即设置RTC_ALRMASSR 和 RTC_ALRMAR 即可。

⑤ 周期性自动唤醒
STM32H743 的 RTC 不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个 16 位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒。
我们可以通过 RTC_CR 寄存器中的 WUTE 位设置使能此唤醒功能。
唤醒定时器的时钟输入可以是:2、4、8 或 16 分频的 RTC 时钟(RTCCLK),也可以是ck_spre 时钟(一般为 1Hz)。
当选择 RTCCLK(假定 LSE 是:32.768 kHz)作为输入时钟时,可配置的唤醒中断周期介于122us(因为 RTCCLK/2 时,RTC_WUTR 不能设置为 0)和 32 s 之间,分辨率最低为:61us。
当选择 ck_spre(1Hz)作为输入时钟时,可得到的唤醒时间为 1s 到 36h 左右,分辨率为 1 秒。并且这个 1s~36h 的可编程时间范围分为两部分:
当 WUCKSEL[2:1]=10 时为:1s 到 18h。
当 WUCKSEL[2:1]=11 时约为:18h 到 36h。
在后一种情况下,会将 2^16 添加到 16 位计数器当前值(即扩展到 17 位,相当于最高位用 WUCKSEL [1]代替)。
初始化完成后,定时器开始递减计数。在低功耗模式下使能唤醒功能时,递减计数保持有效。此外,当计数器计数到 0 时,RTC_ISR 寄存器的 WUTF 标志会置 1,并且唤醒寄存器会使用其重载值(RTC_WUTR 寄存器值)动重载,之后必须用软件清零 WUTF 标志。
通过将 RTC_CR寄存器中的 WUTIE 位置 1来使能周期性唤醒中断时,可以使 STM32H743退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒 STM32H743。

4.总结

在这里插入图片描述

三、RTC相关寄存器介绍

1.RTC基本配置步骤

在这里插入图片描述
正确配置STM32的RTC模块是确保其正常运行的关键步骤之一。以下是基本的RTC配置步骤:
1.使能对RTC的访问:
首先,需要通过RCC寄存器使能对PWR(Power Control,电源控制)和BKP(Backup Registers,后备寄存器)的时钟访问权限。在PWR_CR寄存器中设置特定位来使能对后备寄存器和RTC的访问权限。

2.设置RTC时钟源:
接下来,需要选择RTC的时钟源。通常情况下,选择外部低速晶体振荡器(LSE,32.768kHz)作为RTC的时钟源。要启用LSE,需要在RCC_BDCR寄存器中设置相应的位来激活LSE,并将RTC的计数时钟源设置为LSE。

3.进入配置模式:
在对RTC进行配置之前,需要将RTC模块设置为配置模式。这可以通过设置RTC_CRL寄存器中的特定位来实现。等待RTOFF位变为1,并将CNF位设置为1,以进入配置模式。

4.设置RTC寄存器:
一旦进入配置模式,可以设置RTC寄存器以配置RTC的各种参数。例如,设置RTC的预分频器寄存器(RTC_PRL)来设置分频值,设置RTC的计数器寄存器(RTC_CNT)来设置初始计数值等。

5.退出配置模式:
配置完成后,需要退出配置模式以使RTC模块正常工作。这可以通过清除RTC_CRL寄存器中的CNF位来实现。然后等待RTOFF位变为1,表明RTC退出配置模式,配置完成。

总的来说,以上步骤描述了如何使能和配置STM32的RTC模块,确保其正常运行并提供准确的时间跟踪和日期信息。在配置完成后,RTC模块将能够持续跟踪时间并在需要时提供准确的时间和日期信息。

2.RTC相关寄存器(F1)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.RTC相关寄存器(F4 / F7 / H7)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.总结

在这里插入图片描述

四、相关HAL库驱动介绍

1.RTC相关HAL库驱动(F1)

在这里插入图片描述
针对STM32F1系列的RTC相关HAL库驱动函数和相关寄存器功能描述如下:

1.HAL_RTC_Init(…):
关联寄存器:CRL/CRH/PRLH/PRLL
功能描述:该函数用于初始化RTC模块,包括配置RTC的控制寄存器(CRL/CRH)以及设置RTC的预分频寄存器(PRLH/PRLL)。

2.HAL_RTC_MspInit(…)
功能描述:这是RTC的初始化回调函数,在其中需要使能RTC所需的时钟和外设。
相关操作:通常在该函数中需要调用HAL_RCC_OscConfig(…)函数开启LSE时钟源,并调用HAL_RCCEx_PeriphCLKConfig(…)函数设置RTC的时钟源为LSE。

3.HAL_RCC_OscConfig(…)
关联寄存器:RCC_CR/PWR_CR
功能描述:该函数用于配置时钟源,一般用于开启LSE时钟源。

4.HAL_RCCEx_PeriphCLKConfig(…)
关联寄存器:RCC_BDCR
功能描述:该函数用于配置外设时钟,用于设置RTC的时钟源为LSE。

5.HAL_PWR_EnableBkUpAccess(…)
关联寄存器:PWR_CR
功能描述:该函数用于使能备份域的访问权限,以便可以读写备份寄存器。

6.HAL_RTCEx_BKUPWrite/Read()
关联寄存器:BKP_DRx
功能描述:这些函数用于读写RTC的备份寄存器,用于存储一些特定的数据或参数。

在使用RTC之前,需要确保已经开启了相应的时钟源。通常情况下,需要开启RTC的时钟源(LSE)以及相应的电源时钟,可以通过调用相关的宏或函数来实现:

__HAL_RCC_RTC_ENABLE():使能RTC时钟
__HAL_RCC_PWR_CLK_ENABLE():使能PWR模块时钟
__HAL_RCC_BKP_CLK_ENABLE():使能BKP模块时钟

通过以上的函数和操作,可以实现对STM32F1系列的RTC模块进行初始化和配置,确保其正常运行并提供准确的时间跟踪和日期信息。

2.RTC相关HAL库驱动(F4 / F7 / H7)

在这里插入图片描述
针对STM32F4/F7/H7系列的RTC日历功能,以下是相关的HAL库驱动函数及其功能描述:

1.HAL_RTC_Init(…):
关联寄存器:RTC_CR、RTC_PRER、RTC_WPR
功能描述:初始化RTC模块,包括配置RTC控制寄存器(RTC_CR)、预分频寄存器(RTC_PRER)以及写保护寄存器(RTC_WPR)。

2.HAL_RTC_MspInit(…)
功能描述:RTC的初始化回调函数,在其中需要使能RTC所需的时钟和外设。
相关操作:通常需要调用HAL_RCC_OscConfig(…)函数开启LSE时钟源,并调用HAL_RCCEx_PeriphCLKConfig(…)函数设置RTC的时钟源为LSE。

3.HAL_RCC_OscConfig(…)
关联寄存器:RCC_CR
功能描述:配置时钟源,用于开启LSE时钟源。

4.HAL_RCCEx_PeriphCLKConfig(…)
关联寄存器:RCC_BDCR
功能描述:配置外设时钟,用于设置RTC的时钟源为LSE。

5.HAL_RTC_Set/GetTime(…)
关联寄存器:RTC_TR
功能描述:设置/读取时间,包括小时、分钟和秒。

6.HAL_RTC_Set/GetDate(…)
关联寄存器:RTC_DR
功能描述:设置/读取日期,包括年、月、日和星期。

7.HAL_PWR_EnableBkUpAccess(…)
关联寄存器:PWR_CR
功能描述:使能备份域的访问权限,以便可以读写备份寄存器。

8.HAL_RTCEx_BKUPWrite/Read()
关联寄存器:RTC_BKPxR
功能描述:这些函数用于读写RTC的备份寄存器,用于存储一些特定的数据或参数。

在使用RTC之前,需要确保已经开启了相应的时钟源。可以通过调用以下宏或函数来实现:

__HAL_RCC_RTC_CLK_ENABLE():使能RTC时钟
__HAL_RCC_RTC_ENABLE():使能RTC模块

通过以上的函数和操作,可以实现对STM32F4/F7/H7系列的RTC模块进行初始化和配置,以及设置和读取时间日期信息,从而实现日历功能。

在这里插入图片描述
在这里插入图片描述

3.总结

在这里插入图片描述

五、RTC基本驱动步骤

1.RTC基本驱动步骤(F1)

在这里插入图片描述
针对STM32F1系列的RTC基本驱动步骤,以下是一般性的流程:
1.使能电源时钟并使能后备域访问:
使用宏或函数使能电源时钟和备份时钟,确保后续可以对RTC的后备域进行访问:

__HAL_RCC_PWR_CLK_ENABLE();       // 使能电源时钟
__HAL_RCC_BKP_CLK_ENABLE();       // 使能备份时钟
HAL_PWR_EnableBkUpAccess();       // 使能备份访问

2.开启LSE/选择RTC时钟源/使能RTC时钟:
配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:

HAL_RCC_OscConfig(...);                  // 开启LSE
HAL_RCCEx_PeriphCLKConfig(...);  // 选择RTC时钟源
__HAL_RCC_RTC_ENABLE();               // 使能RTC时钟

3.初始化RTC,设置分频值以及工作参数:
调用HAL_RTC_Init()函数初始化RTC模块,并设置分频值、时钟输出使能等参数:

HAL_RTC_Init(...);            // 初始化RTC
HAL_RTC_MspInit(...);     // 完成RTC底层初始化工作

4.设置RTC的日期和时间:
使用相应的寄存器操作函数,设置RTC的日期和时间信息:

rtc_set_time(...);           // 操作寄存器方式设置RTC的日期和时间

5.获取RTC当前日期和时间:
定义相应的函数,使用寄存器读取当前RTC的日期和时间信息:

rtc_get_time(...);           // 定义函数读取RTC的当前日期和时间

2.RTC基本驱动步骤(F4 / F7 / H7)

在这里插入图片描述
针对STM32F4/F7/H7系列的RTC基本驱动步骤,以下是一般性的流程:
1.使能电源时钟并使能后备域访问:
使用宏或函数使能PWR时钟,并使能备份域访问权限:

__HAL_RCC_PWR_CLK_ENABLE();        // 使能PWR时钟
HAL_PWR_EnableBkUpAccess();        // 使能备份访问

2.开启LSE/选择RTC时钟源/使能RTC:
配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:

HAL_RCC_OscConfig(...);                   // 开启LSE
HAL_RCCEx_PeriphCLKConfig(...);   // 选择RTC时钟源
__HAL_RCC_RTC_CLK_ENABLE();          // 使能RTC时钟

3.初始化RTC(同/异步分频系数和时钟格式):
调用HAL_RTC_Init()函数初始化RTC模块,设置同步/异步分频系数和时钟格式等参数:

HAL_RTC_Init(...);              // 初始化RTC
HAL_RTC_MspInit(...);       // 完成RTC底层初始化工作

4.设置RTC的日期和时间:
使用HAL_RTC_SetTime()和HAL_RTC_SetDate()函数设置RTC的时间和日期:

HAL_RTC_SetTime(...);      // 设置RTC时间
HAL_RTC_SetDate(...);      // 设置RTC日期

5.获取RTC当前日期和时间:
使用HAL_RTC_GetTime()和HAL_RTC_GetDate()函数获取当前RTC的时间和日期:

HAL_RTC_GetTime(...);      // 获取当前RTC时间
HAL_RTC_GetDate(...);      // 获取当前RTC日期

六、时间设置和读取

1.时间设置和读取(F1)

在这里插入图片描述
针对STM32F1系列的RTC,由于其没有日历寄存器,只能存储总秒数,因此需要进行一些额外的操作才能将总秒数转换为日历时间,并提供一些功能函数来处理日历时间。下面是一些相关的函数和结构体说明:
1.全局结构体变量 calendar 存储时间信息:
在代码中通常会定义一个结构体变量用于存储日历时间的各个参数,比如年、月、日、时、分、秒等信息。

2.rtc_date2sec() 函数:
这是一个静态函数,用于将日历时间转换为对应的总秒数。该函数会根据输入的年、月、日、时、分、秒等参数计算出对应的总秒数。

3.rtc_get_time() 函数:
这个函数的功能是将总秒数转换为日历时间。它会根据给定的总秒数,计算出对应的年、月、日、时、分、秒等时间信息,并将结果存储到结构体变量 calendar 中。

4.rtc_is_leap_year() 函数:
这个函数用于判断给定的年份是否是闰年。

5.rtc_get_week() 函数:
这个函数用于计算给定的公历日历日期对应的星期几。

通过这些功能函数,您可以方便地进行时间的设置和读取,以及一些相关的时间计算操作,使得在STM32F1系列芯片上使用RTC更加便捷有效。

2.RTC闹钟配置 和 RTC周期性自动唤醒配置(F4 / F7 / H7)

在这里插入图片描述
RTC闹钟配置步骤,以下是更详细的说明:
1.RTC已初始化:
在配置RTC闹钟之前,确保RTC已经初始化并且相关参数已经设置好。

2.配置闹钟参数并开启中断:
使用HAL_RTC_SetAlarm_IT()函数配置闹钟参数并开启中断:

HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);

3.设置闹钟中断优先级:
使用HAL_NVIC_SetPriority()函数设置闹钟中断的优先级:

HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);

4.使能闹钟中断:
使用HAL_NVIC_EnableIRQ()函数使能闹钟中断:

HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);

5.编写中断服务函数:
定义一个中断服务函数,用于处理RTC闹钟中断事件:

void RTC_Alarm_IRQHandler(void)
{
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

6.HAL库RTC中断共用处理函数:
HAL库提供了RTC中断共用处理函数HAL_RTC_AlarmIRQHandler(),在中断服务函数中调用该函数:

HAL_RTC_AlarmIRQHandler(&hrtc);

7.重定义中断回调函数:
如果需要在闹钟中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数

HAL_RTC_AlarmAEventCallback()void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    // 在此处编写闹钟中断发生时的操作
}

以上是RTC闹钟配置的一般步骤和相应的操作说明。通过这些步骤,您可以实现RTC闹钟的配置和中断处理功能。

RTC周期性自动唤醒配置一般步骤
在这里插入图片描述
RTC周期性自动唤醒配置步骤,以下是更详细的说明:

1.RTC已初始化:
在配置RTC周期性自动唤醒功能之前,确保RTC已经初始化并且相关参数已经设置好。

2.清除RTC WKUP标志位:
使用__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG()函数清除RTC的WKUP标志位:

__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);

3.设置闹钟参数并开启中断:
使用HAL_RTCEx_SetWakeUpTimer_IT()函数设置周期性自动唤醒的时间间隔并开启中断:

HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeup_time, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);

4.设置中断优先级:
使用HAL_NVIC_SetPriority()函数设置RTC周期性自动唤醒中断的优先级:

HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);

5.使能中断:
使用HAL_NVIC_EnableIRQ()函数使能RTC周期性自动唤醒中断:

HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);

6.编写中断处理逻辑:
定义一个中断服务函数,用于处理RTC周期性自动唤醒中断事件:

void RTC_WKUP_IRQHandler(void)
{
    HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
}

7.HAL库RTC中断共用处理函数:
HAL库提供了RTC周期性自动唤醒中断共用处理函数HAL_RTCEx_WakeUpTimerIRQHandler(),在中断服务函数中调用该函数:

HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);

8.重定义中断回调函数:
如果需要在周期性自动唤醒中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数HAL_RTCEx_WakeUpTimerEventCallback():

void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    // 在此处编写周期性自动唤醒中断发生时的操作
}

以上是RTC周期性自动唤醒配置的一般步骤和相应的操作说明。通过这些步骤,您可以实现RTC周期性自动唤醒功能,并在中断发生时执行相应的操作。
在这里插入图片描述

七、相关实验

在这里插入图片描述

1.F1-RTC

rtc.c

#include "./BSP/RTC/rtc.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"


RTC_HandleTypeDef g_rtc_handle; /* RTC控制句柄 */
_calendar_obj calendar;         /* 时间结构体 */

/**
 * @brief       RTC写入后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~41
                        对应 RTC_BKP_DR1~RTC_BKP_DR42
 * @param       data : 要写入的数据,16位长度
 * @retval      无
 */
void rtc_write_bkr(uint32_t bkrx, uint16_t data)
{
    HAL_PWR_EnableBkUpAccess(); /* 取消备份区写保护 */
    HAL_RTCEx_BKUPWrite(&g_rtc_handle, bkrx + 1, data);
}

/**
 * @brief       RTC读取后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~41
                对应 RTC_BKP_DR1~RTC_BKP_DR42
 * @retval      读取到的值
 */
uint16_t rtc_read_bkr(uint32_t bkrx)
{
    uint32_t temp = 0;
    temp = HAL_RTCEx_BKUPRead(&g_rtc_handle, bkrx + 1);
    return (uint16_t)temp; /* 返回读取到的值 */
}

/**
 * @brief       RTC初始化
 *   @note
 *              默认尝试使用LSE,当LSE启动失败后,切换为LSI.
 *              通过BKP寄存器0的值,可以判断RTC使用的是LSE/LSI:
 *              当BKP0==0X5050时,使用的是LSE
 *              当BKP0==0X5051时,使用的是LSI
 *              注意:切换LSI/LSE将导致时间/日期丢失,切换后需重新设置.
 *
 * @param       无
 * @retval      0,成功
 *              1,进入初始化模式失败
 */
uint8_t rtc_init(void)
{
    /* 检查是不是第一次配置时钟 */
    uint16_t bkpflag = 0;

    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能PWR电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能BKP备份时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份区写保护 */
    
    g_rtc_handle.Instance = RTC;
    g_rtc_handle.Init.AsynchPrediv = 32767;     /* 时钟周期设置,理论值:32767, 这里也可以用 RTC_AUTO_1_SECOND */
    g_rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
    if (HAL_RTC_Init(&g_rtc_handle) != HAL_OK)  /* 初始化RTC */
    {
        return 1;
    }
    
    bkpflag = rtc_read_bkr(0);  /* 读取BKP0的值 */
    if ((bkpflag != 0X5050) && (bkpflag != 0x5051))         /* 之前未初始化过,重新配置 */
    {
        rtc_set_time(2020, 4, 25, 20, 25, 35);              /* 设置时间 */
    }

    __HAL_RTC_ALARM_ENABLE_IT(&g_rtc_handle, RTC_IT_SEC);   /* 允许秒中断 */
    __HAL_RTC_ALARM_ENABLE_IT(&g_rtc_handle, RTC_IT_ALRA);  /* 允许闹钟中断 */
    
    HAL_NVIC_SetPriority(RTC_IRQn, 0x2, 0);                 /* 设置RTC中断 */
    HAL_NVIC_EnableIRQ(RTC_IRQn);                           /* 使能中断 */
    
    rtc_get_time(); /* 更新时间 */
    
    return 0;
}

/**
 * @brief       RTC初始化
 *   @note
 *              RTC底层驱动,时钟配置,此函数会被HAL_RTC_Init()调用
 * @param       hrtc:RTC句柄
 * @retval      无
 */
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
    uint16_t retry = 200;
    
    __HAL_RCC_RTC_ENABLE();     /* RTC时钟使能 */

    RCC_OscInitTypeDef rcc_oscinitstruct;
    RCC_PeriphCLKInitTypeDef rcc_periphclkinitstruct;
    
    /* 使用寄存器的方式去检测LSE是否可以正常工作 */
    RCC->BDCR |= 1 << 0;    /* 开启外部低速振荡器LSE */
    
    while (retry && ((RCC->BDCR & 0X02) == 0))  /* 等待LSE准备好 */
    {
        retry--;
        delay_ms(5);
    }

    if (retry == 0)     /* LSE起振失败 使用LSI */
    {
        rcc_oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;  /* 选择要配置的振荡器 */
        rcc_oscinitstruct.LSIState = RCC_LSI_ON;                    /* LSI状态:开启 */
        rcc_oscinitstruct.PLL.PLLState = RCC_PLL_NONE;              /* PLL无配置 */
        HAL_RCC_OscConfig(&rcc_oscinitstruct);                      /* 配置设置的rcc_oscinitstruct */

        rcc_periphclkinitstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;   /* 选择要配置的外设 RTC */
        rcc_periphclkinitstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;   /* RTC时钟源选择 LSI */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclkinitstruct);                /* 配置设置的rcc_periphClkInitStruct */
        rtc_write_bkr(0, 0X5051);
    }
    else
    {
        rcc_oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_LSE ; /* 选择要配置的振荡器 */
        rcc_oscinitstruct.LSEState = RCC_LSE_ON;                    /* LSE状态:开启 */
        rcc_oscinitstruct.PLL.PLLState = RCC_PLL_NONE;              /* PLL不配置 */
        HAL_RCC_OscConfig(&rcc_oscinitstruct);                      /* 配置设置的rcc_oscinitstruct */

        rcc_periphclkinitstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;   /* 选择要配置外设 RTC */
        rcc_periphclkinitstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;   /* RTC时钟源选择LSE */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclkinitstruct);                /* 配置设置的rcc_periphclkinitstruct */
        rtc_write_bkr(0, 0X5055);
    }
}

/**
 * @brief       RTC时钟中断
 *   @note      秒钟中断 / 闹钟中断 共用同一个中断服务函数
 *              根据RTC_CRL寄存器的 SECF 和 ALRF 位区分是哪个中断
 * @param       无
 * @retval      无
 */
void RTC_IRQHandler(void)
{
    if (__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_SEC) != RESET)     /* 秒中断 */
    {
        rtc_get_time();     /* 更新时间 */
        __HAL_RTC_ALARM_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_SEC);            /* 清除秒中断 */
        //printf("sec:%d\r\n", calendar.sec);   /* 打印秒钟 */
    }

    if (__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_ALRAF) != RESET)   /* 闹钟中断 */
    {
        __HAL_RTC_ALARM_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_ALRAF);          /* 清除闹钟中断 */
        printf("Alarm Time:%d-%d-%d %d:%d:%d\n", calendar.year, calendar.month, calendar.date, calendar.hour, calendar.min, calendar.sec);
    }

    __HAL_RTC_ALARM_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_OW);                 /* 清除溢出中断标志 */
    
    while (!__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
}

/**
 * @brief       判断年份是否是闰年
 *   @note      月份天数表:
 *              月份   1  2  3  4  5  6  7  8  9  10 11 12
 *              闰年   31 29 31 30 31 30 31 31 30 31 30 31
 *              非闰年 31 28 31 30 31 30 31 31 30 31 30 31
 * @param       year : 年份
 * @retval      0, 非闰年; 1, 是闰年;
 */
static uint8_t rtc_is_leap_year(uint16_t year)
{
    /* 闰年规则: 四年闰百年不闰,四百年又闰 */
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
 * @brief       设置时间, 包括年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
                HAL默认为年份起点为2000年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
uint8_t rtc_set_time(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;

    seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */

    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 上面三步是必须的! */
    
    RTC->CRL |= 1 << 4;         /* 进入配置模式 */
    
    RTC->CNTL = seccount & 0xffff;
    RTC->CNTH = seccount >> 16;
    
    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */

    while (!__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */

    return 0;
}

/**
 * @brief       设置闹钟, 具体到年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
uint8_t rtc_set_alarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;

    seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */

    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 上面三步是必须的! */
    
    RTC->CRL |= 1 << 4;         /* 进入配置模式 */
    
    RTC->ALRL = seccount & 0xffff;
    RTC->ALRH = seccount >> 16;
    
    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */

    while (!__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */

    return 0;
}

/**
 * @brief       得到当前的时间
 *   @note      该函数不直接返回时间, 时间数据保存在calendar结构体里面
 * @param       无
 * @retval      无
 */
void rtc_get_time(void)
{
    static uint16_t daycnt = 0;
    uint32_t seccount = 0;
    uint32_t temp = 0;
    uint16_t temp1 = 0;
    const uint8_t month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; /* 平年的月份日期表 */

    seccount = RTC->CNTH; /* 得到计数器中的值(秒钟数) */
    seccount <<= 16;
    seccount += RTC->CNTL;

    temp = seccount / 86400; /* 得到天数(秒钟数对应的) */

    if (daycnt != temp) /* 超过一天了 */
    {
        daycnt = temp;
        temp1 = 1970;   /* 从1970年开始 */

        while (temp >= 365)
        {
            if (rtc_is_leap_year(temp1)) /* 是闰年 */
            {
                if (temp >= 366)
                {
                    temp -= 366;    /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                temp -= 365;        /* 平年 */
            }

            temp1++;
        }

        calendar.year = temp1;      /* 得到年份 */
        temp1 = 0;

        while (temp >= 28)      /* 超过了一个月 */
        {
            if (rtc_is_leap_year(calendar.year) && temp1 == 1) /* 当年是不是闰年/2月份 */
            {
                if (temp >= 29)
                {
                    temp -= 29; /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                if (temp >= month_table[temp1])
                {
                    temp -= month_table[temp1]; /* 平年 */
                }
                else
                {
                    break;
                }
            }

            temp1++;
        }

        calendar.month = temp1 + 1; /* 得到月份 */
        calendar.date = temp + 1;   /* 得到日期 */
    }

    temp = seccount % 86400;                                                    /* 得到秒钟数 */
    calendar.hour = temp / 3600;                                                /* 小时 */
    calendar.min = (temp % 3600) / 60;                                          /* 分钟 */
    calendar.sec = (temp % 3600) % 60;                                          /* 秒钟 */
    calendar.week = rtc_get_week(calendar.year, calendar.month, calendar.date); /* 获取星期 */
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      输入公历日期得到星期(起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期)
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @retval      0, 星期天; 1 ~ 6: 星期一 ~ 星期六
 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint8_t week = 0;

    if (month < 3)
    {
        month += 12;
        --year;
    }

    week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7;
    return week;
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      以1970年1月1日为基准, 1970年1月1日, 0时0分0秒, 表示第0秒钟
 *              最大表示到2105年, 因为uint32_t最大表示136年的秒钟数(不包括闰年)!
 *              本代码参考只linux mktime函数, 原理说明见此贴:
 *              http://www.openedv.com/thread-63389-1-1.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      转换后的秒钟数
 */
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t Y, M, D, X, T;
    signed char monx = smon;    /* 将月份转换成带符号的值, 方便后面运算 */

    if (0 >= (monx -= 2))       /* 1..12 -> 11,12,1..10 */
    {
        monx += 12; /* Puts Feb last since it has leap day */
        syear -= 1;
    }

    Y = (syear - 1) * 365 + syear / 4 - syear / 100 + syear / 400; /* 公元元年1到现在的闰年数 */
    M = 367 * monx / 12 - 30 + 59;
    D = sday - 1;
    X = Y + M + D - 719162;                      /* 减去公元元年到1970年的天数 */
    T = ((X * 24 + hour) * 60 + min) * 60 + sec; /* 总秒钟数 */
    return T;
}

rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "./SYSTEM/sys/sys.h"


/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    /* 公历年月日周 */
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
} _calendar_obj;

extern _calendar_obj calendar;                      /* 时间结构体 */

/* 静态函数 */
static uint8_t rtc_is_leap_year(uint16_t year);     /* 判断当前年份是不是闰年 */
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);   /* 将年月日时分秒转换成秒钟数 */

/* 接口函数 */
uint8_t rtc_init(void);                             /* 初始化RTC */
void rtc_get_time(void);                            /* 获取RTC时间信息 */
uint16_t rtc_read_bkr(uint32_t bkrx);               /* 读取后备寄存器 */
void rtc_write_bkr(uint32_t bkrx, uint16_t data);   /* 写后备寄存器 */ 
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day);    /* 根据年月日获取星期几 */
uint8_t rtc_set_time(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);   /* 设置时间 */
uint8_t rtc_set_alarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);  /* 设置闹钟时间 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/RTC/rtc.h"

/* 定义字符数组用于显示周 */
char* weekdays[]={"Sunday","Monday","Tuesday","Wednesday",
                  "Thursday","Friday","Saterday"};

int main(void)
{
    uint8_t tbuf[40];
    uint8_t t = 0;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
    delay_init(72);                         /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    usmart_dev.init(72);                    /* 初始化USMART */
    led_init();                             /* 初始化LED */
    lcd_init();                             /* 初始化LCD */
    rtc_init();                             /* 初始化RTC */

    rtc_set_alarm(2020, 4, 26, 9, 23, 45);  /* 设置一次闹钟 */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "RTC TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    while (1)
    {
        t++;

        if ((t % 10) == 0)                  /* 每100ms更新一次显示数据 */
        {
            rtc_get_time();
            sprintf((char *)tbuf, "Time:%02d:%02d:%02d", calendar.hour, calendar.min, calendar.sec);
            lcd_show_string(30, 120, 210, 16, 16, (char *)tbuf, RED);
            sprintf((char *)tbuf, "Date:%04d-%02d-%02d", calendar.year, calendar.month, calendar.date);
            lcd_show_string(30, 140, 210, 16, 16, (char *)tbuf, RED);
            sprintf((char *)tbuf, "Week:%s", weekdays[calendar.week]);
            lcd_show_string(30, 160, 210, 16, 16, (char *)tbuf, RED);
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();              /* 每200ms,翻转一次LED0 */
        }

        delay_ms(10);
    }
}

2.H750-RTC

rtc.c

#include "./BSP/RTC/rtc.h"
#include "./BSP/LED/led.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"


RTC_HandleTypeDef g_rtc_handle;  /* RTC句柄 */

/**
 * @brief       RTC写入后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~31
                对应 RTC_BKP_DR0~RTC_BKP_DR31
 * @param       data : 要写入的数据,32位长度
 * @retval      无
 */
void rtc_write_bkr(uint32_t bkrx, uint32_t data)
{
    HAL_PWR_EnableBkUpAccess();     /* 取消备份区写保护 */
    HAL_RTCEx_BKUPWrite(&g_rtc_handle, bkrx, data);
}

/**
 * @brief       RTC读取后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~31 
                对应 RTC_BKP_DR0~RTC_BKP_DR31
 * @retval      读取到的值
 */
uint32_t rtc_read_bkr(uint32_t bkrx)
{
    uint32_t temp = 0;
    temp = HAL_RTCEx_BKUPRead(&g_rtc_handle, bkrx);
    return (uint16_t)temp;      /* 返回读取到的值 */
}

/**
 * @brief       RTC时间设置
 * @param       hour,min,sec: 小时,分钟,秒钟 
 * @param       ampm        : AM/PM, 0=AM/24H; 1=PM/12H;
 * @retval      0,成功
 *              !0,异常状态
 */
HAL_StatusTypeDef rtc_set_time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t ampm)
{
    RTC_TimeTypeDef rtc_time_handle;

    rtc_time_handle.Hours = hour;
    rtc_time_handle.Minutes = min;
    rtc_time_handle.Seconds = sec;
    rtc_time_handle.TimeFormat = ampm;
    rtc_time_handle.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    rtc_time_handle.StoreOperation = RTC_STOREOPERATION_RESET;
    return HAL_RTC_SetTime(&g_rtc_handle, &rtc_time_handle, RTC_FORMAT_BIN);
}

/**
 * @brief       RTC日期设置
 * @param       year,month,date : 年(0~99),月(1~12),日(0~31)
 * @param       week            : 星期(1~7,0,非法!)
 * @retval      0,成功
 *              !0,异常状态
 */
HAL_StatusTypeDef rtc_set_date(uint8_t year, uint8_t month, uint8_t date, uint8_t week)
{
    RTC_DateTypeDef rtc_date_handle;

    rtc_date_handle.Date = date;
    rtc_date_handle.Month = month;
    rtc_date_handle.WeekDay = week;
    rtc_date_handle.Year = year;
    return HAL_RTC_SetDate(&g_rtc_handle, &rtc_date_handle, RTC_FORMAT_BIN);
}

/**
 * @brief       获取RTC时间
 * @param       *hour,*min,*sec : 小时,分钟,秒钟
 * @param       *ampm           : AM/PM,0=AM/24H,1=PM.
 * @retval      无
 */
void rtc_get_time(uint8_t *hour, uint8_t *min, uint8_t *sec, uint8_t *ampm)
{
    RTC_TimeTypeDef rtc_time_handle;

    HAL_RTC_GetTime(&g_rtc_handle, &rtc_time_handle, RTC_FORMAT_BIN);

    *hour = rtc_time_handle.Hours;
    *min = rtc_time_handle.Minutes;
    *sec = rtc_time_handle.Seconds;
    *ampm = rtc_time_handle.TimeFormat;
}

/**
 * @brief       获取RTC日期
 * @param       *year,*mon,*date: 年,月,日
 * @param       *week           : 星期
 * @retval      无
 */
void rtc_get_date(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *week)
{
    RTC_DateTypeDef rtc_date_handle;

    HAL_RTC_GetDate(&g_rtc_handle, &rtc_date_handle, RTC_FORMAT_BIN);

    *year = rtc_date_handle.Year;
    *month = rtc_date_handle.Month;
    *date = rtc_date_handle.Date;
    *week = rtc_date_handle.WeekDay;
}

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

/**
 * @breif       获得现在是星期几, 输入公历日期得到星期(只允许1901-2099年)
 * @param       year,month,day:公历年月日
 * @retval      星期号(1~7,代表周1~周日)
 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint16_t temp2;
    uint8_t yearH, yearL;
    yearH = year / 100;
    yearL = year % 100;

    /*  如果为21世纪,年份数加100 */
    if (yearH > 19)yearL += 100;

    /*  所过闰年数只算1900年之后的 */
    temp2 = yearL + yearL / 4;
    temp2 = temp2 % 7;
    temp2 = temp2 + day + table_week[month - 1];

    if (yearL % 4 == 0 && month < 3) temp2--;

    temp2 %= 7;

    if (temp2 == 0) temp2 = 7;

    return temp2;
}

/**
 * @brief       RTC初始化
 *   @note
 *              默认尝试使用LSE,当LSE启动失败后,切换为LSI.
 *              通过BKP寄存器0的值,可以判断RTC使用的是LSE/LSI:
 *              当BKP0==0X5050时,使用的是LSE
 *              当BKP0==0X5051时,使用的是LSI
 *              注意:切换LSI/LSE将导致时间/日期丢失,切换后需重新设置.
 *
 * @param       无
 * @retval      0,成功
 *              1,进入初始化模式失败
 */
uint8_t rtc_init(void)
{
    /* 检查是不是第一次配置时钟 */
    uint16_t bkpflag = 0;
    
    g_rtc_handle.Instance = RTC;
    g_rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24;/* RTC设置为24小时格式 */
    g_rtc_handle.Init.AsynchPrediv = 0X7F;           /* RTC异步分频系数(1~0X7F) */
    g_rtc_handle.Init.SynchPrediv = 0XFF;            /* RTC同步分频系数(0~7FFF) */
    g_rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE;     
    g_rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    g_rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    if (HAL_RTC_Init(&g_rtc_handle) != HAL_OK)
    {
        return 1;
    }

    bkpflag = rtc_read_bkr(0);  /* 读取BKP0的值 */
    if ((bkpflag != 0X5050) && (bkpflag != 0x5051))         /* 之前未初始化过,重新配置 */
    {
        rtc_set_time(23, 59, 56, RTC_HOURFORMAT12_AM);      /* 设置时间 ,根据实际时间修改 */
        rtc_set_date(20, 1, 13, 7);                         /* 设置日期 */
    }

    return 0;
}

/**
 * @brief       RTC底层驱动,时钟配置
 * @param       hrtc:RTC句柄
 * @note        此函数会被HAL_RTC_Init()调用
 * @retval      无
 */
void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)
{
    uint16_t retry = 200;
    
    RCC_OscInitTypeDef rcc_osc_init_handle;
    RCC_PeriphCLKInitTypeDef rcc_periphclk_init_handle;

    __HAL_RCC_RTC_CLK_ENABLE();                                          /* 使能RTC时钟 */
    HAL_PWR_EnableBkUpAccess();                                          /* 取消备份区域写保护 */
    
    /* 使用寄存器的方式去检测LSE是否可以正常工作 */
    RCC->BDCR |= 1 << 0;    /* 尝试开启LSE */

    while (retry && ((RCC->BDCR & 0X02) == 0))  /* 等待LSE准备好 */
    {
        retry--;
        delay_ms(5);
    }
    
    if (retry == 0)     /* LSE起振失败 使用LSI */
    {
        rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSI;        /* 选择要配置的振荡器 */
        rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;                    /* PLL不配置 */
        rcc_osc_init_handle.LSIState = RCC_LSI_ON;                          /* LSI状态:开启 */
        HAL_RCC_OscConfig(&rcc_osc_init_handle);                            /* 配置设置的rcc_oscinitstruct */

        rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC; /* 选择要配置外设 RTC */
        rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; /* RTC时钟源选择LSI */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);              /* 配置设置的rcc_periphclkinitstruct */
        rtc_write_bkr(0, 0X5051);
    }
    else
    {
        rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSE;        /* 选择要配置的振荡器 */
        rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;                    /* PLL不配置 */
        rcc_osc_init_handle.LSEState = RCC_LSE_ON;                          /* LSE状态:开启 */
        HAL_RCC_OscConfig(&rcc_osc_init_handle);                            /* 配置设置的rcc_oscinitstruct */

        rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC; /* 选择要配置外设 RTC */
        rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; /* RTC时钟源选择LSE */
        HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);              /* 配置设置的rcc_periphclkinitstruct */
        rtc_write_bkr(0, 0X5053);
    }
    
    __HAL_RCC_RTC_ENABLE();                                                 /* RTC使能 */
}

/**
 * @breif       设置闹钟时间(按星期闹铃,24小时制)
 * @param       week        : 星期几(1~7) 
 * @param       hour,min,sec: 小时,分钟,秒钟
 * @retval      无
 */
void rtc_set_alarma(uint8_t week, uint8_t hour, uint8_t min, uint8_t sec)
{
    RTC_AlarmTypeDef rtc_alarm_handle;
    
    rtc_alarm_handle.AlarmTime.Hours = hour;                                /* 小时 */
    rtc_alarm_handle.AlarmTime.Minutes = min;                               /* 分钟 */
    rtc_alarm_handle.AlarmTime.Seconds = sec;                               /* 秒 */
    rtc_alarm_handle.AlarmTime.SubSeconds = 0;
    rtc_alarm_handle.AlarmTime.TimeFormat = RTC_HOURFORMAT12_AM;
    
    rtc_alarm_handle.AlarmMask = RTC_ALARMMASK_NONE;                        /* 精确匹配星期,时分秒 */
    rtc_alarm_handle.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
    rtc_alarm_handle.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_WEEKDAY; /* 按星期 */
    rtc_alarm_handle.AlarmDateWeekDay = week;                               /* 星期 */
    rtc_alarm_handle.Alarm = RTC_ALARM_A;                                   /* 闹钟A */
    HAL_RTC_SetAlarm_IT(&g_rtc_handle, &rtc_alarm_handle, RTC_FORMAT_BIN);
    
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 2);   /* 抢占优先级1,子优先级2 */
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}
/**
 * @breif       周期性唤醒定时器设置
 * @param       wksel
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV16        ((uint32_t)0x00000000)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV8         ((uint32_t)0x00000001)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV4         ((uint32_t)0x00000002)
 *   @arg       RTC_WAKEUPCLOCK_RTCCLK_DIV2         ((uint32_t)0x00000003)
 *   @arg       RTC_WAKEUPCLOCK_CK_SPRE_16BITS      ((uint32_t)0x00000004)
 *   @arg       RTC_WAKEUPCLOCK_CK_SPRE_17BITS      ((uint32_t)0x00000006)
 * @note        000,RTC/16;001,RTC/8;010,RTC/4;011,RTC/2;
 * @note        注意:RTC就是RTC的时钟频率,即RTCCLK!
 * @param       cnt: 自动重装载值.减到0,产生中断.
 * @retval      无
 */
void rtc_set_wakeup(uint8_t wksel, uint16_t cnt)
{ 
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_WUTF);  /* 清除RTC WAKE UP的标志 */

    HAL_RTCEx_SetWakeUpTimer_IT(&g_rtc_handle, cnt, wksel);          /* 设置重装载值和时钟 */

    HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 2, 2);                       /* 抢占优先级2,子优先级2 */
    HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

/**
 * @breif       RTC闹钟中断服务函数
 * @param       无
 * @retval      无
 */
void RTC_Alarm_IRQHandler(void)
{
    HAL_RTC_AlarmIRQHandler(&g_rtc_handle);
}

/**
 * @breif       RTC闹钟A中断处理回调函数
 * @param       hrtc:RTC句柄
 * @retval      无
 */
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    printf("ALARM A!\r\n");
}

/**
 * @breif       RTC WAKE UP中断服务函数
 * @param       无
 * @retval      无
 */
void RTC_WKUP_IRQHandler(void)
{
    HAL_RTCEx_WakeUpTimerIRQHandler(&g_rtc_handle); 
}

/**
 * @breif       RTC WAKE UP中断处理处理回调函数
 * @param       hrtc:RTC句柄
 * @retval      无
 */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    LED1_TOGGLE();
}

rtc.h

#ifndef __RTC_H
#define __RTC_H

#include "./SYSTEM/sys/sys.h"

extern RTC_HandleTypeDef g_rtc_handle;

uint8_t rtc_init(void);     /* 初始化RTC */
uint32_t rtc_read_bkr(uint32_t bkrx);               /* 读后备寄存器 */
void rtc_write_bkr(uint32_t bkrx, uint32_t data);   /* 写后备寄存器 */
void rtc_get_time(uint8_t *hour, uint8_t *min, uint8_t *sec, uint8_t *ampm);    /* 获取时间 */
HAL_StatusTypeDef rtc_set_time(uint8_t hour, uint8_t min, uint8_t sec, uint8_t ampm); /* 设置时间 */
void rtc_get_date(uint8_t *year, uint8_t *month, uint8_t *date, uint8_t *week); /* 获取日期 */
HAL_StatusTypeDef rtc_set_date(uint8_t year, uint8_t month, uint8_t date, uint8_t week);  /* 设置日期 */

void rtc_set_wakeup(uint8_t wksel, uint16_t cnt);   /* 设置周期性唤醒 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day);    /* 获取星期 */
void rtc_set_alarma(uint8_t week, uint8_t hour, uint8_t min, uint8_t sec);  /* 设置闹钟 */

#endif

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/MPU/mpu.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/RTC/rtc.h"


int main(void)
{
    uint8_t hour,min,sec,ampm;
    uint8_t year,month,date,week;
    uint8_t tbuf[40];
    uint8_t t=0;
    
    sys_cache_enable();                 /* 打开L1-Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
    delay_init(480);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    usmart_dev.init(240);               /* 初始化USMART */
    led_init();                         /* 初始化LED */
    mpu_memory_protection();            /* 保护相关存储区域 */
    lcd_init();                         /* 初始化LCD */
    rtc_init();                         /* 初始化RTC */
    rtc_set_wakeup(RTC_WAKEUPCLOCK_CK_SPRE_16BITS, 0);  /* 配置WAKE UP中断,1秒钟中断一次 */

    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "RTC TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    while (1)
    {
        t++;

        if ((t % 10) == 0)  /* 每100ms更新一次显示数据 */
        {
            rtc_get_time(&hour, &min, &sec, &ampm);
            sprintf((char *)tbuf, "Time:%02d:%02d:%02d", hour, min, sec);
            lcd_show_string(30, 130, 210, 16, 16, (char*)tbuf, RED);
            rtc_get_date(&year, &month, &date, &week);
            sprintf((char *)tbuf, "Date:20%02d-%02d-%02d", year, month, date);
            lcd_show_string(30, 150, 210, 16, 16, (char*)tbuf, RED);
            sprintf((char *)tbuf, "Week:%d", week);
            lcd_show_string(30, 170, 210, 16, 16, (char*)tbuf, RED);
        }

        if ((t % 20) == 0)
        {
            LED0_TOGGLE();  /* 每200ms,翻转一次LED0 */
        }

        delay_ms(10);
    }
}

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: STM32L151是STMicroelectronics(意法半导体)公司推出的一款低功耗型32位微控制器系列之一。其RTC(实时时钟)模块是该芯片的一个重要功能模块。 RTC模块是用于提供准确的时间和日期信息的。STM32L151的RTC模块采用了低功耗设计,可以在低功耗模式下维持准确的时间和日期计数,以节约能源。该模块具备自动校正功能,可以通过外部32.768kHz的振荡器进行时钟校正,提供更高精度的时间和日期计数。 STM32L151的RTC模块还包含一些其他功能,例如闹钟功能、周期性断功能和温度测量功能等。闹钟功能可以设定某个特定的时间点触发闹钟断,用于提醒一些特定的时间操作。周期性断功能可以按照设定的时间间隔自动触发断,用于定时执行某些任务。温度测量功能可以通过温度传感器测量芯片的温度,并提供给用户读取。 除了基本的时间和日期计数功能外,STM32L151的RTC模块还具备电源失效检测功能。当供电电压异常或掉电情况发生时,RTC模块会自动使用备用电源维持时间和日期计数,以避免数据丢失。 总结来说,STM32L151的RTC模块是一项重要的功能,能够提供准确的时间和日期信息,并支持自动校正、闹钟、周期性断、温度测量和电源失效检测等功能。在低功耗模式下,仍能保持准确性,适合在对能源要求较高的应用场景使用。 ### 回答2: STM32L151是STMicroelectronics推出的一款低功耗微控制器,内置RTC(Real-Time Clock,实时时钟)模块。 RTC模块是一种用于提供实时时间和日期信息的设备,其可以提供高精度的时钟功能,通常用于计时、数据同步等应用。STM32L151微控制器的RTC模块具有以下特性和功能: 1.低功耗特性:STM32L151采用低功耗架构设计,RTC模块也同样具备低功耗特性。它可以在低功耗模式下继续保持正常工作,以节省能源。 2.时钟源选择:RTC模块提供多个时钟源选择,包括低速内部时钟、外部低速振荡器、外部高速振荡器等。用户可以根据需要选择适合的时钟源。 3.日期和时间计数器:RTC模块内部具有32位日期和时间计数器,能够提供秒、分钟、小时、日期、月份、年份等信息。用户可以通过读取和写入寄存器来获取或设置日期和时间。 4.闹钟功能:RTC模块支持多个可编程闹钟功能。用户可以设置闹钟触发的日期、时间和重复周期,以满足各种应用需求。 5.自动唤醒功能:RTC模块支持自动唤醒功能,可以根据预定的日期和时间在低功耗模式下自动唤醒系统。 6.校准功能:RTC模块内置了校准功能,可以校正时间的偏差,提高时间精度。 总结来说,STM32L151的RTC模块是一种功能强大且低功耗的实时时钟模块,具备日期和时间计数器、闹钟功能、自动唤醒功能等特性,可以满足各种计时、数据同步等应用需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值