4. 时钟系统与功耗:时钟源选择与倍频代价
时钟,是嵌入式系统的心脏。但很多人没意识到,这颗“心脏”跳动的代价有多大。我见过不少项目,前期选型时随便挑了个时钟方案,结果后期功耗压不下去,只能推倒重来。今天咱们就把时钟系统的功耗账,一笔一笔算清楚。
4.1 四大时钟源:HSI、HSE、LSI、LSE
MCU内部通常有四个时钟源,它们各有各的脾气。我习惯把它们分成两组:高速组和低速组。
| 时钟源 | 全称 | 典型频率 | 启动时间 | 功耗等级 |
|---|---|---|---|---|
| HSI | 高速内部振荡器 | 8/16 MHz | 几μs | 中等 |
| HSE | 高速外部晶振 | 4~25 MHz | 几ms | 高 |
| LSI | 低速内部振荡器 | 32~40 kHz | 几十μs | 极低 |
| LSE | 低速外部晶振 | 32.768 kHz | 1~2 s | 低 |
看到这个表,你可能会想:HSI启动快、功耗也不高,那为什么还要用HSE?
原因很简单——精度。HSI受温度和电压影响很大,误差可能到±2%甚至更高。而HSE的精度可以做到±50ppm以内。我在做一款NB-IoT终端时,就因为用了HSI导致载波频率偏移,通信距离直接砍半。后来换成HSE,问题才解决。
核心原则:精度要求高(如通信、USB、ADC采样)→ 用HSE;精度要求低(如普通GPIO控制)→ 用HSI省功耗。
低速组这边,LSE是RTC的黄金搭档。32.768 kHz这个频率,分频后正好是1秒,做日历时钟再合适不过。LSI虽然功耗更低,但精度差,做RTC一天能差好几秒。
我的习惯:只要产品需要掉电保存时间,我一定用LSE。LSI只用来做看门狗或唤醒定时器,省那几微安不值得冒时间不准的风险。
4.2 时钟分频与门控:最容易被忽略的功耗优化点
很多人觉得,时钟源选好了就完事了。其实不然。时钟树上的分频器和门控,才是真正的功耗优化战场。
分频器本身不耗多少电,但它决定了后续所有外设的工作频率。你想想看,一个SPI模块跑48 MHz和跑12 MHz,动态功耗差4倍。但很多外设根本不需要那么高的频率。
我建议的做法是:
- 先确定每个外设的最低工作频率(查数据手册)
- 再配置对应的时钟分频器,让频率刚好够用
- 不用的外设,直接关掉时钟
时钟门控(Clock Gating)是MCU厂商给的福利。大部分MCU都有RCC(Reset and Clock Control)寄存器,可以单独控制每个外设的时钟开关。
// 以STM32为例,关闭USART1时钟
RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN;
// 需要时再打开
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
__NOP(); __NOP(); // 等待两个时钟周期,让时钟稳定
USART1->CR1 |= USART_CR1_UE; // 使能USART
注意:关闭时钟前,一定要确保外设处于空闲状态。我曾经在USART还在发送数据时关了时钟,结果数据卡在移位寄存器里,下一包数据发出去就乱码了。排查了整整两天。
还有一个容易被忽略的点:总线时钟分频。AHB、APB1、APB2这些总线,频率越高功耗越大。如果外设都挂在APB1上,而APB1不需要那么高的频率,完全可以降低它的分频系数。
4.3 PLL与倍频:性能的代价
PLL(锁相环)是个好东西。它能把低频时钟倍频到高频,让MCU跑得更快。但好东西都有代价。
PLL的功耗由三部分组成:
- 鉴相器(PFD):比较参考时钟和反馈时钟的相位差
- 电荷泵(Charge Pump):把相位差转换成电压
- 压控振荡器(VCO):产生高频时钟
其中VCO是功耗大头。频率越高,VCO的功耗越大,而且不是线性增长,是指数级的。我实测过某款MCU:
| 工作模式 | 时钟配置 | 系统电流 | 相对功耗 |
|---|---|---|---|
| HSI 8 MHz | 直接使用 | 2.1 mA | 1x |
| HSE 8 MHz | 直接使用 | 2.8 mA | 1.33x |
| HSE 8 MHz + PLL 72 MHz | 倍频到72 MHz | 18.5 mA | 8.8x |
| HSE 8 MHz + PLL 120 MHz | 倍频到120 MHz | 42.3 mA | 20.1x |
看到了吗?从8 MHz倍频到72 MHz,频率只增加了9倍,但功耗增加了8.8倍。再往上到120 MHz,功耗直接翻倍还多。
结论很明确:PLL倍频的功耗代价,远高于频率本身的线性增长。能用低频率解决问题,就别开PLL。
那什么时候必须用PLL?
- 需要高算力(如音频处理、加密运算)
- 外设需要特定频率(如USB需要48 MHz)
- 系统有严格的时序要求
其他情况,我建议尽量绕开PLL。比如做传感器采集,1 MHz都够用,何必开到72 MHz让电池哭泣?
4.4 时钟系统功耗优化流程图
下面这张图,是我做低功耗设计时参考的时钟决策流程。每次画板子前,我都会过一遍。
4.5 实战中的时钟切换策略
很多低功耗场景,不是固定用一个时钟就完事了。系统在不同状态下,对时钟的需求完全不同。
我举个例子:一个电池供电的温湿度采集器。
- 休眠状态:只用LSE做RTC,电流几μA
- 采集状态:用HSI 8 MHz,启动快,采完就睡
- 通信状态:切换到HSE + PLL到48 MHz,保证无线模块的时序
这种动态切换,能比全程跑48 MHz省下80%以上的功耗。但要注意切换时的瞬态问题。
切换时钟的坑:我曾经在切换时钟源时,没有等新时钟稳定就切换了外设时钟。结果系统跑飞了。正确的做法是:先使能新时钟源,等待它稳定(检查标志位),再切换系统时钟,最后关闭旧时钟源。
// 时钟切换示例:从HSI切换到HSE
RCC->CR |= RCC_CR_HSEON; // 使能HSE
while(!(RCC->CR & RCC_CR_HSERDY)); // 等待HSE稳定
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW) | RCC_CFGR_SW_HSE; // 切换系统时钟
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSE); // 等待切换完成
RCC->CR &= ~RCC_CR_HSION; // 关闭HSI
嗯,这里要注意:等待稳定标志位的时间,不同MCU不一样。HSE可能需要几毫秒,LSE甚至要一两秒。如果系统对启动时间有要求,这个等待时间必须算进去。
我的经验:如果系统需要快速响应中断,我会让HSI一直开着作为备用时钟。中断来了先用HSI快速响应,处理完再切回低功耗时钟。虽然HSI一直开着会多耗一点电,但换来的是响应速度,值。
最后说一句:时钟系统的功耗优化,不是选一个低功耗时钟源就完事了。它是一个系统工程,需要从时钟源选择、分频配置、门控管理、动态切换等多个维度去考虑。每优化一步,可能只省下几十微安,但积少成多,整个系统的续航就能翻倍。