前言: 本系列教程将 对应外设原理,HAL库与STM32CubeMX结合在一起讲解,使您可以更快速的学会各个模块的使用 所用工具: 1、芯片: STM32F407ZET6/ STM32F103ZET6 2、STM32CubeMx软件 3、IDE: MDK-Keil软件 4、STM32F1xx/STM32F4xxHAL库 知识概括: 通过本篇博客您将学到: RTC时钟原理 STM32CubeMX创建RTC例程 HAL库定时器RTC函数库 PS: 这里的RTC讲解,我们只将原理,不讲寄存器,如果要看RTC的寄存器,请看这篇文章
什么是RTCRTC (Real Time Clock):实时时钟 RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期 RTC还包含用于管理低功耗模式的自动唤醒单元。
在断电情况下 RTC仍可以独立运行 只要芯片的备用电源一直供电,RTC上的时间会一直走。 RTC实质是一个掉电后还继续运行的定时器,从定时器的角度来看,相对于通用定时器TIM外设,它的功能十分简单,只有计时功能(也可以触发中断)。但其高级指出也就在于掉电之后还可以正常运行。 两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒、分钟、小时( 12 或 24 小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。系统可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。 上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。 无论器件状态如何(运行模式、低功耗模式或处于复位状态),只要电源电压保持在工作范围内,RTC使不会停止工作。
RCT特征:● 可编程的预分频系数:分频系数高为220。
● 32位的可编程计数器,可用于较长时间段的测量。
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟 频率的四分之一以上)。
● 可以选择以下三种RTC的时钟源:
● HSE时钟除以128;
● LSE振荡器时钟;
● LSI振荡器时钟 ● 2个独立的复位类型:
● APB1接口由系统复位;
● RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位 ● 3个专门的可屏蔽中断:
● 1.闹钟中断,用来产生一个软件可编程的闹钟中断。 ● 2.秒中断,用来产生一个可编程的周期性中断信号(长可达1秒)。 ● 3.溢出中断,指示内部可编程计数器溢出并回转为0的状态。 RTC时钟源:
三种不同的时钟源可被用来驱动系统时钟(SYSCLK): ● HSI振荡器时钟
● HSE振荡器时钟
● PLL时钟 这些设备有以下2种二级时钟源: ● 40kHz低速内部RC,可以用于驱动独立看门狗和通过程序选择驱动RTC。 RTC用于从停机/待机模式下自动唤醒系统。
● 32.768kHz低速外部晶体也可用来通过程序选择驱动RTC(RTCCLK)。
RTC原理框图

RTC时钟的框图还是比较简单的,这里我们把他分成 两个部分:APB1 接口:用来和 APB1 总线相连。 此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总 线时钟驱动,用来与 APB1 总线连接。 通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。 RTC 核心接口:由一组可编程计数器组成,分成 两个主要模块 。
第一个模块是 RTC 的 预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个 TR_CLK 周期中 RTC 产生一个中断(秒中断)。

第二个模块是一个 32 位的可编程计数器 (RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记 录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。
RTC具体流程:RTCCLK经过RTC_DIV预分频,RTC_PRL设置预分频系数,然后得到TR_CLK时钟信号,我们一般设置其周期为1s,RTC_CNT计数器计数,假如1970设置为时间起点为0s,通过当前时间的秒数计算得到当前的时间。RTC_ALR是设置闹钟时间,RTC_CNT计数到RTC_ALR就会产生计数中断, - RTC_Second为秒中断,用于刷新时间,
- RTC_Overflow是溢出中断。
- RTC Alarm 控制开关机
RTC时钟选择使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块.(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式). RTC复位过程除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。 系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作
RTC中断秒中断:
这里时钟自带一个秒中断,每当计数加一的时候就会触发一次秒中断,。注意,这里所说的秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。我们通过往秒中断里写更新时间的函数来达到时间同步的效果 闹钟中断:
闹钟中断就是设置一个预设定的值,计数每自加多少次触发一次闹钟中断
CubeMX配置RTC工程创建1设置RCC - 设置高速外部时钟HSE 选择外部时钟源
- 使能外部晶振LSE
RTC设备因为其独特的运行方式(即掉电依旧运行)使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,资源消耗太大,小小的纽扣电池根本吃不消。没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE 2.配置RTC - Activate Clock Source 激活时钟源
- Activate calendar激活日历
这两个都要点,作用也很明显,先是使能时钟源,再使能RTC日历 - RTC_OUT: Not RTC_OUT
- Tamper: ×
第一个是是否使能 tamper(PC13)引脚上输出校正的秒脉冲时钟, 第二个: RTC入侵检测校验功能 RTC校验功能,使能侵入检测功能。RTC时钟经64分频输出到侵入检测引脚TAMPER上
当 TAMPER引脚上的信号从 0变成1或者从 1变成 0(取决于备份控制寄存器BKP_CR的 TPAL位),会产生一个侵入检测事件。侵入检测事件将所有数据备份寄存器内容清除。 - 也就是第一个是使能tamper(PC13)引脚作为时钟脉冲输出
- 第二个是使能tamper(PC13)引脚作为入侵检测功能
下面是两个RTC的中断: - RTC全局中断RTC_IRQHandler()
- 闹钟中断函数RTCAlarm_IRQHandler()
此处设置时间为2020/04/25 13:30:00 Binary data format 十六进制
BCD data format BCD码进制 使用自动配置,初始化时间必须使用BCD data format,原因是库函数存在bug,如果使用Binary data format,月份配置会出错,比如说11月,配置时会赋值为RTC_MONTH_NOVEMBER,而此宏定义值为0x11,也就是说其十进制值为17 Hours: 小时 Minutes: 分钟 Seconds: 秒 Week Day: 星期 Month 月份 Date: 日期 Year: 年份
3 使能串口 
使能一下串口,因为发送日期到上位机4时钟源设置 我的是 外部晶振为8MHz - 1选择外部时钟HSE 8MHz
- 2PLL锁相环倍频9倍
- 3系统时钟来源选择为PLL
- 4设置APB1分频器为 /2
- 5 使能CSS监视时钟
- 6 设置RTC时钟为LSE
5项目文件设置 - 1 设置项目名称
- 2 设置存储路径
- 3 选择所用IDE

6创建工程文件 然后点击GENERATE CODE 创建工程 配置下载工具
新建的工程所有配置都是默认的 我们需要自行选择下载模式,勾选上下载后复位运行
RTC_HAL库函数
- /*设置系统时间*/
- HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
- /*读取系统时间*/
- HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
- /*设置系统日期*/
- HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
- /*读取系统日期*/
- HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
- /*启动报警功能*/
- HAL_StatusTypeDef HAL_RTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
- /*设置报警中断*/
- HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
- /*报警时间回调函数*/
- __weak void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
- /*写入后备储存器*/
- void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data)
- /*读取后备储存器*/
- uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister
复制代码
我们可以看到前面的四个函数,分别是 - 设置系统时间:HAL_RTC_SetTime();
- 读取系统时间: HAL_RTC_GetTime();
- 设置系统日期: HAL_RTC_SetDate();
- 读取系统日期: HAL_RTC_GetDate();
因为系统的时间和日期开始的时候已经设置过了,所以我们这里只用两个读取函数 读取系统时间函数 - /*读取系统时间*/
- HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
复制代码
功能: 获取RTC时钟的时间 参数: 读取系统日期函数 - /*读取系统日期*/
- HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
复制代码功能: 获取RTC时钟的日期 参数: 在stm32f1xx_hal_rtc.h头文件中,可以找到RTC_TimeTypeDef,RTC_DateTypeDef这两个结构体的成员变量。 - /**
- * @brief RTC Time structure definition
- */
- typedef struct
- {
- uint8_t Hours; /*!< Specifies the RTC Time Hour.
- This parameter must be a number between Min_Data = 0 and Max_Data = 23 */
- uint8_t Minutes; /*!< Specifies the RTC Time Minutes.
- This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
- uint8_t Seconds; /*!< Specifies the RTC Time Seconds.
- This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
- } RTC_TimeTypeDef;
复制代码- /**
- * @brief RTC Date structure definition
- */
- typedef struct
- {
- uint8_t WeekDay; /*!< Specifies the RTC Date WeekDay (not necessary for HAL_RTC_SetDate).
- This parameter can be a value of @ref RTC_WeekDay_Definitions */
- uint8_t Month; /*!< Specifies the RTC Date Month (in BCD format).
- This parameter can be a value of @ref RTC_Month_Date_Definitions */
- uint8_t Date; /*!< Specifies the RTC Date.
- This parameter must be a number between Min_Data = 1 and Max_Data = 31 */
- uint8_t Year; /*!< Specifies the RTC Date Year.
- This parameter must be a number between Min_Data = 0 and Max_Data = 99 */
- } RTC_DateTypeDef;
复制代码
程序代码: main.c 在main.c中重写fputc函数,使得能够使用printf函数 - #include "stdio.h"
- int fputc(int ch,FILE *f){
- uint8_t temp[1]={ch};
- HAL_UART_Transmit(&huart1,temp,1,2);
- return ch;
- }
复制代码
定义两个结构体来获取日期和时间: - RTC_DateTypeDef GetData; //获取日期结构体
- RTC_TimeTypeDef GetTime; //获取时间结构体
复制代码
在while循环中添加: - /* Get the RTC current Time */
- HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
- /* Get the RTC current Date */
- HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);
- /* Display date Format : yy/mm/dd */
- printf("%02d/%02d/%02d\r\n",2000 + GetData.Year, GetData.Month, GetData.Date);
- /* Display time Format : hh:mm:ss */
- printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);
- printf("\r\n");
- HAL_Delay(1000);
复制代码
程序中使用HAL_RTC_GetTime(),HAL_RTC_GetDate()读取时间和日期,并保存到结构体变量中,然后通过串口输出读取的时间和日期。
例程测试正常: RTC掉电重置但是呢,在hal库中生成的代码,每次断电就RTC时间会重置,每次上电都会重新初始化时间 因为HAL库设置了一个BKP寄存器保存一个标志。每次单片机启动时都读取这个标志并判断是不是预先设定的值:如度果不是就初始化RTC并设置时间,再设置标志为预期值;如果是预期值就跳过初始化和时间设置,继续执行后面的程序 所以这里我们只需要每次上电执行RTC初始化之前,将标志设置为预期值即可 在rtc.c中的RTC_Init修改为以下内容即可 - void MX_RTC_Init(void)
- {
- /* USER CODE BEGIN RTC_Init 0 */
- RTC_TimeTypeDef time; //时间结构体参数
- RTC_DateTypeDef datebuff; //日期结构体参数
- /* USER CODE END RTC_Init 0 */
- RTC_TimeTypeDef sTime = {0};
- RTC_DateTypeDef DateToUpdate = {0};
- /* USER CODE BEGIN RTC_Init 1 */
- __HAL_RCC_BKP_CLK_ENABLE(); //开启后备区域时钟
- __HAL_RCC_PWR_CLK_ENABLE(); //开启电源时钟
- /* USER CODE END RTC_Init 1 */
- /**Initialize RTC Only
- */
- hrtc.Instance = RTC;
- hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
- hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
- if (HAL_RTC_Init(&hrtc) != HAL_OK)
- {
- Error_Handler();
- }
- /* USER CODE BEGIN Check_RTC_BKUP */
- if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!= 0x5051)
- {
-
- /* USER CODE END Check_RTC_BKUP */
- /**Initialize RTC and set the Time and Date
- */
- sTime.Hours = 0x14;
- sTime.Minutes = 0x30;
- sTime.Seconds = 0x0;
- if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
- {
- Error_Handler();
- }
- DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
- DateToUpdate.Month = RTC_MONTH_APRIL;
- DateToUpdate.Date = 0x25;
- DateToUpdate.Year = 0x20;
- if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
- {
- Error_Handler();
- }
- /* USER CODE BEGIN RTC_Init 2 */
- __HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
- datebuff = DateToUpdate; //把日期数据拷贝到自己定义的data中
- HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5051);//向指定的后备区域寄存器写入数据
- HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)datebuff.Year);
- HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)datebuff.Month);
- HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)datebuff.Date);
- HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)datebuff.WeekDay);
-
- }
- else
- {
- datebuff.Year = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
- datebuff.Month = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
- datebuff.Date = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
- datebuff.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
- DateToUpdate = datebuff;
- if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
- {
- Error_Handler();
- }
- __HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //开启RTC时钟秒中断
- }
-
- }
复制代码
|