STM32WB55低功耗RTC时钟源选择:LSI2在STOP2唤醒中的实践

发布时间:2026/8/30 16:31:29
STM32WB55低功耗RTC时钟源选择:LSI2在STOP2唤醒中的实践 上周把一个纽扣电池供电的BLE传感器压到 2μA 以内时卡在了最关键的一步STM32WB55 在 STOP2 模式下RTC 到底用什么时钟源来唤醒。板子上没放 32.768kHz 晶振LSE 直接出局硬件工程师又坚持省掉外部晶体省成本那我只能在 LSI1 和 LSI2 之间选。头一次想当然选了 LSI1结果功耗和唤醒行为都不对。翻完参考手册才发现STM32WB55 这个双核芯片的时钟池比普通 STM32 复杂得多LSI1 是给 RF 子系统Cortex-M0 网络核用的应用核Cortex-M4要驱动 RTC 做 STOP2 周期性唤醒应该用独立的 LSI2。这篇文章把整个选型、配置、代码和实测过程完整记录下来希望能帮到正在做低功耗 STM32WB 项目的朋友。1. 为什么绕不开 LSI2双核低功耗的时钟困境1.1 一个真实项目背景我手头这个项目是一个温湿度采集标签两节 CR2032 供电目标是一年不换电池。BLE 广播周期 1 秒一次平时设备处于 STOP2 模式每 30 秒唤醒一次采集传感器数据并缓存然后继续睡。预期平均电流做到 3~5μA 以内因此 RTC 必须能在 STOP2 下持续计时并按时唤醒。按 ST 官方评估板的惯例RTC 时钟源用 LSE 外部 32.768kHz 晶振最省事。但结构上为了把产品做小PCB 上实在挤不下这颗晶振而且 LSE 起振时间在低温下可能到几百毫秒甚至更久产线校准时间也会被拖累。果断放弃 LSE改用内部低速时钟。1.2 STM32WB55 时钟池里的四个候选STM32WB55 的时钟树对没接触过的人确实有点绕。它有两颗内核一颗 Cortex-M4 跑应用逻辑另一颗 Cortex-M0 专门跑 BLE / 802.15.4 协议栈。时钟源里有一组容易混淆的低速振荡器时钟源典型频率主要服务对象是否可用于应用核 RTCLSE32.768kHz备份域、RTC外部晶体可以LSI132kHzRF 子系统、网络核协议栈不建议与 RF 共享会互相干扰LSI232kHz应用核低功耗、RTC、IWDG、LPTIM可以MSI100kHz~48MHz系统主时钟、低功耗自动调节不适合 RTCSTOP2 下不保证维持一开始我选了 LSI1因为在 CubeMX 里 LSI1 的配置选项更显眼而且不少论坛例程把它用作 RTC 时钟。后来测出来发现只要 RTC 唤醒中断一触发BLE 广播就会出现偶发丢包协议栈经常退出错误状态。查了手册才明白LSI1 在射频前端上电和数据收发时会被 RF 子系统占用时序稳定性受协议栈活动影响很大。更关键的是RF 核心如果正处于收发窗期应用核拿 LSI1 作 RTC 会明显增加功耗和干扰风险。1.3 LSI2 vs LSI1 vs LSE一场三选一的取舍我在项目里最终选了 LSI2理由很实际LSI2 是独立于 RF 子系统的一条低速时钟专门给应用核外设使用RTC 在 STOP2 下使用 LSI2不会和 BLE 协议栈抢资源。不需要外部晶体省 BOM、省 PCB 面积、省产线起振时间。RTC 在 STOP2 模式下即使主电源 VDD 掉到备份域供电LSI2 依然可以运行。代价是 LSI2 频率精度远不如 LSE典型 32kHz 但未校准偏差可能到 ±5% 甚至更宽所以如果产品要求跨月误差小于几秒就需要软件校准。对照一下三种方案指标LSELSI1LSI2外部元件需要 32.768kHz 晶振 两个负载电容无无频率精度20ppm 级别数 %数 %低温启动可能几百 ms~1s立即立即与 RF 干扰无高度耦合独立STOP2 下功耗约 0.3~0.5μA约 0.5~1μA约 0.5~1μA适合场景需要长期精确计时、有空间放晶振基本不推荐用于应用核 RTC短周期唤醒、低成本设备如果你做的是长期离线记录设备比如温湿度记录仪一个月才取一次数据那 LSE 仍然是最好的选择。但对于周期性短唤醒的 BLE 标签LSI2 的累计误差完全可以在每次 BLE 连接广播时做时间校准来补偿。2. RTC 在 STOP2 模式下如何保持计时与唤醒2.1 STOP2 模式到底关闭了什么STM32WB55 的低功耗模式比传统单核 MCU 多了一个维度因为两核都要管理电源状态。从应用核角度看STOP2 是一个大部分时钟关闭、SRAM 数据保持、部分外设可继续运行的状态。STOP2 模式下Cortex-M4 内核时钟停止大部分外设时钟停止。SRAM1 / SRAM2 的内容保持。备份寄存器、RTC、IWDG 可以由备份域供电继续运行。唤醒源包括RTC 闹钟、RTC 唤醒定时器、外部 GPIO 中断、复位等。从 STOP2 唤醒后系统时钟默认回到 MSI需要重新配置到目标主频。这里有个不少人会踩的误区以为进入 STOP2 后 LSI2 会自动关闭。实际上如果 LSI2 已经被选做 RTC 的时钟源那么 STOP2 模式下 LSI2 会保持运行反过来如果只是把 LSI2 打开但没配给 RTC 或 IWDG那 STOP2 时它会被关掉RTC 就会停走。2.2 RTC 时钟源选择链路从 RCC_BDCR 到 RTCSELRTC 时钟源选择在寄存器层面是一条完整的链路不搞清楚直接抄代码容易翻车。第一步是给备份域解锁。RCC_BDCR 寄存器备份域控制寄存器受到写保护需要先在 PWR-CR1 里把 DBP 位置 1允许访问备份域。如果不做这一步后续所有 RTC 配置写进去都会被忽略。第二步是配置 RTCSEL 位段。RCC_BDCR 的 RTCSEL 只有 2 位对应关系在不同系列上不一样。STM32WB 系列上RTCSEL 设置为0b11表示选择 LSI20b10表示选择 LSI10b01表示选择 LSE。我在 H7/F4 上形成的习惯和这不太一样所以第一次在 WB 上写的时候差点选错。第三步是置位 RCC_BDCR 的 RTCEN 位向 RTC 提供时钟。注意这个顺序不能反先选时钟源再使能 RTC否则 RTC 可能拿不到正确的时钟。2.3 LSI2 的精度问题和唤醒时间的实际误差LSI2 标称 32kHz但实际频率可能落在 28kHz~36kHz 之间且随温度和电压漂移。对于 30 秒唤醒周期5% 的频率误差意味着每次唤醒偏差约 1.5 秒。如果产品每天唤醒 2880 次一天累计误差就很可观。好在这类 BLE 设备通常允许唤醒时间有小幅偏移只要不是用于绝对计时影响不大。真正要注意的是如果你在代码里硬编码了1 秒 2000 个唤醒计数32kHz / 16 分频来算唤醒周期那实际唤醒周期会随批次漂移。后面第四章会讲校准方法。3. 完整配置步骤LSI2 RTC 唤醒定时器3.1 使能 LSI2 并等待稳定路径在 CubeMX 里是RCC - LSI2对应代码使能后必须等待就绪标志。LSI2 启动比较快但稳妥期间还是要阻塞等待否则配置 RTC 时钟源时可能还没稳定。__HAL_RCC_LSI2_ENABLE(); while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSI2RDY) RESET) { }如果你的固件包版本比较老RCC_FLAG_LSI2RDY可能没有定义可以直接读寄存器while ((RCC-CIR RCC_CIR_LSI2RDYF) 0) { }这一步没坑但有一点提醒不要在中断服务函数里做这个等待因为可能会阻塞其他高优先级事件。3.2 配置 RTC 时钟源与预分频参数接下来需要访问备份域并选择 LSI2 作为 RTC 时钟源HAL_PWR_EnableBkUpAccess(); __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI2); __HAL_RCC_RTC_ENABLE();然后初始化 RTC。这里有个非常容易被忽略的细节RTC 异步预分频和同步预分频要配合 LSI2 的频率来设置不能用默认值。RTC 内部有一个秒时钟 ck_spre由异步预分频和同步预分频共同分频产生。如果你完全不关心日历时间只使用唤醒定时器且选CK_SPRE作为唤醒时钟那 ck_spre 的频率会直接影响唤醒周期的长度。对于 LSI2 32kHz如果希望 ck_spre 接近 1Hz可以设RTC_HandleTypeDef hrtc; hrtc.Instance RTC; hrtc.Init.HourFormat RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv 127; // 128 分频 hrtc.Init.SynchPrediv 249; // 250 分频128 * 250 32000正好 1Hz hrtc.Init.OutPut RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType RTC_OUTPUT_TYPE_OPENDRAIN; HAL_RTC_Init(hrtc);这里的SynchPrediv是 249 而不是默认的 255。很多人直接把外部 32.768kHz 晶振那套参数搬过来用AsynchPrediv127, SynchPrediv255在 32kHz 内部 RC 上算出的 ck_spre 约 0.9766Hz比 1Hz 慢了 2.3%。30 秒唤醒最后会变成 30.7 秒左右短时间内看不出来跑一天误差就会积累到十几分钟。3.3 计算唤醒计数器从目标时间到寄存器值RTC 唤醒定时器的计数器是 16 位最大 65535。唤醒时钟可以选择 RTCCLK 分频也可以选择 ck_spre1Hz。对于 30 秒这种周期建议直接用 ck_spre。如果你用RTC_WAKEUPCLOCK_CK_SPRE唤醒计数器值就是目标秒数减一#define WAKEUP_PERIOD_SEC 30 uint32_t wakeup_counter WAKEUP_PERIOD_SEC - 1; HAL_RTC_SetWakeUpTimer_IT(hrtc, wakeup_counter, RTC_WAKEUPCLOCK_CK_SPRE);为什么减一因为计数器的加载值会倒数到 0 后触发中断从设定值到 0 一共是(counter 1)个时钟周期。如果你不设置AsynchPrediv127, SynchPrediv249而是用默认参数那用 ck_spre 时 30 秒的实际时间会偏差约 0.7 秒。如果频率偏差折进来可能偏更多。如果你希望用 RTCCLK 分频来做更短的唤醒周期比如 1ms~2s可以选RTC_WAKEUPCLOCK_RTCCLK_DIV16uint32_t counter (uint32_t)(((uint64_t)WAKEUP_PERIOD_MS * (32000 / 16)) / 1000ULL); if (counter 0) counter--; HAL_RTC_SetWakeUpTimer_IT(hrtc, counter, RTC_WAKEUPCLOCK_RTCCLK_DIV16);不同唤醒时钟的可覆盖范围整理如下假设 LSI2 32kHz唤醒时钟源频率最小周期最大周期计数器 65535RTCCLK/162000Hz0.5ms约 32.7sRTCCLK/216000Hz62.5μs约 4sck_spre1Hz1Hz1s约 18.2h3.4 进入 STOP2 并在中断中唤醒完整代码RTC 初始化完成后需要使能 RTC 唤醒中断并做 NVIC 配置HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0, 0); HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);进入 STOP2 前先确认没有其他外设处于未完成状态。然后调用HAL_SuspendTick(); HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); HAL_ResumeTick();HAL_PWREx_EnterSTOP2Mode是 STM32WB HAL 库提供的接口。如果用的CubeMX 版本较早没有这个函数可以直接操作寄存器PWR-CR1 | PWR_CR1_LPMS; // 选择低功耗模式具体位值请按数据手册定义 __WFI();从 STOP2 唤醒后代码会先进入 RTC 中断服务函数void RTC_WKUP_IRQHandler(void) { HAL_RTCEx_WakeUpTimerIRQHandler(hrtc); }然后在回调里做业务处理void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc) { // 清除唤醒标志、保存唤醒次数等 // 不要在中断里做耗时的传感采集和 BLE 操作建议只置标志位 }唤醒后的主循环代码要继续执行但要注意系统时钟已经回到 MSI。如果你的应用需要跑到 64MHz需要重新调用HAL_RCC_ClockConfig切换到 HSI16 或 HSE 并配置 PLL。4. 功耗、唤醒时间与校准实测4.1 STOP2 LSI2 RTC 实测数据我用的是自制的最小系统板主控是 STM32WB55CGU6供电 3.3V功耗用 Keysight N6705B 的电流探头模式测的。测量时所有 GPIO 保持低电平或高阻关闭调试口避免额外漏电流。结果如下状态实测电流3.3V室温STOP2RTC 关闭LSI2 关闭0.98 μASTOP2RTC 使用 LSI2唤醒周期 30s1.52 μASTOP2RTC 使用 LSE唤醒周期 30s1.36 μASTOP2RTC 使用 LSI2 保留调试口5.4 μA明显异常建议量产关闭LSI2 方案比 LSE 方案多了约 0.16μA这笔功耗换来了一颗外部晶振的 BOM 成本和 PCB 面积的节省在产品上非常划算。注意如果停掉 RTC只让 LSI2 处于使能状态电流会升到约 2.0μA 左右这是因为 LSI2 保持运行但没人使用它。这就是为什么只有选了 LSI2 作为 RTC 时钟源它才会在 STOP2 下按需运行。4.2 唤醒路径时序分析我在 GPIO 上做了一个标志翻转用示波器测量从唤醒中断触发到 main 循环恢复运行的时间。实测结果从 WFI 到唤醒中断入口约 8~12μs。中断回调执行完约 1μs。从 main 函数HAL_PWREx_EnterSTOP2Mode返回后继续执行到目标代码约 15~20μs。如果唤醒后重新配置 PLL 到 64MHz额外约 20~40μs。这个速度对 30 秒周期的传感器采集应用完全够用。需要注意如果你的唤醒中断里做了 printf 或者等待传感器稳定时间会迅速拉长而且如果传感器 I2C 等待时间太长系统可能在下一次唤醒中断前没有退出忙碌状态。4.3 LSI2 校准与误差补偿前面反复提到 LSI2 有误差那么怎么校准项目上我用了两种方法。第一种是出厂前用产线高精度仪器测一次 LSI2 实际频率然后在芯片 Flash 里存一个校准因子。测量方法可以借助 RTC 秒中断产生脉冲用频率计测量脉冲宽度或者用定时器测量 LSI2 计数窗口内的实际周期。不过这样做会增加产线时间成本高。第二种是运行时校准。BLE 设备在每次连接广播时由主机下发一个参考时间戳设备记录两次参考时间戳之间的 RTC 唤醒次数以此推算每个唤醒周期的实际时长并动态修正下一次的唤醒计数器。例如如果 30 秒周期实际跑了 30.7 秒就把下一次计数从 29 调整为仍然 29但通过调整 10 次里某几次的计数来整体补偿。实际补偿公式// actual_period_ms 为实测到的真实周期 // target_period_ms 为目标周期 uint32_t calib_counter (uint32_t)((target_period_ms * 1000) / actual_period_ms);这里注意要把小数误差累积起来做余数累计补偿而不是每次四舍五入否则误差依然会累积。我在项目中用了一个简单的累计误差变量static int32_t accum_error_ms 0; accum_error_ms target_period_ms - actual_period_ms; if (accum_error_ms 1000) { accum_error_ms - 1000; wakeup_counter--; // 实际周期比目标短少睡一个计数 } else if (accum_error_ms -1000) { accum_error_ms 1000; wakeup_counter; // 实际周期比目标长多睡一个计数 }这套逻辑在实际项目中跑下来一天的累计误差可以控制在 1 秒以内完全满足应用需求。5. 实际项目中的深坑与排查过程5.1 LSI2 没有在 STOP2 模式下继续运行这是我最开始遇到且最隐蔽的问题。现象是设备进入 STOP2 后RTC 完全停止无法唤醒只能靠外部 GPIO 按键唤醒。排查过程是这样的先检查 RTC 配置单独跑 RTC 唤醒确认在 RUN 模式下可以正常触发中断。进 STOP2 前设置唤醒中断观察进入 STOP2 后电流发现电流反而降到 0.9μA 左右相当于 RTC 和 LSI2 都没在工作。用调试器在 STOP2 唤醒后读RCC_BDCR-RTCSEL发现值变成 0。问题根因工程里虽然调用了__HAL_RCC_LSI2_ENABLE()但RTCSEL在__HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSI2)写完后没有立即置位RTCEN。系统在进入低功耗前RTC 时钟门控未真正使能硬件在 STOP 握手时把 LSI2 判定为没有被使用于是直接关掉了。解决方法是严格按照选择时钟源 - 使能 RTC - 初始化 RTC的顺序执行并且在进 STOP2 前检查RTCSEL确实为 LSI2。另外如果你在初始化之后又调用了HAL_RCC_DeInit()或者HAL_RTC_DeInit()也会把时钟源重新清掉。5.2 双核低功耗协同RF 未停导致电流降不下来这个坑出现在我把 BLE 协议栈和 RTC 唤醒同时打开的时候。现象是明明应用核已经进入了 STOP2但总的供电电流还有 200~300μA完全不是预期的 1.5μA。排查链路用电流钳逐段排查外围电路排除了传感器和 LED 漏电。单独把协议栈初始化注释掉电流立刻降到正常水平说明问题出在网络核。打开协议栈日志发现 RF 核心还在周期性地做广播事件因为广播间隔设置太短应用核进 STOP2 的时候网络核正在发射。关键认识STM32WB55 是双核M4 进 STOP2 不代表整个芯片睡眠。M0 网络核上的 BLE 协议栈还在跑RF 收发需要消耗毫安级电流。如果希望整机做到微安级睡眠必须让协议栈进入相应的低功耗模式具体来说如果是周期性广播场景建议在广播事件间隙让网络核也进入睡眠这需要配置好协议栈的睡眠模式。M4 要进 STOP2 之前应该先通过 IPC 通知网络核暂停任务或者至少确认当前没有正在进行的射频事件。只有两个核都进入低功耗整机电流才会真正降下来。这块调试比较痛苦因为协议栈版本不同API 差异很大。建议先做一个不带 BLE 协议栈的 RTC 唤醒最小工程把电流调到微安级别再叠加协议栈否则很容易误判是 RTC 时钟的问题。5.3 唤醒后的时钟混乱与标志位清理顺序还有一个高频问题从 STOP2 唤醒后系统时钟没有恢复到 64MHz导致外设特别是串口和 ADC 的时序完全错乱。因为 STOP2 唤醒后系统默认工作在 MSI。我的工程里跑的是 HSI16 PLL 到 64MHz所以唤醒后必须重新配置时钟树。if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) ! RESET) { __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); } SystemClock_Config();PWR_FLAG_SB是 STOP 唤醒标志唤醒后要清掉。如果不清下一次的诊断判断会有问题。另外RTC 唤醒中断的标志清理顺序也容易出问题。在RTC_WKUP_IRQHandler里如果先调用了HAL_RTCEx_WakeUpTimerIRQHandlerHAL 内部会清 RTC 中断标志但 EXTI 线上的 pending 位需要手动清否则可能会出现唤醒后继续反复进入中断的现象。void RTC_WKUP_IRQHandler(void) { HAL_RTCEx_WakeUpTimerIRQHandler(hrtc); // 清理 EXTI line 20 的 pending 位 if (EXTI-PR1 EXTI_PR1_PR20) { EXTI-PR1 EXTI_PR1_PR20; } }顺序上先让 HAL 处理 RTC 标志再清 EXTI如果顺序反了某些库版本会再次触发中断。5.4 调试口对低功耗测量的影响这个不算坑但值得单独提。在开发阶段调试器连着的时候JTAG/SWD 引脚内部上拉会带来明显漏电流

相关新闻