一、RTC简介
二、STM32 RTC框图介绍
2.1、STM32 F1 RTC结构框图
2.2、STM32 F4 / F7 / H7 RTC结构框图
三、RTC相关寄存器介绍
3.1、RTC基本配置步骤
3.2、RTC相关寄存器(F1)
3.3、RTC相关寄存器(F4 / F7 / H7)
四、RTC相关HAL库驱动介绍
4.1、RTC相关HAL库驱动(F1)
4.2、RTC相关HAL库驱动(F4 / F7 / H7)
五、RTC基本驱动步骤
5.1、RTC基本驱动步骤(F1)
5.2、RTC基本驱动步骤(F4 / F7 / H7)
六、时间设置和读取
6.1、时间设置和读取(F1)
6.2、RTC闹钟配置 和 RTC周期性自动唤醒配置(F4 / F7 / H7)
七、编程实战
7.1、F1-RTC
7.2、H750-RTC
八、总结
一、RTC简介
RTC(Real Time Clock,实时时钟)是一种专门用于记录时间的设备或模块,通常作为计算机系统中的一部分存在。其本质是一个计数器,以秒为单位进行计数,可以提供精确的时间信息,并且具有以下特性:
-
提供时间信息: RTC能够提供当前的时间,通常以秒钟数的形式表示,但也可以提供更精细的时间分辨率,如毫秒或微秒级别。
-
持久性: RTC具有持久性,即在MCU(Microcontroller Unit,微控制器单元)掉电后仍然能够继续运行,因此能够确保时间信息的连续性和准确性。
-
低功耗: RTC通常具有低功耗特性,以确保在长时间内使用时消耗的能量较少,这对于依靠电池供电的应用尤为重要,因为它可以延长电池的使用寿命。
总的来说,RTC在许多应用中扮演着关键的角色,特别是在需要准确记录时间并且需要在掉电后继续运行的场景下,如数据记录、日志记录、定时任务等。
常用的RTC方案?
常用的RTC方案包括片上RTC外设方案和独立RTC芯片方案。下面是它们的对比:
片上RTC外设方案:
- 信息差异: 提供秒/亚秒信号,通常只提供基本的时间信息,如秒、亚秒等。
- 功耗: 功耗较高,因为RTC外设通常与MCU的其他功能集成在一起,共享同一个电源。
- 体积: 不占用额外体积,因为RTC外设已经集成在MCU芯片内部。
- 成本: 成本较低,因为不需要额外购买RTC芯片,只需在选择MCU时考虑是否需要RTC外设。
独立RTC芯片方案:
- 信息差异: 提供秒信号和日历功能,能够记录日期、月份、年份等日历信息。
- 功耗: 功耗较低,因为独立RTC芯片通常设计为低功耗模式,能够长时间运行。
- 体积: 体积较大,因为独立RTC芯片需要额外的空间来安装。
- 成本: 成本较高,因为需要购买额外的RTC芯片,并且可能需要设计额外的外围电路。
综合来看,选择片上RTC外设方案还是独立RTC芯片方案取决于具体的应用需求和设计考虑:
- 如果需要基本的时间信息记录,对功耗和成本有较低要求,并且希望节省空间,可以选择片上RTC外设方案。
- 如果需要更丰富的时间信息记录,对功耗和成本有更高要求,并且可以接受额外的空间占用,可以选择独立RTC芯片方案。
BCD码
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的十进制数,因此在一些高性能、大数据量的应用中可能不够灵活。
时间戳
时间戳(Timestamp)是指一种表示日期和时间的方式,通常是一个数字或字符串,表示自某个特定起点(通常是某个固定的起始时间,如1970年1月1日午夜格林尼治时间)经过了多长的时间间隔,单位可以是秒、毫秒、微秒等。
常见的时间戳是指 Unix 时间戳,它表示自1970年1月1日午夜格林尼治时间(也称为 Unix 纪元时间)以来经过的秒数。Unix 时间戳是计算机领域中最常用的时间表示方式之一,因为它简单、清晰,并且在不同系统之间具有良好的可移植性。
时间戳的优点在于它不受时区的影响,能够统一地表示全球范围内的时间,便于进行时间的比较和计算。在软件开发、数据库管理、日志记录等领域广泛应用。
举例来说,当前时间戳(以秒为单位)可以通过编程语言或操作系统提供的相关函数获取,例如在 Unix/Linux 系统中可以使用 time()
函数获取当前时间戳。
二、STM32 RTC框图介绍
2.1、STM32 F1 RTC结构框图
STM32 F1系列的RTC(Real-Time Clock,实时时钟)模块是一个功能强大的硬件模块,用于提供精确的时间和日期信息,并在设备掉电后保持时间信息。下面是对其结构的详细解释:
-
RTC预分频器:
- RTC模块包含一个预分频器,用于将外部时钟源(HSE、LSI、LSE)的频率分频为RTC模块所需的时钟频率。预分频器允许通过配置来调整RTC模块的时钟频率,以满足不同的应用需求。
-
32位可编程计数器:
- RTC模块内部包含一个32位的可编程计数器,用于计算秒数、分钟数、小时数等。这个计数器可以通过配置进行初始化,并在每个时钟周期更新。它允许RTC模块跟踪时间的流逝,以及提供精确的日期和时间信息。
-
待机唤醒:
- RTC模块具有待机唤醒功能,可以在设备进入低功耗待机模式时继续运行,以提供持续的时间跟踪和唤醒功能。这使得设备可以在低功耗模式下保持时间信息,并在需要时快速唤醒。
-
RTC控制寄存器与APB1接口:
- RTC模块与APB1总线相连,通过RTC控制寄存器可以对RTC模块进行配置和控制。这些寄存器允许软件对RTC模块的各个功能进行设置,包括时钟源选择、预分频器设置、计数器初始化等。
-
三个时钟源:
- RTC模块可以使用三种不同的时钟源来提供时钟信号:外部高速时钟(HSE)/ 128、低速内部时钟(LSI,频率为40kHz)和低速外部时钟(LSE,频率为32.768kHz)。这些时钟源中的任何一个都可以被选择,以提供RTC模块所需的时钟信号。
-
RTC工作在后备区域:
- RTC模块通常工作在设备的后备区域,这意味着即使设备的主要电源VDD掉电,RTC模块仍然可以正常工作,并且可以继续提供时间跟踪和唤醒功能。这种特性对于需要持续跟踪时间的应用非常有用,如实时时钟、定时器和报警系统等。
2.2、STM32 F4 / F7 / H7 RTC结构框图
STM32 F4、F7和H7系列的RTC(Real-Time Clock,实时时钟)模块提供了更多的功能和灵活性,用于提供精确的时间和日期信息,并在设备掉电后保持时间信息。以下是对其结构的详细解释:
-
RTC时钟源:
- RTC模块可以使用多种时钟源来提供时钟信号,包括外部低速时钟(LSE,频率为32.768kHz)、外部高速时钟(HSE_RTC)和低速内部时钟(LSI)。这些时钟源中的任何一个都可以被选择,以提供RTC模块所需的时钟信号。
-
预分频器:
- RTC模块包含异步预分频器和同步预分频器,用于将时钟源的频率分频为RTC模块所需的时钟频率。预分频器允许通过配置来调整RTC模块的时钟频率,以满足不同的应用需求。
-
亚秒寄存器:
- RTC模块包含亚秒寄存器,用于提供更精确的时间跟踪。亚秒寄存器可以在每秒钟产生多个时钟周期,以提供更高的时间分辨率。
-
唤醒预分频器和自动重载寄存器:
- RTC模块具有唤醒预分频器和唤醒自动重载寄存器,用于配置唤醒功能并在设备进入低功耗模式时继续运行。唤醒预分频器和自动重载寄存器可以设置唤醒周期,并在达到指定的唤醒时间时产生中断。
-
RTC和日历:
- RTC模块不仅提供了时间跟踪功能,还提供了日历功能,可以跟踪年、月、日、小时、分钟和秒等日期信息。这些信息可以在RTC模块的寄存器中存储,并可以通过读取和写入操作进行访问和更新。
-
时间戳:
- RTC模块还支持时间戳功能,可以用来记录事件发生的时间。时间戳可以记录时间和日期信息,并在需要时读取和处理。
-
闹钟和入侵检测:
- RTC模块具有闹钟功能,可以设置闹钟触发时间,并在达到指定的闹钟时间时产生中断。此外,RTC模块还支持入侵检测功能,可以检测外部事件的发生,并在检测到入侵事件时产生中断。
-
备份和RTC入侵控制寄存器:
- RTC模块具有备份寄存器和RTC入侵控制寄存器,用于存储和配置RTC模块的备份数据和入侵检测功能。这些寄存器可以用来存储设备的配置信息、用户数据等,并在需要时进行读取和更新。
三、RTC相关寄存器介绍
3.1、RTC基本配置步骤
正确配置STM32的RTC模块是确保其正常运行的关键步骤之一。以下是基本的RTC配置步骤:
-
使能对RTC的访问:
- 首先,需要通过RCC寄存器使能对PWR(Power Control,电源控制)和BKP(Backup Registers,后备寄存器)的时钟访问权限。在PWR_CR寄存器中设置特定位来使能对后备寄存器和RTC的访问权限。
-
设置RTC时钟源:
- 接下来,需要选择RTC的时钟源。通常情况下,选择外部低速晶体振荡器(LSE,32.768kHz)作为RTC的时钟源。要启用LSE,需要在RCC_BDCR寄存器中设置相应的位来激活LSE,并将RTC的计数时钟源设置为LSE。
-
进入配置模式:
- 在对RTC进行配置之前,需要将RTC模块设置为配置模式。这可以通过设置RTC_CRL寄存器中的特定位来实现。等待RTOFF位变为1,并将CNF位设置为1,以进入配置模式。
-
设置RTC寄存器:
- 一旦进入配置模式,可以设置RTC寄存器以配置RTC的各种参数。例如,设置RTC的预分频器寄存器(RTC_PRL)来设置分频值,设置RTC的计数器寄存器(RTC_CNT)来设置初始计数值等。
-
退出配置模式:
- 配置完成后,需要退出配置模式以使RTC模块正常工作。这可以通过清除RTC_CRL寄存器中的CNF位来实现。然后等待RTOFF位变为1,表明RTC退出配置模式,配置完成。
总的来说,以上步骤描述了如何使能和配置STM32的RTC模块,确保其正常运行并提供准确的时间跟踪和日期信息。在配置完成后,RTC模块将能够持续跟踪时间并在需要时提供准确的时间和日期信息。
3.2、RTC相关寄存器(F1)
3.3、RTC相关寄存器(F4 / F7 / H7)
四、RTC相关HAL库驱动介绍
4.1、RTC相关HAL库驱动(F1)
针对STM32F1系列的RTC相关HAL库驱动函数和相关寄存器功能描述如下:
-
HAL_RTC_Init(…):
- 关联寄存器:CRL/CRH/PRLH/PRLL
- 功能描述:该函数用于初始化RTC模块,包括配置RTC的控制寄存器(CRL/CRH)以及设置RTC的预分频寄存器(PRLH/PRLL)。
-
HAL_RTC_MspInit(…)
- 功能描述:这是RTC的初始化回调函数,在其中需要使能RTC所需的时钟和外设。
- 相关操作:通常在该函数中需要调用HAL_RCC_OscConfig(…)函数开启LSE时钟源,并调用HAL_RCCEx_PeriphCLKConfig(…)函数设置RTC的时钟源为LSE。
-
HAL_RCC_OscConfig(…)
- 关联寄存器:RCC_CR/PWR_CR
- 功能描述:该函数用于配置时钟源,一般用于开启LSE时钟源。
-
HAL_RCCEx_PeriphCLKConfig(…)
- 关联寄存器:RCC_BDCR
- 功能描述:该函数用于配置外设时钟,用于设置RTC的时钟源为LSE。
-
HAL_PWR_EnableBkUpAccess(…)
- 关联寄存器:PWR_CR
- 功能描述:该函数用于使能备份域的访问权限,以便可以读写备份寄存器。
-
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模块进行初始化和配置,确保其正常运行并提供准确的时间跟踪和日期信息。
4.2、RTC相关HAL库驱动(F4 / F7 / H7)
针对STM32F4/F7/H7系列的RTC日历功能,以下是相关的HAL库驱动函数及其功能描述:
-
HAL_RTC_Init(…):
- 关联寄存器:RTC_CR、RTC_PRER、RTC_WPR
- 功能描述:初始化RTC模块,包括配置RTC控制寄存器(RTC_CR)、预分频寄存器(RTC_PRER)以及写保护寄存器(RTC_WPR)。
-
HAL_RTC_MspInit(…)
- 功能描述:RTC的初始化回调函数,在其中需要使能RTC所需的时钟和外设。
- 相关操作:通常需要调用HAL_RCC_OscConfig(…)函数开启LSE时钟源,并调用HAL_RCCEx_PeriphCLKConfig(…)函数设置RTC的时钟源为LSE。
-
HAL_RCC_OscConfig(…)
- 关联寄存器:RCC_CR
- 功能描述:配置时钟源,用于开启LSE时钟源。
-
HAL_RCCEx_PeriphCLKConfig(…)
- 关联寄存器:RCC_BDCR
- 功能描述:配置外设时钟,用于设置RTC的时钟源为LSE。
-
HAL_RTC_Set/GetTime(…)
- 关联寄存器:RTC_TR
- 功能描述:设置/读取时间,包括小时、分钟和秒。
-
HAL_RTC_Set/GetDate(…)
- 关联寄存器:RTC_DR
- 功能描述:设置/读取日期,包括年、月、日和星期。
-
HAL_PWR_EnableBkUpAccess(…)
- 关联寄存器:PWR_CR
- 功能描述:使能备份域的访问权限,以便可以读写备份寄存器。
-
HAL_RTCEx_BKUPWrite/Read()
- 关联寄存器:RTC_BKPxR
- 功能描述:这些函数用于读写RTC的备份寄存器,用于存储一些特定的数据或参数。
在使用RTC之前,需要确保已经开启了相应的时钟源。可以通过调用以下宏或函数来实现:
- __HAL_RCC_RTC_CLK_ENABLE():使能RTC时钟
- __HAL_RCC_RTC_ENABLE():使能RTC模块
通过以上的函数和操作,可以实现对STM32F4/F7/H7系列的RTC模块进行初始化和配置,以及设置和读取时间日期信息,从而实现日历功能。
五、RTC基本驱动步骤
5.1、RTC基本驱动步骤(F1)
针对STM32F1系列的RTC基本驱动步骤,以下是一般性的流程:
-
使能电源时钟并使能后备域访问:
- 使用宏或函数使能电源时钟和备份时钟,确保后续可以对RTC的后备域进行访问:
__HAL_RCC_PWR_CLK_ENABLE(); // 使能电源时钟 __HAL_RCC_BKP_CLK_ENABLE(); // 使能备份时钟 HAL_PWR_EnableBkUpAccess(); // 使能备份访问
- 使用宏或函数使能电源时钟和备份时钟,确保后续可以对RTC的后备域进行访问:
-
开启LSE/选择RTC时钟源/使能RTC时钟:
- 配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:
HAL_RCC_OscConfig(...); // 开启LSE HAL_RCCEx_PeriphCLKConfig(...); // 选择RTC时钟源 __HAL_RCC_RTC_ENABLE(); // 使能RTC时钟
- 配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:
-
初始化RTC,设置分频值以及工作参数:
- 调用HAL_RTC_Init()函数初始化RTC模块,并设置分频值、时钟输出使能等参数:
HAL_RTC_Init(...); // 初始化RTC HAL_RTC_MspInit(...); // 完成RTC底层初始化工作
- 调用HAL_RTC_Init()函数初始化RTC模块,并设置分频值、时钟输出使能等参数:
-
设置RTC的日期和时间:
- 使用相应的寄存器操作函数,设置RTC的日期和时间信息:
rtc_set_time(...); // 操作寄存器方式设置RTC的日期和时间
- 使用相应的寄存器操作函数,设置RTC的日期和时间信息:
-
获取RTC当前日期和时间:
- 定义相应的函数,使用寄存器读取当前RTC的日期和时间信息:
rtc_get_time(...); // 定义函数读取RTC的当前日期和时间
- 定义相应的函数,使用寄存器读取当前RTC的日期和时间信息:
以上是一个简要的RTC基本驱动步骤示例,具体实现时需要根据实际需求和硬件平台进行相应的配置和调整。
5.2、RTC基本驱动步骤(F4 / F7 / H7)
针对STM32F4/F7/H7系列的RTC基本驱动步骤,以下是一般性的流程:
-
使能电源时钟并使能后备域访问:
- 使用宏或函数使能PWR时钟,并使能备份域访问权限:
__HAL_RCC_PWR_CLK_ENABLE(); // 使能PWR时钟 HAL_PWR_EnableBkUpAccess(); // 使能备份访问
- 使用宏或函数使能PWR时钟,并使能备份域访问权限:
-
开启LSE/选择RTC时钟源/使能RTC:
- 配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:
HAL_RCC_OscConfig(...); // 开启LSE HAL_RCCEx_PeriphCLKConfig(...); // 选择RTC时钟源 __HAL_RCC_RTC_CLK_ENABLE(); // 使能RTC时钟
- 配置RTC的时钟源为外部低速时钟(LSE),并使能RTC时钟:
-
初始化RTC(同/异步分频系数和时钟格式):
- 调用HAL_RTC_Init()函数初始化RTC模块,设置同步/异步分频系数和时钟格式等参数:
HAL_RTC_Init(...); // 初始化RTC HAL_RTC_MspInit(...); // 完成RTC底层初始化工作
- 调用HAL_RTC_Init()函数初始化RTC模块,设置同步/异步分频系数和时钟格式等参数:
-
设置RTC的日期和时间:
- 使用HAL_RTC_SetTime()和HAL_RTC_SetDate()函数设置RTC的时间和日期:
HAL_RTC_SetTime(...); // 设置RTC时间 HAL_RTC_SetDate(...); // 设置RTC日期
- 使用HAL_RTC_SetTime()和HAL_RTC_SetDate()函数设置RTC的时间和日期:
-
获取RTC当前日期和时间:
- 使用HAL_RTC_GetTime()和HAL_RTC_GetDate()函数获取当前RTC的时间和日期:
HAL_RTC_GetTime(...); // 获取当前RTC时间 HAL_RTC_GetDate(...); // 获取当前RTC日期
- 使用HAL_RTC_GetTime()和HAL_RTC_GetDate()函数获取当前RTC的时间和日期:
以上是一个简要的RTC基本驱动步骤示例,具体实现时需要根据实际需求和硬件平台进行相应的配置和调整。
六、时间设置和读取
6.1、时间设置和读取(F1)
针对STM32F1系列的RTC,由于其没有日历寄存器,只能存储总秒数,因此需要进行一些额外的操作才能将总秒数转换为日历时间,并提供一些功能函数来处理日历时间。下面是一些相关的函数和结构体说明:
-
全局结构体变量 calendar 存储时间信息:
- 在代码中通常会定义一个结构体变量用于存储日历时间的各个参数,比如年、月、日、时、分、秒等信息。
-
rtc_date2sec() 函数:
- 这是一个静态函数,用于将日历时间转换为对应的总秒数。该函数会根据输入的年、月、日、时、分、秒等参数计算出对应的总秒数。
-
rtc_get_time() 函数:
- 这个函数的功能是将总秒数转换为日历时间。它会根据给定的总秒数,计算出对应的年、月、日、时、分、秒等时间信息,并将结果存储到结构体变量 calendar 中。
-
rtc_is_leap_year() 函数:
- 这个函数用于判断给定的年份是否是闰年。
-
rtc_get_week() 函数:
- 这个函数用于计算给定的公历日历日期对应的星期几。
通过这些功能函数,您可以方便地进行时间的设置和读取,以及一些相关的时间计算操作,使得在STM32F1系列芯片上使用RTC更加便捷有效。
6.2、RTC闹钟配置 和 RTC周期性自动唤醒配置(F4 / F7 / H7)
RTC闹钟配置一般步骤
RTC闹钟配置步骤,以下是更详细的说明:
-
RTC已初始化:
- 在配置RTC闹钟之前,确保RTC已经初始化并且相关参数已经设置好。
-
配置闹钟参数并开启中断:
- 使用HAL_RTC_SetAlarm_IT()函数配置闹钟参数并开启中断:
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
- 使用HAL_RTC_SetAlarm_IT()函数配置闹钟参数并开启中断:
-
设置闹钟中断优先级:
- 使用HAL_NVIC_SetPriority()函数设置闹钟中断的优先级:
HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
- 使用HAL_NVIC_SetPriority()函数设置闹钟中断的优先级:
-
使能闹钟中断:
- 使用HAL_NVIC_EnableIRQ()函数使能闹钟中断:
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
- 使用HAL_NVIC_EnableIRQ()函数使能闹钟中断:
-
编写中断服务函数:
- 定义一个中断服务函数,用于处理RTC闹钟中断事件:
void RTC_Alarm_IRQHandler(void) { HAL_RTC_AlarmIRQHandler(&hrtc); }
- 定义一个中断服务函数,用于处理RTC闹钟中断事件:
-
HAL库RTC中断共用处理函数:
- HAL库提供了RTC中断共用处理函数HAL_RTC_AlarmIRQHandler(),在中断服务函数中调用该函数:
HAL_RTC_AlarmIRQHandler(&hrtc);
- HAL库提供了RTC中断共用处理函数HAL_RTC_AlarmIRQHandler(),在中断服务函数中调用该函数:
-
重定义中断回调函数:
- 如果需要在闹钟中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数HAL_RTC_AlarmAEventCallback():
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { // 在此处编写闹钟中断发生时的操作 }
- 如果需要在闹钟中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数HAL_RTC_AlarmAEventCallback():
以上是RTC闹钟配置的一般步骤和相应的操作说明。通过这些步骤,您可以实现RTC闹钟的配置和中断处理功能。
RTC周期性自动唤醒配置一般步骤
RTC周期性自动唤醒配置步骤,以下是更详细的说明:
-
RTC已初始化:
- 在配置RTC周期性自动唤醒功能之前,确保RTC已经初始化并且相关参数已经设置好。
-
清除RTC WKUP标志位:
- 使用__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG()函数清除RTC的WKUP标志位:
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
- 使用__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG()函数清除RTC的WKUP标志位:
-
设置闹钟参数并开启中断:
- 使用HAL_RTCEx_SetWakeUpTimer_IT()函数设置周期性自动唤醒的时间间隔并开启中断:
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeup_time, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
- 使用HAL_RTCEx_SetWakeUpTimer_IT()函数设置周期性自动唤醒的时间间隔并开启中断:
-
设置中断优先级:
- 使用HAL_NVIC_SetPriority()函数设置RTC周期性自动唤醒中断的优先级:
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0);
- 使用HAL_NVIC_SetPriority()函数设置RTC周期性自动唤醒中断的优先级:
-
使能中断:
- 使用HAL_NVIC_EnableIRQ()函数使能RTC周期性自动唤醒中断:
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
- 使用HAL_NVIC_EnableIRQ()函数使能RTC周期性自动唤醒中断:
-
编写中断处理逻辑:
- 定义一个中断服务函数,用于处理RTC周期性自动唤醒中断事件:
void RTC_WKUP_IRQHandler(void) { HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc); }
- 定义一个中断服务函数,用于处理RTC周期性自动唤醒中断事件:
-
HAL库RTC中断共用处理函数:
- HAL库提供了RTC周期性自动唤醒中断共用处理函数HAL_RTCEx_WakeUpTimerIRQHandler(),在中断服务函数中调用该函数:
HAL_RTCEx_WakeUpTimerIRQHandler(&hrtc);
- HAL库提供了RTC周期性自动唤醒中断共用处理函数HAL_RTCEx_WakeUpTimerIRQHandler(),在中断服务函数中调用该函数:
-
重定义中断回调函数:
- 如果需要在周期性自动唤醒中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数HAL_RTCEx_WakeUpTimerEventCallback():
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { // 在此处编写周期性自动唤醒中断发生时的操作 }
- 如果需要在周期性自动唤醒中断发生时执行特定操作,可以重定义HAL库提供的中断回调函数HAL_RTCEx_WakeUpTimerEventCallback():
以上是RTC周期性自动唤醒配置的一般步骤和相应的操作说明。通过这些步骤,您可以实现RTC周期性自动唤醒功能,并在中断发生时执行相应的操作。
七、编程实战
7.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函数写入后备寄存器
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;
// 使用HAL函数读取后备寄存器
temp = HAL_RTCEx_BKUPRead(&g_rtc_handle, bkrx + 1);
return (uint16_t)temp; /* 返回读取到的值 */
}
/**
* @brief 设置RTC时间
* @param syear : 年份
* @param smon : 月份
* @param sday : 日期
* @param hour : 小时
* @param min : 分钟
* @param sec : 秒钟
* @retval 0 表示成功
*/
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);
// 使能RTC后备寄存器和电源时钟
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
// 设置RTC CRL寄存器,写入时间前先设置写保护位
RTC->CRL |= 1 << 4;
// 分别写入低16位和高16位
RTC->CNTL = seccount & 0xFFFF;
RTC->CNTH = seccount >> 16;
// 清除写保护位
RTC->CRL &= ~(1 << 4);
// 等待RTC寄存器的操作完成
while(!__HAL_RTC_ALARM_GET_FLAG(&g_rtc_handle, RTC_FLAG_RTOFF));
return 0;
}
/**
* @brief 获取RTC时间
*/
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}; /* 平年的月份日期表 */
// 读取RTC寄存器的时间
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;
}
}
temp
1++;
}
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 RTC初始化
* @retval 0 表示成功
*/
uint8_t rtc_init(void)
{
// 使能RTC时钟和后备寄存器
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_BKP_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();
// 配置RTC参数并初始化
g_rtc_handle.Instance = RTC;
g_rtc_handle.Init.AsynchPrediv = 32767;
g_rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
HAL_RTC_Init(&g_rtc_handle);
// 如果后备寄存器未被初始化过,则设置默认时间并写入标记
if (rtc_read_bkr(0) != 0x8888)
{
rtc_set_time(2088, 8, 8, 8, 8, 8);
rtc_write_bkr(0, 0x8888);
}
return 0;
}
/**
* @brief RTC MSP初始化
* @param hrtc : RTC句柄
*/
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
// 使能RTC外设时钟
__HAL_RCC_RTC_ENABLE();
RCC_OscInitTypeDef rcc_oscinitstruct;
RCC_PeriphCLKInitTypeDef rcc_periphclkinitstruct;
// 配置LSE时钟源
rcc_oscinitstruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
rcc_oscinitstruct.LSEState = RCC_LSE_ON;
rcc_oscinitstruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&rcc_oscinitstruct);
// 配置RTC时钟源
rcc_periphclkinitstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
rcc_periphclkinitstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
HAL_RCCEx_PeriphCLKConfig(&rcc_periphclkinitstruct);
}
/**
* @brief 将年月日时分秒转换成秒钟数
* @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;
}
/**
* @brief 判断年份是否是闰年
* @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 将年月日时分秒转换成秒钟数
* @param year : 年份
* @param month : 月份
* @param day : 日期
* @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;
}
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; /* 时间结构体 */
uint8_t rtc_init(void); /* 初始化RTC */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day); /* 获取星期 */
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); /* 将年月日时分秒转换成秒钟数 */
void rtc_get_time(void); /* 获取RTC当前时间 */
uint8_t rtc_set_time(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec); /* 设置RTC的时间 */
#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/RTC/rtc.h"
int main(void)
{
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 */
rtc_init(); /* 初始化RTC */
while (1)
{
rtc_get_time(); /* 获取RTC时间 */
printf("Date:%04d-%02d-%02d ", calendar.year, calendar.month, calendar.date);
printf("Time:%02d:%02d:%02d \r\n", calendar.hour, calendar.min, calendar.sec);
delay_ms(1000); /* 延时1秒 */
}
}
HAL库源码
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);
}
}
7.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初始化函数
* @retval 无
*/
uint8_t rtc_init(void)
{
RTC_TimeTypeDef rtc_time_init_struct; /* RTC时间结构体 */
RTC_DateTypeDef rtc_date_init_struct; /* RTC日期结构体 */
/* RTC句柄实例化 */
g_rtc_handle.Instance = RTC;
g_rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24; /* 设置小时格式为24小时制 */
g_rtc_handle.Init.AsynchPrediv = 0x7F; /* 设置RTC异步预分频值 */
g_rtc_handle.Init.SynchPrediv = 0xFF; /* 设置RTC同步预分频值 */
g_rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE; /* 关闭RTC输出 */
g_rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; /* RTC输出极性 */
g_rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; /* RTC输出类型 */
HAL_RTC_Init(&g_rtc_handle); /* 初始化RTC */
/* 设置RTC时间 */
rtc_time_init_struct.Hours = 20; /* 设置小时 */
rtc_time_init_struct.Minutes = 20; /* 设置分钟 */
rtc_time_init_struct.Seconds = 20; /* 设置秒钟 */
rtc_time_init_struct.TimeFormat = RTC_HOURFORMAT12_AM; /* 设置小时格式为12小时制 */
rtc_time_init_struct.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; /* 设置夏令时 */
rtc_time_init_struct.StoreOperation = RTC_STOREOPERATION_RESET; /* 设置RTC的存储操作 */
HAL_RTC_SetTime(&g_rtc_handle, &rtc_time_init_struct, RTC_FORMAT_BIN); /* 将时间设置到RTC */
/* 设置RTC日期 */
rtc_date_init_struct.Year = 8; /* 设置年份 */
rtc_date_init_struct.Month = 8; /* 设置月份 */
rtc_date_init_struct.Date = 8; /* 设置日期 */
rtc_date_init_struct.WeekDay = 5; /* 设置星期几 */
HAL_RTC_SetDate(&g_rtc_handle, &rtc_date_init_struct, RTC_FORMAT_BIN); /* 将日期设置到RTC */
}
/**
* @brief RTC外设初始化函数
* @param hrtc: RTC句柄
* @retval 无
*/
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
RCC_OscInitTypeDef rcc_osc_init_handle; /* RCC振荡器初始化结构体 */
RCC_PeriphCLKInitTypeDef rcc_periphclk_init_handle; /* RCC外设时钟初始化结构体 */
/* RTC时钟使能 */
__HAL_RCC_RTC_CLK_ENABLE(); /* 使能RTC时钟 */
HAL_PWR_EnableBkUpAccess(); /* 使能后备区域访问 */
/* 配置LSE振荡器 */
rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSE; /* 振荡器类型为LSE */
rcc_osc_init_handle.LSEState = RCC_LSE_ON; /* 打开LSE振荡器 */
rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE; /* 关闭PLL */
HAL_RCC_OscConfig(&rcc_osc_init_handle); /* 配置振荡器 */
/* 配置RTC时钟源 */
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); /* 配置RTC时钟源 */
/* 使能RTC时钟 */
__HAL_RCC_RTC_ENABLE(); /* 使能RTC时钟 */
}
rtc.h
#ifndef __RTC_H
#define __RTC_H
#include "./SYSTEM/sys/sys.h"
uint8_t rtc_init(void);
#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/RTC/rtc.h"
extern RTC_HandleTypeDef g_rtc_handle; /* RTC句柄 */
int main(void)
{
RTC_TimeTypeDef rtc_t; /* RTC时间结构体 */
RTC_DateTypeDef rtc_d; /* RTC日期结构体 */
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(); /* 保护相关存储区域 */
rtc_init(); /* 初始化RTC */
while (1)
{
HAL_RTC_GetTime(&g_rtc_handle, &rtc_t, RTC_FORMAT_BIN); /* 获取RTC时间 */
HAL_RTC_GetDate(&g_rtc_handle, &rtc_d, RTC_FORMAT_BIN); /* 获取RTC日期 */
printf("Date:20%02d-%02d-%02d ",rtc_d.Year, rtc_d.Month, rtc_d.Date); /* 打印日期 */
printf("Time:%02d:%02d:%02d \r\n",rtc_t.Hours, rtc_t.Minutes, rtc_t.Seconds); /* 打印时间 */
delay_ms(1000); /* 延时1秒 */
}
}
HAL库源码
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文章来源:https://www.toymoban.com/news/detail-843745.html
#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, &m);
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);
}
}
八、总结
文章来源地址https://www.toymoban.com/news/detail-843745.html
到了这里,关于【正点原子STM32】RTC实时时钟(RTC方案、BCD码、时间戳、RTC相关寄存器和HAL库驱动、RTC基本配置步骤、RTC基本驱动步骤、时间设置和读取、RTC闹钟配置和RTC周期性自动唤醒配置)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!