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升级功耗管理流程:
- 初始化阶段:关闭所有不需要的外设时钟(时钟门控)
- 下载阶段:降低CPU频率到16MHz,进入Sleep模式等待数据包
- 校验阶段:提高CPU频率到128MHz,全速计算哈希值
- 写入阶段:降低CPU频率到32MHz,每次写入后进入Stop模式
- 完成阶段:进入Standby模式,等待用户操作
这个流程我用了很多年,效果不错。当然,具体参数要根据你的MCU型号和电池容量来调整。比如有些MCU的Stop模式唤醒时间特别长,那你就得权衡一下,是睡久一点省电,还是醒着等数据包更划算。
最后说一句:低功耗设计不是一蹴而就的。我建议你在开发板上先测一遍各个模式的电流,做到心中有数。然后拿着示波器,看电流波形,确认每个阶段的功耗是否符合预期。嗯,这一步虽然麻烦,但能帮你省下后面大量的调试时间。