引言

在电池供电的物联网设备中,STM32F4 常被用于需要周期性唤醒的低功耗任务。通常,RTC 依赖外部低速晶振(LSE,32.768kHz)提供精准时钟,但在高湿度、强振动或低温环境下,LSE 可能停振,导致 RTC 无法计时,系统陷入死循环或无法唤醒。本文提出一种备用方案:当检测到 LSE 停振时,切换至内部低速时钟(LSI),并利用 RTC 备份域寄存器保存唤醒计数,实现无外部晶振的秒级唤醒。

原理剖析

RTC 与备份域

STM32F4 的 RTC 模块位于备份域(Backup Domain),由 VBAT 引脚供电,当主电源掉电时,RTC 仍能运行。备份域包含 20 个 32 位备份寄存器(BKPxR),用于在复位或掉电后保存用户数据。RTC 时钟源可选 LSE、LSI 或 HSE/128,其中 LSI 约 32kHz,精度较低但无需外部晶振。

LSE 停振检测

RTC 提供时钟安全系统(CSS)功能,可监测 LSE 振荡器。当 LSE 失效时,CSS 产生中断,系统可及时响应。然而,CSS 仅适用于 LSE,且需在初始化时使能。若未使能,则需通过轮询 RTC_ISR 的 LSERDY 位检测。

备份域寄存器的作用

备份寄存器在系统复位(包括软件复位)后内容保持不变,但若备份域电源丢失(VBAT 掉电),则数据清零。利用这一特性,我们可在 LSE 停振时,将 RTC 计数周期切换为 LSI,并在备份寄存器中存储唤醒次数或时间戳,实现秒级唤醒的连续性。

实现方案

硬件设计

  • MCU:STM32F407VET6(或其他 F4 系列)
  • 外部晶振:LSE(32.768kHz)和 HSE(8MHz)
  • VBAT 接 3V 纽扣电池或超级电容

软件架构

  1. 初始化 RTC 使用 LSE,并使能 CSS 中断。
  2. 在 CSS 中断服务函数中,检测到 LSE 失效,切换时钟源至 LSI,并设置唤醒周期为 1 秒。
  3. 使用备份寄存器 RTC_BKPxR 存储唤醒计数,每次唤醒后递增。
  4. 主程序进入低功耗模式(STOP 模式),RTC 唤醒中断触发后恢复运行。

配置步骤

1. 使能电源和备份域时钟

void RTC_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
    PWR_BackupAccessCmd(ENABLE); // 允许访问备份域
    RCC_BackupResetCmd(ENABLE);  // 复位备份域(可选)
    RCC_BackupResetCmd(DISABLE);
}

2. 配置 LSE 和 RTC

void RTC_Config_LSE(void) {
    RCC_LSEConfig(RCC_LSE_ON);
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待 LSE 就绪
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    RCC_RTCCLKCmd(ENABLE);
    RTC_WaitForSynchro();
    // 设置 1 秒中断
    RTC_ITConfig(RTC_IT_WUT, ENABLE);
    RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits); // 1Hz
    RTC_SetWakeUpCounter(0); // 1 秒后唤醒
}

3. 使能 CSS 中断

void CSS_Init(void) {
    RCC_LSEConfig(RCC_LSE_ON);
    RCC_ITConfig(RCC_IT_LSECSS, ENABLE);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = RCC_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

4. CSS 中断处理函数

void RCC_IRQHandler(void) {
    if (RCC_GetITStatus(RCC_IT_LSECSS) != RESET) {
        RCC_ClearITPendingBit(RCC_IT_LSECSS);
        // 切换至 LSI
        RCC_LSICmd(ENABLE);
        while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
        // 重新配置唤醒周期(LSI 频率约 32kHz,需校准)
        RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits); // 使用 1Hz 分频,但 LSI 不精确,需软件校准
        RTC_SetWakeUpCounter(0);
        // 在备份寄存器中标记 LSE 失效
        RTC_WriteBackupRegister(RTC_BKP_DR0, 0xA5A5);
    }
}

5. 备份寄存器读写函数

uint32_t ReadWakeCounter(void) {
    return RTC_ReadBackupRegister(RTC_BKP_DR1);
}

void WriteWakeCounter(uint32_t cnt) {
    RTC_WriteBackupRegister(RTC_BKP_DR1, cnt);
}

6. 主程序与低功耗模式

int main(void) {
    RTC_Init();
    CSS_Init();
    RTC_Config_LSE();
    // 初始化备份寄存器
    if (ReadWakeCounter() == 0xFFFFFFFF) WriteWakeCounter(0);
    while (1) {
        // 进入 STOP 模式,RTC 唤醒
        PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
        // 唤醒后执行任务
        uint32_t cnt = ReadWakeCounter();
        cnt++;
        WriteWakeCounter(cnt);
        // 根据 cnt 执行不同任务
    }
}

注意事项

  • LSI 精度问题:LSI 频率受温度影响,误差可达 10% 以上。若需精确秒级唤醒,可在初始化时测量 LSI 实际频率(通过定时器捕获),并动态调整唤醒计数器值。
  • 备份域复位:在开发调试时,若频繁复位,备份寄存器内容可能保留,导致逻辑混乱。建议在初始化时检查备份寄存器标志(如 DR0 中的魔数),若无效则清零。
  • CSS 中断优先级:CSS 中断应设为最高优先级,确保 LSE 失效时能及时切换,避免系统挂起。
  • 唤醒后时钟源:切换至 LSI 后,RTC 精度降低,若应用需要精确时间,可考虑使用 HSE/128 作为备用,但 HSE 在 STOP 模式下可能关闭,需权衡。
  • 功耗考虑:LSI 功耗略高于 LSE,但差异不大。在 STOP 模式下,RTC 仍运行,功耗约 1μA 左右。

总结

本文通过利用 STM32F4 的备份域寄存器和 CSS 机制,实现了 LSE 停振后的无缝切换,确保秒级唤醒功能不中断。该方法无需额外硬件,仅通过软件配置即可提升系统可靠性,适用于对成本敏感且环境恶劣的嵌入式应用。开发者可根据实际需求扩展,如增加时间校准算法或使用外部 RTC 芯片作为冗余。