本次测试使用的开发板为正点原子的 STM32F429IGT6 阿波罗开发板,RT-Thread 的 RTC 驱动没有实现闹钟设置的部分,在测试时花了一些时间查找修改办法,在此记录一下。 1 工程的创建和配置 本次测试创建的工程是基于芯片创建的 RT-Thread 的标准工程,使用的开发环境是 RT-Thread Studio,各软件包的版本如下
1.1 CubeMX 的配置 创建工程后首先对时钟和RTC的功能进行配置,在这里我们使用 CubeMX 进行图形化的配置,双击工程中的“CubeMX Settings”打开图形化配置软件 CubeMX 进行配置。 1.1.1 时钟源的选择 RT-Thread Studio创建好的工程默认使用的是内部的低速晶振作为时钟源,因此我们首先进行时钟源的选择配置,本次使用的开发板用的是无源的晶振,HSE为25MHz,LSE为32.768KHZ。在配置界面选择 BYPASS Clock Source 表示使用的是有源晶振,选择 Crystal 表示使用的是无源晶振。时钟源选择之后在右侧的引脚描述图中可以看到对应的引脚已被配置,配置完成的界面如下图所示。
1.1.2 Debug 引脚配置 本次使用四线的STLINK进行程序的烧写和调试,相应的引脚按照下图所示进行配置。需要注意的是在裸机代码中如果Debug引脚不进行配置的话,烧写一次程序后板子将不能进行第二次烧写,原因是Debug引脚没有进行配置复用成其他功能了,此时应该调整BOOT0和BOOT1引脚的电平,使用SRAM启动,然后烧写一次程序后再将BOOT0接地,就能正常烧写程序。
1.1.3 控制台串口的配置 控制台串口的配置如下,我们使用的是串口1作为控制台串口,串口工作模式是异步通信,串口的收发引脚是PA9和PA10。配置好串口后可以在右侧的引脚图中看到PA9和PA10已经复用为串口1的功能。串口的通讯参数按照默认的115200 8n1进行配置。 因为CubeMX配置后的工程使用的是文件 cubemx/Inc/stm32f4xx_hal_conf.h 里面的宏定义来确定外设的使能,所以在这里需要配置一个串口,以便自动打开宏 HAL_UART_MODULE_ENABLED,否则编译会报错。
1.1.4 RTC的配置 本次测试的目的是测试RTC的闹钟功能,因此需要对RTC进行配置。F429有Alarm A和Alarm B两个闹钟,本次以Alarm A为例进行测试,配置界面如下图所示。初始的时间可以按照实际的进行设置,也可以不进行设置。
RTC外设没有独立的中断,是通过触发EXTI来产生RTC外设中断。因此我们还需要对中断进行配置,配置界面如下所示,我们使能了RTC Alarm中断,中断优先级测试时没有对其进行修改,按照默认进行配置。
1.1.5 时钟树配置 配置完了时钟源和外设,我们需要对时钟树进行配置,如下图所示。
1.1.6 代码生成 所有选项配置好之后我们点击CubeMX右上角的“GENERATE CODE”生成配置代码,生成之后关闭CubeMX软件,然后查看Studio软件,弹出如下图所示的对话框表示生成成功。
1.2 RT-Thread Settings 的配置 我们需要配置 RT-Thread 的设备驱动,使能 RTC 外设的驱动和 Alarm 的功能,如下所示。
除此之外还能进行时区的设置,如下所示,默认为东八区
1.3 board.h 的修改 上述配置完成之后需要在文件 drivers/board.h 中开启宏 BSP_USING_ONCHIP_RTC,以使能 RTC 硬件配置,如下所示。
2 编译及错误修改 上述的配置全部完成之后,点击编译按钮,对配置好的功能进行编译。
RTT Source Code v4.0.4 版本的 RTC 在移植时会有函数重复定义和缺少头文件的问题,需要注意一下。错误信息如下 ../drivers/drv_rtc.c:38:8: error: unknown type name 'time_t' static time_t get_rtc_timestamp(void) ../drivers/drv_rtc.c:211:17: error: conflicting types for 'rt_hw_rtc_register' static rt_err_t rt_hw_rtc_register(rt_device_t device, const char *name, rt_uint32_t flag) ^ In file included from F:HotNet_ProjectHN1000_GCUhn1000_gcu_app
t-threadcomponentsdriversinclude/rtdevice.h:33:0, from ../drivers/drv_rtc.c:13: F:HotNet_ProjectHN1000_GCUhn1000_gcu_app
t-threadcomponentsdriversinclude/drivers/rtc.h:42:10: note: previous declaration of 'rt_hw_rtc_register' was here rt_err_t rt_hw_rtc_register(rt_rtc_dev_t *rtc, 解决方法为
1. 在 drivers/drv_rtc.c 中增加头文件 #include
2. 文件 rt-thread/components/drivers/include/drivers/rtc.h 中注释掉 rt_hw_rtc_register() 函数的声明,如下图所示。
3 源码的修改 使用时发现源码有一些问题导致不能正确的产生 RTC 闹钟的中断,将修改的源码部分记录如下。 3.1 alarm.c 的修改 alarm.c 文件位于路径 rt-thread/components/drivers/rtc/alarm.c。参考本文的第 6.1 小节的解释,将 alarm.c 中的所有的 gmtime_r 替换为 localmtime_r。 3.2 drv_rtc.c 的修改 drv_rtc.c 文件位于路径drivers/drv_rtc.c。本部分内容参考文章 drv_rtc.c alarm 的实现。在 RT-Thread 源码提供的drv_rtc.c文件是缺少 RT_DEVICE_CTRL_RTC_SET_ALARM 和 RT_DEVICE_CTRL_RTC_GET_ALARM 的实现,参考论坛中的 @Mii 的文章对其进行修改,主要添加了函数 set_rtc_alarm_stamp() 和 get_rtc_alarm_stamp(),修改的详细步骤参考上面的文章链接,修改后的 drv_rtc.c 如下,添加的内容均有 2022-06-08 时间标记。 /* * Copyright (c) 2006-2018, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2018-12-04 balanceTWK first version */ #include "board.h" #include #include #ifdef BSP_USING_ONCHIP_RTC #ifndef HAL_RTCEx_BKUPRead #define HAL_RTCEx_BKUPRead(x1, x2) (~BKUP_REG_DATA) #endif #ifndef HAL_RTCEx_BKUPWrite #define HAL_RTCEx_BKUPWrite(x1, x2, x3) #endif #ifndef RTC_BKP_DR1 #define RTC_BKP_DR1 RT_NULL #endif //#define DRV_DEBUG #define LOG_TAG "drv.rtc" #include #define BKUP_REG_DATA 0xA5A5 static struct rt_device rtc; static RTC_HandleTypeDef RTC_Handler; static time_t get_rtc_timestamp(void) { RTC_TimeTypeDef RTC_TimeStruct = { 0 }; RTC_DateTypeDef RTC_DateStruct = { 0 }; struct tm tm_new; HAL_RTC_GetTime(&RTC_Handler, &RTC_TimeStruct, RTC_FORMAT_BIN); HAL_RTC_GetDate(&RTC_Handler, &RTC_DateStruct, RTC_FORMAT_BIN); tm_new.tm_sec = RTC_TimeStruct.Seconds; tm_new.tm_min = RTC_TimeStruct.Minutes; tm_new.tm_hour = RTC_TimeStruct.Hours; tm_new.tm_mday = RTC_DateStruct.Date; tm_new.tm_mon = RTC_DateStruct.Month - 1; tm_new.tm_year = RTC_DateStruct.Year + 100; LOG_D("get rtc time."); return mktime(&tm_new); } static rt_err_t set_rtc_time_stamp(time_t time_stamp) { RTC_TimeTypeDef RTC_TimeStruct = { 0 }; RTC_DateTypeDef RTC_DateStruct = { 0 }; struct tm *p_tm; p_tm = localtime(&time_stamp); if (p_tm->tm_year < 100) { return -RT_ERROR; } RTC_TimeStruct.Seconds = p_tm->tm_sec; RTC_TimeStruct.Minutes = p_tm->tm_min; RTC_TimeStruct.Hours = p_tm->tm_hour; RTC_DateStruct.Date = p_tm->tm_mday; RTC_DateStruct.Month = p_tm->tm_mon + 1; RTC_DateStruct.Year = p_tm->tm_year - 100; RTC_DateStruct.WeekDay = p_tm->tm_wday + 1; if (HAL_RTC_SetTime(&RTC_Handler, &RTC_TimeStruct, RTC_FORMAT_BIN) != HAL_OK) { return -RT_ERROR; } if (HAL_RTC_SetDate(&RTC_Handler, &RTC_DateStruct, RTC_FORMAT_BIN) != HAL_OK) { return -RT_ERROR; } LOG_D("set rtc time.");HAL_RTCEx_BKUPWrite(&RTC_Handler, RTC_BKP_DR1, BKUP_REG_DATA); return RT_EOK; } /* 后添加的设置闹钟的接口 2022-06-08 */ static rt_err_t set_rtc_alarm_stamp(struct rt_rtc_wkalarm wkalarm) { RTC_AlarmTypeDef sAlarm = { 0 }; if (wkalarm.enable == RT_FALSE) { if (HAL_RTC_DeactivateAlarm(&RTC_Handler, RTC_ALARM_A) != HAL_OK) { return -RT_ERROR; } LOG_I("stop rtc alarm."); } else { /* Enable the Alarm A */ sAlarm.AlarmTime.Hours = wkalarm.tm_hour; sAlarm.AlarmTime.Minutes = wkalarm.tm_min; sAlarm.AlarmTime.Seconds = wkalarm.tm_sec; sAlarm.AlarmTime.SubSeconds = 0x0; sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET; sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY; // 日期或者星期无效 sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL; sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE; sAlarm.AlarmDateWeekDay = 1; sAlarm.Alarm = RTC_ALARM_A; // 这里要和 CubeMX 配置的一致,均为 Alarm-A,修改的话两个都要修改 if (HAL_RTC_SetAlarm_IT(&RTC_Handler, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) { return -RT_ERROR; } LOG_I("set rtc alarm."); } return RT_EOK; /* * sAlarm.AlarmMask 闹钟掩码字段选择,即选择闹钟时间哪些字段无效。 * RTC_ALARMMASK_NONE (全部有效) * RTC_ALARMMASK_DATEWEEKDAY (日期或星期无效) * RTC_ALARMMASK_HOURS (小时无效) * RTC_ALARMMASK_MINUTES (分钟无效) * RTC_ALARMMASK_SECONDS (秒钟无效) * RTC_ALARMMASK_ALL (全部无效) * * sAlarm.AlarmDateWeekDaySel 闹钟日期或者星期选择。要想这个配置有效,AlarmMask 不能配置为 RTC_AlarmMask_DateWeekDay,否则会被 MASK 掉 * RTC_ALARMDATEWEEKDAYSEL_DATE (日期) * RTC_ALARMDATEWEEKDAYSEL_WEEKDAY (星期) * * sAlarm.AlarmDateWeekDay 具体的日期或者日期 * 当 AlarmDateWeekDaySel 设置成星期时,该成员 取值范围:1~7 * 当 AlarmDateWeekDaySel 设置成日期,该成员取值范围:1~31 */ } /* 后添加的获取闹钟时间的接口 2022-06-08 */ struct rt_rtc_wkalarm get_rtc_alarm_stamp(void) { RTC_AlarmTypeDef sAlarm = { 0 }; struct rt_rtc_wkalarm wkalarm; if (HAL_RTC_GetAlarm(&RTC_Handler, &sAlarm, RTC_ALARM_A, RTC_FORMAT_BIN) != HAL_OK) { LOG_E("get rtc alarm fail!."); } wkalarm.tm_sec = sAlarm.AlarmTime.Seconds; wkalarm.tm_min = sAlarm.AlarmTime.Minutes; wkalarm.tm_hour = sAlarm.AlarmTime.Hours; LOG_I("get rtc alarm."); return wkalarm; } /* 后添加的设置闹钟中断的接口 2022-06-08 */ void RTC_Alarm_IRQHandler(void) { /* USER CODE BEGIN RTC_Alarm_IRQn 0 */ /* USER CODE END RTC_Alarm_IRQn 0 */ HAL_RTC_AlarmIRQHandler(&RTC_Handler); /* USER CODE BEGIN RTC_Alarm_IRQn 1 */ /* USER CODE END RTC_Alarm_IRQn 1 */ } /* 后添加的设置闹钟中断的回调函数接口 2022-06-08 */ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { rt_alarm_update(&rtc, 1); // 该函数的两个形参没有用到,传什么都可以,作用是发送一个事件集,唤醒 alarmsvc 线程 } static void rt_rtc_init(void) { #ifndef SOC_SERIES_STM32H7 __HAL_RCC_PWR_CLK_ENABLE(); #endif RCC_OscInitTypeDef RCC_OscInitStruct = { 0 }; #ifdef BSP_RTC_USING_LSI RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; RCC_OscInitStruct.LSEState = RCC_LSE_OFF; RCC_OscInitStruct.LSIState = RCC_LSI_ON; #else RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; RCC_OscInitStruct.LSEState = RCC_LSE_ON; RCC_OscInitStruct.LSIState = RCC_LSI_OFF; #endif HAL_RCC_OscConfig(&RCC_OscInitStruct); } static rt_err_t rt_rtc_config(struct rt_device *dev) { RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = { 0 }; HAL_PWR_EnableBkUpAccess(); PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC; #ifdef BSP_RTC_USING_LSI PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI; #else PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; #endif HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct); /* Enable RTC Clock */ __HAL_RCC_RTC_ENABLE(); RTC_Handler.Instance = RTC; if (HAL_RTCEx_BKUPRead(&RTC_Handler, RTC_BKP_DR1) != BKUP_REG_DATA) { LOG_I("RTC hasn't been configured, please use command to config."); #if defined(SOC_SERIES_STM32F1) RTC_Handler.Init.OutPut = RTC_OUTPUTSOURCE_NONE; RTC_Handler.Init.AsynchPrediv = RTC_AUTO_1_SECOND; #elif defined(SOC_SERIES_STM32F0) /* set the frequency division */ #ifdef BSP_RTC_USING_LSI RTC_Handler.Init.AsynchPrediv = 0XA0; RTC_Handler.Init.SynchPrediv = 0xFA; #else RTC_Handler.Init.AsynchPrediv = 0X7F; RTC_Handler.Init.SynchPrediv = 0x0130; #endif /* BSP_RTC_USING_LSI */ RTC_Handler.Init.HourFormat = RTC_HOURFORMAT_24; RTC_Handler.Init.OutPut = RTC_OUTPUT_DISABLE; RTC_Handler.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; RTC_Handler.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; #elif defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7) || defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32H7) /* set the frequency division */ #ifdef BSP_RTC_USING_LSI RTC_Handler.Init.AsynchPrediv = 0X7D; #else RTC_Handler.Init.AsynchPrediv = 0X7F; #endif /* BSP_RTC_USING_LSI */ RTC_Handler.Init.SynchPrediv = 0XFF; RTC_Handler.Init.HourFormat = RTC_HOURFORMAT_24; RTC_Handler.Init.OutPut = RTC_OUTPUT_DISABLE; RTC_Handler.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH; RTC_Handler.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; #endif if (HAL_RTC_Init(&RTC_Handler) != HAL_OK) { return -RT_ERROR; } } return RT_EOK; } static rt_err_t rt_rtc_control(rt_device_t dev, int cmd, void *args) { rt_err_t result = RT_EOK; RT_ASSERT(dev != RT_NULL); switch (cmd) { case RT_DEVICE_CTRL_RTC_GET_TIME: *(rt_uint32_t *) args = get_rtc_timestamp(); LOG_D("RTC: get rtc_time %x
", *(rt_uint32_t * )args); break; case RT_DEVICE_CTRL_RTC_SET_TIME: if (set_rtc_time_stamp(*(rt_uint32_t *) args)) { result = -RT_ERROR; } LOG_D("RTC: set rtc_time %x
", *(rt_uint32_t * )args); break; case RT_DEVICE_CTRL_RTC_SET_ALARM: if (set_rtc_alarm_stamp(*(struct rt_rtc_wkalarm *) args)) { result = -RT_ERROR; } LOG_D("RTC: set rtc_alarm tme : hour: %d , min: %d , sec: %d
", *(struct rt_rtc_wkalarm * )args->tm_hour, *(struct rt_rtc_wkalarm * )args->tm_min, *(struct rt_rtc_wkalarm * )args->tm_sec); break; case RT_DEVICE_CTRL_RTC_GET_ALARM: *(struct rt_rtc_wkalarm *) args = get_rtc_alarm_stamp(); LOG_D("RTC: get rtc_alarm time : hour: %d , min: %d , sec: %d
", *(struct rt_rtc_wkalarm * )args->tm_hour, *(struct rt_rtc_wkalarm * )args->tm_min, *(struct rt_rtc_wkalarm * )args->tm_sec); break; } return result; } #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops rtc_ops = { RT_NULL, RT_NULL, RT_NULL, RT_NULL, RT_NULL, rt_rtc_control }; #endif static rt_err_t rt_hw_rtc_register(rt_device_t device, const char *name, rt_uint32_t flag) { RT_ASSERT(device != RT_NULL); rt_rtc_init(); if (rt_rtc_config(device) != RT_EOK) { return -RT_ERROR; } #ifdef RT_USING_DEVICE_OPS device->ops = &rtc_ops; #else device->init = RT_NULL; device->open = RT_NULL; device->close = RT_NULL; device->read = RT_NULL; device->write = RT_NULL; device->control = rt_rtc_control; #endif device->type = RT_Device_Class_RTC; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; device->user_data = RT_NULL; /* register a character device */ return rt_device_register(device, name, flag); } int rt_hw_rtc_init(void) { rt_err_t result; result = rt_hw_rtc_register(&rtc, "rtc", RT_DEVICE_FLAG_RDWR); if (result != RT_EOK) { LOG_E("rtc register err code: %d", result); return result; } LOG_D("rtc init success"); return RT_EOK; } INIT_DEVICE_EXPORT(rt_hw_rtc_init); #endif /* BSP_USING_ONCHIP_RTC */ 4 测试用例和测试结果 4.1 测试用例 测试用例如下,本次定义了一个执行闹钟测试命令 5 秒的闹钟,然后通过日志信息来查看闹钟的响应,测试代码如下。 static rt_alarm_t alarm = RT_NULL; /* 闹钟的用户回调函数 */ void user_alarm_callback(rt_alarm_t alarm, time_t timestamp) { struct tm p_tm; localtime_r(×tamp, &p_tm); // 时间戳转换 LOG_D("user alarm callback function."); LOG_D("curr time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec); // 打印闹钟中断产生时的时间,和设定的闹钟时间比对,以确定得到的是否是想要的结果 } /* 闹钟示例 */ void alarm_sample(void) { time_t curr_time; struct tm p_tm; struct rt_alarm_setup setup; curr_time = time(NULL) + 5; // 将闹钟的时间设置为当前时间的往后的 5 秒 localtime_r(&curr_time, &p_tm); // 将时间戳转换为本地时间,localtime_r 是线程安全的 LOG_D("now time: %04d-%02d-%02d %02d:%02d:%02d", p_tm.tm_year + 1900, p_tm.tm_mon + 1, p_tm.tm_mday, p_tm.tm_hour, p_tm.tm_min, p_tm.tm_sec - 5); // 打印当前时间,其中秒应该减去 5,因为前面加了 5 setup.flag = RT_ALARM_ONESHOT; // 单次闹钟 setup.wktime.tm_year = p_tm.tm_year; setup.wktime.tm_mon = p_tm.tm_mon; setup.wktime.tm_mday = p_tm.tm_mday; setup.wktime.tm_wday = p_tm.tm_wday; setup.wktime.tm_hour = p_tm.tm_hour; setup.wktime.tm_min = p_tm.tm_min; setup.wktime.tm_sec = p_tm.tm_sec; alarm = rt_alarm_create(user_alarm_callback, &setup); // 创建一个闹钟并设置回调函数 if (RT_NULL != alarm) { rt_alarm_start(alarm); // 启动闹钟 } else { LOG_E("rtc alarm create failed"); } rt_alarm_dump(); // 打印闹钟的信息 } MSH_CMD_EXPORT(alarm_sample, alarm sample); /* 删除闹钟测试 */ void delete_alarm_sample(void) { rt_alarm_delete(alarm); // 删除闹钟 rt_alarm_dump(); // 打印闹钟的信息 } MSH_CMD_EXPORT(delete_alarm_sample, delete alarm sample); 4.2 测试结果 测试结果如下,测试结果的分析在下面的结果中以注释的内容呈现。 msh >alarm_sample // 命令行输入命令进行闹钟功能的测试 [D/main] now time: 2022-06-08 22:26:32 // 打印要设置闹钟时的当前时间 [I/drv.rtc] set rtc alarm. [I/drv.rtc] get rtc alarm. | id | YYYY-MM-DD hh:mm:ss | week | flag | en | +----+---------------------+------+------+----+ | 0 | 2022-06-08 22:26:37 | 3 | O | 1 | +----+---------------------+------+------+----+ msh > msh >[I/drv.rtc] stop rtc alarm. [D/main] user alarm callback function. // 执行了闹钟的回调函数 [D/main] curr time: 2022-06-08 22:26:37 // 在闹钟的回调函数中打印当前的时间,验证闹钟时间是否正确 [I/drv.rtc] stop rtc alarm. msh >rt_alarm_dump | id | YYYY-MM-DD hh:mm:ss | week | flag | en | +----+---------------------+------+------+----+ | 0 | 2022-06-08 22:26:37 | 3 | O | 0 | +----+---------------------+------+------+----+ msh > msh >alarm_sample // 再次实验闹钟功能 [D/main] now time: 2022-06-08 22:26:51 [I/drv.rtc] set rtc alarm. [I/drv.rtc] get rtc alarm. | id | YYYY-MM-DD hh:mm:ss | week | flag | en | +----+---------------------+------+------+----+ | 0 | 2022-06-08 22:26:56 | 3 | O | 1 | | 1 | 2022-06-08 22:26:37 | 3 | O | 0 | +----+---------------------+------+------+----+ msh > msh >[I/drv.rtc] stop rtc alarm. [D/main] user alarm callback function. [D/main] curr time: 2022-06-08 22:26:56 [I/drv.rtc] stop rtc alarm. msh > msh >rt_alarm_dump // 有两个闹钟 | id | YYYY-MM-DD hh:mm:ss | week | flag | en | +----+---------------------+------+------+----+ | 0 | 2022-06-08 22:26:56 | 3 | O | 0 | | 1 | 2022-06-08 22:26:37 | 3 | O | 0 | +----+---------------------+------+------+----+ msh > msh >delete_alarm_sample // 删除闹钟测试 [I/drv.rtc] stop rtc alarm. // 删除后只有一个闹钟 | id | YYYY-MM-DD hh:mm:ss | week | flag | en | +----+---------------------+------+------+----+ | 0 | 2022-06-08 22:26:37 | 3 | O | 0 | +----+---------------------+------+------+----+ msh > 5 源码分析 5.1 RTC设备初始化源码分析 RTC 设备注册和初始化的过程如下所示。 rt_hw_rtc_init // RTC硬件初始化 (drv_rtc.c) |-> rt_hw_rtc_register // RTC硬件注册 (drv_rtc.c) |-> rt_rtc_init // RTC时钟源初始化,默认使用LSE (drv_rtc.c) |-> rt_rtc_config // 配置了分频因子、信号输出极性和类型等参数 (drv_rtc.c) |-> HAL_RTC_Init // RTC外设的初始化 |-> HAL_RTC_MspInit // RTC MSP 初始化,由 CubeMX 配置生成 |-> rt_device_register // RTC设备注册 |-> rt_object_init 从函数的调用流程和分析我们可以得出,其实在 CubeMX 配置 RTC 的参数时只有生成的函数 HAL_RTC_MspInit(cubemx/Src/stm32f4xx_hal_msp.c) 被调用了,该函数的具体内容如下,也就是说只有配置的 RTC 的中断初始化被调用。
在 CubeMX 中配置的分频因子,时间日志和闹钟的初始值等参数均没有被使用,因此可以不用在 CubeMX 中配置相关的参数。分频因子的配置由 RT-Thread 源码默认设定了,在函数 rt_rtc_config 里面实现,开发板的时间需要手动的使用 date 命令或者 set_time() 函数进行设置。 void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc) { RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; if(hrtc->Instance==RTC) { /* USER CODE BEGIN RTC_MspInit 0 */ /* USER CODE END RTC_MspInit 0 */ /** Initializes the peripherals clock */ PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC; PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { Error_Handler(); } /* Peripheral clock enable */ __HAL_RCC_RTC_ENABLE(); /* RTC interrupt Init */ HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn); /* USER CODE BEGIN RTC_MspInit 1 */ /* USER CODE END RTC_MspInit 1 */ } } 5.2 RTC闹钟源码分析 RTC闹钟源码调用过程如下。
在闹钟的启动函数中我们可以看到该函数调用了在文件 drv_rtc.c 中添加的函数 set_rtc_alarm_stamp() 将设定的闹钟值写入到对应的寄存器中 闹钟的处理流程可以概括为创建一个名字为 alarmsvc 的线程,该线程一直在等待一个闹钟事件集,该事件集有闹钟的中断处理函数中进行发送。alarmsvc 线程获取到该闹钟事件集后就更新闹钟事件,然后进行调用函数 alarm_wakeup() 进行闹钟的唤醒操作,最终调用了用户使用函数 rt_alarm_create() 在创建闹钟时设定的回调函数,执行用户设定的操作。 // 启动闹钟的流程分析 rt_alarm_start // 启动闹钟 (alarm.c) |-> alarm_setup // 根据不同的闹钟模式对闹钟值进行设置 (alarm.c) |-> alarm_set // 将设置的闹钟值写入对应的寄存器中 (alarm.c) |-> rt_device_control(RT_DEVICE_CTRL_RTC_SET_ALARM) // (device.c) |-> device_control // (device.c) |-> rt_rtc_control // (drv_rtc.c) |-> set_rtc_alarm_stamp // 自己添加的设置闹钟的函数 (drv_rtc.c) |-> HAL_RTC_SetAlarm_IT // (stm32f4xx_hal_rtc.c) // 闹钟处理流程分析 rt_alarm_system_init // 闹钟系统初始化 (alarm.c) |-> rt_thread_create("alarmsvc", rt_alarmsvc_thread_init) // 创建闹钟处理线程 (alarm.c) rt_alarmsvc_thread_init // 闹钟处理线程 (alarm.c) |-> rt_event_recv(&_container.event) // 等待闹钟事件集 (alarm.c) |-> alarm_update // 更新闹钟事件 (alarm.c) |-> alarm_wakeup // 闹钟唤醒的操作 (alarm.c) |-> switch (alarm->flag & 0xFF00) // 根据闹钟的不同模式对闹钟的唤醒时间进行不同的操作 (alarm.c) |-> alarm->callback(alarm, timestamp); // 执行用户定义的闹钟中断的回调函数 (alarm.c) // 闹钟中断的处理流程 RTC_Alarm_IRQHandler // 闹钟中断的处理函数 (drv_rtc.c) |-> HAL_RTC_AlarmIRQHandler // HAL库提供的中断处理函数 (stm32f4xx_hal_rtc.c) |-> HAL_RTC_AlarmAEventCallback // 自己重写的闹钟中断回调函数 (drv_rtc.c) |-> rt_alarm_update // RTT提供的更新闹钟时间,实际是发用一个闹钟事件集 (alarm.c) |-> rt_event_send // 发送闹钟事件集,线程 alarmsvc 在移植阻塞等待该事件集 (ipc.c) 6 问题记录 6.1 时区问题 参考官方文档 时间函数 说底层驱动不应当使用带有时区的时间,而应该使用格林威治时间,即 UTC+0,但是在使用函数 set_time(rt-thread/components/drivers/rtc/rtc.c) 设置RTC的时间时却使用了函数 localtime_r,也就是说写入到寄存器的时间确实是带时区的时间,查看相应的寄存器写入的也是带时区的时间,这部分代码和文档的说法有矛盾。
在 alarm.c 的函数 alarm_update() 获取的却是不带时区的时间,然后在函数 alarm_wakeup() 中对不带时区的现在时间和带时区的闹钟时间进行对比,结果就是 sec_now 永远小于 sec_alarm,导致变量 wakeup 的值永远为 FALSE,也就是永远不能执行用户定义的alarm回调函数 alarm->callback。解决办法是将 alarm.c 中的所有的 gmtime_r 替换为 localmtime_r。
原作者:crystal266
|