STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的 RTC 外设。

本系列教程所编写的驱动源码:https://github.com/Mculover666/HAL_Driver_Lib,好用的话,记得点个Star呀!

1. 准备工作

硬件准备

首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

软件准备

  • 需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码。
2.生成MDK工程

选择芯片型号

打开STM32CubeMX,打开MCU选择器:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

搜索并选中芯片STM32L431RCT6:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

配置时钟源

  • 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
  • 如果使用默认内部时钟(HSI),这一步可以略过;

这里我都使用外部时钟:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

配置串口

开发板板载了一个CH340换串口,连接到USART1。

接下来开始配置USART1:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

配置RTC

RTC外设全称 Real-Time Clock,主要用处为:

  • 日历:输出年月日、时分秒、星期
  • 闹钟:提供闹钟中断
  • 唤醒:低功耗模式唤醒中断

① 配置RTC外设的时钟来源

首先选中RTC外设,激活时钟源:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
RTC外设的时钟来源有三种:

  • 外部低速时钟(LSE):产生32.768KHz的时钟信号
  • 内部低速时钟(LSI):产生的32KHz时钟信号
  • 外部高速时钟分频(HSE_RTC):产生的32KHz时钟信号

小熊派开发板上设计了外部晶振,切换到 Clock Configuration 页面,配置为使用外部晶振(若无法选择,检查LSE时钟是否配置为外部晶振):
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
② 配置预分频器

RTC外设时钟源信号进来后经过两个预分频器,如图中红框所示:

  • 异步预分频器(async):7bit、默认值为128,产生ck_apre时钟信号,为亚秒级计数器RTC_***提供时钟;
  • 同步预分频器(sync):15bit、默认值为256,产生ck_spre时钟信号,为日历更新提供时钟;
    STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
    本文中采用LSE作为RTC外设时钟源,在两个分频器的值都是默认值的情况下,最后产生的时钟节拍为 1Hz。

f s p r e = 32768 / 128 / 256 = 1 H z f_{spre} = 32768/128/256=1Hz fspre?=32768/128/256=1Hz
所以,此处两个预分频器的值保持默认即可:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
③ 配置日历

激活日历功能,并设置日期和时间初始值:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

关于二进制码和BCD码,比如当前是36秒(十进制),换算为十六进制就是一个字节0x24,但BCD码是用一个字节的高4位和低4位分别来表示3和6,3的十六进制是0x03,6的十六进制是0x06,合在一起BCD码就是0x36。

配置时钟树

STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

生成工程设置

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

代码生成设置

最后设置生成独立的初始化文件:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

生成代码

点击GENERATE CODE即可生成MDK-V5工程:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

3. 在MDK中编写、编译、下载用户代码

printf重定向

STM32CubeMX_09 | 重定向printf函数到串口输出的多种方法

RTC时间/日期设置与读取

时分秒可以从RTC时间寄存器(RTC_TR)中读出:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
日期可以从RTC日期寄存器(RTC_DR)中读出:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
在HAL库中提供了读取时间、读取日期、设置时间、设置日期的API:

/** @defgroup RTC_Exported_Functions_Group2 RTC Time and Date functions
  * @{
  */
/* RTC Time and Date functions ************************************************/
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);
/**
  * @}
  */

其中时间结构体RTC_TimeTypedef的设计如下:

/**
  * @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 = 12 if the RTC_HourFormat_12 is selected.
                                 This parameter must be a number between Min_Data = 0 and Max_Data = 23 if the RTC_HourFormat_24 is selected */

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

  uint8_t TimeFormat;       /*!< Specifies the RTC AM/PM Time.
                                 This parameter can be a value of @ref RTC_AM_PM_Definitions */

  uint32_t SubSeconds;     /*!< Specifies the RTC_*** RTC Sub Second register content.
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity */

  uint32_t SecondFraction;  /*!< Specifies the range or granularity of Sub Second register content
                                 corresponding to Synchronous pre-scaler factor value (PREDIV_S)
                                 This parameter corresponds to a time unit range between [0-1] Second
                                 with [1 Sec / SecondFraction +1] granularity.
                                 This field will be used only by HAL_RTC_GetTime function */

  uint32_t DayLightSaving;  /*!< Specifies RTC_DayLightSaveOperation: the value of hour adjustment.
                                 This parameter can be a value of @ref RTC_DayLightSaving_Definitions */

  uint32_t StoreOperation;  /*!< Specifies RTC_StoreOperation value to be written in the BKP bit
                                 in CR register to store the operation.
                                 This parameter can be a value of @ref RTC_StoreOperation_Definitions */
} RTC_TimeTypeDef;

其中日期结构体RTC_DataTypedef的设计如下:

/**
  * @brief  RTC Date structure definition
  */
typedef struct
{
  uint8_t WeekDay;  /*!< Specifies the RTC Date WeekDay.
                         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;

编写测试程序

基于上述API,编写测试程序:每秒读取一次日期和时间。

首先在main函数中创建变量:

/* USER CODE BEGIN 1 */
HAL_StatusTypeDef status;
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;

/* USER CODE END 1 */

接着编写初始化部分的代码:

/* USER CODE BEGIN 2 */
printf("RTC test on bearpi borad by mculover666!\r\n");  

/* USER CODE END 2 */

最后编写循环中的代码:

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
	/* USER CODE END WHILE */
	
	/* USER CODE BEGIN 3 */
	// read rtc time
	status = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
	if (status != HAL_OK) {
	    printf("get time fail, status is %d\r\n", status);
	}
	
	// read rtc date
	status = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
	if (status != HAL_OK) {
	    printf("get date fail, status is %d\r\n", status);
	} 
	printf("%d-%d-%d(%d) %d:%d:%d:%d\r\n", sDate.Year, sDate.Month, sDate.Date, sDate.WeekDay,
	    sTime.Hours, sTime.Minutes, sTime.Seconds, sTime.SubSeconds);
	
	HAL_Delay(1000);
}
/* USER CODE END 3 */

测试结果为:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

4. RTC闹钟功能

设置闹钟

RTC外设带有Alarm A和 Alarm B两个闹钟,两个闹钟用法相同,这里我用 Alarm A 演示如何使用。

配置开启闹钟:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
设定闹钟值,MASK用来决定闹钟匹配时是否屏蔽该字段:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
当RTC当前值和闹钟设定值相同时,会将RTC初始值和状态寄存器(RTC_ISR)中的 ALRAF 标志位硬件置位:

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

RTC闹钟的中断

RTC外设没有独立的中断,但是ST巧妙的将RTC外设都连接到了外部中断EXTI,通过触发EXTI来产生RTC外设中断。

通过查阅参考手册可以看到使能 RTC 闹钟中断的步骤:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
前两步配置并使能EXTI、选择上升沿有效,配置并使能 RTC_Alarm 中断,在cubemx中直接使能即可:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
第三步配置RTC生成闹钟中断,在上一小节设置闹钟时间时,cubemx生成的代码中会自动生成该步代码。

至此,配置完成,生成代码。

编写闹钟中断回调函数

cubemx中默认配置了生成外设中断服务函数,并在其中调用HAL的处理函数:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
所以在stm32l4xx_it.c文件中可以看到闹钟中断处理函数:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
按照HAL库的中断处理思想,编写回调函数,这里需要注意,因为RTC外设所有的中断都是通过EXTI触发的,所以中断触发后,HAL会根据不同的标志位去调用不同的回调函数。

HAL库提供了两种机制供我们使用,通过宏定义USE_HAL_RTC_REGISTER_CALLBACKS的值来判断。

① 当宏定义USE_HAL_RTC_REGISTER_CALLBACKS的值为0时,HAL库默认提供了弱定义的回调函数:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
我们只需要重新实现即可,如下:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// RTC Alarm A Event callback
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(hrtc);
    
    printf("---> alarm a callback! <---\r\n");
}
/* USER CODE END 0 */

② 当宏定义USE_HAL_RTC_REGISTER_CALLBACKS的值为1时,HAL库并提供了回调函数注册机制,API如下:

/* Callbacks Register/UnRegister functions  ***********************************/
#if (USE_HAL_RTC_REGISTER_CALLBACKS == 1)
HAL_StatusTypeDef HAL_RTC_RegisterCallback(RTC_HandleTypeDef *hrtc, HAL_RTC_CallbackIDTypeDef CallbackID, pRTC_CallbackTypeDef pCallback);
HAL_StatusTypeDef HAL_RTC_UnRegisterCallback(RTC_HandleTypeDef *hrtc, HAL_RTC_CallbackIDTypeDef CallbackID);
#endif /* (USE_HAL_RTC_REGISTER_CALLBACKS == 1) */

其中CallbackID是一个枚举类型,pCallback 是一个函数指针,定义如下:
STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)
所以我们可以在main.c中编写如下的回调函数,用于处理Alarm A闹钟中断:

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// RTC Alarm A Event callback
void AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
    printf("---> alarm a callback! <---\r\n");
}

/* USER CODE END 0 */

最后在main函数初始化代码之后,注册该回调函数:

/* USER CODE BEGIN 2 */
printf("RTC test on bearpi borad by mculover666!\r\n");

status = HAL_RTC_RegisterCallback(&hrtc, HAL_RTC_ALARM_A_EVENT_CB_ID, AlarmAEventCallback);
if (status != HAL_OK) {
	printf("rtc register callback fail!\r\n");
} else {
	printf("rtc register callback success!\r\n");
}
/* USER CODE END 2 */

测试结果

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

?

STM32CubeMX | 40 - 实时时钟RTC的使用(日历和闹钟)

上一篇:redis专题:redis的常用数据结构及使用场景


下一篇:React Hook(自定义 Hook)