引言

在嵌入式低功耗设计中,STOP 模式是功耗与唤醒速度的折中首选。STM32F4 的 STOP 模式电流可低至 µA 级,但唤醒延迟(通常 10-100µs)受时钟源、电压调节器状态等因素影响,导致时序敏感任务(如精确采样、无线时隙同步)出现抖动。RTC 闹钟可周期性唤醒,但精度受限于 LSE 32.768kHz;LPTIM 支持低功耗运行,却无法在 STOP 下独立计时(需外部时钟)。本文组合两者:RTC 负责粗粒度唤醒(毫秒级),LPTIM 在唤醒后立即进行微秒级校准,从而对齐外部事件。

原理剖析

1. STOP 模式唤醒路径

  • 进入 STOP:PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI),内核时钟停止,但 RTC、LPTIM(若配置为外部时钟)可继续运行。
  • 唤醒源:RTC 闹钟(WKUP 引脚或 EXTI 线)、LPTIM 比较事件(需连接至 EXTI)、外部中断等。
  • 唤醒延迟:从事件发生到 CPU 执行第一条指令,包含时钟稳定时间(HSI 启动约 8µs)、电压调节器恢复时间(低功耗模式需 30-50µs)。

2. RTC 闹钟特性

  • 使用 LSE(32.768kHz)或 LSI 时钟,闹钟比较寄存器支持秒/分钟/小时/日期,精度最低 1 秒。
  • 唤醒周期可配置为 1Hz 至 1 天,但无法产生亚秒级中断(除非使用 RTC 亚秒寄存器,但 F4 系列不支持)。

3. LPTIM 特性

  • LPTIM 可在 STOP 模式下运行,但必须选择外部时钟源(如 LSE 或外部引脚时钟)。
  • 支持 16 位计数器,可产生比较匹配中断,并映射到 EXTI 唤醒。
  • 由于 LSE 频率固定,LPTIM 可实现 1/32768 秒(约 30.5µs)的定时精度,但无法覆盖长时间(最大 2 秒)。

4. 组合策略

  • 设计目标:在唤醒时刻 T_wake 对齐到微秒级。
  • 方案:RTC 设置闹钟在 T_wake 前 5ms 唤醒 CPU(粗唤醒),CPU 唤醒后立即启动 LPTIM 计时,LPTIM 定时至剩余时间(5ms - 实际已过时间),然后触发精确事件(如 GPIO 翻转或 DMA)。
  • 关键:利用 RTC 唤醒后的固定延迟(可测量)进行补偿,LPTIM 负责微调。

配置步骤

1. 时钟与电源配置

void SystemClock_Config(void) {
    // 使能 PWR 时钟
    __HAL_RCC_PWR_CLK_ENABLE();
    // 配置 LSE 作为 RTC 和 LPTIM 时钟源
    __HAL_RCC_LSE_CONFIG(RCC_LSE_ON);
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET);
    // 选择 LSE 作为 RTC 时钟
    __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE);
    __HAL_RCC_RTC_ENABLE();
    // 使能 LPTIM1 时钟(挂在 APB1,但时钟源为 LSE)
    __HAL_RCC_LPTIM1_CLK_ENABLE();
}

2. RTC 闹钟配置

void RTC_Alarm_Config(void) {
    RTC_HandleTypeDef hrtc;
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;  // 异步分频,产生 256Hz
    hrtc.Init.SynchPrediv = 255;   // 同步分频,产生 1Hz
    HAL_RTC_Init(&hrtc);

    // 设置闹钟在 5 秒后(示例)
    RTC_AlarmTypeDef sAlarm = {0};
    sAlarm.AlarmTime.Hours = 0;
    sAlarm.AlarmTime.Minutes = 0;
    sAlarm.AlarmTime.Seconds = 5;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATE_WEEKDAY; // 仅匹配时分秒
    sAlarm.Alarm = RTC_ALARM_A;
    HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);

    // 使能 EXTI 线 17(RTC 闹钟)
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

3. LPTIM 配置

void LPTIM_Config(void) {
    LPTIM_HandleTypeDef hlptim;
    hlptim.Instance = LPTIM1;
    hlptim.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC; // 使用 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);

    // 设置比较值,例如 100 个 LSE 周期(约 3.05ms)
    HAL_LPTIM_SetOnce_IT(&hlptim, 100);

    // 使能 EXTI 线 23(LPTIM1 比较事件)
    HAL_NVIC_SetPriority(LPTIM1_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(LPTIM1_IRQn);
}

4. 进入 STOP 与唤醒处理

volatile uint32_t wake_time_us;

void EnterStopWithRTC(void) {
    // 设置 RTC 闹钟为 5ms 后(通过修改秒和亚秒?F4 不支持亚秒,故用秒,但可设置秒+1,然后测量延迟)
    // 为简化,此处使用秒级,实际可结合 RTC 预分频值产生 1Hz 中断,但精度不足,故用 LPTIM 校准。
    // 进入 STOP
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    // 唤醒后重新配置系统时钟(因为 STOP 会关闭 HSI)
    SystemClock_Config();
    // 记录唤醒时刻(通过 DWT 计数器)
    wake_time_us = DWT_GetMicrosecond();
}

void RTC_Alarm_IRQHandler(void) {
    HAL_RTC_AlarmIRQHandler(&hrtc);
}

void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
    // RTC 唤醒,启动 LPTIM 进行精确定时
    HAL_LPTIM_SetOnce_IT(&hlptim, 150); // 例如 150*30.5us ≈ 4.575ms
    HAL_LPTIM_Start_IT(&hlptim);
}

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

void HAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef *hlptim) {
    // 精确时刻到达,执行任务(如 GPIO 翻转)
    HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
}

完整代码示例(整合)

// main.c 核心部分
int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Init(); // 配置 PA0 为输出
    DWT_Init();  // 初始化 DWT 计数器用于微秒计时
    RTC_Alarm_Config();
    LPTIM_Config();

    while (1) {
        // 设置 RTC 闹钟为 1 秒后(示例)
        Set_RTC_Alarm_Seconds(1);
        // 进入 STOP
        HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
        // 唤醒后,系统时钟已重新配置,但 LPTIM 和 RTC 仍运行
        // 此时可进行任务
        // 注意:LPTIM 在 STOP 唤醒后可能仍在运行,但需重新初始化?
    }
}

// 精确延时函数(基于 DWT)
void DWT_Delay_us(uint32_t us) {
    // 实现略
}

注意事项

  • 时钟源选择:LPTIM 必须使用 LSE 或外部时钟,否则在 STOP 下无法运行。确保 LSE 稳定后再进入 STOP。
  • 唤醒延迟补偿:每次唤醒延迟可能不同(受温度、电压影响),建议在运行时测量实际延迟(如通过 GPIO 翻转和逻辑分析仪),并动态调整 LPTIM 比较值。
  • RTC 闹钟精度:F4 的 RTC 闹钟仅支持秒级,若需毫秒级,可考虑使用 RTC 的唤醒定时器(WUT)但精度仍有限。本方案中 RTC 只负责粗唤醒,实际时序由 LPTIM 保证。
  • LPTIM 溢出:16 位计数器在 32.768kHz 下最大约 2 秒,若需更长,可级联或使用外部分频。
  • 中断优先级:RTC 和 LPTIM 中断优先级需合理设置,避免在唤醒处理中被打断。
  • 系统时钟恢复:从 STOP 唤醒后,需重新配置系统时钟(如 HSI),否则外设时钟可能异常。
  • 调试建议:使用逻辑分析仪测量 GPIO 翻转时刻,对比预期时间,调整 LPTIM 比较值。

总结

通过 RTC 闹钟的粗粒度唤醒与 LPTIM 的微调,STM32F4 在 STOP 模式下可实现微秒级的时序对齐。该方法兼顾低功耗与实时性,适用于周期性精确采样、无线通信同步等场景。开发者需注意时钟源配置和唤醒延迟补偿,通过实际测量优化参数,即可获得稳定可靠的时序性能。