# 引言 在物联网传感器节点、可穿戴设备等场景中,系统常需在“沉睡”与“苏醒”间快速切换。STM32F4 系列(如 STM32F411)的 STOP 模式可将功耗降至 10µA 以下,但默认的唤醒源(如 RTC 闹钟)分辨率仅为秒级,难以满足微秒级采样或同步需求。LPTIM 作为低功耗定时器,可在 STOP 模式下运行,且支持外部时钟源(如 LSE),理论上可实现微秒级定时。然而,STOP 模式下 SRAM 内容保留,但外设寄存器会丢失,若 RTC 配置被意外复位,将导致时间基准错乱。本文将提供一套完整方案,实现 LPTIM 微秒唤醒且 RTC 计数不丢失。 # 原理剖析 ## 1. STOP 模式下的时钟与外设状态 - **STOP 模式**:内核时钟停止,但 SRAM、备份域(含 RTC)保持供电。所有外设时钟(PCLK1/2)被关闭,外设寄存器内容丢失(除非配置为备份域或具有独立供电)。 - **LPTIM 特殊性**:LPTIM 可由 LSE(32.768kHz)或 LSI(约 32kHz)驱动,在 STOP 模式下继续计数。其预分频器可产生低至 1/32768 秒(约 30.5µs)的时基,通过配置比较值可实现微秒级唤醒。 - **RTC 计数保持**:RTC 位于备份域,由 VBAT 供电,即使主电源切断也能运行。但在 STOP 模式下,若 RTC 的 APB 接口时钟关闭,其寄存器仍可通过备份域访问,但需注意:若代码在唤醒后重新初始化 RTC,会覆盖原有计数。 ## 2. 关键设计思路 - **LPTIM 唤醒**:配置 LPTIM 为单次计数模式,比较值设为所需微秒数对应的时钟周期数。唤醒后,通过中断标志位判断唤醒源。 - **RTC 计数保护**:在进入 STOP 前,将 RTC 的当前计数(如秒计数)保存到备份寄存器(RTC_BKPxR)中。唤醒后,先读取备份值,再与当前 RTC 值比对,若发现 RTC 被复位(计数归零),则用备份值恢复。 - **时钟源选择**:LPTIM 使用 LSE 时,精度高(32.768kHz),但需注意 LSE 的启动时间;LSI 精度稍差(±1%),但启动快。 # 配置步骤 ## 1. 初始化 RTC 与备份寄存器 ```c void RTC_Init(void) { // 使能 PWR 时钟,允许访问备份域 __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // 使能 LSE __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET); // 配置 RTC 时钟源为 LSE,并使能 RTC __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); __HAL_RCC_RTC_ENABLE(); // 初始化 RTC 为秒中断模式(示例) RTC_HandleTypeDef hrtc = {0}; hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; // 异步预分频 hrtc.Init.SynchPrediv = 255; // 同步预分频,得到 1Hz HAL_RTC_Init(&hrtc); // 设置初始时间(可选) RTC_TimeTypeDef sTime = {0}; sTime.Hours = 0; sTime.Minutes = 0; sTime.Seconds = 0; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN); } // 保存当前 RTC 秒计数到备份寄存器 void RTC_SaveCounter(void) { RTC_TimeTypeDef sTime; HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); uint32_t seconds = sTime.Hours * 3600 + sTime.Minutes * 60 + sTime.Seconds; HAL_RTCEx_BKUPWrite(RTC, RTC_BKP_DR0, seconds); } // 恢复 RTC 计数(若被复位) uint32_t RTC_RestoreCounter(void) { uint32_t saved = HAL_RTCEx_BKUPRead(RTC, RTC_BKP_DR0); RTC_TimeTypeDef sTime; HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); uint32_t current = sTime.Hours * 3600 + sTime.Minutes * 60 + sTime.Seconds; if (current < saved) { // 检测到复位(当前值小于保存值) // 重新设置 RTC 时间为保存值 RTC_TimeTypeDef newTime; newTime.Hours = saved / 3600; newTime.Minutes = (saved % 3600) / 60; newTime.Seconds = saved % 60; HAL_RTC_SetTime(&hrtc, &newTime, RTC_FORMAT_BIN); return saved; } return current; } ``` ## 2. 配置 LPTIM 用于微秒唤醒 ```c // 假设 LSE 为 32.768kHz,预分频 1,则计数周期约 30.5µs // 若需 100µs 唤醒,则比较值 = 100 / 30.5 ≈ 3(取整) void LPTIM_Init(uint32_t compare_value) { // 使能 LPTIM 时钟(在 STM32F4 中,LPTIM 位于 APB1,但需在 STOP 前配置) __HAL_RCC_LPTIM1_CLK_ENABLE(); // 选择 LSE 作为 LPTIM 时钟源 RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_LPTIM1; PeriphClkInit.Lptim1ClockSelection = RCC_LPTIM1CLKSOURCE_LSE; HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit); // 初始化 LPTIM LPTIM_HandleTypeDef hlptim = {0}; hlptim.Instance = LPTIM1; hlptim.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK; // 实际由 LSE 驱动 hlptim.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1; hlptim.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE; hlptim.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH; hlptim.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE; hlptim.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL; HAL_LPTIM_Init(&hlptim); // 设置比较值 HAL_LPTIM_SetOnceMode(&hlptim, compare_value); // 使能中断 HAL_NVIC_SetPriority(LPTIM1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(LPTIM1_IRQn); } // 进入 STOP 模式并等待 LPTIM 唤醒 void EnterStopWithLPTIM(uint32_t compare_value) { // 保存 RTC 计数 RTC_SaveCounter(); // 配置 LPTIM 比较值 LPTIM_Init(compare_value); // 启动 LPTIM 单次计数 HAL_LPTIM_Start_IT(&hlptim, compare_value); // 进入 STOP 模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后,重新配置系统时钟(STOP 模式会丢失时钟配置) SystemClock_Config(); // 恢复 RTC 计数(若被复位) RTC_RestoreCounter(); // 停止 LPTIM HAL_LPTIM_Stop_IT(&hlptim); } // LPTIM 中断处理 void LPTIM1_IRQHandler(void) { HAL_LPTIM_IRQHandler(&hlptim); } void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim) { // 唤醒后的处理,如读取传感器等 // 注意:此时系统时钟尚未恢复,避免复杂操作 } ``` ## 3. 主程序示例 ```c int main(void) { HAL_Init(); SystemClock_Config(); RTC_Init(); while (1) { // 进入 STOP 模式,100µs 后唤醒 EnterStopWithLPTIM(3); // 3 * 30.5µs ≈ 91.5µs // 唤醒后执行任务 uint32_t current_time = RTC_RestoreCounter(); printf("Wake at %lu s\n", current_time); HAL_Delay(1000); // 模拟任务 } } ``` # 注意事项 - **LPTIM 时钟源选择**:LSE 精度高但启动慢(约 1-2 秒),若系统频繁启停,建议使用 LSI(约 32kHz,精度 ±1%),但需校准。 - **比较值计算**:微秒换算公式:`compare_value = (time_us * clock_hz) / 1000000`。例如 LSE 32.768kHz,100µs 对应 3.2768,取整为 3,实际唤醒时间约 91.5µs,误差可接受。 - **RTC 备份寄存器**:备份寄存器数量有限(通常 20 个),且掉电后由 VBAT 供电,需确保 VBAT 连接电池或大电容。 - **唤醒后时钟恢复**:STOP 模式会关闭 PLL 和 HSI,唤醒后必须重新配置系统时钟,否则外设工作异常。建议在中断中仅设置标志,主循环中恢复时钟。 - **中断优先级**:LPTIM 中断优先级应高于 RTC 闹钟(若同时使用),避免竞争。 - **调试技巧**:使用逻辑分析仪测量 GPIO 翻转时间,验证实际唤醒延迟。 # 总结 通过 LPTIM 与 RTC 的协同,STM32F4 在 STOP 模式下可实现微秒级定时唤醒,同时利用备份寄存器保护 RTC 计数,确保时间基准不丢失。此方案适用于需要高精度周期采样的低功耗场景,如环境监测、工业传感器等。开发者可根据实际需求调整 LPTIM 时钟源和比较值,平衡功耗与精度。