STM32F4 低功耗 STOP 模式下的微秒级定时唤醒:LPTIM 与 RTC 计数的无缝协作
👁 2 阅读 · 2026-08-27 · 嵌入式
在电池供电的嵌入式系统中,低功耗与实时响应是永恒的矛盾。STM32F4 的 STOP 模式虽能将功耗降至微安级,但传统 RTC 唤醒仅支持毫秒级,且唤醒后需重新校准时间。本文深入剖析如何利用 LPTIM(低功耗定时器)在 STOP 模式下实现微秒级定时唤醒,同时通过巧妙的外设状态保持策略,确保 RTC 计数不丢失,从而兼顾超低功耗与高精度时序。
# 引言
在物联网传感器节点、可穿戴设备等场景中,系统常需在“沉睡”与“苏醒”间快速切换。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 时钟源和比较值,平衡功耗与精度。