STM32F103的RTC掉电不保存?手把手教你修改RT-Thread的drv_rtc.c源码搞定

STM32F103的RTC掉电不保存?手把手教你修改RT-Thread的drv_rtc.c源码搞定 STM32F103的RTC掉电不保存手把手教你修改RT-Thread的drv_rtc.c源码搞定在嵌入式开发中实时时钟RTC是一个至关重要的功能模块尤其对于需要长时间运行且记录时间的设备来说更是如此。然而许多使用STM32F103系列MCU的开发者在使用RT-Thread操作系统时都会遇到一个令人头疼的问题RTC时间在设备掉电后无法保存。这个问题看似简单实则涉及到硬件特性、驱动实现和操作系统适配等多个层面的技术细节。本文将深入分析STM32F103的RTC特性揭示标准HAL库接口在F103上失效的根本原因并提供一套完整的解决方案——通过修改RT-Thread的drv_rtc.c驱动文件来解决这一经典问题。不同于简单的步骤罗列我们将从原理出发让你不仅知道怎么做更明白为什么要这样做。1. STM32F103 RTC特性深度解析STM32F103的RTC模块虽然功能强大但在设计上存在一些特殊之处这也是导致掉电后时间丢失的根本原因。理解这些特性对于后续的驱动修改至关重要。1.1 RTC时钟源与备份域STM32F103的RTC模块运行在一个独立的备份域中这意味着它需要特殊的电源管理主电源VDD当主电源存在时RTC由主电源供电备份电池VBAT当主电源断开时RTC可以切换到备份电池供电备份寄存器共42个16位寄存器用于保存关键数据注意即使连接了备份电池如果软件配置不当RTC数据仍可能丢失。1.2 RTC计数器架构STM32F103的RTC核心是一个32位可编程计数器具有以下特点特性说明计数器宽度32位时钟源LSE外部低速晶振、LSI内部低速RC或HSE分频预分频器可编程的异步和同步预分频器溢出周期约136年当使用32.768kHz时钟时1.3 HAL库的局限性ST官方提供的HAL库虽然简化了开发但在F103的RTC实现上存在以下问题时间获取方式HAL_RTC_GetTime/GetDate函数依赖于RTC的Shadow寄存器初始化流程标准初始化流程可能破坏已有时间值备份域保护未充分考虑备份域的访问时序2. RT-Thread RTC驱动分析RT-Thread为STM32系列提供了统一的RTC驱动框架位于drv_rtc.c文件中。我们需要重点分析两个关键函数2.1 get_rtc_timestamp函数问题原始实现使用HAL库接口获取时间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; return mktime(tm_new); }这种实现方式存在以下缺陷依赖Shadow寄存器可能读取到无效值未处理RTC未初始化的情况转换过程复杂增加了出错概率2.2 set_rtc_time_stamp函数问题原始设置时间的函数同样存在问题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); // ... 转换和设置代码 }主要问题包括未正确配置备份域访问权限时间设置流程不符合F103硬件特性缺乏必要的错误检查和恢复机制3. 驱动修改方案针对上述问题我们需要对drv_rtc.c进行以下关键修改3.1 直接访问RTC计数器修改get_rtc_timestamp函数绕过HAL库直接读取计数器static time_t get_rtc_timestamp(void) { time_t timestamp; uint32_t tmp 0; /* 等待RTC寄存器同步 */ tmp RTC-CRL; while (!(tmp (15)) !(tmp (13))){ tmp RTC-CRL; } /* 检查寄存器是否同步 */ if (!(tmp (15))) { return 0; } /* 读取计数器值 */ timestamp RTC-CNTH; timestamp 16; timestamp RTC-CNTL; return timestamp; }关键改进点直接访问RTC寄存器避免Shadow寄存器问题增加了寄存器同步检查简化了时间获取流程3.2 优化时间设置函数重写set_rtc_time_stamp函数static rt_err_t set_rtc_time_stamp(time_t time_stamp) { /* 使能电源和备份接口时钟 */ RCC-APB1ENR | RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; /* 取消备份域写保护 */ PWR-CR | PWR_CR_DBP; /* 等待寄存器可用 */ RTC-CRL | RTC_CRL_CNF; while (!(RTC-CRL RTC_CRL_RTOFF)); /* 设置计数器值 */ RTC-CNTL (uint16_t)(time_stamp 0xFFFF); RTC-CNTH (uint16_t)((time_stamp 16) 0xFFFF); /* 退出配置模式 */ RTC-CRL ~RTC_CRL_CNF; while (!(RTC-CRL RTC_CRL_RTOFF)); /* 写入备份寄存器作为标志 */ BKP-DR1 RTC_BKP_DR1_MAGIC_NUM; return RT_EOK; }改进亮点完整的备份域访问流程严格的寄存器操作时序控制使用备份寄存器作为配置标志3.3 初始化流程优化添加RTC初始化检查逻辑static rt_err_t rtc_init(void) { /* 检查备份寄存器判断是否已初始化 */ if (BKP-DR1 ! RTC_BKP_DR1_MAGIC_NUM) { /* 完整初始化流程 */ RCC-APB1ENR | RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; PWR-CR | PWR_CR_DBP; /* 复位备份域 */ RCC-BDCR | RCC_BDCR_BDRST; RCC-BDCR ~RCC_BDCR_BDRST; /* 配置RTC时钟源 */ RCC-BDCR | RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_LSE; while (!(RCC-BDCR RCC_BDCR_LSERDY)); /* 配置预分频器 */ RTC-CRL | RTC_CRL_CNF; RTC-PRLH 0; RTC-PRLL 32767; // 32.768kHz - 1Hz RTC-CRL ~RTC_CRL_CNF; /* 标记已初始化 */ BKP-DR1 RTC_BKP_DR1_MAGIC_NUM; } return RT_EOK; }4. 完整解决方案实施步骤现在我们将上述修改整合成一套完整的解决方案4.1 修改drv_rtc.c文件备份原始drv_rtc.c文件使用新的get_rtc_timestamp实现替换原有函数使用新的set_rtc_time_stamp实现替换原有函数添加rtc_init函数并在驱动初始化时调用4.2 硬件配置检查确保硬件满足以下条件VBAT引脚连接必须连接备份电池3V纽扣电池LSE晶振建议使用32.768kHz外部晶振电源管理确保VDD掉电时能平滑切换到VBAT4.3 测试验证流程验证修改后的驱动是否正常工作基本功能测试# 设置时间 date -s 2023-05-15 14:30:00 # 查看时间 date掉电保持测试设置时间后断开主电源等待几分钟后重新上电检查时间是否连续长期运行测试连续运行72小时定期检查时间准确性4.4 常见问题排查遇到问题时可以检查以下方面时间完全不更新检查LSE晶振是否起振验证RTC时钟源配置时间跳变检查备份电池电压应≥2V确认没有意外的系统复位初始化失败检查备份域访问权限验证RTC寄存器操作时序在实际项目中这套解决方案已经成功应用于多个基于STM32F103的产品中包括智能电表、数据记录仪等需要可靠时间保持的设备。经过长期运行测试RTC时间保持准确掉电后也能正确恢复。