4、低功耗模式设计:MCU的睡眠模式、外设时钟门控、动态电压频率调整

好,咱们接着聊OTA升级中的电源管理。这一节,我打算重点讲讲MCU的低功耗模式设计。说实话,这部分内容在OTA升级里特别关键——你想想看,升级过程可能持续几十秒甚至几分钟,如果电池电量撑不住,升级到一半断电了,那设备就变砖了。我早年做一款智能门锁的OTA时,就吃过这个亏。

4.1 MCU的三种睡眠模式:Sleep、Stop、Standby

大部分现代MCU都提供了至少三种睡眠模式。它们之间的区别,说白了就是「谁还在工作」以及「醒来要多快」。

模式 CPU状态 外设状态 RAM保持 唤醒时间 典型电流
Sleep 停止 全部运行 几微秒 mA级
Stop 停止 可配置 几十微秒 μA级
Standby 掉电 全部关闭 毫秒级 nA级

Sleep模式:CPU停了,但外设还在跑。适合OTA升级中「等待数据包」的场景。比如Wi-Fi模块在收数据,MCU可以睡一会儿,等DMA把数据搬完了再醒来处理。我习惯在每次接收完一个数据包后,让MCU进入Sleep模式,等下一个中断到来。

Stop模式:这个模式我最常用。CPU和外设的时钟都停了,但RAM里的数据还在。OTA升级过程中,我们需要保存下载进度、校验结果、当前状态机这些关键信息。Stop模式刚好满足需求——功耗低,醒来后还能接着干。我曾经在项目中遇到一个问题:进入Stop模式前忘了关闭ADC,结果唤醒电流比预期高了10倍。嗯,这里要注意,进入Stop前一定要检查所有外设的时钟是否已关闭。

Standby模式:这是最省电的模式,但代价也最大——RAM里的数据全丢了。OTA升级中,Standby模式一般用在「升级完成,等待用户操作」的阶段。比如升级成功后,设备进入待机状态,只有RTC在跑,等用户按键唤醒。我个人建议,不要在升级过程中使用Standby模式,除非你能把关键状态存到备份寄存器里。

核心原则:OTA升级过程中,优先使用Stop模式。既能省电,又能保住升级进度。只有在升级彻底完成后,才考虑进入Standby模式。

4.2 外设时钟门控:别让没用的模块偷电

很多工程师容易忽略一个问题:MCU进入低功耗模式后,外设的时钟还在跑。你想想看,OTA升级时,你只需要Wi-Fi模块和Flash控制器在工作,其他像ADC、DAC、定时器、USB这些模块,完全可以关掉。

时钟门控(Clock Gating)就是干这个的。它允许我们在软件层面,单独控制每个外设的时钟开关。我一般会在OTA升级的初始化函数里,把不需要的外设时钟全部关闭:

// 以STM32为例,关闭OTA升级中不需要的外设时钟
void OTA_LowPower_Init(void)
{
    // 关闭ADC、DAC、定时器时钟
    __HAL_RCC_ADC_CLK_DISABLE();
    __HAL_RCC_DAC_CLK_DISABLE();
    __HAL_RCC_TIM1_CLK_DISABLE();
    __HAL_RCC_TIM2_CLK_DISABLE();
    
    // 保留必要的:Flash、DMA、通信接口
    // __HAL_RCC_FLASH_CLK_ENABLE();   // 默认开启
    // __HAL_RCC_DMA1_CLK_ENABLE();    // 用于数据传输
    // __HAL_RCC_USART1_CLK_ENABLE();  // 用于OTA通信
}

这里有个小技巧:我习惯在进入低功耗模式前,先读一下RCC寄存器,确认哪些外设时钟还在跑。有时候代码写多了,容易漏掉某个模块。我曾经调试一个项目,发现设备在OTA升级时功耗比预期高了30%,查了半天,原来是GPIO的时钟没关——GPIO的时钟虽然小,但架不住它一直开着。

避坑指南:关闭外设时钟后,记得检查该外设是否处于空闲状态。如果外设正在传输数据,强行关闭时钟可能导致总线挂死。我一般会在关闭时钟前,先检查外设的状态寄存器。

4.3 动态电压频率调整(DVFS):按需供电

DVFS,说白了就是「看人下菜碟」。OTA升级的不同阶段,对计算能力的需求是不一样的:

  • 下载阶段:主要靠通信模块,MCU只需要处理中断和校验,频率可以降到最低
  • 校验阶段:需要计算哈希值,对CPU性能有一定要求,频率可以适当提高
  • 写入阶段:Flash写入是瓶颈,CPU大部分时间在等待,频率可以降下来

我做过一个实际项目,MCU支持4个频率档位:16MHz、32MHz、64MHz、128MHz。OTA升级时,我这样分配:

// OTA升级各阶段的频率配置
void OTA_DVFS_Config(OTA_Phase phase)
{
    switch(phase)
    {
        case OTA_PHASE_DOWNLOAD:
            // 下载阶段,主要靠DMA,CPU频率降到最低
            SetSystemClock(16000000);  // 16MHz
            SetCoreVoltage(VOLTAGE_LOW);
            break;
            
        case OTA_PHASE_VERIFY:
            // 校验阶段,需要计算SHA256,频率拉高
            SetSystemClock(128000000); // 128MHz
            SetCoreVoltage(VOLTAGE_HIGH);
            break;
            
        case OTA_PHASE_FLASH_WRITE:
            // Flash写入阶段,CPU在等待,频率适中
            SetSystemClock(32000000);  // 32MHz
            SetCoreVoltage(VOLTAGE_MEDIUM);
            break;
            
        case OTA_PHASE_IDLE:
            // 空闲等待,进入Stop模式
            EnterStopMode();
            break;
    }
}

为什么要这么做?因为频率和电压是成正比的。频率越高,需要的电压就越高,功耗就越大。我实测过,128MHz时的功耗是16MHz时的5倍以上。OTA升级如果持续几分钟,这个差距就很可观了。

注意:切换频率和电压时,一定要按照芯片手册的时序来操作。先降频再降压,先升压再升频。顺序搞反了,芯片可能会死机。我曾经在切换频率时忘了等待PLL锁定,结果系统直接跑飞了。

4.4 三种策略的协同使用

这三种策略不是孤立的,它们需要配合使用。我总结了一个典型的OTA升级功耗管理流程:

  1. 初始化阶段:关闭所有不需要的外设时钟(时钟门控)
  2. 下载阶段:降低CPU频率到16MHz,进入Sleep模式等待数据包
  3. 校验阶段:提高CPU频率到128MHz,全速计算哈希值
  4. 写入阶段:降低CPU频率到32MHz,每次写入后进入Stop模式
  5. 完成阶段:进入Standby模式,等待用户操作

这个流程我用了很多年,效果不错。当然,具体参数要根据你的MCU型号和电池容量来调整。比如有些MCU的Stop模式唤醒时间特别长,那你就得权衡一下,是睡久一点省电,还是醒着等数据包更划算。

最后说一句:低功耗设计不是一蹴而就的。我建议你在开发板上先测一遍各个模式的电流,做到心中有数。然后拿着示波器,看电流波形,确认每个阶段的功耗是否符合预期。嗯,这一步虽然麻烦,但能帮你省下后面大量的调试时间。