☰
PCA9422+STM32F427ZI:电池供电设备的低功耗电源管理与实践
2026/10/10 5:03:26 网站建设 项目流程

做电池供电的嵌入式设备,最让人头疼的往往不是程序跑不起来,而是板子上那几路电怎么来、怎么分、怎么省。我最近把一个便携式环境记录仪从“充电IC + DC-DC + LDO + 电量计”的散装方案,整体切换成 PCA9422 + STM32F427ZI 的组合,前后花了两个多月把硬件、驱动、功耗和量产测试全部捋顺。这篇文章把我认为值得复用的设计思路和踩坑记录完整写下来,给正在做电池供电产品、尤其是想把多路电源管理交给一颗 PMIC 的读者参考。

1. 选型逻辑:一块 PMIC 省掉的东西比你想的多

1.1 散装电源方案的痛点

先说老方案。之前项目里用的是一个独立的线性充电芯片、两颗 DC-DC、一颗 LDO、一颗库仑计、一个外部 ADC,再配一堆分压电阻和滤波电容。功能上确实能跑,但有两个让我很烦躁的问题。

第一是上电时序。STM32F427ZI 的外设电压域、SD 卡、传感器、RTC 备份域,要求不同 rail 的建立顺序和建立时间。散件方案里,每颗 DC-DC 的 EN 引脚虽然可以串 RC 做延时,但批量生产时电阻电容的误差会让时序漂移,偶尔出现某次开机外设没起来的问题,排查起来非常痛苦。

第二是待机功耗。散装方案里每颗芯片都有各自的工作电流和泄漏电流,加起来很难压到 100μA 以下。为了把整机待机功耗做下去,我甚至给某些芯片单独加过负载开关,又引入新的控制逻辑,得不偿失。

1.2 PCA9422 的内部资源与边界

PCA9422 属于面向可穿戴和便携设备的 PMIC,内部把几样东西集中到一起了:

  • 两路同步 Buck 开关电源,输出电压可编程,带 DVS(动态调压)能力;
  • 一路 LDO,适合给对纹波敏感的模拟部分供电;
  • 一路线性锂电池充电管理,支持恒流/恒压/涓流,终止电压可配置;
  • 内置库仑计和电压/温度 ADC,可以直接读出剩余电量和电池电压;
  • I2C 接口、中断输出、电源状态输出(POK)等数字接口。

这里我必须提醒一句:PCA9422 有不同后缀和批次,输出电压范围、Buck 输出电流、充电电流上限要以你手上那颗料的数据手册为准。我下面写到的“某个寄存器写成多少对应多少伏”,只是用来说明设计方法,不要照着抄完就上线,务必对着手册核对一遍。

选择它主要是看中两点:线性充电 + 电源路径管理,配合两路 Buck 和 LDO,刚好覆盖 STM32F427ZI 这类主控平台最常见的 3.3V / 1.8V / 3.0V 电源树;同时它自带的库仑计和 ADC 又省掉了一颗电量计和一堆模拟前端电路。

1.3 为什么主控选 STM32F427ZI

可能有人会问:做电池供电为什么不用低功耗 MCU,反而用 STM32F427ZI 这颗 Cortex-M4F、180MHz、2MB Flash 的大家伙?

我的想法是,设备的功能定位决定主控选型。这个记录仪除了采集传感器数据,还要跑浮点运算、加密、大文件存储,甚至本地做一些简单的信号处理。低功耗 MCU 跑这些任务会非常吃力,说不定比偶尔高性能运行的 STM32F427ZI 更费电。真正的功耗优化不是选一颗“睡得很死”的 MCU,而是让主控在需要干活时高效肝完、赶紧睡觉,配合 PMIC 把待机电流压到极限。

STM32F427ZI 的 Stop 模式配合 RTC,整机静态电流可以做到几十微安量级;2MB Flash 也让日志和配置存储不需要外挂 SPI Flash,少一颗芯片就少一路供电和一部分待机损耗。

2. 硬件连接:从电池到 STM32 之间的完整信号链

2.1 电源树分配

硬件设计的第一步是把每一路负载分配清楚。下面是我最终采用的电源树:

输出目标电压主要负载说明
Buck13.3VSTM32F427ZI、SD 卡、传感器、电平转换系统主电源轨,电流最大
Buck21.8V外部 PSRAM / 射频模块、IO 域数字小电流轨,和 3.3V 分开
LDO3.0VRTC 备份域、唤醒逻辑、模拟参考常开电源,必须一直有电

这里的核心思路是:常开轨(LDO 3.0V)单独走,负责 RTC 和唤醒回路,这样系统休眠时可以把 Buck1、Buck2 全部关掉,只留 LDO 挂着,待机电流由 3.0V 这条小轨决定。Buck1 和 Buck2 可以根据负载情况独立开关,甚至支持 DVS 动态降压,在 MCU 轻负载时把电压往下调一点来省功耗。

需要注意的是 STM32F427ZI 的 VDDA 模拟电源,我建议也单独加一级磁珠或小电感再做 π 型滤波,避免 Buck 的开关纹波窜进 ADC 采集链路。PCA9422 的 LDO 我选择给 RTC 和模拟参考供电,也是考虑到它比 Buck 更安静。

2.2 I2C、IRQ、使能脚的接法

PCA9422 和 STM32F427ZI 之间的控制接口主要是 I2C。我用了 MCU 的 I2C1,SDA/SCL 都接 4.7kΩ 上拉到 3.3V,总线频率设在 400kHz。PMIC 芯片地址通常由地址引脚的电平决定,焊接前就要通过原理图把地址定好,免得量产时才发现地址冲突。

IRQ 中断脚接到 STM32F427ZI 的某个 EXTI 引脚,配置成下降沿触发、上拉输入。PMIC 的中断脚一般是开漏输出,所以外部必须加上拉电阻,否则电平根本拉不高。充电完成、电池低电量、温度异常这些事件,我全走 IRQ 通知 MCU,MCU 在中断里读状态寄存器,而不是让主程序轮询。轮询的方式也能用,但会频繁唤醒 CPU,跟省电的目标背道而驰。

还有一个容易被忽略的脚是 POK(Power OK)或同类电源状态输出。这个引脚表示内部各路输出是否达到正常工作电压。我把它接到 STM32 的 NRST 或者一个输入 GPIO 上做开机判定,确保在 Buck1 电压没稳定前,MCU 不会被错误上电。实际用起来,这比单纯依赖 MCU 内部 POR 更可靠。

2.3 NTC、电感电容和布局注意

PCA9422 的充电管理一般要求电池端接 NTC 热敏电阻到温度检测引脚,用来做充电温度保护。我的板子用的是 10kΩ NTC,放在电池包内部,通过连接器引出来。如果这个引脚悬空或者电阻分压配错,PMIC 可能直接拒绝充电。

电感方面,Buck 输出我用了 2.2μH 的功率电感,额定电流按负载的 1.5 倍选,再并联一颗 10μF 和两颗 100nF 陶瓷电容。这里有个经验:Buck 的输出电容宁多勿少,但 ESR 要低,否则瞬态响应会变差,MCU 高负载时可能出现电压跌落。开关节点要短而粗,反馈走线远离电感,这些都是老生常谈,但布线时真的很容易被忽略。

3. 上电时序与寄存器初始化:先把电“立”起来

3.1 上电那一刻,MCU 是怎么活过来的

很多第一次用 PMIC 的开发者会默认:板子上电后 MCU 就一定能跑。实际上,PCA9422 可能存在出厂默认状态、运输模式或深度睡眠模式,电池刚接上时不一定立刻输出各路电压。

我遇到的情况是:裸板第一次上电,按下电源键后 STM32F427ZI 完全没反应,3.3V 轨上连电压都量不到。后来查了数据手册才明白,PMIC 可能处于 shipping mode,需要插一下 USB 或者在 EN 引脚上给一个唤醒信号。这部分对开发板可能不算问题,但打样回来的板子在产线上第一次通电时非常常见。

正常启动时序应该是:电池接入或 VBUS 插入 → PMIC 进入相应模式,各路输出按默认寄存器值建立 → POK 拉高 → STM32F427ZI 的 NRST 释放 → MCU 开始执行固件。这意味着 PMIC 的默认寄存器值在 MCU 代码还没跑起来之前就开始工作,所以原理图阶段就要想清楚:Buck1 的默认输出电压是多少、默认是否使能、放电电流多大。如果默认值是 1.8V,而你的电路按 3.3V 设计,那上电瞬间外设就已经在低电压下工作,等 MCU 初始化改电压时可能已经出问题了。

3.2 寄存器初始化顺序

PMIC 初始化最好放在系统时钟配置之前。我的初始化顺序是:

  1. 读 Device ID,确认 I2C 通信正常;
  2. 设置工作模式,让 PMIC 进入正常供电模式;
  3. 配置 Buck1、Buck2、LDO 的目标电压和 DVS 压摆率;
  4. 配置充电电流、终止电压、NTC 阈值;
  5. 初始化库仑计,设置电池容量参数;
  6. 使能中断,清掉残留中断标志。

顺序不能乱。比如先使能中断再清标志,残留的充电状态事件会立刻触发一个假中断,让驱动误以为发生了插拔事件。

3.3 初始化代码示例

下面是我在 STM32 HAL 环境下写的 PMIC 驱动简化版。寄存器地址和位定义在我的板子上是这样定义的,你拿到手后务必对着自己物料的数据手册改。

#define PMIC_I2C_ADDR 0x50 // 7-bit I2C address #define PMIC_REG_DEV_ID 0x00 #define PMIC_REG_MODE 0x08 #define PMIC_REG_BUCK1_VOUT 0x10 #define PMIC_REG_BUCK2_VOUT 0x11 #define PMIC_REG_LDO_VOUT 0x12 #define PMIC_REG_CHG_CTRL 0x20 #define PMIC_REG_CHG_CURR 0x21 #define PMIC_REG_CHG_VTERM 0x22 #define PMIC_REG_GAUGE_CTRL 0x30 #define PMIC_REG_INT_EN 0x31 #define PMIC_REG_STATUS 0x32 static uint8_t pmic_read(uint8_t reg, uint8_t *val) { return HAL_I2C_Mem_Read(&hi2c1, PMIC_I2C_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); } static uint8_t pmic_write(uint8_t reg, uint8_t val) { return HAL_I2C_Mem_Write(&hi2c1, PMIC_I2C_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); } void pmic_init(void) { uint8_t id = 0; // 1. 验证通信 if (pmic_read(PMIC_REG_DEV_ID, &id) != 0) { error_handler(); // I2C 无应答,检查地址和上电状态 } // 2. 进入正常工作模式 pmic_write(PMIC_REG_MODE, 0x0F); // AllOn // 3. 设置输出电压 // 某颗料的 Buck VOUT = 0.6V + code * 25mV // 3.3V -> code = 108 = 0x6C // 1.8V -> code = 48 = 0x30 pmic_write(PMIC_REG_BUCK1_VOUT, 0x6C); pmic_write(PMIC_REG_BUCK2_VOUT, 0x30); pmic_write(PMIC_REG_LDO_VOUT, 0x35); // 3.0V // 4. 充电配置:200mA,4.2V 终止电压 pmic_write(PMIC_REG_CHG_CURR, 0x0A); pmic_write(PMIC_REG_CHG_VTERM, 0x84); pmic_write(PMIC_REG_CHG_CTRL, 0x01); // 5. 开启库仑计 pmic_write(PMIC_REG_GAUGE_CTRL, 0x01); // 6. 使能中断并清残留状态 pmic_write(PMIC_REG_INT_EN, 0x03); pmic_read(PMIC_REG_STATUS, &id); }

要注意 HAL 库的HAL_I2C_Mem_Write/Read参数里地址要左移一位,这是初学者最容易搞错的地方。另外我习惯给每次 I2C 操作加上短超时,PMIC 在刚复位时可能短暂无应答,超时重试比死等更稳。

4. 驱动层设计:把 PMIC 变成 STM32 的一个“外设”

4.1 寄存器读写封装与容错

直接在所有代码里散着写HAL_I2C_Mem_Read不是不行,但后续维护会非常痛苦。我封装了一个PMIC_Handle结构体,把 I2C 实例、地址、中断回调都收进去,上层逻辑只调用 API,不关心底层协议。

typedef struct { I2C_HandleTypeDef *hi2c; uint8_t addr; void (*callback)(uint32_t events); } PMIC_Handle;

写寄存器操作我做了三层防护:一是每次读操作前先确认上次传输结束;二是在关键配置写入后马上读回比对;三是把初始化用的电压表固化到驱动里,切换不同物料时只改一张表。实际生产中发现,极少数板子在 I2C 走线过长或总线负载较高时会出现偶发写失败,重试一次基本就能解决。为了这个“偶发”,我在充电配置、电压配置等关键写入函数里加了最多三次重试,并且把错误计数打印出来,方便产线定位。

4.2 充电状态机

PCA9422 内部的充电管理已经实现了涓流、恒流、恒压、截止、再充电这些基本状态,但 MCU 侧还是建议做一个状态机,因为它需要根据充电状态去做 UI 显示、电量计算和错峰供电。思路如下:

状态进入条件退出条件
无充电VBUS 未插入检测到 VBUS
涓流充电电池电压低于阈值电池电压升到涓流阈值
恒流充电电池电压正常电压达到恒压目标
恒压充电电压达到终止值电流降到截止电流
充电完成电流低于截止阈值电池电压回落,触发再充电

状态机的核心是事件驱动,而不是每个循环都去翻寄存器。IRQ 触发后,我在中断里读取状态寄存器,判断当前事件属于“插上了充电器”“充完了”还是“温度过高”,然后置位对应的软件事件标志,主循环根据标志驱动 UI 和日志。这样可以避免 MCU 频繁查看 PMIC 状态,把更多时间留在睡眠里。

4.3 中断处理与事件分发

EXTI 中断服务函数里只做两件事:读状态寄存器、调用注册的回调。不要在中断里做长任务,尤其是充电完整判断,读取寄存器后应当立即退出中断,把真正的逻辑放到主循环里。我踩过一次坑:中断里直接写了 Flash 日志,结果 Flash 写入耗时把 I2C 时序拖乱了,PMIC 的应答超时,后边所有状态读取全部失败。

事件分发我用了一个简单的标志位集合,主循环按优先级处理。优先级最高的是温度异常,其次是充电完成和低电量,最后才是电压跌落之类的次要告警。

4.4 库仑计的使用与校准

库仑计读出来的原始值是累积电荷量,要得到剩余百分比,必须告诉它电池的总容量。这个参数我在量产阶段用“满充满放”的方式标定:先把电池充到终止电压并维持到电流截止,然后以固定负载放空,记录库仑计累计值,把这个值写回设计容量寄存器。之后设备正常使用,剩余电量百分比就比较准了。

如果不做这一步,新电池第一次使用的头几轮电量显示会非常离谱,可能显示 100% 一秒掉到 80%。这不是 PMIC 坏了,是库仑计还不知道这颗电池的真实容量。另外库仑计自身有偏移,我建议每 50 次完整充电周期后做一次满充电校准,让设备自己把容量误差修正掉。

5. 实测调优:充电曲线、功耗数据与踩坑记录

5.1 充电电流设置与温度保护实测

这个记录仪用的是单节 1500mAh 锂电池,最大充电电流是 0.5C,也就是 750mA。但考虑到 USB 供电能力,我把恒定电流阶段设成 500mA,USB 输入电压在 5V 时余量足够。

实际用电子负载模拟电池,配合高精度电流探头记录充电曲线,得到的结果和理论很接近:电池电压在 3.0V 以下时进入涓流,大概以 60mA 充到 3.2V;之后进入恒流段,500mA 一直充到 4.2V;到 4.2V 后电流开始线性下降,从 500mA 降到 50mA 大约花了 40 分钟。这段恒压过程特别考验 NTC 的响应速度,电池发热后 NTC 阻值会变,如果分压电阻配得上,温度阈值跳变会很干净,不会出现断断续续充、停、充、停的现象。

5.2 各路输出负载能力与纹波验证

Buck1 的 3.3V 轨在载入 500mA 电子负载时,输出纹波实测约 18mV,开关纹波频率稳定;Buck2 的 1.8V 轨负载 200mA 时纹波约 12mV。这个成绩对 STM32F427ZI 和普通数字电路完全够用,但给模拟传感器供电前最好再用 LDO 或 π 型滤波过一遍。

有一个数据让我意外:Buck 在空载或轻负载时纹波比中载更大。原因是 PMIC 在轻负载下可能进入省电的 PFM 模式,开关频率下降、纹波上升。如果某路供电的设备对纹波特别敏感,可以在寄存器里把工作模式固定为 PWM,代价是轻载效率下降。这个取舍要按实际电路来调。

5.3 整机功耗数据

把整机跑起来,实测了几组数据:

工作状态整机电流说明
活跃运行82mA @ 3.7VSTM32F427ZI 全速跑 + 传感器 + SD 卡写入
轻负载12mA @ 3.7VMCU 降频,传感器周期采样
睡眠58μA @ 3.7VMCU Stop + RTC + 常开 LDO
关机运输2μA @ 3.7VPMIC 进入 shipped 模式

58μA 里大部分是 LDO 3.0V 常开轨的功耗和 RTC 的消耗。这个数据比我之前散装方案好了接近一个数量级。如果你的产品要求更极端,可以把常开轨换成更小的 PMIC 内部 LDO 或干脆用一颗低功耗 RTC 芯片来带。

5.4 几个值得警惕的坑

这块板子踩过的坑不少,挑几个对任何 PMIC 项目都有参考价值的写出来。

第一个坑是I2C 总线地址和总线负载。P410 上的 PMIC 跟另一个传感器 I2C 地址冲突,导致初始化时读 Device ID 偶尔读到传感器回包。解决办法是硬件上给两个器件不同的地址分配,软件上在初始化时连续读两次 Device ID 做校验,防止误判。

第二个坑是DVS 压摆率和负载跌落。开发阶段我为了调试省事,直接把 Buck1 电压从 3.3V 写到 2.8V,结果 STM32F427ZI 当场复位。原因不是电压目标值写错,而是压摆率设置得太陡,3.3V 轨上挂的电容负载来不及响应,瞬时电压跌到 MCU 欠压阈值以下。DVS 是给负载有准备的场景用的,不是让你随手乱改的。动态调压前,要么通过寄存器配置较慢的压摆率,要么先把大负载设备切到低功耗状态再动电压。

第三个坑是NTC 悬空导致不充电。早期测试板我没有接电池,直接用可调电源给 VBUS 供电,结果充电状态一直是“检测不到电池”。最后发现 PMIC 在 TS 引脚没有检测到有效 NTC 时,会默认电池温度不可信,直接锁死充电。调试时一定要把 NTC 接上,或者临时在寄存器里把温度检测关闭,否则你会在充电状态机上浪费一整周。

第四个坑是量产运输模式的唤醒时序。产线最终测试完,需要让设备进入 shipped 模式以降低运输电流。但产线员工如果先拔出电池再进 shipped 模式,或者进了 shipped 模式后又短接了电池,会导致 PMIC 完全锁住、按开机键没反应,需要经历一次完整的 VBUS 插拔才能恢复。我在产测工装里加了一步:先插 USB 让系统识别外部电源,再触发 shipped 模式命令,最后才断开电池连接,彻底解决了这个问题。

第五个坑比较隐蔽:充电状态寄存器里的“满”标志和实际截止不是同一时刻。PMIC 在电流降到截止阈值后置位满充标志,但电池内部其实还会继续吸收一点点电。如果满充后立刻把充电电路关掉,电池电压会回落到 4.18V 甚至更低,容量少了约 2%。改进做法是满充标志出现后再维持恒压 10 分钟,再切回充电完成状态,这样电池电压能稳定在 4.19V 以上。

我在实际项目里体会到,PMIC 这类芯片最容易被低估的是软件复杂度。硬件把连接画对只是第一步,真正决定功耗和稳定性的,是上电时序、寄存器顺序、中断策略和状态机这些看不见的代码。如果你也在做类似的电池供电项目,建议先花两天时间把数据手册里的默认寄存器值全部整理出来,画一张 PMIC 全状态迁移图,再开始写驱动,后面能省下大量的调试时间。另外,板子打样回来第一天,别急着调功能,先用电流表把每一路电源的空载电流摸一遍,包括每个状态的整机电流,这个基线数据会让后续所有优化都有据可依。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询