做电池供电的嵌入式产品,电源部分永远比你想的要难。我在一块以 STM32F407ZG 为主控的便携采集板上吃过亏,最早的方案是一路升降压出 3.3V 主轨、两颗 LDO 分给传感器和射频前端,充电再放一颗专用充电 IC,结果低温测试、USB 热插拔、电池亏电三个场景轮流出问题。后来我把整套供电换成 PCA9422 + STM32F407ZG 的组合:PCA9422 承担电池充电、多路 DC-DC、LDO 和状态上报,主控通过 I2C 实时控制每一路输出的电压和开关。这个改动让电源部分从一群孤立元件变成了一颗可编程外设,待机电流、上电时序、故障定位都变得可控了。这篇文章我把原理图设计、PCB 布局、驱动编写到实测调试的完整过程讲一遍,给同样要做低功耗便携设备、准备从分立电源切到 PMIC 的朋友做个参考。
1. 为什么我得给 STM32F407ZG 系统配一颗专用 PMIC
1.1 先理清 F407ZG 到底要几路电,分别什么脾气
STM32F407ZG 是 LQFP144 封装的 Cortex-M4F 主控,跑满 168MHz 时整机电流可以到 150mA 到 250mA 的量级,VDD 和 VDDA 都要求 1.8V 到 3.6V,通常设计取 3.3V。除了主控本身,它还有一个 VBAT 域,给 RTC 和备份寄存器供电,电流只有几微安到十几微安,但要求任何时候都不能掉出规格之外,否则时间和备份数据就没了。采集板上还有 LCD、外部存储、MEMS 传感器、射频模块,各自需要 3.3V、1.8V、1.2V 不等的电源轨。
真正难处理的不是"供几路电",而是这几路电的脾气不一样。负载是跳变的,LCD 刷新、射频发射、SD 卡写扇区都会在几十微秒内扯掉几十毫安电流,电源轨必须有足够快的瞬态响应;待机时整个系统要压到几十微安才有意义,但分立器件每个都有静态功耗,加起来就失控;多路电源的上电顺序如果不对,外设会通过 GPIO 倒灌电流给还没就绪的主控,轻则 ADC 读数漂移,重则直接复位死机。
1.2 分立方案为什么总是差一口气
拆开看分立方案并不复杂:升降压负责 3.3V 主轨,LDO 负责 1.8V 和 1.2V,充电 IC 负责电池,这在 DIY 板子上很常见。问题是每颗芯片之间没有联系。使能脚的时序要自己用 RC 或复位芯片去搭,充电 IC 的状态要专门占用 GPIO 去读,低功耗时每一颗 LDO 的静态电流、每一个分压电阻的漏电流都是账单。最难受的是调试:系统一旦异常,你分不清是主轨纹波太大、时序没对齐还是充电中断干扰了 GPIO 唤醒。
还有一层是生产问题。固定电阻设定电压的方案,想改输出电压只能改 BOM,改 BOM 就要重新验证、重新贴片。我做过一次量产的板子,只因为把传感器供电从 1.8V 调成 1.2V,整批改了三个电阻位置,还差点造成错料。从那之后我对"软件可调电源"的需求就变得非常强烈。
1.3 决定换 PMIC 的三个理由
换 PMIC 不是头脑一热。第一个理由是集成度:PCA9422 一颗芯片里包含了充电管理、两路 BUCK、LDO、状态监测和 I2C 接口,把原先四五颗芯片加一堆电阻电容的活收进一个很小的封装,外围器件数量断崖式下降。第二个理由是可控性:每一路 BUCK/LDO 的电压和开关都能通过寄存器读写,不用改电阻就能在软件里动态调整输出,这对调试和做低功耗状态切换非常有用。第三个理由是故障诊断:充电状态、上电标志、中断事件都能读出来,问题定位比分立方案快一个量级。
注意,PMIC 不是拿来省 BOM 钱的,单颗芯片并不便宜。它省的是设计时间、PCB 面积、排错成本和长期维护成本。如果你做的是大批量低成本玩具,分立方案可能仍然更合适;但凡是带电池、带低功耗要求的正经产品,PMIC 这条路基本是绕不开的。
2. PCA9422 资源拆解与四路电源轨的分配方案
2.1 片内功能模块逐个对号入座
我手头这颗 PCA9422 变体包含充电管理、两路 BUCK、两路 LDO、I2C 接口和中断状态模块,内部没有很多花哨的东西,但每一块都要放在合适的位置上。先把我这个项目里的分配表列出来:
| 功能模块 | 能力 | 在项目里的角色 |
|---|---|---|
| 充电管理 | CC/CV 充电,充电电流可配,输入限流 | USB 给单节锂电充电 |
| BUCK1 | 高效率降压,输出电压可配置 | 3.3V 主轨,给 F407ZG VDD/VDDA |
| BUCK2 | 同上 | 1.8V 轨,给外部存储器和 LCD 逻辑 |
| LDO1/LDO2 | 低压差线性稳压 | 模拟传感器轨和常电备份轨 |
| I2C 接口 | 寄存器配置与状态回读 | 主控控制总线 |
| 中断/状态 | 充电完成、故障、电源事件 | EXTI 唤醒主控 |
这里必须先提醒一句:PCA9422 有不同批次和变体,功能框图可能略有出入,特别是充电部分,有的是内置线性充电,有的是外置功率管。我以手头这版内置线性充电的配置来写,充电电流控制在 300mA。如果你的变体是开关充电,外围电感和处理逻辑要相应调整,但"主控通过 I2C 配合 PMIC 管理电源"的思路完全一样。
2.2 四路输出的分配与选压逻辑
分配电源轨不是随便接,原则是大电流走 BUCK、小电流且对噪声敏感的走 LDO。BUCK1 输出 3.3V,给主控和所有 3.3V 外设,这路电流最大,必须用开关电源保证效率。F407ZG 的 VDDA 对纹波敏感,所以我的 VDDA 不是直接从 3.3V 铜皮拉的,而是经过一颗磁珠加 10µF 和 100nF 的滤波网络,再进芯片的模拟供电脚。
BUCK2 输出 1.8V,专门给外部存储器和 LCD 的接口电平。这一路是纯数字负载,纹波要求不苛刻,但对瞬态响应有要求,存储器连续写入时电流从几毫安跳到几十毫安,BUCK2 能扛住。LDO1 输出 1.2V,给 MEMS 传感器的模拟内核供电,电流只有几毫安,用 BUCK 反而大材小用,LDO 噪声低、外围简单,还省了电感。
LDO2 我留给了板上的独立 RTC 芯片,而 F407ZG 的 VBAT 没有占 LDO 资源,用的是二极管 OR 电路:3.3V 轨在时由 3.3V 给 VBAT,3.3V 掉电后由电池正极通过肖特基二极管补上。这个细节在后面踩坑环节还会重点讲,这里先按下不表。
2.3 动态电压调节在这类项目里到底怎么用
DVS 是 PCA9422 这类 PMIC 的标配卖点,但很多人在 F407ZG 项目里不知道怎么用,因为 F407ZG 内核自带稳压器,外部压根不需要给它调核压。真正值得做 DVS 的是外围负载。我这里的实际做法是给射频前端供电:发射时把输出电压设成 2.9V,降低路径损耗和发射电流;接收时降到 2.5V,省下那一小段无效功耗。BUCK2 挂存储芯片的场景就不适合 DVS,存储芯片对电压有硬性要求,直接关轨比调电压更省。
原理其实很简单:CMOS 数字电路的功耗和电压平方成正比,所以如果你能找到"功耗随电压变化明显、且能在低电压下正常工作的负载",给它做动态调压就是划算的。找不到这样的负载,就老老实实用"开/关轨",不要为了用 DVS 而用 DVS,那只会增加代码复杂度和隐患。
3. 原理图、布局与上电时序:决定系统稳定性的硬件细节
3.1 输入回路:USB、电池与保护
USB VBUS 进来先放一颗 TVS 和一颗自恢复保险丝,再进 PCA9422 的 VBUS 输入脚。输入限流我按 USB 2.0 规范配到 500mA,防止把电脑 USB 口拉垮,也防止电池低压时充电电流过大把 USB 供电搞崩。电池正极进电池脚,负极走单点回流,电池包内的 NTC 接到 PMIC 的 TS 脚做充电温度保护。
有一件小事必须提:电池焊盘附近一定要加 0.1µF 和 1µF 的去耦电容。我第一次画板时漏了这颗小电容,充电电流切换的瞬间,主轨电压就会抖一下,示波器上看得清清楚楚,加上去之后整个安静了。这种问题不会出现在数据手册的典型应用图里,但会在实际电路里准时等你。
3.2 每颗 BUCK 的外围要怎么配
每颗 BUCK 都需要电感和输入/输出电容。电感值按推荐范围选,我 BUCK1 用 2.2µH,BUCK2 用 1µH;输出电容 22µF 加 100nF,输入电容 4.7µF。看起来都是常规值,但有两个别人不太会写进文档的坑:
第一,X5R 电容在 DC 偏压下容量会明显衰减。同样是 22µF 的 X5R,耐压 6.3V 的规格在 3.3V 偏压下实际可能只剩 12µF 左右,所以选型时要么选更高耐压规格,要么按衰减后的容量核算纹波。第二,输出电容离芯片引脚太远等于白放,瞬态响应会大打折扣。电容和电感的位置,我最后是拿镊子顶在芯片引脚旁边试出来的,这个位置优先级高于美观。
3.3 布局硬性规则与我的落地顺序
布局这块我有几条硬性规则:开关节点(SW 网络)的铜皮面积尽量小,电感和芯片之间的走线要短粗,绝不能把 SW 节点拖到别的地方去绕一圈;模拟电源和数字电源在芯片处单点汇聚,F407ZG 的 VDDA 供电走磁珠隔离后再进模拟引脚,避免 DC-DC 噪声直接从铺铜串进来;I2C 上拉电阻用 4.7kΩ,快速模式 400kHz 下可以换 2.2kΩ,走线尽量短,避免长走线带来的边沿振铃。
电池电压采样也要单独说。如果你用主控 ADC 采 VBAT,采样线要独立拉一根细走线到电池正极焊盘,不要在充电路径上取压。充电时电池电流会在 PCB 铜箔上产生压降,在充电路径上采样,读到的电压会比真实值高,直接影响低电量判断的准确性。这个和呼吸灯的噪声一样,属于"看不见但一定在"的问题。
3.4 上电时序如何用 PMIC 落实
PCA9422 上电后的一系列动作,我整理成了下面这张表:
| 阶段 | 动作 | 说明 |
|---|---|---|
| 0 | VBUS 插入或电池接入 | PMIC 内部 POR,默认输出按出厂配置启动 |
| 1 | BUCK1 输出 3.3V 建立 | 主轨软启动期间不释放 MCU 复位 |
| 2 | 外置复位监控芯片释放 NRST | 检测 3.3V 高于阈值后延迟释放 |
| 3 | MCU 启动,I2C 配置其余各路轨 | 软件显式设置电压、使能、充电参数 |
| 4 | 初始化完成,进入 RUN 状态机 | 其余轨按负载依赖顺序使能 |
这里我选择保留一颗小的外置复位芯片,而不是完全依赖 PMIC 的电源好信号。原因是 PCA9422 的电源好逻辑未必和 STM32 的复位时序要求完全匹配,外置复位芯片成本极低,但能确保 NRST 在电源真正稳定后才释放。这个决定我在两版样机上验证过,回报远大于成本。
4. I2C 驱动编写与关键寄存器配置实战
4.1 通信底层的三个小坑
F407ZG 的 I2C1 在 PB6/PB7 上,复用功能要配成 AF4,引脚模式要设成开漏加上拉。这里最容易犯的错是忘了设置复用功能,或者把引脚配成了推挽输出,导致总线没有应答。第二,PCA9422 的 7 位地址我这边是 0x60,在 HAL 驱动里要把地址左移一位作为从机地址传入。第三,HAL 的 I2C 读写在总线异常时可能一直等,必须给 Timeout 参数设一个合理值,我习惯设 50ms,宁可让它超时返回错误,也不要卡死主循环。
还有一个经验:如果系统里存在两个相同地址的设备,ADDR 引脚可以把 PCA9422 的地址改成第二个值。我这次没有用到双 PMIC,但预留了这个设计,万一后续要给设备加第二块电池管理,不用改板子。
4.2 寄存器初始化顺序,以及为什么这样排
初始化顺序我踩过几次觉得最稳的是这样:上电后先延时 10ms,等 PCA9422 内部上电时序完成;然后读设备 ID 寄存器,验证总线通不通;如果读到 0xFF 说明芯片没响应,直接返回错误,不要继续往下写。总线都没通,后面写什么都是空谈。
之后才按 BUCK1、BUCK2、LDO、充电参数、中断使能的顺序配置。为什么要先配 BUCK 再配 LDO?因为 LDO 的输入通常接自 BUCK,先把输入轨建立起来,再去打开 LDO,能避免 LDO 输入端悬空时出现异常上电状态。中断使能放最后,是怕芯片在配置过程中上报一堆状态,把主控的 EXTI 打断得晕头转向。
4.3 实测可用的 C 代码
下面这套代码我在两版样机上跑过,HAL 库环境下直接用。寄存器偏移定义需要按你手里数据手册的实际值替换,这正是我喜欢用宏封装的原因,换料时只改一处。
/* pca9422.h */ #ifndef PCA9422_H #define PCA9422_H #include "stm32f4xx_hal.h" #define PCA9422_I2C_ADDR_7BIT 0x60 /* 寄存器偏移,请替换为数据手册实际值 */ #define PCA9422_REG_DEV_ID 0x00 #define PCA9422_REG_BUCK1_VSEL 0x01 #define PCA9422_REG_BUCK2_VSEL 0x02 #define PCA9422_REG_LDO1_VSEL 0x03 #define PCA9422_REG_RAIL_CTRL 0x04 #define PCA9422_REG_CHG_CTRL 0x05 #define PCA9422_REG_STATUS 0x06 #define PCA9422_REG_INT_MASK 0x07 typedef struct { I2C_HandleTypeDef *hi2c; uint16_t addr; /* 7 位地址左移一位后的值 */ uint8_t dev_id; } pca9422_t; #define PCA9422_RAIL_BUCK1 0 #define PCA9422_RAIL_BUCK2 1 #define PCA9422_RAIL_LDO1 2 #define PCA9422_RAIL_LDO2 3 uint8_t pca9422_init(pca9422_t *dev); void pca9422_set_buck1_mv(pca9422_t *dev, uint16_t mv); void pca9422_rail_enable(pca9422_t *dev, uint8_t rail, uint8_t en); uint8_t pca9422_read_status(pca9422_t *dev); #endif/* pca9422.c */ #include "pca9422.h" static uint8_t pca9422_reg_read(pca9422_t *dev, uint8_t reg) { uint8_t val = 0; HAL_I2C_Mem_Read(dev->hi2c, dev->addr, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 50); return val; } static void pca9422_reg_write(pca9422_t *dev, uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(dev->hi2c, dev->addr, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 50); } static uint16_t buck_mv_to_code(uint16_t mv) { /* 按数据手册电压表做查表映射,步进和范围以规格为准 */ if (mv <= 1200) return 0; if (mv >= 3300) return 0xFF; return (uint16_t)((mv - 1200) / 10); /* 示例算法,按实际表替换 */ } uint8_t pca9422_init(pca9422_t *dev) { dev->addr = (uint16_t)(PCA9422_I2C_ADDR_7BIT << 1); HAL_Delay(10); dev->dev_id = pca9422_reg_read(dev, PCA9422_REG_DEV_ID); if (dev->dev_id == 0xFF) return 0; pca9422_set_buck1_mv(dev, 3300); pca9422_set_buck2_mv(dev, 1800); /* LDO1 设 1.2V,LDO2 保持关闭,留给 VBAT OR 电路 */ pca9422_reg_write(dev, PCA9422_REG_LDO1_VSEL, 1200); pca9422_rail_enable(dev, PCA9422_RAIL_BUCK1, 1); pca9422_rail_enable(dev, PCA9422_RAIL_BUCK2, 1); pca9422_rail_enable(dev, PCA9422_RAIL_LDO1, 1); pca9422_rail_enable(dev, PCA9422_RAIL_LDO2, 0); /* 充电电流设为 300mA,输入限流 500mA */ pca9422_reg_write(dev, PCA9422_REG_CHG_CTRL, 0x1E); /* 只开放充电完成和故障中断 */ pca9422_reg_write(dev, PCA9422_REG_INT_MASK, 0x03); return 1; } void pca9422_set_buck1_mv(pca9422_t *dev, uint16_t mv) { uint8_t code = (uint8_t)buck_mv_to_code(mv); pca9422_reg_write(dev, PCA9422_REG_BUCK1_VSEL, code); } void pca9422_rail_enable(pca9422_t *dev, uint8_t rail, uint8_t en) { uint8_t val = pca9422_reg_read(dev, PCA9422_REG_RAIL_CTRL); if (en) val |= (1 << rail); else val &= ~(1 << rail); pca9422_reg_write(dev, PCA9422_REG_RAIL_CTRL, val); } uint8_t pca9422_read_status(pca9422_t *dev) { return pca9422_reg_read(dev, PCA9422_REG_STATUS); }4.4 状态切换与 DVS 命令的实际用法
进入待机时,我不会直接把 BUCK1 也关掉,因为 F407ZG 还需要 3.3V 保持备份域和唤醒逻辑。做法是先通过 RAIL_CTRL 关闭 BUCK2 和 LDO1,再让主控进 STOP 模式;唤醒后先回读 RAIL_CTRL 确认寄存器写入成功,再打开外设轨。PCA9422 的寄存器大多可读,写完回读这个动作非常值钱,能避免总线上的突发干扰造成静默写失败。DVS 切换同理:射频状态切换时,先写 VSEL 寄存器,延时 1ms 等输出稳定,再让射频模块开始工作;如果先发了射频命令再调电压,发射瞬间的输出跌落会直接体现在误码率上。
5. 充电管理、低电量保护与整机电源状态机
5.1 充电流程怎么分工
充电的 CC/CV 过程和终止判定是由 PCA9422 硬件完成的,主控不需要干预每个电流采样周期,只需要在充电事件中断和周期性轮询时读取状态。我把状态位和我的处理方式整理成了下表:
| 状态 | 主控处理 |
|---|---|
| 充电中 | 点亮充电指示灯,继续正常运行 |
| 充电完成 | 关指示灯,记录完成时间戳 |
| 充电故障 | 关闭指示,日志记录故障码,稍后重试 |
| 电池亏电 | 进入低电量保护流程,准备关机 |
比较重要的一点是,首次配置充电参数前,最好先把充电功能关闭,等电压、输入限流都写好后,最后再打开充电使能。这样能防止芯片用出厂默认的大电流参数在装配现场直接给电池猛冲,尤其是刚焊完还没来得及验证散热的情况下。
5.2 简易电量计实现
PCA9422 会提供电池电压相关的状态读数,但想拿它做精确电量估计是不够的。我这里的做法是:主控 ADC 定期采样电池电压,做一次滑动滤波,然后对照电池厂家给的 OCV 曲线查表换算剩余电量百分比。虽然 OCV 曲线的精度有限,带载时电压会掉,放电平台期几乎看不出变化,但作为产品功能展示已经足够。
我用的滤波非常朴素:
static uint16_t adc_filter(uint16_t sample) { static uint32_t avg = 0; if (avg == 0) avg = sample << 3; else avg = (avg * 7 + sample * 9) >> 3; /* 权重快,响应快 */ return (uint16_t)(avg >> 3); }关键不是算法高级,而是电量百分比要加滞回区间。比如 15% 报警之后,电压波动又跳到 15.5%,你不能马上取消报警,否则就是指示灯反复闪烁。我的做法是报警阈值 15%,恢复阈值 20%,中间隔开 5% 的死区。
5.3 按键开机、软关机与低电量关断
机械按键开机是最常见的需求。电池接入后,PCA9422 处于待机,按键通过 PMIC 的电源控制引脚拉起系统主供电,主控开始运行,初始化完成后把各路轨配置成 RUN 状态。软关机则反过来:主控先把需要保存的数据落盘,再通过 I2C 关闭 BUCK2、LDO1、外设等电源轨,最后让 PMIC 进入关机或深度待机状态,整机只保留 RTC 和按键唤醒用的常电域。
低电量关断的策略我按电池电压分了两档:3.4V 时提醒用户,3.2V 时强制保存数据并关机。这个阈值不能定太高,锂电池放到 3.2V 其实还很安全,主要是预留充电前自恢复的余量。USB 插入检测到之后,PMIC 自动走充电流程,主控检测到 VBUS 事件可以选择直接开机,也可以选择只充电不开机,取决于产品定位。
5.4 整机电源状态机
把上面这些串起来,就是一个简单的状态机:
| 状态 | MCU | PMIC 各轨 | 触发条件 |
|---|---|---|---|
| RUN | 全速运行 | BUCK1/BUCK2/LDO1 全开 | 开机或事件唤醒 |
| SLEEP | STOP 模式 | 只留 BUCK1 | 空闲超时或按键 |
| OFF | 断电 | 只留常电域 | 软关机或低电量保护 |
| FAULT | 记录日志后尝试恢复 | 按异常部位关轨 | 充电故障、温度保护等 |
这个状态机是我整个电源管理软件的主干,PCA9422 的寄存器读写只是为它服务的工具。把状态转换条件想清楚,代码写起来会非常顺,也不会出现"我已经睡了但外设还在耗电"的逻辑漏洞。
6. 实测数据与四个真实踩坑的完整排查链路
6.1 实验室里测到的关键数据
以下数据来自我的样板,不同电池、布线和负载差异会带来明显偏差,参考趋势即可:
| 场景 | 电压/电流 | 纹波 | 备注 |
|---|---|---|---|
| 满负载 168MHz + 外设全开 | 3.3V 轨约 160mA | 24mVpp | BUCK2 约 42mA,LDO1 约 5mA |
| STOP 模式 | 整机约 28µA | — | 电池 3.7V 下实测 |
| 充电过程 | 300mA CC,4.2V CV | — | 终止电流约 20mA |
| BUCK1 效率 | 100mA 时约 89% | — | 含 PCB 损耗 |
待机电流能压到 30µA 以内,功劳主要在"不用的轨全部关掉"这一个动作上。如果哪个外设没被正确关轨,整机电流会直接多出几百微安,排查手段很直接:逐轨电流探针测量。
6.2 坑一:I2C 偶发卡死,SDA 被拉低
症状是系统连续运行几小时后,I2C 读写开始报错,重启后恢复,但断断续续。排查链路是这样的:先在 HAL 的错误回调里打印错误码,发现都是 NACK 错误;拿示波器抓 SCL 和 SDA,看到 SDA 在地址字节后被死死拉低,SCL 还在正常翻。这说明从机内部状态已经和总线状态不同步了,大概率是某次通信被充电事件打断,漏掉了 STOP 条件。
修复分两步:第一步,代码里加一个"总线恢复序列",每次通信失败后,先把 SCL 拉低翻转 9 次再发 STOP,强制从机释放 SDA,而不是直接无限重试;第二步,把 PCA9422 的 EN 脚接到一个 GPIO,如果总线恢复还不行,就拉低 EN 再拉高,让芯片彻底复位。加了这两层保护之后,这个坑再没出现过。
6.3 坑二:上电瞬间主轨过冲
症状是第一次上电偶发死机或者 ADC 校准值异常,复位之后一切正常。我把 3.3V 主轨、NRST、I2C 使能信号四通道同时采样,发现 VBUS 插入瞬间,BUCK1 输出冲到了 3.78V,超过了 F407ZG 的绝对最大额定值。复位芯片的阈值恰好设在高位,所以有时候能挡住,有时候挡不住。
原因有两层:输入电压快速上升时,BUCK 的软启动和反馈环路还没来得及收敛;输出电容在低偏压下实际容值又不足,加剧了过冲。修复动作是把输出电容从 10µF 换成 22µF,并且选了更高耐压规格改善 DC 偏压特性;输入端电容也从 1µF 加到 4.7µF。改完之后过冲从 0.48V 降到 0.15V,上电时序图干净了很多。
6.4 坑三:VBAT 域在低电量时丢数据
症状比上一条更隐蔽:电池用到接近 3.0V 时,RTC 时间会跳回默认值,备份 RAM 里的校准数据也丢了。万用表量 VBAT 引脚发现,低电量时 VBAT 已经被拖到 1.5V 以下。原因是 3.3V 轨掉电后,VBAT 只靠一颗普通二极管从电池供电,二极管压降加上 PCB 走线损耗,在电池低压时直接把 VBAT 拉出了规格。
修复做法是把 VBAT 改成二极管 OR 电路:3.3V 正常时由 3.3V 供,3.3V 掉电后再走肖特基二极管接电池,同时在 VBAT 脚加 1µF 和 10µF 两级电容。这样电池电压即使低到 3.0V,扣除二极管压降后 VBAT 仍然在 STM32 规格内,RTC 域不会再丢数据。
6.5 坑四:NTC 温度保护误触发
最后一个坑是充电温度保护误触发。样机在室温 35℃ 环境里充电,跑一会儿就报温度过高故障,手摸外壳只是温热。用热成像看,发热源不是电池,而是 PCA9422 旁边的充电回路和电感。线性充电本身就有比较大的热耗散,我算过一笔账:5V 输入、4.0V 电池电压、300mA 充电电流,线性充电的功率损耗大约是 (5-4.0)×0.3,也就是 0.3W 左右,全变成热量;如果 NTC 摆在 PCB 热源旁边,它读到的温度当然比电池真实温度高。
修复分两路:硬件上把 NTC 挪到靠近电池本体的位置,中间不要跨过电感和大电流铜皮;软件上对温度状态连续读三次、间隔 2 秒,确认不是跳变噪声才触发保护,同时把充电电流可调这个能力用起来——高温环境自动从 300mA 降到 200mA,宁可充慢一点也不要误保护。
最后再分享一点体会:把 PCA9422 当成一个 I2C 外设来管理之后,我几乎不再在硬件上堆电阻调电压了,所有电源行为都变成了软件状态机的输入。第一版样机别急着把功耗优化到极致,先把"上电后寄存器回读打印"和"状态中断日志"做好,后续调低功耗、做长期老化测试都靠这两样东西定位问题。如果板子面积和成本允许,建议把每一路轨的使能状态和充电状态都映射到日志里,等产品跑完一年老化再回头看,你会感谢当初多写的那几行代码。