本篇详细的记录了如何使用STM32CubeMX配置STM32L431RCT6的 RTC 外设。
1. 准备工作本系列教程所编写的驱动源码:https://github.com/Mculover666/HAL_Driver_Lib,好用的话,记得点个Star呀!
硬件准备
首先需要准备一个开发板,这里我准备的是STM32L4的开发板(BearPi):
软件准备
- 需要安装好Keil - MDK及芯片对应的包,以便编译和下载生成的代码。
选择芯片型号
打开STM32CubeMX,打开MCU选择器:
搜索并选中芯片STM32L431RCT6:
配置时钟源
- 如果选择使用外部高速时钟(HSE),则需要在System Core中配置RCC;
- 如果使用默认内部时钟(HSI),这一步可以略过;
这里我都使用外部时钟:
配置串口
开发板板载了一个CH340换串口,连接到USART1。
接下来开始配置USART1:
配置RTC
RTC外设全称 Real-Time Clock,主要用处为:
- 日历:输出年月日、时分秒、星期
- 闹钟:提供闹钟中断
- 唤醒:低功耗模式唤醒中断
① 配置RTC外设的时钟来源
首先选中RTC外设,激活时钟源:
RTC外设的时钟来源有三种:
- 外部低速时钟(LSE):产生32.768KHz的时钟信号
- 内部低速时钟(LSI):产生的32KHz时钟信号
- 外部高速时钟分频(HSE_RTC):产生的32KHz时钟信号
小熊派开发板上设计了外部晶振,切换到 Clock Configuration 页面,配置为使用外部晶振(若无法选择,检查LSE时钟是否配置为外部晶振):
② 配置预分频器
RTC外设时钟源信号进来后经过两个预分频器,如图中红框所示:
- 异步预分频器(async):7bit、默认值为128,产生ck_apre时钟信号,为亚秒级计数器RTC_***提供时钟;
- 同步预分频器(sync):15bit、默认值为256,产生ck_spre时钟信号,为日历更新提供时钟;
本文中采用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
所以,此处两个预分频器的值保持默认即可:
③ 配置日历
激活日历功能,并设置日期和时间初始值:
关于二进制码和BCD码,比如当前是36秒(十进制),换算为十六进制就是一个字节0x24,但BCD码是用一个字节的高4位和低4位分别来表示3和6,3的十六进制是0x03,6的十六进制是0x06,合在一起BCD码就是0x36。
配置时钟树
STM32L4的最高主频到80M,所以配置PLL,最后使HCLK = 80Mhz即可:
生成工程设置
代码生成设置
最后设置生成独立的初始化文件:
生成代码
点击GENERATE CODE即可生成MDK-V5工程:
printf重定向
STM32CubeMX_09 | 重定向printf函数到串口输出的多种方法
RTC时间/日期设置与读取
时分秒可以从RTC时间寄存器(RTC_TR)中读出:
日期可以从RTC日期寄存器(RTC_DR)中读出:
在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 */
测试结果为:
设置闹钟
RTC外设带有Alarm A和 Alarm B两个闹钟,两个闹钟用法相同,这里我用 Alarm A 演示如何使用。
配置开启闹钟:
设定闹钟值,MASK用来决定闹钟匹配时是否屏蔽该字段:
当RTC当前值和闹钟设定值相同时,会将RTC初始值和状态寄存器(RTC_ISR)中的 ALRAF 标志位硬件置位:
RTC闹钟的中断
RTC外设没有独立的中断,但是ST巧妙的将RTC外设都连接到了外部中断EXTI,通过触发EXTI来产生RTC外设中断。
通过查阅参考手册可以看到使能 RTC 闹钟中断的步骤:
前两步配置并使能EXTI、选择上升沿有效,配置并使能 RTC_Alarm 中断,在cubemx中直接使能即可:
第三步配置RTC生成闹钟中断,在上一小节设置闹钟时间时,cubemx生成的代码中会自动生成该步代码。
至此,配置完成,生成代码。
编写闹钟中断回调函数
cubemx中默认配置了生成外设中断服务函数,并在其中调用HAL的处理函数:
所以在stm32l4xx_it.c文件中可以看到闹钟中断处理函数:
按照HAL库的中断处理思想,编写回调函数,这里需要注意,因为RTC外设所有的中断都是通过EXTI触发的,所以中断触发后,HAL会根据不同的标志位去调用不同的回调函数。
HAL库提供了两种机制供我们使用,通过宏定义USE_HAL_RTC_REGISTER_CALLBACKS的值来判断。
① 当宏定义USE_HAL_RTC_REGISTER_CALLBACKS的值为0时,HAL库默认提供了弱定义的回调函数:
我们只需要重新实现即可,如下:
/* 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 是一个函数指针,定义如下:
所以我们可以在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 */
测试结果
?