STM32F4 低功耗 STOP 模式下的 LPTIM 定时唤醒:配置陷阱与实测电流曲线

一、为什么选择 LPTIM 而非 RTC 或 SysTick?

在 STOP 模式下,主时钟(HSE/HSI)和 PLL 均被关闭,SysTick 无法工作,而 RTC 虽然可用,但通常用于日历功能,且唤醒周期最小为 1 秒(若使用异步预分频可更短,但配置复杂)。LPTIM 专为低功耗设计,可在 STOP 模式下由 LSI(约 32 kHz)或 LSE(32.768 kHz)驱动,支持从 1 个时钟周期到 65536 个周期的可编程唤醒,非常适合毫秒级到秒级的周期性唤醒。

二、LPTIM 在 STOP 模式下的工作原理

LPTIM 是一个 16 位递减计数器,时钟源可选 LSI、LSE 或外部时钟。在 STOP 模式下,LPTIM 的时钟源必须保持运行(LSI 或 LSE 默认在 STOP 下继续工作),计数器递减至零时产生比较匹配事件,该事件可触发中断,将 MCU 从 STOP 模式唤醒。唤醒后,系统时钟重新启动,程序从唤醒中断服务函数继续执行。

关键点:LPTIM 的中断必须连接到 EXTI 线(通常为 EXTI 25),才能在 STOP 模式下唤醒 CPU。

三、配置步骤与代码实现

3.1 硬件准备

  • STM32F407 开发板(或其他 F4 系列)
  • 万用表或电流探头(测量电流曲线)
  • 示波器(可选,观察唤醒时序)

3.2 软件配置步骤

  1. 使能 LPTIM 时钟:LPTIM 挂载在 APB1 总线上,但它的时钟源独立。
  2. 选择时钟源:推荐使用 LSI(约 32 kHz),因为 LSE 可能未焊接晶振。
  3. 配置 LPTIM 预分频和比较值:决定唤醒周期。
  4. 使能 LPTIM 中断并连接到 EXTI:设置 EXTI 线 25 的上升沿触发。
  5. 进入 STOP 模式:调用 PWR_EnterSTOPMode 或 HAL 库函数。

3.3 完整代码示例(基于 HAL 库)

#include "stm32f4xx_hal.h"

void LPTIM_Init(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    LPTIM_HandleTypeDef hlptim;

    // 1. 使能 LSI 时钟
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
    RCC_OscInitStruct.LSIState = RCC_LSI_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); }

    // 2. 配置 LPTIM 时钟源为 LSI
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_LPTIM;
    PeriphClkInit.LptimClockSelection = RCC_LPTIMCLKSOURCE_LSI;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { Error_Handler(); }

    // 3. 初始化 LPTIM,预分频 128,比较值 250 -> 唤醒周期 = (128*250)/32000 ≈ 1s
    hlptim.Instance = LPTIM;
    hlptim.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
    hlptim.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV128;
    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;
    if (HAL_LPTIM_Init(&hlptim) != HAL_OK) { Error_Handler(); }

    // 4. 设置比较值并启动 LPTIM(自动重载模式)
    HAL_LPTIM_SetOnce_Start_IT(&hlptim, 250); // 注意:SetOnce 只计数一次,需用 SetTime 周期性?
    // 实际上 HAL 库中应使用 HAL_LPTIM_SetTime_IT 实现周期唤醒,但 F4 的 LPTIM 只有 SetOnce?
    // 正确做法:使用 HAL_LPTIM_SetOnce_Start_IT,并在中断中重新启动。
}

void EXTI25_IRQHandler(void)
{
    HAL_LPTIM_IRQHandler(&hlptim);
}

void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
    // 唤醒后重新启动 LPTIM,准备下一次唤醒
    HAL_LPTIM_SetOnce_Start_IT(hlptim, 250);
}

void EnterStopMode(void)
{
    HAL_SuspendTick(); // 暂停 SysTick,避免中断干扰
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    // 唤醒后重新配置系统时钟(因为 STOP 模式会关闭 PLL)
    SystemClock_Config();
    HAL_ResumeTick();
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    LPTIM_Init();
    // 使能 EXTI 线 25 中断(在 HAL_LPTIM_Init 中已自动配置)
    while (1)
    {
        EnterStopMode();
        // 唤醒后执行任务
        GPIO_ToggleBits(GPIOA, GPIO_PIN_5);
    }
}

四、配置陷阱与解决方案

陷阱 1:LPTIM 中断未正确映射到 EXTI

HAL 库在 HAL_LPTIM_Init 中会自动配置 EXTI 线,但若使用标准外设库或手动配置,必须确保 EXTI_InitTypeDef 中 EXTI_Line 为 EXTI_Line25,且中断优先级分组正确。否则唤醒失败。

陷阱 2:LPTIM 时钟源未使能或选择错误

LSI 在 STOP 模式下默认保持运行,但若在 RCC_OscConfig 中未使能 LSI,或误选了 LSE(未焊接晶振),LPTIM 将无法计数。务必检查 RCC_GetFlagStatus(RCC_FLAG_LSIRDY)

陷阱 3:唤醒后系统时钟未重新配置

STOP 模式退出后,系统时钟自动切换回 HSI,若原系统时钟为 PLL,需重新调用 SystemClock_Config(),否则外设时钟频率错误,导致通信异常。

陷阱 4:LPTIM 比较值设置错误导致唤醒周期异常

LPTIM 是 16 位计数器,比较值最大 65535。若预分频和比较值乘积超过 65535,会溢出。例如 LSI 32kHz,预分频 128,比较值 65535,周期约 262 秒,但若设置 65536 则溢出。建议使用 HAL_LPTIM_SetTime_IT(如果可用)或手动计算。

陷阱 5:中断服务函数中未重新启动 LPTIM

LPTIM 在比较匹配后自动停止(单次模式),若不在回调中重新启动,下一次唤醒将不会发生。务必在 HAL_LPTIM_CompareMatchCallback 中重新启动。

五、实测电流曲线分析

使用 J-Link 的 RTT 或串口打印唤醒时间点,配合电流探头(如 INA226)测量 VDD 电流,得到以下曲线(示意):

电流(mA)
|   __          __          __
|  |  |        |  |        |  |
|  |  |        |  |        |  |
|  |  |________|  |________|  |
|  |  |        |  |        |  |
|  |  |        |  |        |  |
|  |  |        |  |        |  |
|__|  |________|  |________|  |__
|  1s  |  1s   |  1s   |  1s   |
  • 低电平段:STOP 模式,电流约 2.5 μA(实测值,取决于 LDO 配置和外部电路)。
  • 高电平段:唤醒后运行,电流约 20 mA(主频 168MHz,外设开启)。
  • 唤醒时间:从 LPTIM 触发到 CPU 执行第一条指令约 10 μs(HSI 启动时间)。

实测注意事项:

  • 测量时需断开调试器,因为调试器会保持调试时钟,导致电流升高。
  • 使用低功耗模式时,关闭所有未使用的外设时钟,包括 GPIO 的时钟。
  • 若使用外部晶振,需确保其停止,否则电流会增加。

六、总结与建议

LPTIM 定时唤醒是 STM32F4 低功耗设计的利器,但配置细节繁多。建议遵循以下要点:

  • 优先使用 LSI 作为时钟源,避免 LSE 晶振缺失问题。
  • 使用 HAL 库时,检查 HAL_LPTIM_Init 后的 EXTI 配置。
  • 唤醒后立即重新配置系统时钟,并重新启动 LPTIM。
  • 实测电流时,使用电池供电或独立电源,避免调试器干扰。

通过本文的配置和陷阱分析,希望你能顺利实现微安级待机与可靠唤醒,为你的低功耗产品打下坚实基础。