STM32F4 低功耗 STOP 模式:保留 RTC 与备份域数据的完整指南

一、为什么 STOP 模式会丢失 SRAM 数据?

STM32F4 系列(如 STM32F407)的电源系统分为多个域:

  • VDD 域:主电源,包括 CPU、Flash、SRAM1/SRAM2 等。
  • 备份域:由 VBAT 供电,包含 RTC、备份寄存器(20 个 32 位)、备份 SRAM(4KB,仅部分型号)。

STOP 模式下,VDD 域中的大部分外设时钟停止,但 SRAM 和寄存器内容保持供电(只要 VDD 仍存在)。然而,若进入 STOP 模式时未正确设置 PWR_CR 寄存器的 FPDS 位(Flash 掉电),Flash 可能掉电,导致唤醒后无法从 Flash 读取代码,引发启动异常。此外,若使用 待机模式(Standby),整个 VDD 域掉电,SRAM 数据必然丢失,因此必须使用 STOP 模式来保留 SRAM。

核心问题:STOP 模式下,若 FPDS=1(Flash 掉电),唤醒后需要重新初始化 Flash 接口;若 LPDS=1(内部稳压器低压),SRAM 供电电压降低,可能影响数据保持。因此,要保留 SRAM 数据,必须确保 LPDS=0(保持正常电压),并正确处理 Flash 掉电后的恢复。

二、配置步骤:保留 RTC 与备份域数据

1. 使能 PWR 和 RTC 时钟

void RTC_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    PWR_BackupAccessCmd(ENABLE); // 允许访问备份域
    RCC_LSEConfig(RCC_LSE_ON);    // 启用外部低速晶振(32.768kHz)
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_InitTypeDef RTC_InitStruct;
    RTC_InitStruct.RTC_HourFormat = RTC_HourFormat_24;
    RTC_InitStruct.RTC_AsynchPrediv = 0x7F;
    RTC_InitStruct.RTC_SynchPrediv = 0xFF;
    RTC_Init(&RTC_InitStruct);
}

2. 写入备份寄存器保存关键数据

void BackupData_Write(uint32_t data) {
    PWR_BackupAccessCmd(ENABLE); // 确保备份域可写
    RTC_WriteBackupRegister(RTC_BKP_DR0, data);
}

uint32_t BackupData_Read(void) {
    return RTC_ReadBackupRegister(RTC_BKP_DR0);
}

3. 进入 STOP 模式并保留 SRAM

void EnterStopMode(void) {
    // 保存 SRAM 数据(无需额外操作,只要 VDD 存在)
    // 关键:设置 PWR 寄存器,确保 SRAM 供电正常
    PWR_StopModeCmd(DISABLE); // 禁用 STOP 模式下的低压(LPDS=0)
    PWR_FlashPowerDownCmd(ENABLE); // 允许 Flash 掉电(可选,降低功耗)
    // 配置唤醒源:RTC 闹钟或外部中断
    RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
    // 进入 STOP 模式
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
    // 唤醒后:系统时钟默认 HSI,需重新配置
    SystemInit();
    // 若 Flash 掉电,需重新初始化 Flash 接口
    if (PWR_GetFlagStatus(PWR_FLAG_FLASH_PD) != RESET) {
        FLASH_Unlock();
        FLASH_OB_Unlock();
        // 重新配置 Flash 等待周期等(通常 SystemInit 已处理)
        PWR_ClearFlag(PWR_FLAG_FLASH_PD);
    }
}

4. 唤醒后检查备份域数据完整性

void CheckAndRestore(void) {
    uint32_t saved = BackupData_Read();
    if (saved != 0xDEADBEEF) { // 预设魔数
        // 数据丢失,执行恢复逻辑
        BackupData_Write(0xDEADBEEF);
    }
}

三、完整代码示例:RTC 闹钟唤醒 + 数据保持

#include "stm32f4xx.h"

volatile uint32_t g_counter = 0;

void RTC_Config(void) {
    // 使能 PWR 时钟并允许备份域访问
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    PWR_BackupAccessCmd(ENABLE);
    // 启用 LSE
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
    // 配置 RTC 时钟源
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    // 初始化 RTC
    RTC_InitTypeDef rtc_init;
    rtc_init.RTC_HourFormat = RTC_HourFormat_24;
    rtc_init.RTC_AsynchPrediv = 0x7F;
    rtc_init.RTC_SynchPrediv = 0xFF;
    RTC_Init(&rtc_init);
    // 设置闹钟 10 秒后
    RTC_AlarmTypeDef alarm;
    alarm.RTC_AlarmTime.RTC_H12 = RTC_H12_AM;
    alarm.RTC_AlarmTime.RTC_Hours = 0;
    alarm.RTC_AlarmTime.RTC_Minutes = 0;
    alarm.RTC_AlarmTime.RTC_Seconds = 10;
    alarm.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay; // 仅匹配时间
    RTC_SetAlarm(RTC_Format_BIN, RTC_Alarm_A, &alarm);
    RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
    // 使能闹钟中断
    RTC_ITConfig(RTC_IT_ALRA, ENABLE);
    NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

void RTC_Alarm_IRQHandler(void) {
    if (RTC_GetITStatus(RTC_IT_ALRA) != RESET) {
        RTC_ClearITPendingBit(RTC_IT_ALRA);
        g_counter++;
        // 唤醒后重新配置时钟
        SystemInit();
    }
}

int main(void) {
    // 初始化 RTC 和备份寄存器
    RTC_Config();
    BackupData_Write(0xDEADBEEF);
    while (1) {
        // 正常工作
        // 进入 STOP 模式前,保存计数器到备份寄存器
        BackupData_Write(g_counter);
        EnterStopMode();
        // 唤醒后检查数据
        uint32_t val = BackupData_Read();
        if (val != g_counter) {
            // 数据不一致,恢复
            g_counter = val;
        }
    }
}

四、注意事项与常见陷阱

  • LPDS 位必须清零PWR_StopModeCmd(DISABLE) 确保内部稳压器在 STOP 模式下保持正常电压,否则 SRAM 可能因电压过低而丢失数据。
  • Flash 掉电后的恢复:若使能了 PWR_FlashPowerDownCmd(ENABLE),唤醒后必须重新初始化 Flash 接口(通常 SystemInit() 会处理,但需检查 PWR_FLAG_FLASH_PD 标志并清除)。
  • 备份域访问权限:写入备份寄存器前必须调用 PWR_BackupAccessCmd(ENABLE),否则写操作无效。
  • 系统时钟重新配置:STOP 模式唤醒后,系统时钟自动切换到 HSI(16MHz),若原系统时钟为 PLL,需重新配置。
  • 使用待机模式的误区:待机模式(Standby)会掉电 SRAM,若需保留数据,必须使用 STOP 模式。
  • 调试建议:在进入 STOP 模式前,将关键数据写入备份寄存器,并在唤醒后比较,以验证数据完整性。

五、总结

通过正确配置 PWR 寄存器,STM32F4 可以在 STOP 模式下保留 SRAM 和备份域数据,同时利用 RTC 实现定时唤醒。关键在于确保 LPDS=0、处理 Flash 掉电恢复,并利用备份寄存器作为数据保险。此方案适用于需要超低功耗且要求快速唤醒的场景,如智能手表、传感器节点等。