第二章 时钟系统优化:MSI与HSI16选择策略、PLL关闭与旁路技巧、RTC与LSE时钟配置、时钟门控技术
时钟系统,说白了就是MCU的心脏。心跳多快、跳不跳、什么时候跳,直接决定了功耗的高低。很多工程师一上来就盯着休眠模式、停止模式折腾,结果发现功耗死活降不下去——回头一看,时钟树根本没优化。
这一章,我就带你把这颗“心脏”彻底调明白。
2.1 MSI vs HSI16:选谁更省电?
STM32L4内部有两个低频振荡器:MSI(多速内部振荡器)和HSI16(16 MHz高速内部振荡器)。很多人习惯性用HSI16,觉得它精度高、稳定。但低功耗场景下,这个选择不一定对。
核心结论:MSI在低频率下功耗远低于HSI16,且支持硬件自动调频,是低功耗运行的首选。
我在一个电池供电的温湿度采集项目中,最初用的HSI16分频到2 MHz,整机电流一直在80 µA左右徘徊。后来换成MSI的2 MHz模式,电流直接掉到35 µA。为什么?因为MSI内部是低功耗振荡器架构,HSI16是高速振荡器降频使用,底噪和功耗都高。
| 特性 | MSI | HSI16 |
|---|---|---|
| 频率范围 | 100 kHz ~ 48 MHz | 16 MHz(固定) |
| 典型功耗(2 MHz) | ~15 µA | ~80 µA |
| 启动时间 | ~5 µs | ~1 µs |
| 精度 | ±1%(校准后) | ±1% |
| 硬件自动调频 | 支持(配合PLL) | 不支持 |
我的选择策略:
- 系统主频 ≤ 8 MHz:无脑选MSI,功耗优势明显
- 系统主频 8~48 MHz:MSI + PLL升频,比HSI16 + PLL省电约20%
- 需要快速启动(< 2 µs):选HSI16,但只在启动瞬间用,之后切到MSI
小技巧:MSI有一个“硬件自动调频”模式,配合PLL使用时,系统可以在低负载时自动降频到MSI的基频,高负载时通过PLL升频。这个功能在FreeRTOS空闲任务里特别好用——空闲时跑100 kHz,有任务时瞬间切到48 MHz。
2.2 PLL关闭与旁路:别让它偷你的电
PLL(锁相环)是个好东西,能把低频时钟倍频到高频。但它也是个电老虎。我见过太多人,系统明明只跑2 MHz,PLL却一直开着——白白多耗30~50 µA。
什么时候必须关PLL?
- 进入Stop模式前:必须关闭PLL,否则无法进入最低功耗状态
- 系统主频 ≤ 8 MHz且使用MSI:直接旁路PLL,用MSI直出
- 外设时钟需求低于PLL输出:比如只跑UART 115200,用MSI 2 MHz就够了
我曾经踩过的坑:在Stop模式下,PLL没关干净,导致电流比预期高了40 µA。后来查手册才发现,PLL关闭后需要等待PLLRDY位清零,才算真正关掉。代码里加了个while循环等标志位,问题解决。
PLL旁路配置示例:
// 关闭PLL并旁路
RCC->CR &= ~RCC_CR_PLLON; // 关闭PLL
while(RCC->CR & RCC_CR_PLLRDY); // 等待PLL完全关闭
// 选择MSI作为系统时钟
RCC->CFGR &= ~RCC_CFGR_SW; // 清除时钟源选择位
RCC->CFGR |= RCC_CFGR_SW_MSI; // 选择MSI
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_MSI); // 等待切换完成
省电口诀:不用PLL就关,关了要等确认,确认完再切时钟源。三步缺一不可。
2.3 RTC与LSE时钟配置:时间不能停,但电要省
RTC(实时时钟)在低功耗系统里几乎是标配——定时唤醒、记录时间、维护日历。但RTC的时钟源选择,直接影响休眠时的功耗。
STM32L4的RTC有三个时钟源可选:
- LSE(32.768 kHz外部晶振):精度最高(±5 ppm),功耗最低(~0.5 µA),我90%的项目都用它
- LSI(32 kHz内部振荡器):免外部晶振,但精度差(±5%),温漂大,不适合长时间计时
- HSE(外部高速时钟分频):精度高但功耗大,低功耗场景基本不用
| 时钟源 | 精度 | 功耗 | 启动时间 | 推荐场景 |
|---|---|---|---|---|
| LSE | ±5 ppm | ~0.5 µA | ~1 s | 电池供电、长时间计时 |
| LSI | ±5% | ~0.3 µA | ~100 µs | 短时间定时、唤醒 |
| HSE | ±10 ppm | ~5 µA | ~1 ms | 高精度、不关心功耗 |
我的配置习惯:
- 需要日历功能:必须用LSE,否则一天能差几十分钟
- 只需要定时唤醒(比如每10秒醒一次):用LSI,省掉外部晶振和两个电容,BOM成本降0.2元
- 混合方案:平时用LSI做短定时,每隔1小时用LSE校准一次LSI的漂移
关键配置:RTC的异步预分频和同步预分频要算好。LSE 32.768 kHz,异步预分频设为128,同步预分频设为256,得到1 Hz的秒脉冲。这个组合功耗最低,因为异步分频器工作在低频,同步分频器工作在高频但分频比小。
// RTC时钟配置示例(LSE + 1 Hz输出)
RCC->BDCR |= RCC_BDCR_LSEON; // 开启LSE
while(!(RCC->BDCR & RCC_BDCR_LSERDY)); // 等待LSE稳定(约1秒)
RCC->BDCR &= ~RCC_BDCR_RTCSEL; // 清除RTC时钟源
RCC->BDCR |= RCC_BDCR_RTCSEL_LSE; // 选择LSE
RTC->PRER = (127 << 16) | 255; // 异步128分频,同步256分频
RTC->CR |= RTC_CR_BYPSHAD; // 旁路影子寄存器,减少访问延迟
注意:LSE启动时间很长,最长能到2秒。如果系统上电后立刻需要RTC,建议先切到LSI,等LSE稳定后再切换。我有个产品因为这个原因,上电后前2秒RTC不走,导致日志时间戳全乱了。
2.4 时钟门控技术:不给外设发粮
时钟门控,说白了就是“用谁开谁,不用就关”。STM32L4的每个外设都有独立的时钟使能位,在RCC寄存器里控制。但很多人只会在初始化时开时钟,之后再也不关。
时钟门控的核心原则:
- 外设不用时,关掉它的时钟(包括APB1、APB2、AHB总线时钟)
- 进入低功耗模式前,检查所有外设时钟是否已关闭
- DMA和中断触发的外设,用完后立即关时钟
我常用的做法:写一个外设时钟管理函数,每次使用外设前开时钟,使用后关时钟。配合RTOS的信号量,确保不会在中断里误关。
// 外设时钟管理示例
void USART_SendByte(USART_TypeDef* usart, uint8_t data) {
// 开时钟
if(usart == USART1) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
}
// 等待发送完成
while(!(usart->ISR & USART_ISR_TXE));
usart->TDR = data;
while(!(usart->ISR & USART_ISR_TC));
// 关时钟
if(usart == USART1) {
RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN;
}
}
哪些外设的时钟必须一直开着?
- RTC(如果要用)
- LPTIM(低功耗定时器,用于唤醒)
- IWDG(独立看门狗,如果使能了)
- DMA(如果数据还在传输中)
其他外设,包括GPIO、SPI、I2C、ADC、DAC,用完后一律关时钟。GPIO的时钟尤其容易被忽略——很多人初始化完GPIO就不管了,结果GPIO时钟一直开着,多耗几µA。
终极技巧:在进入Stop模式前,遍历所有外设时钟寄存器,打印出还在使能的外设列表。我写了一个debug函数,每次调试时跑一遍,能发现很多“漏网之鱼”。
2.5 本章知识体系
下面这张图,把时钟系统优化的核心逻辑串起来了。你可以把它贴在工位上,每次做低功耗设计时对照着检查。