引言

在嵌入式系统设计中,低功耗是永恒的主题,尤其是对于电池供电的 IoT 节点、可穿戴设备或环境监测器。STM32F4 系列凭借其强大的性能和丰富的外设,成为许多产品的首选。然而,进入 STOP 模式后,主时钟停止,传统定时器(如 TIMx)无法工作,如何实现周期性唤醒并保持时间基准?本文将介绍一种经典方案:使用 LPTIM 实现微安级定时唤醒,同时利用 RTC 的备份寄存器与闹钟功能,确保唤醒后日历同步。

原理剖析

1. STOP 模式下的时钟域

STM32F4 的 STOP 模式会关闭除 RTC、LPTIM(若使用 LSI 或 LSE 时钟)和部分 GPIO 外的所有时钟。此时,系统电流可降至几十微安(典型值约 10-20 µA @ 25°C)。LPTIM 是一个低功耗定时器,它可以在 STOP 模式下继续运行,但必须使用 LSI(约 32 kHz)或 LSE(32.768 kHz)作为时钟源。

2. LPTIM 的唤醒机制

LPTIM 支持多种计数模式,其中“单次模式”或“连续模式”均可。我们通常配置为“单次模式”,即计数到设定值后产生中断,并触发唤醒事件。LPTIM 的时钟源选择 LSI,因为 LSI 在 STOP 模式下仍可工作,且功耗极低。

3. RTC 日历同步策略

RTC 由 LSE 驱动(若 LSE 未启用,也可用 LSI),在 STOP 模式下继续走时。但 LPTIM 唤醒后,系统时钟(如 HSI)重新启动,RTC 的时间可能因时钟切换产生微小偏差。为了确保日历同步,我们可以在进入 STOP 前,将当前 RTC 时间存入备份寄存器(BKP),唤醒后读取并校准。或者更简单:利用 RTC 闹钟中断,但这里我们使用 LPTIM 作为唤醒源,RTC 仅作为时间基准,唤醒后直接读取 RTC 时间即可,因为 RTC 在 STOP 模式下依然精确走时。

硬件与软件准备

  • 开发板:STM32F407 Discovery 或任意 F4 系列板
  • 工具:STM32CubeIDE 或 Keil MDK
  • 库:HAL 库(本文基于 HAL)

配置步骤

1. 时钟配置

  • 系统时钟:使用 HSE 或 HSI,PLL 倍频至 168 MHz(F407)
  • LSI 使能:用于 LPTIM 时钟源
  • LSE 使能(可选):用于 RTC,精度更高

在 CubeMX 中,将 LSI 使能,并将 LPTIM 时钟源选为 LSI。RTC 时钟源选为 LSE(若板上有 32.768 kHz 晶振)或 LSI。

2. LPTIM 配置

  • 选择 LPTIM1(F4 系列通常有 LPTIM1)
  • 时钟源:LSI
  • 预分频器:根据所需唤醒周期计算。例如,LSI 频率为 32 kHz,若需 1 秒唤醒,则预分频设为 1(不分频),自动重载值设为 32000-1。
  • 模式:单次模式(Single mode)
  • 中断:使能比较匹配中断

3. RTC 配置

  • 使能 RTC,选择 LSE 或 LSI 作为时钟源
  • 配置日期和时间(初始值)
  • 使能备份寄存器(BKP)用于存储标志(可选)

4. 低功耗模式配置

  • 在进入 STOP 前,确保 LPTIM 已启动,并设置唤醒源为 LPTIM
  • 使用 HAL_PWR_EnterSTOPMode() 进入 STOP 模式

完整代码示例

以下代码基于 HAL 库,演示了如何配置 LPTIM 和 RTC,并实现周期性唤醒。

/* 包含头文件 */
#include "main.h"
#include "lptim.h"
#include "rtc.h"

/* 全局变量 */
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;

/* LPTIM 初始化 */
void LPTIM_Init(void)
{
    LPTIM_HandleTypeDef hlptim;
    hlptim.Instance = LPTIM1;
    hlptim.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK; // 实际应改为 LSI,但 HAL 中需通过 RCC 配置
    hlptim.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV1;
    hlptim.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
    hlptim.Init.Output.Polarity = LPTIM_OUTPUTPOLARITY_HIGH;
    hlptim.Init.Output.Pin = LPTIM_OUTPUT_PIN_1;
    hlptim.Init.Output.Mode = LPTIM_OUTPUTMODE_PWM;
    hlptim.Init.Clock.Filter = LPTIM_CLOCKFILTER_NONE;
    HAL_LPTIM_Init(&hlptim);

    /* 设置自动重载值,假设 LSI=32kHz,1秒唤醒 */
    HAL_LPTIM_SetOnce_IT(&hlptim, 32000 - 1);
}

/* RTC 初始化 */
void RTC_Init(void)
{
    RTC_HandleTypeDef hrtc;
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;  // 根据时钟源计算
    hrtc.Init.SynchPrediv = 255;
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    HAL_RTC_Init(&hrtc);

    /* 设置初始时间:2024-01-01 00:00:00 */
    sTime.Hours = 0;
    sTime.Minutes = 0;
    sTime.Seconds = 0;
    sDate.Year = 24;
    sDate.Month = 1;
    sDate.Date = 1;
    HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}

/* LPTIM 中断回调 */
void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim)
{
    // 唤醒后执行的任务
    // 读取 RTC 时间
    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    printf("Wake up at %02d:%02d:%02d\n", sTime.Hours, sTime.Minutes, sTime.Seconds);

    // 重新设置 LPTIM 以准备下一次唤醒
    HAL_LPTIM_SetOnce_IT(&hlptim, 32000 - 1);
}

/* 主函数 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    LPTIM_Init();
    RTC_Init();

    // 启动 LPTIM
    HAL_LPTIM_Start_IT(&hlptim);

    while (1)
    {
        // 进入 STOP 模式,LPTIM 唤醒
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
        // 唤醒后重新配置系统时钟(因为 STOP 会关闭 PLL)
        SystemClock_Config();
        // 可以在这里执行其他任务
    }
}

注意:上述代码中 LPTIM 时钟源配置需在 CubeMX 中正确设置,HAL 库中通过 __HAL_RCC_LPTIM1_CLK_ENABLE()__HAL_RCC_LSI_ENABLE() 使能时钟,并确保 LPTIM 时钟选择为 LSI。

注意事项

  1. 时钟源选择:LPTIM 必须使用 LSI 或 LSE,不能使用 HSI,否则在 STOP 模式下会停止。确保 LSI 已使能。
  2. 唤醒后系统时钟重配:从 STOP 模式唤醒后,系统时钟默认使用 HSI,需要重新调用 SystemClock_Config() 恢复为 PLL 高速时钟,否则外设可能运行异常。
  3. LPTIM 重载值计算:LSI 频率有误差(典型 ±5%),若需精确周期,建议使用 LSE(32.768 kHz)并校准。
  4. RTC 精度:RTC 使用 LSE 时精度更高,但 LSE 晶振可能未焊接,需检查硬件。若使用 LSI,则 RTC 误差较大。
  5. 中断优先级:LPTIM 中断优先级应高于其他可屏蔽中断,确保唤醒后及时处理。
  6. 功耗优化:进入 STOP 前,关闭不必要的外设时钟,并将 GPIO 设置为模拟输入或低功耗状态,以降低漏电流。
  7. 调试:在调试时,STOP 模式可能导致调试器断开,建议使用 HAL_DBGMCU_EnableDBGStopMode() 保持调试时钟。

总结

通过 LPTIM 与 RTC 的配合,STM32F4 可以在 STOP 模式下实现微安级功耗的定时唤醒,同时保持日历的准确同步。本文提供了完整的配置思路和代码示例,开发者可根据实际需求调整唤醒周期和任务逻辑。这种方案在智能传感器、远程监控等场景中极具实用价值。