前阵子做一个便携数据记录仪,结构很简单:MCU 采集传感器、存数据、定期唤醒上报,核心要求是断电后 RTC 得继续走,主电源切掉以后全靠一颗 CR2032 纽扣电池供电。当时以为这种经典方案最多半天就能搞定,结果从硬件到软件来回折腾了两天,踩了晶振不起振、复位清时间、待机电流居高不下好几个坑,最后全部排查完,实测备份电流稳定到了 1µA 级别。这篇文章不聊虚的,直接把我的完整方案、电路选型、参数计算、固件关键代码和排查经验都放出来,给正在做同类低功耗、RTC 备电项目,或者第一次碰 STM32 外部 32.768kHz 晶振和备份域供电的朋友做参考。
1. 项目概述与整体方案设计
1.1 需求分析:什么场景真的需要纽扣电池 RTC
先说清楚应用场景。市面上绝大多数电子设备在主电源存在时,RTC 直接由系统电源供电,时间走不走完全看电源。但有一类设备,比如数据记录仪、智能电表、冷链运输标签、电动工具控制板,它们要么需要长时间无人值守,要么主电源可能被物理断开(比如用户关掉总闸、拔出电池),此时设备的时间基准不能丢,否则恢复供电后数据时间戳全乱套。
另一类场景是超低功耗系统,主系统大部分时间处于休眠或者完全断电状态,整机只保留 RTC + 待机唤醒电路,让系统可以在指定时间点或周期醒来干活。这时候纽扣电池不是"后备电源"而是"唯一的供电来源",它要把 RTC 的电流扛几个月甚至几年,对整体功耗的约束就非常严格。
所以这个项目的核心需求可以拆成四条:主电上电系统正常工作、主电断电后 RTC 继续计时、纽扣电池供电电流尽量低、恢复主电后 RTC 时间不能丢也不能被程序误覆盖。搞清楚需求之后,硬件方案选型就不会跑偏。
1.2 供电架构:主电源、VBAT 与纽扣电池的关系
STM32 内部有个备份域(Backup Domain),包含 RTC、LSE 外部低速晶振、备份寄存器和部分唤醒逻辑。这个备份域可以由主电源 VDD 供电,也可以由 VBAT 引脚单独供电。芯片内部有一组电源切换电路,VDD 存在时由 VDD 给备份域供电;VDD 跌到某个阈值以下,内部自动切换成 VBAT 供电,不用软件干预。
很多人第一次做这个方案时,习惯性地给 VBAT 串二极管、加隔离 MOS、甚至自己搞一个电源自动切换电路,结果反而引入了额外压降和漏电流。实测下来,除非你有特殊应用(比如电池既要给 RTC 又要给外部 RAM),否则直接让 VBAT 接纽扣电池正极,电池负极接 GND,就是最简单可靠的方案。
1.3 芯片选型:为什么我更推荐 STM32L 系列
如果项目还没定死芯片,做低功耗 RTC 备电优先选 STM32L 系列,比如 STM32L151、STM32L431、STM32L496 这种。原因很直接:F1 系列的备份域电流大概 2~3µA,L 系列能做到 0.6µA 左右,差别非常明显。用 220mAh 的 CR2032 来算,2µA 和 0.6µA 对应的理论寿命分别是 12 年左右和 40 年,虽然都够用,但你要考虑电池自放电、温度、电压窗口这些现实因素,余量越大越稳。
如果你们公司物料只有 F103,也能做,F103 的备份域电流并不是高到不能接受,但软件上要多处理备份域写保护、BKP 寄存器这些细节。所以我的建议是:新项目用 L4 系列或 GD32E 系列的低功耗型号,老项目沿用 F1 也没问题,具体可以通过实测来验证是否满足需求。
2. 硬件电路设计:纽扣电池供电的坑与对策
2.1 VBAT 引脚的正确接法
VBAT 引脚看起来简单,实际上有不少细节。STM32 数据手册的要求是:VBAT 接 1.8V 到 3.6V 的电池,典型用 3V 纽扣电池。如果不用外部电池,VBAT 必须直接接到 VDD,不允许悬空,这一点新手特别容易漏。
实际项目里我在 VBAT 和 GND 之间加了一个 100nF 陶瓷电容,放在电池座附近,用来滤除电源线引入的高频噪声。注意这里不建议加大电解电容,因为纽扣电池供电时每个微安都要精打细算,大电容自身的漏电流在这个场景下不可忽略,100nF 已经足够稳压和滤波。
还有一点:VBAT 引脚走线要尽量短,直接靠近电池座正极,不要在中间打太多过孔。STM32 在 VDD 和 VBAT 之间有电源切换电路,如果走线太长,等效电感会增大,在 VDD 快速掉电时可能影响切换速度,极端情况下会有短暂的时间校准丢失风险。
2.2 电源切换方案对比:二极管、MOS 管还是直接接
不少网上参考设计会在 VBAT 前面串二极管,理由是想防倒灌。但 STM32 内部已经有切换电路,VBAT 引脚本身具备隔离功能,VDD 存在时电池不会被反向充电。这里必须强调:不能用那种把主电源通过二极管和电池并联给 VBAT 的方案,因为 CR2032 不可充电,如果主电源电压高于电池电压,会持续给纽扣电池灌电流,时间长了电池鼓包、漏液都是可能的。
如果你坚持在外围加切换电路,一般有两种做法:
第一种是 P 沟道 MOS 管做电源路径管理,主电存在时 MOS 关闭、电池通路断开,主电掉电后 MOS 自动导通。问题是这个电路待机时 MOS 自身的 G-S 泄漏电流和分压电阻电流通常在微安级别,比 STM32 备份域还耗电,反而把低功耗优势吃掉了。
第二种是用专业的电源多路复用器,比如 TPS2121、LTC4415,这类芯片静态电流很低,切换速度也很快,但成本高、封装复杂,小项目完全没有必要。我做这类低功耗备电时始终用最朴素的方案:VBAT 直接接电池,不额外增加任何有源器件。
2.3 32.768kHz 晶振电路设计与匹配计算
RTC 要准确计时,外部 32.768kHz 晶振是标配。这里有个热词叫"stm32 内部 32khz 做 rtc",确实 STM32 内部有 LSI 低速时钟,约 32kHz,但 LSI 的精度通常在 ±5% 左右,而且受温度影响大,做唤醒定时器可以,做 RTC 日历的话一天能差几十秒,完全不可接受。因此低功耗 RTC 项目必须使用外部 LSE 晶振。
晶振电路的设计核心是两个匹配电容。以我常用的 32.768kHz、负载电容 CL=6pF 的贴片晶振举例,PCB 上晶振焊盘和 STM32 引脚之间还有杂散电容 Cstray,经验值大概 2~4pF。匹配电容的计算公式是:
CL = (C1 × C2) / (C1 + C2) + Cstray
如果两个电容取相同容值 CX,则 CL = CX / 2 + Cstray。反过来算匹配电容:
CX = (CL - Cstray) × 2
代入 CL=6pF,Cstray=3pF,得到 CX = 6pF。实际 E24 系列贴片电容没有 6pF 整标称值,可以用 6.8pF,效果完全没问题。如果晶振规格是 CL=12.5pF,则 CX = (12.5 - 3) × 2 = 19pF,用 18pF 或 20pF 都可以。
晶振布局上,LSE 晶振必须靠近 STM32 芯片的 OSC32_IN 和 OSC32_OUT 引脚,两个匹配电容分别从对应引脚就近接 GND,晶振正下方不要走数字信号线和电源线。我见过不少板子芯片已经走出很远的走线才接电容,这种布局起振困难,甚至需要把示波器探头搭上才勉强能起振。
2.4 电池座、ESD 保护与防反接设计
纽扣电池座的选型主要是材质和稳压结构。CR2032 电池座有两种常见类型:贴片卧式弹片式和贴片直脚式。我项目里用的是带绝缘环的卧式贴片座,这个绝缘环能防止电池正面负极和弹片之外的部分意外短路。电池座的 GND 弹片应直接连到 PCB 铺铜地,正极弹片用较宽的走线引出到 VBAT。
ESD 保护方面,电池座暴露在外壳按键开孔附近时,人体静电可能通过电池触点直接灌进 VBAT。由于 VBAT 直连芯片备份域,过压或 ESD 容易损坏备份域电路。建议在 VBAT 对地并联一颗 5V 或 6V 的 TVS 管,注意选择超低漏电流型号,比如 SD05、ESDA5V3,它们的泄漏电流通常在 1µA 以下,相对整个备份电流还可以接受。如果是全封闭结构没有外露触点,TVS 也可以省略,但加一颗更稳妥。
防反接这个功能主要靠电池座的结构保证。市面上正规的 CR2032 座都是负极朝下、正极朝上,弹片位置决定了电池装反不会形成回路。如果你用的电池座没有明显的正负极防呆槽,那就要在结构上增加筋位或者选用带极性标识的型号,这一点做结构评审时最好专门确认。
3. 纽扣电池选型与功耗预算
3.1 CR2032 特性与自放电
CR2032 是锂-二氧化锰电池,标称电压 3V,开路电压在全新时约 3.2~3.3V,标称容量在 200~240mAh 之间,具体看品牌和批次。它最突出的优势是自放电小,常温下每年自放电约 1% 左右,官方文档里存储寿命通常标 5~10 年。
但它也有短板:内阻不算小,尤其是大电流脉冲能力弱。如果你的系统在电池供电阶段有瞬间电流(比如 RF 发射脉冲)或者唤醒瞬间有大电流,会导致电池电压瞬间跌落,严重时低于 STM32 VBAT 的最低工作电压,RTC 会出错。所以纽扣电池比较适合纯 RTC 备电这种微安级电流场景,不适合带射频发射这种毫安级以上脉冲负载。
如果系统确实需要在电池供电阶段做无线发射,更合理的方案是纽扣电池 + 超低功耗 DC/DC 升压,或者干脆换成锂亚电池(比如 ER14250)配合超级电容。这里只讨论纯 RTC 备电的场景。
3.2 系统功耗项一览
做功耗预算前,先要把系统在电池供电阶段的全部耗电路径列出来。以我的 STM32L431 项目为例,主电断开后,仍然通电的模块包括:
- STM32 备份域:RTC 日历 + LSE 振荡器 + 备份寄存器,典型 0.65µA,加上备份 RTC 所需电压稳压器后约 1µA 左右
- VBAT 滤波电容漏电流:100nF 陶瓷电容几乎可以忽略,但如果用了 1µF/10µF 的高容值电容,高温下泄漏会增加
- TVS 漏电流:低漏电流 TVS 大约 0.1~0.5µA
- 电池座接触电阻损耗:一般引脚接触良好时是微欧级,基本可忽略
把这些加总,理想情况下电池供电电流在 1.5µA 以内,这个数字决定了电池寿命的上限。
3.3 电池寿命估算示例
寿命估算公式非常简单:
理论寿命(小时) = 电池容量(mAh) / 系统平均电流(µA)
假设系统平均电流为 1.5µA,CR2032 容量 220mAh:
220mAh / 1.5µA ≈ 146666 小时 ≈ 16.7 年
这个数字看起来非常可观,但实际寿命要打折。原因有三个:电池自放电大约每年消耗 1%~2%;电池电压会随着放电逐渐从 3.3V 降到 2.0V 左右,但 STM32 L4 的 VBAT 最低工作电压约 1.2V,电压窗口其实足够;温度升高自放电加快,60°C 环境下自放电率远高于常温。
考虑到这些因素,实际工程中我一般把理论寿命乘以 0.6 左右作为参考值。也就是说,平均电流 1.5µA 的系统,CR2032 保守估计能扛 8~10 年。如果你的设计寿命要求超过 10 年,建议要么把平均电流压到 1µA 以下,要么换更大容量的 CR2477 或者锂亚电池。
4. 软件实现:低功耗 RTC 完整代码流程
4.1 低功耗模式选择:Stop 还是 Standby
STM32 低功耗模式选错了,电流直接差一个数量级。先看 L 系列:带 RTC 的情况下,STOP2 模式系统电流可以到 2~3µA,SRAM 内容保留,唤醒时间约几微秒,非常适合"醒来干活继续睡"的周期性任务。SHUTDOWN 模式是电流最低的模式,但唤醒相当于复位,而且它不能保留 SRAM 内容,如果你的唤醒条件不够独立,不太实用。
F1 系列则只有 STOP 模式和 STANDBY 模式。STOP 模式保留 SRAM,电流大概几十微安到一百多微安,在低功耗项目里反而显得有点鸡肋;STANDBY 模式电流可以到 3.4µA 左右(带 RTC),但唤醒后程序重新从头执行,需要靠备份寄存器判断唤醒来源。
我的做法是:如果芯片支持 STOP2 且唤醒后需要恢复复杂状态,就用 STOP2;如果整机要求极低功耗、唤醒后只需要干一件简单事情,就用 STANDBY。这个项目里我用 STOP2 + RTC WakeUpTimer 周期性唤醒,做完整数据采集后重新入睡。
4.2 RTC 初始化:外部 LSE 时钟配置
RTC 初始化代码在不同芯片上有差异,但整体思路一致:启动 LSE,等待 LSE 稳定,配置 RTC 时钟源,设置分频系数,使能 RTC。以 STM32L4 的 HAL 库为例,核心配置如下:
void MX_RTC_Init(void) { RTC_HandleTypeDef hrtc; /* 使能PWR时钟,并解除RTC写保护 */ __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkupAccess(); /* 如果RTC还没配置过,才执行完整初始化 */ if ((RCC->BDCR & RCC_BDCR_RTCEN) != RCC_BDCR_RTCEN) { /* 复位备份域 */ __HAL_RCC_BACKUPRESET_FORCE(); __HAL_RCC_BACKUPRESET_RELEASE(); /* 使能LSE并等待就绪 */ __HAL_RCC_LSE_CONFIG(RCC_LSE_ON); uint32_t timeout = 1000000; while ((__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET) && (timeout > 0)) { timeout--; } if (timeout == 0) { /* LSE起振失败,需要进入错误处理 */ } /* 选择LSE作为RTC时钟源并使能RTC */ __HAL_RCC_RTC_CONFIG(RCC_RTCCLKSOURCE_LSE); __HAL_RCC_RTC_ENABLE(); /* RTC相关配置 */ hrtc.Instance = RTC; hrtc.Init.HourFormat = RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv = 127; /* 时钟源32768Hz,异步分频128,得到256Hz */ hrtc.Init.SynchPrediv = 255; /* 同步分频256,得到1Hz */ hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; HAL_RTC_Init(&hrtc); /* 设置初始时间 */ RTC_TimeTypeDef sTime = {0}; RTC_DateTypeDef sDate = {0}; sTime.Hours = 0x0; sTime.Minutes = 0x0; sTime.Seconds = 0x0; sTime.TimeFormat = RTC_HOURFORMAT24; sDate.Date = 0x1; sDate.Month = RTC_MONTH_JANUARY; sDate.Year = 0x24; sDate.WeekDay = RTC_WEEKDAY_MONDAY; HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD); HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD); } }有几个地方特别容易出错。第一,LSE 起振需要时间,通常几百毫秒到一秒,所以必须做超时处理,不能死等。第二,如果此前已经配置过 RTC(第二次软复位进入),RTCEN 位已经置位,直接重新执行 HAL_RTC_Init,部分库实现会重置 RTC 计数器,导致时间变成初始值,所以一定要判断 RTCEN 状态,已经运行的就跳过初始化。第三个坑是 HAL_PWR_EnableBkupAccess 之后,写 BDCR 寄存器前建议先加一条 DMB 指令或者空的读操作,防止编译器乱序优化导致写时序异常。
4.3 进入低功耗与定时唤醒
在 STOP2 模式下,RTC WakeUpTimer 可以配置为定期触发唤醒中断。唤醒定时器的时基通常选择 RTC_WAKEUPCLOCK_CK_SPRE_16BITS,也就是由 1Hz 的同步预分频输出提供时钟,计数器是 16 位,最长能睡 65535 秒(约 18 小时),不够的话可以嵌套多层唤醒逻辑。
进入 STOP2 的代码大致如下:
void EnterLowPowerAndWaitWakeUp(uint32_t seconds) { /* 设置RTC唤醒定时器 */ HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, seconds, RTC_WAKEUPCLOCK_CK_SPRE_16BITS); /* 设置唤醒后使用的时钟源 */ HAL_RCCEx_SetWakeUpSource(RCC_WAKEUPSOURCE_HSI); /* 关闭调试器连接时的掉电检查,避免调试状态影响低功耗 */ HAL_DBGMCU_DisableDBGStandbyMode(); /* 停止系统嘀嗒定时器,防止SysTick中断唤醒系统 */ HAL_SuspendTick(); /* 进入STOP2模式 */ HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); /* --- 唤醒后才执行 --- */ /* 重新配置系统时钟 */ SystemClock_Config(); HAL_ResumeTick(); }这里有一个很隐蔽的问题:从 STOP2 唤醒后,芯片的 Flash、GPIO 配置和系统时钟可能处于恢复状态,如果在唤醒严重依赖系统时钟的代码(比如串口输出、ADC采样)之前没有重新调用 SystemClock_Config,实际工作时钟可能还是低速的 MSI,导致外设时序错乱。我调试低功耗时遇到过一次唤醒后 UART 波特率异常,排查完就是这个原因。
4.4 备份域寄存器与掉电标志
RTC 时间掉电不丢,除了硬件接好 VBAT 之外,软件还要会用备份域寄存器保存系统状态标志。STM32L4 的备份寄存器有 32 个,每个 32 位,F1 系列也有 20 个 16 位 BKP 寄存器。它们是备份域的一部分,VDD 掉电后只要 VBAT 还在,数据就不会丢。
我在这个项目里用两个备份寄存器存状态:一个存"RTC 是否已经初始化过",另一个存"上次断电时系统的运行阶段"。这样恢复供电后,程序可以从复位向量开始查这些标志,判断是需要完整初始化还是直接恢复运行。
// 写入标志 HAL_PWR_EnableBkupAccess(); HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0x12345678); uint32_t flag = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);还有一个容易忽略的细节:如果从 STANDBY 模式唤醒,整颗芯片相当于经历了一次上电复位,但备份域没有复位。所以程序开头必须检查复位标志寄存器中的 STANDBY 标志位,判断当前是冷上电还是 Standby 唤醒。如果是后者,跳过 RTC 初始化和时间写入,直接进入正常运行流程;如果是前者,再走完整初始化。F1 的写法是检查 PWR->CSR 的 SBF 位,L4 是检查 RCC->RSR 里的相关标志,HAL 库封装成了 __HAL_PWR_GET_FLAG(PWR_FLAG_SB)。
5. 实测功耗数据与优化记录
5.1 实测方法:万用表怎么测才能测得准
测微安级电流,最忌讳直接拿普通万用表电流档往电池回路上一串就开测。常见台式万用表的最小电流档分辨率可以到 0.01µA,但量程内阻压降不可忽略,实际会影响被测电路工作状态。更常用的办法是使用万用表电压档配合精密采样电阻(比如 100Ω 或 1kΩ 的 0.1% 电阻)串联测量压降,再换算电流。如果手头有皮安级电流计或者源表,那就更省事。
我用的是 Keysight 34461A 万用表的 µA 档直接串联测试,同时注意一个坑:万用表切换量程时会有短暂开路,如果此时电池电压跌落,RTC 时间可能被干扰。所以我都是先用粗短跳线并联在万用表两端,等系统稳定后再断开跳线读取电流,测试完先恢复跳线再拆表笔。
5.2 各模式电流对比
以 STM32L431 + LSE + RTC 为例,实测数据大概这样:
| 模式/条件 | 实测电流 | 说明 |
|---|---|---|
| VDD 断开,VBAT 仅供电备份域(RTC+LSE) | 0.82µA | 25°C,CR2032 实测供电 |
| 整机 STOP2 + RTC WakeUpTimer | 3.1µA | 包含 LDO 静态电流和外设泄漏 |
| 整机 STANDBY + RTC WakeUpTimer | 2.6µA | L4 标准模式 |
| F103 VBAT 备份域(RTC+LSE) | 2.9µA | 对比用,F1 系列明显偏高 |
注意这些数字和环境温度强相关,说明手册里给的典型值通常在 25°C 下,温度到 40°C 以上电流会翻倍甚至更高。如果你的产品要在高温环境长期运行,做功耗预算时一定要按最高工作温度下的实测值来算。
5.3 影响功耗的隐藏细节
功耗优化做到 3µA 以下后,很多平时忽略的因素开始起决定性作用。我实际排查过程中发现过几个非常隐蔽的漏电路径:
第一个是 GPIO 配置。所有不用的引脚,如果默认是浮空输入,外部噪声会通过引脚内部保护二极管产生亚阈值漏电。解决方法是把未使用引脚全部配置为模拟模式或高阻输出,并且输出低电平,不要让引脚悬空。HAL 库建议在 GPIO_Init 里把每个引脚都显式配置。
第二个是外部下拉/上拉电阻。如果我为了霍尔传感器、按键输入等加了大阻值电阻,掉电后这些电阻如果直接从 VBAT 取电,就会形成漏电路径。这种电阻要从主电源 VDD 侧供电,不能挂到 VBAT 上,否则纽扣电池要额外给它们供电。
第三个是调试接口。如果 ST-Link 或者 J-Link 还连着,芯片默认会进入调试状态,部分低功耗模式会被阻止或者电流增大。这个在现场调试时最容易让人误判"为什么我电流降到不低"。所以正式测试前要断开调试器,或者把 SWDIO/SWCLK 引脚在代码里配置为普通 GPIO 甚至禁用。
6. 常见问题与排查技巧实录
6.1 LSE 晶振不起振怎么办
晶振不起振是最常见的问题,现象是 RTC 时间不走,或者程序卡在等待 LSERDY 超时循环里。排查思路按概率从高到低排列:
第一看匹配电容有没有焊错位置。有不少原理图把两个匹配电容设计成串联在晶振两个引脚之间,但实际上是两个电容分别从 OSC_IN、OSC_OUT 到 GND,焊错位置根本没有谐振回路,自然无法起振。
第二看晶振封装型号和负载电容是否匹配。32.768kHz 晶振有圆柱封装 2×6、贴片 3215、2012 等不同规格,负载电容从 4pF 到 12.5pF 都有。如果原理图按 6pF 匹配电容设计,实际焊了 12.5pF 负载电容的晶振,起振困难或者频率偏差过大。这里最好核对 BOM 物料规格和晶振丝印。
第三看 PCB 布局。LSE 引脚附近不能有高频数字线,特别是 I2C、SPI 这种边沿很陡的信号。我在一个项目里发现 LSE 晶振下方经过了一条 SPI_SCK,结果 SPI 通信时 RTC 秒跳变异常,移开走线后恢复正常。
如果以上都排查完还不振,可以试试用示波器探头的 1 倍衰减档直接夹在 OSC_OUT 上测量,有时候探头的电容能把晶振"拉起来"看到波形,这其实反过来说明电路余量不足。更专业的做法是把 OSC_OUT 映射到 MCO 引脚上输出 32.768kHz 观测,但注意 MCO 会增加额外负载,测试完要关掉。
6.2 掉电后再上电时间被清零
时间清零通常分两种情况。第一种是 VBAT 根本没接好,比如电池座虚焊、接触不良、纽扣电池没装到位、VBAT 引脚空焊。这种情况断电后备份域供电缺失,RTC 直接丢失时间。排查办法就是断电后测 VBAT 引脚对地电压,应该接近 3V;如果测量值是 0V,就是硬件连接问题。
第二种是软件问题:程序在每次上电复位后都认为"需要初始化 RTC",无条件写了一次当前时间,导致备份域里原来的时间被覆盖。这种情况的坑在于电表观察 RTC 是有数值的,但数值不是断电前的真实时间,而是代码里写的固定初始值。解决办法是前面提到的 RTCEN 判断,或者是利用备份寄存器标志位判断"首次使用才初始化"。
我见过一个更隐蔽的变种:程序里 HAL_RTC_Init 每次都执行,但 HAL 库内部会先关 RTC 再配置,即使不写新时间,RTC 计数器也被重置了。所以那些"看起来没动时间"的代码,只要它每次初始化都完整走一遍 RTC_Init 流程,就等于把时间清零。正确做法是把 RTC 初始化拆分成"时钟使能"和"时间写入"两个阶段,只有备份标志显示从未初始化时才写入时间。
6.3 纽扣电池电压正常但系统反复复位
有一种反直觉的现象:电池电压测量有 2.8V,但系统上电后反复复位或者 STM32 无法从待机唤醒。这种问题大多出在纽扣电池的内阻和瞬时电流能力上。CR2032 在冷启动时如果给一个大电容充电,瞬间电流可能达到几十毫安,电池电压被拉低到复位阈值以下,形成"上电复位-充电-又复位"的循环。
解决方法是在 VBAT 通路串联一个小阻值限流电阻,比如 100Ω 左右,给上电冲击提供缓冲。另一种做法是引入一个缓启动电容,让备份域电压爬升更平缓。但要注意不能加太大电容,否则反而延长电压建立时间。
如果 VDD 侧和 VBAT 侧同时存在,还要检查主电源和电池电源之间是否存在倒灌路径。用万用表二极管档测 VDD 到 VBAT 的正反向导通压降,正常情况下单向截止,如果两个方向都有压降,说明有寄生二极管或者防护器件漏电,需要认真检查原理图。
6.4 用 LSI 做 RTC 造成的时间精度问题
我曾经偷懒过一版:不想为 RTC 增加外部晶振,直接用 LSI 内部 32kHz 做 RTC 时钟源。结果设备放了一晚上,第二天早上时间偏了二十多分钟。LSI 的标称精度就那样,而且它主要用于看门狗、唤醒源或者 CPU 时钟,做 RTC 日历时间基准对大多数应用来说是不可接受的。
如果你的项目对时间精度有要求,同时又想省外部晶振,可以考虑带数字校准功能的高精度实时时钟专用芯片或者带温补的 RTC 模块,但那样电路复杂度和成本就上去了。普通 RTC 备电应用,老老实实用外部 32.768kHz + 匹配电容,精度可以到 ±20ppm,对应每天误差不到 2 秒,配合软件 NTP 校准还能进一步收敛。
7. 一点排查小技巧和我的个人经验
最后分享一个我自己的排查习惯:每次拿到低功耗相关的板子,我第一件事不是看代码,而是用热成像仪看整板温度分布。漏电元件通常会微热,一颗 1µA 漏电流的元件在 3V 供电下功耗只有 3µW,热成像未必看得出来;但如果漏电到了 10µA 以上,局部温度和环境温度会有一个很明显的差异,这时候再配合逐路断开排查就能快速定位问题。
另外,在实际项目里我习惯给整个备电回路的电池电流做一个"基准标定":先用精密源表给 VBAT 加 3.0V 电压,分别记录芯片单独开启 RTC、开启备份寄存器读写、开启 WakeUpTimer 时的电流变化。这样等整板被测出异常电流,能直接对比基准数据,判断是芯片行为异常还是外围电路漏电,省的把时间耗在拆板子猜零件上。这个做法对我的项目帮助很大,建议你也可以试试。