2. STM32L4 时钟系统与低功耗模式
时钟,是 MCU 的心脏。这话一点不夸张。STM32L4 的时钟树比 F 系列复杂不少,但好处是——你可以按需裁剪功耗。我刚开始用 L4 时,总觉得时钟配置太绕,后来发现,只要抓住几个关键振荡器,剩下的就是连线题。
2.1 时钟树详解:四大振荡器
STM32L4 有四个主要时钟源。我习惯把它们分成两组:一组是“干活用的”,一组是“省电用的”。
| 时钟源 | 频率范围 | 典型功耗 | 主要用途 |
|---|---|---|---|
| HSI16 | 16 MHz | ~100 µA | 系统启动、快速唤醒 |
| MSI | 100 kHz – 48 MHz | ~5 µA (低频段) | 低功耗运行、自动调频 |
| HSE | 4 – 48 MHz | 取决于外部晶振 | 高精度、USB、以太网 |
| LSE | 32.768 kHz | ~0.6 µA | RTC、LPTIM、待机唤醒 |
核心原则: 系统时钟 (SYSCLK) 只能选一个主源。但外设时钟可以独立选择,甚至可以在运行中切换。
2.2 MSI:L4 的低功耗王牌
MSI 是 STM32L4 独有的。它内部有一个 RC 振荡器,但通过自动校准,精度能做到 ±0.25% 以内。我在一个电池供电的传感器项目里,全程用 MSI 跑 2 MHz,功耗比 HSI 低了将近 40%。
MSI 还有一个绝活:PLL 输入自动切换。当系统从 Stop 模式唤醒时,MSI 可以在 3 µs 内稳定输出,而 HSI 需要 5 µs 以上。你想想看,对于频繁休眠唤醒的场景,这 2 µs 的差距就是功耗的分水岭。
我的习惯: 只要应用不需要 USB 或高精度定时,我就用 MSI 做主时钟。省电,还省一颗外部晶振。
2.3 时钟配置实战:从复位到运行
系统上电后,默认使用 MSI 跑 2 MHz。这个频率足够做初始化。我一般会在 main 函数开头做三件事:
- 切换时钟源:根据需求切换到 HSI 或 HSE。
- 配置 PLL:如果需要更高频率,用 PLL 倍频。
- 设置 AHB/APB 分频:确保外设总线不超频。
下面是一个典型的配置代码,切换到 HSE 并倍频到 80 MHz:
void SystemClock_Config(void)
{
// 1. 使能 HSE
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 2. 配置 SYSCLK、AHB、APB 分频
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);
}
注意: 切换时钟源时,一定要先确认新时钟已经稳定。HAL 库的 HAL_RCC_OscConfig 内部会等待就绪,但如果你自己操作寄存器,别忘了轮询 HSERDY 或 MSIRDY 标志位。
2.4 低功耗模式:三种模式,三种哲学
STM32L4 有三种低功耗模式。我经常跟学员说:选哪种模式,取决于你“睡多深”以及“醒多快”。
| 模式 | CPU 状态 | SRAM 保持 | 唤醒时间 | 典型功耗 |
|---|---|---|---|---|
| Sleep | 停止 | 全部 | ~5 µs | ~1 mA |
| Stop | 掉电 | 全部 | ~5 µs | ~1 µA |
| Standby | 掉电 | 仅备份区 | ~50 µs | ~0.1 µA |
2.5 Sleep Mode:浅睡,随时待命
Sleep 模式只停了 CPU 内核,外设和 SRAM 都还在跑。说白了,就是“闭眼不关机”。
我常用 Sleep 模式配合 RTOS 的空闲任务。当所有任务都挂起时,让 CPU 睡下去,中断一来就醒。这样既不丢数据,又能省电。
// 进入 Sleep 模式
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
// 任意中断唤醒后,继续执行下一行代码
避坑: 进入 Sleep 前,记得关掉不用的外设时钟。否则外设还在跑,功耗降不下来。我曾经在调试时发现 Sleep 模式功耗有 5 mA,查了半天,原来是 USART 没关。
2.6 Stop Mode:深度睡眠,保留现场
Stop 模式把 CPU 和大部分外设的时钟都停了,但 SRAM 和寄存器内容保留。唤醒后,从断点继续执行。
这里有个关键点:电压调节器。Stop 模式有两种调节器状态:
- MR (Main Regulator):唤醒快,功耗稍高。
- LPR (Low Power Regulator):唤醒慢,功耗更低。
我一般用 LPR,除非应用对唤醒时间有硬性要求。比如一个温湿度采集器,每 10 秒醒一次,LPR 完全够用。
// 进入 Stop 模式,使用低功耗调节器
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 唤醒后,需要重新配置系统时钟
SystemClock_Config();
重要: 从 Stop 模式唤醒后,系统时钟会回到 MSI 2 MHz。你必须手动重新配置时钟源。我见过有人忘了这一步,结果外设跑在 2 MHz 下,通信全部超时。
2.7 Standby Mode:最深睡眠,几乎关机
Standby 模式是最省电的。CPU、SRAM、大部分外设全部掉电。只有备份域(RTC、LSE、备份寄存器)还活着。
唤醒方式只有几种:WKUP 引脚、RTC 闹钟、复位。唤醒后,系统相当于重新启动,但你可以通过检查 PWR_FLAG_SB 来判断是从 Standby 唤醒的。
// 进入 Standby 模式
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnterSTANDBYMode();
// 唤醒后,从复位向量开始执行
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET) {
// 清除标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
// 执行唤醒后的初始化
}
经验: Standby 模式适合“心跳式”应用。比如电池供电的烟雾报警器,每分钟醒一次,检测完就睡。功耗可以做到 0.1 µA 以下,一节 CR2032 电池用两年没问题。
2.8 LPTIM:低功耗定时器,省电利器
LPTIM 是 STM32L4 的“省电定时器”。它可以在 Stop 模式下运行,时钟源来自 LSE 或 LSI。这意味着,CPU 睡了,LPTIM 还在走。
我做过一个项目,需要每 100 ms 唤醒一次采集数据。如果用普通定时器,CPU 必须保持运行。用 LPTIM,CPU 可以睡在 Stop 模式,LPTIM 计时到了再唤醒。功耗从 5 mA 降到了 2 µA。
配置 LPTIM 很简单:
// 使能 LPTIM 时钟
__HAL_RCC_LPTIM1_CLK_ENABLE();
// 配置 LPTIM
LPTIM_HandleTypeDef hlptim1;
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC;
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV128;
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
hlptim1.Init.OutputPolarity = LPTIM_OUTPUTPOLARITY_HIGH;
hlptim1.Init.UpdateMode = LPTIM_UPDATE_IMMEDIATE;
hlptim1.Init.CounterSource = LPTIM_COUNTERSOURCE_INTERNAL;
HAL_LPTIM_Init(&hlptim1);
// 设置自动重载值,100 ms 唤醒一次
HAL_LPTIM_TimeOut_Start_IT(&hlptim1, 100, 0);
我的建议: LPTIM 的时钟源优先选 LSE。LSI 精度太差,温度变化时漂移严重。LSE 是晶振,稳定得多。
2.9 本章知识结构
下面这张图,把时钟源、低功耗模式、LPTIM 的关系串起来了。你可以把它当作本章的“思维导图”。
2.10 避坑总结
最后,我把这些年踩过的坑列出来,你遇到了可以直接翻回来查:
- 时钟切换后外设死掉:切换时钟源后,外设时钟可能丢失。记得重新配置外设时钟分频。
- Stop 模式唤醒后系统跑飞:唤醒后没重新配置系统时钟,外设跑在 2 MHz 下。进 Stop 前保存时钟配置,唤醒后恢复。
- LPTIM 不工作:检查 LPTIM 的时钟源是否使能。LSE 没起振,LPTIM 就停摆。
- Standby 模式唤醒后数据丢失:SRAM 在 Standby 模式下掉电。需要保存的数据,放到备份寄存器或 RTC 备份域里。
一句话总结: 时钟是功耗的开关,模式是功耗的档位。选对时钟源,用对低功耗模式,L4 的功耗可以做到让你惊讶。