STM32 RTC实战指南:从原理到低功耗数据记录仪设计

STM32 RTC实战指南:从原理到低功耗数据记录仪设计 1. 项目概述为什么嵌入式系统离不开RTC在嵌入式开发领域尤其是基于STM32这类微控制器的项目中时间是一个既基础又关键的概念。你可能需要记录设备何时启动、何时采集了数据或者让设备在每天的特定时刻执行某个任务。这时候系统内部的SysTick定时器虽然能提供毫秒级的延时但它一断电就清零无法提供“真实世界”的、持续流逝的时间。这就是RTCReal-Time Clock实时时钟模块大显身手的地方。简单来说STM32的RTC就是一个独立的“电子手表”。它通常由一个32.768kHz的低速外部晶振LSE驱动功耗极低即使在主电源VDD断开的情况下也能依靠后备电池VBAT供电持续不断地走时。对于需要长时间运行、记录时间戳、实现定时唤醒或执行日历相关功能的设备如数据记录仪、智能仪表、穿戴设备、安防系统等RTC是不可或缺的核心外设。我刚开始接触STM32时也觉得RTC配置起来有点繁琐寄存器多还要考虑时钟源、日历初始化、中断配置等一系列问题。但一旦打通了任督二脉你会发现它其实是STM32外设里非常“忠诚”和可靠的一个伙伴。本文将基于STM32F1/F4系列深入拆解RTC的原理、配置方法、常见坑点以及高级应用目标是让你不仅能“配通”更能“吃透”。2. RTC整体架构与核心设计思路要玩转RTC不能只停留在调用HAL库函数的层面理解其硬件架构和设计逻辑至关重要。这能帮助你在出问题时快速定位是晶振没起振还是备份域没解锁2.1 独立供电域RTC的“生命线”STM32的RTC模块设计得非常巧妙它位于一个独立的备份域Backup Domain中。这个域包括RTC、备份寄存器Backup Registers和入侵检测引脚。它的供电由VBAT引脚专门负责。主电源VDD供电时VBAT引脚通常通过一个肖特基二极管连接到VDD此时由主电源为备份域供电同时为后备电池如果连接了充电。主电源VDD断开时VBAT引脚自动切换到由后备电池通常是3V的纽扣电池如CR2032供电。此时整个MCU主系统掉电但备份域包括RTC依然保持运行时间不会丢失备份寄存器中的数据也得以保存。注意这是第一个关键点。如果你的项目需要RTC在断电后继续走时必须在硬件上连接后备电池到VBAT引脚并且确保VBAT引脚的电容器容量足够通常1-10uF以在切换电源时保持电压稳定。2.2 时钟源选择精准度的基石RTC的时钟源决定了其计时的精度和功耗。STM32通常提供三种选择LSE低速外部晶振通常是32.768kHz的晶振。这是最常用、最推荐的选择。因为32768 2^15经过15次分频2的幂次方正好得到1Hz的秒信号便于日历计算且精度相对较高。LSI低速内部RC振荡器频率大约为40kHz不同系列有差异如F1约40kHzF4约32kHz。精度较差典型误差±1%受温度和电压影响大。但其优点是无需外部元件成本低。适用于对时间精度要求不高的低成本应用。HSE_RTC高速外部晶振分频将高速外部晶振如8MHz经过一个可编程的分频器通常是一个专用的预分频器分频到1Hz左右。这通常用于没有连接LSE但又需要比LSI更高精度的场合但功耗会比LSE高。设计思路对于绝大多数需要可靠日历功能的项目请务必选择并焊接32.768kHz的LSE晶振及其匹配电容通常为6-22pF具体参考晶振手册和STM32数据手册。这是保证长期计时精度的硬件基础。2.3 日历与闹钟核心功能解析RTC的核心功能模块是日历和闹钟。日历本质上是一个由秒、分、时、日、月、年、星期等字段组成的计数器组。在STM32中日历时间通常存储在一组影子寄存器中我们通过APB1总线读取的是这些影子寄存器的值这避免了在读取过程中比如正在进位发生数据错乱。初始化时我们需要设置一个初始的日期和时间。闹钟可以设定一个特定的日期和时间或每周的某天、每天的某时某分某秒当RTC时间到达该设定点时会产生一个闹钟中断ALARM。这是实现定时任务、唤醒停机Stop模式的核心机制。配置逻辑RTC的配置流程有一个固定的“解锁-配置-锁定”顺序主要是因为其寄存器属于备份域受到写保护。基本顺序是使能电源和备份域时钟 - 解锁备份域写保护 - 选择RTC时钟源 - 配置RTC预分频器以得到1Hz时钟 - 初始化日历时间 - 配置并使能所需中断如秒中断、闹钟中断 - 锁定备份域。3. 核心细节解析与实操要点理解了架构我们来看看配置过程中的那些“魔鬼细节”。这些往往是新手最容易栽跟头的地方。3.1 备份域访问与写保护备份域寄存器包括RTC控制寄存器默认是写保护的以防止软件意外修改导致RTC错乱。操作前必须先后解锁两个保护RCC寄存器写保护通过向RCC-APB1ENR寄存器使能PWREN和BKPENF1系列或RCC-APB1ENR1使能PWRENF4/F7/H7系列备份接口时钟默认常开。备份域写保护向备份域控制寄存器RCC-BDCRF1或RCC-BDCRF4中的DBP位写1。在HAL库中使用__HAL_RCC_PWR_CLK_ENABLE()和HAL_PWR_EnableBkUpAccess()函数完成。实操心得很多人在程序复位后重新配置RTC失败就是因为忘了在初始化流程中重新执行这个解锁操作。记住一个原则凡是涉及修改RTC相关配置包括第一次初始化必须先解锁备份域。3.2 LSE晶振的起振与调试LSE不起振是RTC无法工作的头号原因。除了检查硬件焊接晶振、电容、电池电压软件上也要注意使能与等待在RCC-BDCR中置位LSEON后必须等待LSERDY标志位被硬件置1表明晶振已稳定运行。HAL库的HAL_RCC_OscConfig()函数内部会处理这个等待但如果你用LL库或寄存器操作务必加上超时判断。驱动能力配置RCC-BDCR中的LSEBYP位和RCC-BDCRF4或RCC-CSRF1中的LSEDRV[1:0]位用于配置LSE模式。通常LSEBYP为0使用晶振模式LSEDRV选择中等驱动能力即可。如果晶振不易起振可以尝试提高驱动能力。软件调试最直接的判断方法是在初始化LSE后读取RCC-BDCR的LSERDY位。也可以将RTC时钟输出到某个引脚如PC13如果支持用示波器测量是否有32.768kHz的波形。3.3 预分频器的计算与配置这是将输入时钟如32.768kHz转换为1Hz秒信号的关键。RTC通常有一个异步预分频器ASYNCH Predivider和一个同步预分频器SYNCH Predivider。异步预分频器PREDIV_A7位F1或8位F4等分频系数为PREDIV_A1。它用于对RTC时钟源进行初次分频以降低同步电路的频率减少功耗。其输出时钟ck_apre驱动日历的亚秒部分如果有。同步预分频器PREDIV_S15位分频系数为PREDIV_S1。它对ck_apre进行进一步分频得到最终的1Hz时钟ck_spre用于驱动日历的秒、分、时等主要部分。计算公式RTC_CLK (PREDIV_A 1) * (PREDIV_S 1)对于32.768kHz的LSE最经典、误差最小的配置是PREDIV_A 127PREDIV_S 255。因为(1271)*(2551) 128*256 32768完美分频。注意在HAL库的HAL_RTC_Init()函数中会调用HAL_RTC_MspInit()你需要在这里完成RTC时钟源的选择和使能。而预分频器的设置是在HAL_RTC_Init()之后通过HAL_RTC_SetTime()和HAL_RTC_SetDate()函数调用时其参数RTC_TimeTypeDef和RTC_DateTypeDef结构体中的AsynchPrediv和SynchPrediv成员传入的。很多新手直接在HAL_RTC_MspInit里配置预分频器寄存器那是无效的因为HAL库的初始化流程会在后面覆盖它。3.4 日历的初始化与读取日历的初始化设置初始时间必须在RTC时钟源配置正确且使能后进行。初始化流程进入初始化模式RTC-ISR寄存器的INIT位置1。在HAL库中HAL_RTC_Init()会处理。等待RTC-ISR寄存器的INITF位置1表示进入初始化模式成功。配置预分频器寄存器RTC-PRER。设置时间寄存器RTC-TR和日期寄存器RTC-DR。退出初始化模式INIT位清0。读取日历由于日历计数器是独立运行的直接读取RTC-TR和RTC-DR可能在读取瞬间遇到进位如23:59:59到00:00:00导致读取的数据不一致比如秒是59分却是00。STM32的解决方案是先读取RTC-SSR亚秒或RTC-TR再读取RTC-DR然后再读一次RTC-TR。比较两次读取的RTC-TR值。如果相同说明读取过程中没有发生进位数据有效如果不同则需要重新读取。 在HAL库中HAL_RTC_GetTime()和HAL_RTC_GetDate()函数内部已经实现了这个同步机制并提供了Format参数RTC_FORMAT_BIN或RTC_FORMAT_BCD来选择二进制或BCD码格式。强烈建议使用这两个函数来读取时间避免自己实现时遗漏细节。4. 实操过程与核心环节实现下面我们以一个典型的STM32CubeIDE项目为例展示如何从零配置一个具有闹钟唤醒功能的RTC。4.1 硬件准备与CubeMX图形化配置硬件连接STM32F103C8T6最小系统板或其他型号。32.768kHz晶振两个引脚连接到OSC32_IN和OSC32_OUT匹配电容通常各接一个6-22pF的电容到地。3V纽扣电池座电池正极接VBAT引脚负极接地。ST-Link调试器。CubeMX配置步骤时钟树Clock Configuration在RCC设置中将Low Speed Clock (LSE)选择为Crystal/Ceramic Resonator。在时钟树图中你会看到LSE被自动选为RTC Clock Source。引脚分配Pinout Configuration找到RTC外设激活它。在Mode下勾选Activate Clock Source和Activate Calendar。在Parameter Settings选项卡Asynchronous Predivider value: 设置为127。Synchronous Predivider value: 设置为255。Hour Format: 选择24小时制。在NVIC Settings选项卡使能RTC Alarm中断和RTC Tamper and TimeStamp中断如果用到秒中断也在这里使能。生成代码配置好工程名、路径、工具链后生成代码。4.2 关键代码解析与填充CubeMX生成的代码搭建了框架但核心逻辑需要我们在/* USER CODE BEGIN */和/* USER CODE END */之间填充。4.2.1 初始化与时间设置在main.c的main()函数中初始化之后/* 1. 初始化RTC */ MX_RTC_Init(); // CubeMX生成的函数内部调用了HAL_RTC_Init /* 2. 设置初始时间可选通常第一次运行或检测到备份寄存器标志时执行 */ RTC_TimeTypeDef sTime {0}; RTC_DateTypeDef sDate {0}; /* 检查是否是第一次配置通过备份寄存器BKP_DR1的值判断 */ if (HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR1) ! 0x32F1) { // 0x32F1是一个自定义魔数 /* 是第一次需要设置时间 */ sTime.Hours 14; sTime.Minutes 30; sTime.Seconds 0; sTime.TimeFormat RTC_HOURFORMAT12_AM; // 24小时制下此参数无效 sTime.DayLightSaving RTC_DAYLIGHTSAVING_NONE; sTime.StoreOperation RTC_STOREOPERATION_RESET; if (HAL_RTC_SetTime(hrtc, sTime, RTC_FORMAT_BIN) ! HAL_OK) { Error_Handler(); } sDate.WeekDay RTC_WEEKDAY_MONDAY; sDate.Month RTC_MONTH_MAY; sDate.Date 6; sDate.Year 24; // 2024年 if (HAL_RTC_SetDate(hrtc, sDate, RTC_FORMAT_BIN) ! HAL_OK) { Error_Handler(); } /* 设置完成后在备份寄存器写入魔数标记已初始化 */ HAL_RTCEx_BKUPWrite(hrtc, RTC_BKP_DR1, 0x32F1); } else { /* 不是第一次RTC已在运行可以直接读取时间 */ printf(RTC has been initialized before.\r\n); }4.2.2 读取并打印当前时间可以在主循环中定期读取RTC_TimeTypeDef gTime; RTC_DateTypeDef gDate; char timeStr[30]; char dateStr[30]; while (1) { HAL_RTC_GetTime(hrtc, gTime, RTC_FORMAT_BIN); HAL_RTC_GetDate(hrtc, gDate, RTC_FORMAT_BIN); /* 格式化输出 */ sprintf(timeStr, Time: %02d:%02d:%02d, gTime.Hours, gTime.Minutes, gTime.Seconds); sprintf(dateStr, Date: 20%02d-%02d-%02d %s, gDate.Year, gDate.Month, gDate.Date, weekDayStr[gDate.WeekDay]); // weekDayStr是一个星期几的字符串数组 printf(%s | %s\r\n, dateStr, timeStr); HAL_Delay(1000); // 每秒打印一次 }4.2.3 配置闹钟实现定时唤醒假设我们要设置一个在每天14点35分0秒触发的闹钟用于从停机Stop模式唤醒。void RTC_SetAlarm(void) { RTC_AlarmTypeDef sAlarm {0}; sAlarm.AlarmTime.Hours 14; sAlarm.AlarmTime.Minutes 35; sAlarm.AlarmTime.Seconds 0; sAlarm.AlarmTime.TimeFormat RTC_HOURFORMAT12_AM; sAlarm.AlarmTime.DayLightSaving RTC_DAYLIGHTSAVING_NONE; sAlarm.AlarmTime.StoreOperation RTC_STOREOPERATION_RESET; sAlarm.AlarmMask RTC_ALARMMASK_NONE; // 所有字段都匹配精确到秒 // 如果需要每周重复可以设置 sAlarm.AlarmMask RTC_ALARMMASK_DATEWEEKDAY; 并设置 sAlarm.AlarmDateWeekDaySel 和 sAlarm.AlarmDateWeekDay sAlarm.AlarmSubSecondMask RTC_ALARMSUBSECONDMASK_NONE; sAlarm.AlarmDateWeekDaySel RTC_ALARMDATEWEEKDAYSEL_DATE; sAlarm.AlarmDateWeekDay 1; // 当AlarmDateWeekDaySel为DATE时表示日期1号这里通常配合掩码使用 sAlarm.Alarm RTC_ALARM_A; // 使用Alarm A if (HAL_RTC_SetAlarm_IT(hrtc, sAlarm, RTC_FORMAT_BIN) ! HAL_OK) { Error_Handler(); } }然后在闹钟中断回调函数中处理唤醒事件/* 在stm32f1xx_it.c中重写RTC_Alarm_IRQHandler或确保HAL库的中断服务程序被正确调用 */ void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) { /* 闹钟A事件发生设备从停机模式被唤醒后会执行到这里 */ printf(Alarm A Triggered! System Woke Up.\r\n); // 这里可以执行唤醒后的任务比如采集数据、发送信号等 }最后在需要进入低功耗的地方调用停机模式函数void Enter_Stop_Mode(void) { printf(Entering Stop Mode...\r\n); HAL_Delay(100); /* 关闭所有外设时钟仅保留RTC和必要的中断 */ HAL_SuspendTick(); // 挂起SysTick避免中断唤醒 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); /* 唤醒后从这里继续执行 */ SystemClock_Config(); // **重要** 从停机模式唤醒后HSI是系统时钟源必须重新配置系统时钟HSE/PLL HAL_ResumeTick(); printf(Woke Up from Stop Mode.\r\n); }5. 常见问题与排查技巧实录即使按照步骤操作RTC仍然可能“罢工”。下面是我在项目中踩过的坑和解决方法。5.1 RTC不计数或时间不准现象程序运行后读取到的时间始终是初始值或者走时速度明显不对过快或过慢。排查步骤检查LSE是否起振这是最常见的原因。使用示波器测量OSC32_IN/OUT引脚。如果没有示波器可以尝试将RTC时钟输出到MCO引脚如果MCU支持用逻辑分析仪或另一个MCU的输入捕获测量。软件上在初始化后检查RCC-BDCR的LSERDY位。检查后备电池和VBAT引脚确保纽扣电池电压正常2.5VVBAT引脚在VDD断开时确实有电。测量VBAT引脚电压。检查预分频器配置确认PREDIV_A和PREDIV_S的值计算正确并且被正确写入寄存器。使用调试器查看RTC-PRER寄存器的值。检查备份域访问确保在操作RTC前已经正确执行了__HAL_RCC_PWR_CLK_ENABLE()和HAL_PWR_EnableBkUpAccess()。在调试时可以在初始化开始和结束设置断点观察相关寄存器位。检查初始化流程是否在设置时间前等待了INITF标志是否在退出初始化模式后才开始计时HAL库函数通常处理了这些但如果自己用寄存器操作极易遗漏。5.2 闹钟中断不触发现象设置了闹钟但到了预定时间没有进入中断回调函数。排查步骤检查NVIC配置在CubeMX和代码中确认RTC_Alarm_IRQn中断已经使能并且优先级设置合理。检查闹钟掩码AlarmMasksAlarm.AlarmMask设置错误是最容易导致闹钟不触发的原因。如果设置为RTC_ALARMMASK_ALL则所有字段都不比较闹钟每秒都会触发。如果只想在特定分钟触发需要正确设置掩码例如RTC_ALARMMASK_HOURS | RTC_ALARMMASK_DATEWEEKDAY表示只比较分钟和秒。检查闹钟使能位设置闹钟后确认RTC-CR寄存器的ALRAEAlarm A Enable位已被置1。HAL库的HAL_RTC_SetAlarm_IT()函数会设置它。检查中断标志在调试时可以在RTC_Alarm_IRQHandler中断服务函数或HAL_RTC_AlarmAEventCallback回调函数中设置断点。也可以查看RTC-ISR寄存器的ALRAF标志是否被置1。系统时钟源问题如果设备从停机模式唤醒闹钟中断是唤醒源之一。但唤醒后系统时钟会切换回HSI内部16MHz RC如果你的应用依赖于高精度时钟如USART通信必须在唤醒后立即重新配置系统时钟调用SystemClock_Config()否则后续代码包括打印调试信息可能因时钟不对而无法工作让你误以为中断没触发。5.3 时间读取出现错乱如59分00秒现象读取到的时间组合不合理比如秒是59分却是00。原因与解决这就是没有处理“读取同步”问题。绝对不要直接读取RTC-TR和RTC-DR寄存器来拼接时间。必须使用HAL库提供的HAL_RTC_GetTime()和HAL_RTC_GetDate()函数或者严格按照数据手册的流程读取两次TR比较一致后再使用。HAL库函数内部已经封装了同步机制是最安全省事的选择。5.4 断电再上电后时间复位现象设备断电移除VDD后即使接了后备电池重新上电后RTC时间也回到了初始值。排查步骤确认VBAT电路这是首要怀疑对象。检查纽扣电池是否电量充足VBAT引脚的退耦电容通常100nF是否焊接良好电池座接触是否良好。用万用表测量断电瞬间和断电后VBAT引脚的电压。检查备份域复位软件上在初始化RTC前检查RCC-BDCR的BDRST位是否被意外置位这会导致整个备份域包括RTC复位。通常只有在需要彻底清除RTC和备份寄存器时才操作这个位。检查初始化逻辑你的代码中是否有逻辑错误导致每次上电都重新初始化RTC时间通常我们会用一个备份寄存器如BKP_DR1存储一个“已初始化”标志只有第一次上电或电池彻底没电后才执行初始化。确保这个逻辑正确。5.5 使用LSI作为时钟源的精度问题现象使用LSI时发现一天下来误差可能达到几分钟甚至更多。分析与解决LSI的精度本身就很差典型±1.5%全温全压范围可能到±5%。32768Hz的1%误差就是327.68Hz一天误差高达0.01 * 86400秒 864秒超过14分钟。软件校准STM32的RTC模块提供了时钟校准Clock Calibration功能。可以通过输出一个512Hz或1Hz的时钟到RTCCLK引脚用高精度频率计测量计算误差然后写入RTC-CALR寄存器进行补偿。但这需要额外的测试设备和生产环节。网络同步对于联网设备可以通过NTP网络时间协议定期同步RTC时间修正累积误差。结论对时间精度有要求的项目务必使用LSE晶振。LSI仅适用于对误差不敏感、成本压力极大的场景。6. 高级应用与优化技巧掌握了基础功能后可以探索一些更高级的用法让RTC发挥更大价值。6.1 亚秒级时间戳与时间戳功能某些STM32系列如F4F7的RTC支持亚秒Sub-second寄存器RTC_SSR和时间戳TimeStamp功能。亚秒寄存器它记录的是当前秒内经过了多少个ck_apre周期。结合预分频器配置可以实现毫秒甚至更精细的时间分辨率。例如PREDIV_A127则ck_apre 32768/(1271)256Hz亚秒寄存器值递减从255到0。通过公式SubSeconds (PREDIV_A - SSR) / (PREDIV_A 1) * 1000可以计算出当前毫秒数。时间戳功能可以配置一个外部引脚如PC13TAMPER引脚作为时间戳触发引脚。当该引脚上发生指定边沿事件时RTC会自动将当前的完整日历时间包括亚秒锁存到一组时间戳寄存器中并产生中断。这对于记录外部事件发生的精确时刻非常有用比如记录按键按下、传感器触发的时间。6.2 闹钟掩码与周期性触发闹钟掩码AlarmMask非常灵活可以实现复杂的触发逻辑而不仅仅是定点触发。每周闹钟设置sAlarm.AlarmDateWeekDaySel RTC_ALARMDATEWEEKDAYSEL_WEEKDAY并设置sAlarm.AlarmDateWeekDay RTC_WEEKDAY_MONDAY同时将AlarmMask中的日期/星期掩码打开。这样闹钟会在每周一的设定时分秒触发。每小时闹钟设置AlarmMask RTC_ALARMMASK_HOURS | RTC_ALARMMASK_DATEWEEKDAY这样只比较分钟和秒字段。当分钟和秒匹配时就会触发实现每小时一次的闹钟在设定的分钟和秒。每分钟闹钟设置AlarmMask RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES | RTC_ALARMMASK_DATEWEEKDAY这样只比较秒字段可以实现每分钟触发在设定的秒数。6.3 低功耗模式下的RTC应用RTC是STM32进入深度睡眠Sleep、停机Stop甚至待机Standby模式后仍能保持运行并唤醒系统的关键。停机Stop模式在此模式下所有核心时钟关闭SRAM和寄存器内容保持。RTC可以正常工作并通过闹钟Alarm或唤醒引脚WKUP将系统唤醒。唤醒后程序从进入停机模式的下一条语句开始执行但系统时钟源会切换为HSI必须重新配置系统时钟这是最容易出错的地方。待机Standby模式这是功耗最低的模式SRAM和大部分寄存器内容丢失相当于一次软复位。只有RTC、备份寄存器和待机电路有电。RTC可以通过闹钟或WKUP引脚将系统从待机模式唤醒。唤醒后程序从头开始执行相当于复位但RTC的时间和备份寄存器的内容得以保留。可以利用备份寄存器存储一个“唤醒标志”让程序区分是上电复位还是待机唤醒从而执行不同的初始化流程。低功耗设计要点进入低功耗前关闭所有不必要的外设时钟和GPIO口设置为模拟输入以降低功耗。根据唤醒需求正确配置RTC闹钟或使能WKUP引脚。深刻理解不同低功耗模式对系统状态的影响特别是时钟和程序执行流。唤醒后及时处理累积的中断事件如果有并恢复系统到正常工作状态。7. 项目实战一个简易的数据记录仪让我们把上面的知识整合起来设计一个简易的数据记录仪。功能是每10分钟通过ADC采集一次温度传感器如NTC的电压值并连同时间戳一起存储到外部EEPROM或Flash中。设备大部分时间处于停机模式以省电由RTC闹钟每10分钟唤醒一次。设计要点硬件STM32F103C8T6LSE晶振VBAT电池温度传感器分压电路接到ADC引脚I2C接口的EEPROM如AT24Cxx。RTC配置使用LSE配置闹钟A每10分钟触发一次。如何实现10分钟可以设置闹钟在每分钟的0秒触发掩码屏蔽时、分、日然后在中断回调函数里用一个软件计数器计满10次才执行一次数据记录。主程序流程上电后初始化系统时钟、GPIO、ADC、I2C、RTC。检查备份寄存器标志若未初始化则设置RTC初始时间并写入标志。配置一个10分钟周期的闹钟或配置为每分钟触发软件计数。进入主循环。主循环中读取一次当前时间和温度可选用于调试显示然后调用Enter_Stop_Mode()函数进入停机模式。闹钟中断服务程序唤醒后系统时钟变为HSI首先调用SystemClock_Config()重配时钟。软件计数器加1。如果计数器达到10则执行读取RTC当前时间读取ADC温度值将“时间戳温度值”打包通过I2C写入EEPROM最后清零计数器。重新配置闹钟如果是单次模式为下一次唤醒做准备。函数返回后程序回到主循环的Enter_Stop_Mode()函数之后会再次读取一次时间温度可选然后继续进入停机模式。数据读取可以通过一个串口命令将EEPROM中存储的所有数据记录读取并上传到电脑。这个项目综合运用了RTC的计时、闹钟中断、低功耗唤醒、备份寄存器标志位等多个功能是一个很好的练手项目。在实现过程中你会对RTC的整个工作流程有更深刻的理解。调试时可以先用LED指示状态或者通过串口打印关键步骤的信息逐步验证RTC走时、闹钟触发、低功耗进入与唤醒、数据存储等各个环节是否正常。