ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32+纽扣电池低功耗RTC供电设计实战与避坑指南

STM32+纽扣电池低功耗RTC供电设计实战与避坑指南 年前帮朋友改一款带日历和定时功能的智能家居控制面板前期各项功能调得很顺利一上整机测试就露馅装机三个月后用户反馈时间经常回到默认值拆开一看纽扣电池电压已经掉到2V以下。问题出在哪儿不是电池选小了也不是RTC读写的逻辑写错了而是整个设计里没有一个人认真核算过VBAT回路的漏电流更没人意识到RTC晶振的负载电容匹配会直接影响起振和整机功耗。今天把手头这个“STM32 纽扣电池低功耗RTC供电”的项目完整复盘一遍把电路选型、软件配置、功耗测试和那些资料里不会写的避坑细节一次性讲清楚。适合正在做低功耗产品、准备用STM32做离线时钟或数据记录设备的工程师参考。1. 项目概述与需求拆解1.1 这个项目到底要解决什么问题RTC实时时钟最核心的需求就一句话主系统断电后时间还得继续走。实际产品里通常有两种做法一种是用带RTC的独立时钟芯片比如DS3231、PCF8563外挂电池和晶振另一种就是直接用MCU内部RTC靠VBAT引脚接纽扣电池来维持计时。STM32系列绝大多数型号都内置了RTC模块和备份域外部只需要一颗32.768kHz晶振、一个纽扣电池座、几个电容电阻就能实现断电走时功能。这个项目的麻烦在于“纽扣电池供电”这几个字。纽扣电池容量本身很小CR2032标称容量也就是220mAh左右但放电特性和普通锂电池完全不一样等效内阻更高不适合持续大电流放电而且不可充电。如果用一颗正品CR2032给RTC单独供电理想状态下待机电流做到几微安理论上能用好几年但实际上很多产品连三个月都撑不过去。这类问题的根源一半在硬件电路泄露电流控制不住另一半在软件没有真正进入低功耗状态或者LSE晶振电路设计不合理导致振荡器一直在临界状态消耗额外电能。还有一个容易被忽略的点STM32的RTC是有备份域的备份域不仅包含RTC计数器还包含20个左右的备份寄存器。这些寄存器在VDD掉电后由VBAT供电保持数据。如果电路设计时VBAT和VDD之间的电源切换逻辑没处理好或者PCB漏电流偏大备份域就会频繁复位表现出来就是时间丢、配置丢、设备反复回到出厂状态。这些问题在实验室里用稳压电源很难发现一旦整机放到真实用户手里问题就全部暴露出来。1.2 方案选型为什么选STM32内部RTC而不是外部时钟芯片做这个项目之前也纠结过要不要用外部RTC芯片。外部芯片的好处是精度容易做高比如DS3231带温度补偿年误差可以控制在几分钟以内而且独立于MCUMCU崩溃或者程序卡死不影响走时。但坏处也明显多一颗料、多一个晶振、多一路电池供电电路、成本上升而且对于大部分消费类产品来说STM32内置RTC的精度已经够用了。STM32内部RTC配合外部32.768kHz晶振正常校准后日误差可以做到1秒以内对于智能家居、数据记录、定时控制这类场景完全够用。关键是STM32的VBAT供电设计很成熟官方参考手册和应用笔记都有明确说明硬件上不需要额外加电平转换或者复杂的电源管理芯片只需要做好电源切换和防漏电就行。再者STM32的备份寄存器在休眠和待机模式下仍然可以保存数据这个特性可以用来存设备状态、校准参数、开机次数等这些功能用外部RTC芯片实现起来会麻烦很多。从整个系统功耗来看如果产品需要周期性唤醒工作MCU内部RTC可以直接参与低功耗唤醒省掉外部RTC芯片和MCU之间的I2C或SPI通信功耗。我最后选了STM32L151C8T6这颗料L系列本身就是针对低功耗设计的Stop模式下RTC可以维持工作配合RTC闹钟和唤醒定时器可以做到系统平时完全睡死只在需要的时候醒来干活。2. 整体电路设计——从备份域原理到器件选型2.1 先搞清楚STM32的备份域供电关系RTC要能在掉电后继续走靠的是STM32内部的备份域隔离设计。备份域包括RTC、备份寄存器、RTC相关的外部引脚PC14/PC15等这部分电路的供电可以来自VDD也可以来自VBAT。当VDD正常时备份域由VDD供电VBAT引脚上如果接了电池电池基本不消耗电流当VDD掉电后芯片内部会切换到VBAT供电RTC和备份寄存器靠着电池继续工作。从这个供电关系能看出一个关键点VBAT上的电池只有在VDD掉电时才真正出力。所以很多产品在调试阶段用稳压电源给板子供电VDD一直存在就发现不了VBAT回路漏电的问题。等到整机拔掉电源、只靠纽扣电池维持时间的时候如果VBAT电路上有任何漏电路径电池就会快速耗尽。设计时一定要单独核算VBAT网络上的静态电流包括电池座漏电、PCB板漏电、二极管反向漏电、MCU的VBAT引脚漏电。还有一个容易忽略的事情备份域和主电源域是有电压检测阈值的。如果VDD下降到低于VBAT一定范围内部电源切换电路才会彻底切到电池如果VDD缓慢下降、反复徘徊在阈值附近备份域可能反复复位。硬件上要避免这个问题通常会在VBAT引脚并联一个小电容同时在切换电路上做一点电压迟滞处理。2.2 电源切换电路二极管、低压差还是电源路径管理最简单的VBAT连接方式就是用一颗二极管把VDD和VBAT隔开再让纽扣电池通过另一颗二极管接到VBAT形成一个“或”逻辑。VDD存在时VDD通过二极管给VBAT供电VDD掉电时电池通过自己的二极管给VBAT供电。这个方案的优点是便宜、简单缺点是两颗二极管都有正向压降而且反向漏电流会直接影响电池寿命。二极管的正向压降问题在于VDD经过二极管后变低了如果系统VDD是3.0V二极管压降0.3VVBAT实际只有2.7V还在STM32 VBAT允许的1.65V~3.6V范围内问题不大。真正麻烦的是反向漏电流。普通整流二极管比如1N4007反向漏电流在微安级以上直接用在这种场景电池几周就没了。低压差肖特基二极管比如BAT54系列漏电流常温下几百nA看起来很小但在追求亚微安级待机的系统里依然太大。我实测过一批BAT54S常温下反向漏电流在200nA左右温度升高后还会成倍上涨这已经比RTC本身的功耗还要高了。实际工程里有两个方向一个是选低漏电流二极管比如BAV199这种双管封装的内部是两颗低漏二极管串联反向漏电流可以做到1nA级别代价是正向压降更大另一个是直接用PMOS管做理想二极管用比较器或专用电源路径管理芯片控制PMOS的通断VDD存在时PMOS导通VDD掉电时PMOS断开几乎不产生压降待机漏电也只有纳安级。对于量产产品我比较推荐后者成本多不了几毛钱但可靠性提升明显。2.3 纽扣电池座和防反接设计纽扣电池座看着不起眼翻车率其实很高。早期打样时用过压接式的CR2032电池座弹片力度不够整机震动后电池接触不良RTC偶尔掉时间。后来换成了带定位柱的焊脚式电池座接触明显稳定。另一个教训是电池座正负极焊盘间距如果设计得太近加上助焊剂残留两个焊盘之间的PCB表面会产生漏电路径静态功耗直接多出好几微安。选型时尽量选底部带绝缘槽的电池座焊盘之间距离越大越好。防反接这个问题容易被忽略因为CR2032电池做得比较厚正负极极性明显而且很多电池座本身有机械防反结构。但如果产品结构空间受限、必须使用电池弹片或者导线连接电池仓时一定要在PCB上预留防反接二极管或者至少用TVS管做反向电压保护。STM32的VBAT引脚对反向电压很敏感一旦电池装反轻则烧毁VBAT引脚重则直接损坏芯片内部备份域电路。比较稳妥的做法是在电池正极串联一颗小阻值电阻限制反接时的冲击电流再在VBAT对地并联一颗反向TVS管。抗冲击和接触可靠性也要考虑。纽扣电池在低温环境下内阻会变大如果RTC闹钟唤醒瞬间需要从电池吸取较大电流电池电压会瞬时跌落可能导致系统误复位。设计时VBAT引脚旁边一定要放一个1uF左右的陶瓷电容而且要靠近MCU引脚放置这个电容一方面提供唤醒瞬间的瞬态电流另一方面也起到去耦作用。2.4 32.768kHz晶振电路负载电容匹配是重中之重纽扣电池RTC的功耗其实不高真正影响走时和功耗的往往是晶振电路。STM32的RTC可以使用外部32.768kHz晶振LSE也可以使用内部低速RC振荡器LSI。内部LSI精度太差常温下误差百分之几一天能差出好几分钟只能用来做唤醒时钟不能作为RTC日历时钟源。所以要做掉电走时间外部32.768kHz晶振几乎是必须的。晶振电路最常见的问题是不起振或者起振后功耗偏大。不起振的原因多数出在负载电容匹配上。32.768kHz晶振的负载电容CL常见规格有6pF、7pF、9pF、12.5pF而MCU的LSE引脚本身有寄生电容PCB走线也有寄生电容实际匹配电容要根据CL和寄生电容反推。以12.5pF负载晶振为例如果引脚寄生电容和PCB分布电容合计约3pF~5pF那么两个外部电容通常选6.8pF~7.5pF左右公式大概就是CL ≈ (Cg * Cd) / (Cg Cd) Cs其中Cg和Cd是两个外部电容Cs是寄生电容。这里有个常见误区很多设计人员为了“保险”把外部电容从6.8pF换成10pF甚至12pF结果晶振负性阻抗变小起振困难或者振荡器工作在有源区边缘反过来拉高功耗。我踩过这个坑之后总结的经验是外部匹配电容宁小勿大如果晶振数据手册给了一个范围优先取中间偏小的值。另外晶振旁边要留GND包地避免附近信号线交叉特别是不能有高频信号从晶振下面穿过。2.5 去耦电容和PCB布局的隐藏坑很多人画板子只关注MCU电源脚的去耦电容忽略了VBAT引脚。VBAT上的去耦电容非常关键这颗电容直接决定了掉电瞬间备份域的供电质量。我建议在VBAT引脚放两个电容一个4.7uF的钽电容或者X5R陶瓷电容用于电池供电时的大容量储能一个0.1uF的陶瓷电容放在最靠近VBAT引脚的位置用于滤除高频噪声。陶瓷电容选型时要注意耐压和温漂一般选X5R或X7R介质避免用Y5V这种温度特性差的电容低温下容量会掉得很难看。PCB布局上还有几个隐蔽问题第一RTC晶振的走线要尽量短最好放在MCU晶振引脚的同一侧不要打过孔不要绕远路第二VBAT网络的走线要和VDD或者其他电源走线保持距离避免回流路径上的压降影响掉电切换判断第三PCB板厂加工完成后的清洗工艺要到位助焊剂残留是导致微安级漏电的头号杀手。我见过一块板子贴上电池后静态电流有7uA怎么查都查不出来最后用洗板水仔细刷了一遍电流直接降到2uA以下。3. 软件实现RTC配置与低功耗模式切换3.1 使用STM32CubeMX配置RTC和LSE代码层面第一步是把RTC时钟源和日历配置好。建议直接用STM32CubeMX生成初始化代码省去手动操作备份域寄存器的麻烦。CubeMX里要做的几个关键配置如下RTC时钟源选择LSE开启RTC闹钟或唤醒定时器配置异步预分频和同步预分频让RTC计数频率变成1Hz。异步预分频和同步预分频的组合很灵活常见做法是异步预分频设为127或128同步预分频设为255或256两者配合让RTCCLK分频后正好秒跳一次。还要注意开启RTC中断并且把对应的NVIC中断优先级设置好。如果使用了RTC闹钟唤醒要选择闹钟A或闹钟B配置闹钟匹配的日期和时间掩码。比如每天定时唤醒就配置小时、分钟、秒的掩码匹配如果只是设定一个固定延时后唤醒可以直接配置为秒更新中断或者指定时间点匹配。3.2 核心代码框架初始化RTC并写入时间CubeMX生成的HAL代码已经足够完整但有几个地方需要手工补一下细节。RTC初始化时备份域访问是必须开启的否则写寄存器不生效。示例代码如下void MX_RTC_Init(void) { RTC_TimeTypeDef sTime {0}; RTC_DateTypeDef sDate {0}; // 使能电源时钟并允许访问备份域 __HAL_RCC_PWR_CLK_ENABLE(); HAL_PWR_EnableBkUpAccess(); // LSE作为RTC时钟源 RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_LSE; RCC_OscInitStruct.LSEState RCC_LSE_ON; if (HAL_RCC_OscConfig(RCC_OscInitStruct) ! HAL_OK) { Error_Handler(); } RCC_PeriphCLKInitTypeDef PeriphClkInitStruct {0}; PeriphClkInitStruct.PeriphClockSelection RCC_PERIPHCLK_RTC; PeriphClkInitStruct.RTCClockSelection RCC_RTCCLKSOURCE_LSE; if (HAL_RCCEx_PeriphCLKConfig(PeriphClkInitStruct) ! HAL_OK) { Error_Handler(); } // 使能RTC时钟 __HAL_RCC_RTC_ENABLE(); hrtc.Instance RTC; hrtc.Init.HourFormat RTC_HOURFORMAT_24HOUR; hrtc.Init.AsynchPrediv 127; hrtc.Init.SynchPrediv 255; hrtc.Init.OutPut RTC_OUTPUT_DISABLE; hrtc.Init.OutPutPolarity RTC_OUTPUT_POLARITY_HIGH; hrtc.Init.OutPutType RTC_OUTPUT_TYPE_OPENDRAIN; if (HAL_RTC_Init(hrtc) ! HAL_OK) { Error_Handler(); } }写日历和时间的时候直接调用HAL_RTC_SetTime和HAL_RTC_SetDate即可但要注意设置之前把时间设置成手动更新模式避免RTC在写的瞬间还在走字导致写进去的秒数据不连续。HAL库内部已经处理了这个问题一般不需要额外操心。3.3 低功耗模式选择Stop还是StandbySTM32的低功耗模式有好几种Sleep、Stop、Standby。Sleep模式只关CPU时钟外设还在跑电流比较大不适合电池场景。Stop模式关闭大部分时钟SRAM和寄存器内容保持RTC可以继续工作并产生唤醒事件典型功耗在几微安到几十微安之间取决于型号。Standby模式更彻底除了备份域和少量唤醒逻辑整个芯片几乎全断电功耗最低可以做到1uA以下但唤醒后系统相当于复位程序从头开始跑SRAM内容丢失。如果产品需要在唤醒后接着之前的任务继续做只能选Stop模式并且要自己管理好恢复流程。如果唤醒后本来就是全新的一个周期比如定时采集传感器、上报一次数据再继续睡建议用Standby模式代码简单漏电又低。我手上的项目因为是数据记录设备每次唤醒后要读上次断电前存在备份寄存器里的编号再用RTC时间补一条日志所以用的是Stop模式加RTC闹钟唤醒。进Stop模式的代码非常简单void Enter_Stop_Mode(void) { HAL_RTC_SetAlarm_IT(hrtc, sAlarm, RTC_ALARM_A); __HAL_RTC_ALARM_ENABLE(hrtc, RTC_ALARM_A); HAL_SuspendTick(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); HAL_ResumeTick(); // 唤醒后重新配置系统时钟 SystemClock_Config(); }需要注意进入Stop模式前要把调试接口处理好否则调试器的功耗会叠加进去。量产固件里不需要调试口可以考虑在初始化后关闭调试器的时钟或者通过选项字节禁止调试口。还有一个细节进入Stop模式前如果串口或者其他外设还开着即使进入WFI状态外设时钟没有关闭的话照样耗电所以最好把不用的外设全部DeInit。3.4 备份寄存器的利用技巧STM32备份域里除了RTC时间还有一组备份寄存器这个区域在VDD掉电后由VBAT电池维持。很多项目里把它当成简易非易失存储用存储设备序列号、校准参数、报警标志位甚至开机次数。备份寄存器的读写需要先开启备份域访问权限然后直接通过地址访问HAL库提供了对应的函数。我习惯把RTC校准值、上次关机原因、设备累积开机次数都存在这个区域省了一颗外置EEPROM也省了掉电后恢复状态的麻烦。这里有一个使用上的注意事项备份寄存器在系统复位比如看门狗复位时数据不清除但在VDD和VBAT同时掉电时会丢失。如果需要关键数据在异常掉电时不丢除了保证VBAT供电还要在软件里做数据校验比如写一个固定魔数加CRC校验上电时先检查数据合法性不合法就恢复默认值防止电池没电时读出乱码导致设备状态异常。4. 功耗实测与电池寿命估算4.1 低功耗测量工具和方法别被万用表带偏调低功耗最怕测不准。大部分万用表的电流档都有内阻尤其uA档内阻可能到几百欧姆串联到电池回路里会产生压降导致MCU供电电压降低芯片电压检测误判甚至系统还没进入低功耗就被破坏。更麻烦的是系统在工作状态和休眠状态之间电流差异巨大可能从几毫安瞬间跳到几微安普通万用表根本反映不出动态变化。我常用的方法有两种。一种是用示波器加电流探头可以观察动态电流波形但电流探头通常精度有限测uA级电流不够准。另一种更实用在电池和负载之间串联一个精密采样电阻比如10Ω再用示波器或者数据采集器测量采样电阻两端的电压根据欧姆定律算电流。10Ω电阻在1uA电流下只有10uV压降用普通示波器测不出来需要高分辨率仪器或者高倍率放大器所以实际测试时我一般先用1kΩ采样电阻做粗测确认大概量级后再切换到高精度方案。还有一种粗测办法是测纽扣电池空载电压和带载电压的差值再结合电池内阻估算电流这种方法精度差但适合在没有精密仪器的现场快速判断板子是否异常漏电。我自己调试时习惯在电池正极和VBAT引脚之间串一个跳线帽方便随时断开电池、插入电流表这个设计非常实用。4.2 寿命估算CR2032到底能用多久电池寿命的计算没有想象中那么复杂核心公式就是电池容量除以平均电流。CR2032标称容量通常按220mAh算但实际可用容量跟放电电流、温度、截止电压都有关系而且纽扣电池本身有自放电每年大约1%到3%的容量损失。假设系统待机平均电流为2uA理论寿命是220mAh / 0.002mA 110000小时大约12.5年加上自放电实际用个8到10年问题不大。如果待机平均电流涨到10uA理论寿命瞬间降到2.5年再加上高低温环境影响实际可能一年多就趴窝。这里要注意“平均电流”和“峰值电流”的区别。很多设备不是一直2uA待机而是每隔一段时间醒来一次醒来的瞬间电流可能是几十毫安持续几毫秒。用公式算平均电流时要把工作状态电流和持续时间都折算进去。举个例子系统每小时醒来一次每次工作50ms、电流20mA待机电流2uA那么一个小时的平均电流大约是20mA * 0.05s / 3600s 2uA约等于0.278uA 2uA也就是2.278uA左右。虽然唤醒电流不小但占空比极低对总平均电流贡献不大。4.3 实测下来最耗电的地方这个项目整机实测下来静态电流做了好几轮优化才从最初的8uA降到2uA左右。第一版电路里最耗电的竟然是反接保护二极管的选择问题一开始用了普通肖特基二极管反向漏电流在3.3V下实测就有3uA多直接让电池寿命拦腰斩。换成低漏电方案后电流直接下降好几uA。第二版里发现PCB上LSE晶振两个引脚之间有轻微漏电清洗板子后电流又降了一截。第三版发现是MCU的一个GPIO悬空导致内部上拉电阻一直消耗电流在CubeMX里把未使用的引脚统一配置为模拟输入后电流才终于稳到2uA以内。从这些经历总结来看真正的“漏电大户”依次是外部保护器件反向漏电、PCB板面污染漏电、GPIO悬空导致内部上下拉电阻耗电、LSE振荡器功耗异常、以及电池座本身接触不良造成的不稳定损耗。测功耗时一定要把每一路都切出来单独排查否则很难定位。5. 常见问题排查与避坑总结5.1 RTC常见故障速查表实际项目调试和后续使用中我把遇到的典型问题和排查思路整理成一个速查表方便对应到现象快速定位。故障现象可能原因排查方法断电后时间丢失VBAT供电没有接好或者VBAT电压过低万用表测VBAT电压确认电池座接触正常检查备份域初始化是否有失败时间误差明显LSE晶振负载电容不匹配或晶振本身ESR偏高用示波器测LSE振荡波形确认幅度和频率调整负载电容换低ESR晶振电池寿命极短VBAT网络有漏电路径或系统未真正进入低功耗断开VDD只接电池测整机电流逐段断开外设定位漏电点唤醒后系统复位或者时间错乱掉电切换时VBAT电压跌落或备份域复位标志触发检查VBAT电容测量掉电瞬间VBAT波形查看复位标志LSE晶振不起振负载电容太大PCB走线寄生电容过多或晶振虚焊减小匹配电容检查晶振焊接尝试换一颗晶振RTC写时间失败备份域访问未使能或者RTC还没同步完成检查PWR时钟和BKP访问使能确保上电延时后再写RTC5.2 上电瞬间复位坑VBAT电容和电源切换时序这个坑是我当时折腾最久的一个。现象是这样的板子整机供电程序正常跑VDD掉电后重新上电RTC时间虽然还在但备份寄存器的内容偶尔会丢。抓波形发现VDD从0V快速拉高到3.3V的瞬间VBAT引脚电压会有几毫秒的跌落跌到备份域电压阈值以下导致备份域被复位。原因在于VDD上电瞬间单片机和各种电容都在充电瞬时电流很大把系统电源轨拉低而VBAT引脚虽然是电池供电但VDD切换过来时两个电源域之间有耦合瞬间的压降足够触发复位。解决方法是把VBAT引脚的去耦电容加大一点并且在电源切换电路上增加RC延时让VDD稳定之后再切换避免上电瞬间的尖峰影响到VBAT域。同时软件上要养成习惯每次上电后检查备份域复位标志如果发现是备份域复位导致的时间丢失就重新把时间配置一遍至少保证设备不会因为异常复位变成“默认时间旧配置”的混乱状态。5.3 就这些坑做一个避坑清单总结纽扣电池用CR2032或CR1220时优先选低漏电的电源切换方案普通肖特基二极管慎用实测反向漏电可能让整机功耗翻倍。32.768kHz晶振的匹配电容宁小勿大养成看规格书关于负载电容参数的习惯不要随手焊两个20pF上去。VBAT引脚一定要有1uF以上的去耦电容放得越靠近MCU越好掉电瞬间的保持能力全靠它。PCB清洗一定要到位助焊剂残留造成的漏电在微安级别普通万用表测不出来但足以让纽扣电池迅速亏空。软件上进入低功耗前把不需要的引脚统一配置成模拟输入把不用的外设全部DeInit否则待机电流永远降不下去。产品固件里建议周期性检查RTC时间并利用备份寄存器存储校验信息异常复位后可以自动恢复合理状态而不是傻傻地回到出厂默认值。一点收尾的个人经验纽扣电池低功耗RTC供电这种事原理上一点也不复杂官方手册翻一翻都有真正容易出问题的都是藏在细节里的“实测才暴露”的坑。我现在的习惯是原理图阶段就把VBAT网络单独命名、单独分组所有与电池相关的器件统一标注低压低漏电要求画PCB时给LSE晶振单独包地打样回来后第一时间先不跑任何程序直接上电池测静态电流数据合格了再开始调软件。最后再提醒一句如果产品要量产纽扣电池一定要买正品市面上一些便宜的国产扣式电池自放电率很高实验室测试没问题放几个月就电压掉光了这种问题排查起来非常痛苦最好一开始就在选型和采购环节把好关。
返回列表