搞嵌入式的人都知道,电池供电产品的电源管理从来不是一颗稳压器能解决的问题。我最近在做一台低功耗环境监测终端,用了一颗 PCA9422 作为电源管理核心,搭配一颗 PIC18F86J15 做系统控制,才真正把充电、放电、休眠、电量检测这些麻烦事理顺。这篇文章把我从选型、硬件链路设计、固件流程到实测调试的完整过程整理出来,希望对正在做可穿戴设备、IoT 节点或者便携仪器的朋友有参考价值。
先说结论:PCA9422 负责功率路径和充电安全,PIC18F86J15 负责电源策略和状态判断。它们之间靠 I2C 通信和一根中断线协作,本质上组成了一套"硬件状态机 + 软件决策"的电源管理系统。我下面按实际开发顺序来拆解这套方案。
1. 为什么把 PMIC 和 MCU 分开,而不是让单片机直接管电源
1.1 电池系统里真正麻烦的是功率路径
刚开始做电池产品的人很容易有个误区:以为电源管理就是"电池接一个 LDO,MCU 控制 ADC 采集电压,再控制几个 MOSFET 开关"。真做起来才发现,充电、放电、冷启动、过放保护、适配器热插拔这些场景,靠 MCU 直接操作分立器件,不仅代码复杂,而且风险极高。
电池供电系统里最麻烦的有三件事:输入源切换(适配器接入时优先用适配器供电并给电池充电,适配器断开时无缝切到电池)、充电曲线控制(恒流、恒压、截止电流),以及异常保护(过温、过压、过流、反接)。如果让 MCU 用 GPIO 去控制 MOSFET 搭这些逻辑,一旦发生瞬间切换,MCU 的中断延迟根本来不及响应,轻则系统复位,重则电池保护板动作甚至损坏。
PCA9422 这种 PMIC 的价值就在这里:它把功率级的逻辑做成了内部硬件状态机。适配器插入、拔出、充满、欠压这些事件,由芯片内部模拟比较器和时序控制器自动响应,不需要 MCU 介入就能保证安全。MCU 只负责读状态、写配置、决定何时进入低功耗,也就是把"体力活"和"脑力活"分开。
1.2 两颗芯片的角色分工
在这套方案里,角色划分非常清晰:
PCA9422是功率域的主人。它管理输入源选择、电池充放电通路、多路系统输出电源,以及相关的电压电流保护。它对外提供 I2C 寄存器接口、中断输出引脚和使能控制引脚。MCU 只需要通过 I2C 告诉它"我要以多少电流充电""哪一路输出要开启""我准备进入休眠了",剩下具体的功率操作全部由它完成。
PIC18F86J15是策略域的主人。它负责整个系统的运行逻辑:开机时先判断是适配器供电还是电池供电,根据场景决定是否启动传感器、无线模块等负载;运行过程中监控 PMIC 中断事件,记录充电状态;系统空闲时让 PMIC 进入低功耗待机模式,同时自己也进入 SLEEP。它是一颗 8 位 MCU,但外设比较均衡,有 MSSP 模块(可配置成 I2C 主机)、多个外部中断引脚,以及足够用的 Flash 和 RAM,做电源策略控制绰绰有余。
我选这颗 PIC18F86J15 的主要原因是它的唤醒延迟低、中断引脚多,可以同时接 PMIC 中断和按键唤醒;而且它能在 3.3V 下稳定运行,和 PMIC 的输出电压域匹配简单。如果你手头有类似外设配置的 8 位或者低功耗 32 位 MCU,完全可以把同样的思路迁移过去,核心逻辑是通用的。
2. PCA9422 的硬件链路与工作状态机
2.1 从 USB 输入到系统负载的完整电流路径
理解 PCA9422 的功能,关键是先画清楚功率链路。我一般会在白板上画三条线:
- 输入线:USB 5V 或者适配器电压从 VBUS 引脚进入,经过输入 OVP 和输入限流保护,再进入充电模块。
- 电池线:电池正极接 BAT 引脚。充电模块把能量灌进电池;放电时电池通过内部功率路径给系统输出供电。
- 系统输出线:芯片内部会生成一路或几路系统电源(有的型号会引出降压输出、LDO 输出或者负载开关输出),供 MCU、传感器、无线模块等使用。
这里最关键的是电源路径管理。当 VBUS 有效时,系统负载优先由适配器供电,同时充电模块以设定电流给电池充电;当 VBUS 突然拔出,内部路径管理电路会在微秒量级内切到电池供电,保证系统不掉电。这一点非常重要,我之前见过用分立方案的板子,USB 拔掉的瞬间系统就复位,就是这个切换过程没做好。
VBUS(5V) → 输入保护 → 电源路径管理 → 系统输出 │ ↓ 充电模块 → BAT(电池)我在系统设计说明里画的就是这样一条简化路径。实际芯片内部还有反向阻断、电池欠压检测、放电过流保护等模块,但理解主链路就够了,剩下的细节看数据手册。
2.2 三套运行状态:充电态、放电态、休眠态
PCA9422 的核心状态大致可以归为三类,MCU 的所有策略都是围绕这三套状态展开的:
充电态(Adapter Connected / Charging):适配器接入,系统从 VBUS 取电,充电模块按恒流/恒压/涓流三个阶段给电池充电。此时 MCU 应该做的是:读取充电状态寄存器、记录充电进度、根据温度传感器决定是否限制充电电流。
放电态(Battery Powered / Discharging):适配器断开,系统由电池通过放电功率路径供电。此时 MCU 要重点关注电池电压和剩余电量,在低电量时主动关闭非必要负载,最后通过 PMIC 的放电终止保护或者自己控制关机逻辑进入休眠。
休眠态(Ship Mode / Standby):系统整体进入极低功耗。PMIC 关断大部分输出,只保留唤醒监测电路和电源路径管理逻辑,MCU 也进入睡眠。这个状态下整个系统的电流一般可以压到微安级别,适合长时间待机。
区分这三个状态,MCU 侧不需要猜,PCA9422 一般会把当前状态映射到寄存器位里,同时通过中断引脚主动通知。我第一次调的时候就吃了亏:只靠轮询寄存器判断状态,结果适配器刚拔掉那一下没有立刻感知到,系统在错误的策略下运行了几十毫秒。后来改成了中断触发 + 寄存器确认的双重机制,才算稳定。
2.3 与 MCU 之间的关键接口
硬件上,PCA9422 和 PIC18F86J15 之间的连接其实非常少,我列了一个表:
| 信号 | 方向 | 说明 |
|---|---|---|
| SCL / SDA | 双向 | I2C 通信,速率一般取 100kHz~400kHz,配置所有工作参数并读取状态 |
| /INT | PMIC → MCU | 中断输出,低电平有效,充电完成、适配器插拔、故障保护等事件都会拉低 |
| EN_SYS | MCU → PMIC | 系统使能信号,用于让 PMIC 从休眠态恢复或进入休眠态 |
| GPIO 指示 | PMIC → MCU | 有些型号会留电源良好、充电中、充满等状态引脚,可接可不接,用 I2C 也能读到 |
这里有一个值得注意的细节:/INT 和 I2C 的时序配合。PMIC 在中断事件发生时拉低 /INT,MCU 被外部中断唤醒后,第一件事不是立刻去读寄存器,而是先等待几毫秒,等 PMIC 内部状态稳定后再读。我早期测试时在中断服务函数里马上读寄存器,读回来的状态经常是旧的,或者读到中间态。后来在中断服务里加了一个pmic_int_flag = 1的标记,在主循环里延迟处理,问题就消失了。
3. PIC18F86J15 的初始化流程与关键配置
3.1 上电第一件事不是配时钟,而是读取系统来源
很多人在 PIC 上写裸机程序,习惯先把振荡器、GPIO、定时器全部配好,再去碰外部芯片。但在 PMIC + MCU 的系统里,上电顺序应该反过来:先确认"我这次启动是从哪里来的",再决定底层硬件怎么初始化。
我实际的启动流程是这样的:
- MCU 上电复位后,先把 I2C 模块配置成主机模式(这步要尽量早)。
- 通过 I2C 读 PCA9422 的设备 ID 寄存器,确认通信正常。
- 读电源来源状态:如果当前是 VBUS 供电,系统可以正常全速运行,甚至可以做一次电池充电管理;如果当前是电池供电,且电池电压已经偏低,就要跳过自检等耗电操作,直接进入低功耗策略或提示用户充电。
- 根据电源来源设置系统运行模式,然后再初始化传感器、无线模块等其他外设。
这个流程听起来简单,但很实用。比如环境监测终端在仓库里放了一个月,电池快没电了,用户按了开机键。如果 MCU 上电先做一堆传感器初始化、无线模块搜网,电流瞬间拉高,电池电压会被拉低,PMIC 的欠压保护直接跳掉,表现为"按下开机没反应"。先判断电源来源然后决定要不要全速启动,就能避免这个问题。
3.2 I2C 底层读写封装:稳比快重要
PIC18F86J15 的 MSSP 模块可以配置成 I2C 主机模式。我的配置方法如下:
- 设置
SSPCON1为主模式(SSPM3:SSPM0 = 0b1000),开启 I2C 使能。 - 用
SSPADD设置波特率。以内部时钟 16MHz、目标波特率 100kHz 为例,SSPADD = (Fosc / (4 * 100000)) - 1 = 39。实际我会写成一个带参数的计算宏,方便调试时切换速率。 - 使能
SSPSTAT的 SMP 位( slew rate control),在 100kHz 低速模式下可以放宽时序要求。
读写 PMIC 寄存器的代码,我封装成了三个函数:
// 写单个寄存器 uint8_t pmic_reg_write(uint8_t reg, uint8_t val) { i2c_start(); i2c_write(PMIC_ADDR << 1 | 0); // 写地址 i2c_write(reg); i2c_write(val); i2c_stop(); return pmic_wait_ack(); } // 读单个寄存器 uint8_t pmic_reg_read(uint8_t reg) { uint8_t val; i2c_start(); i2c_write(PMIC_ADDR << 1 | 0); i2c_write(reg); i2c_restart(); i2c_write(PMIC_ADDR << 1 | 1); // 读地址 val = i2c_read(0); // NACK,表示只读一个字节 i2c_stop(); return val; }有两个地方是我反复踩过的坑:
第一个是超时处理。如果 PMIC 处于某种保护状态,I2C 可能会不响应。底层函数必须加超时,不能死等 ACK。我在循环里加了计数器,超过一定次数就返回错误,避免 MCU 被 I2C 总线卡死。PMIC 本身很少出这个问题,但总线上的其他设备偶尔会拖住 SCL。
第二个是回读验证。写完寄存器后不要直接信结果,立刻读一遍回来比对。PCA9422 的部分寄存器带保护属性,或者受状态机限制,写入不生效的情况是存在的。如果写配置时没回读,后面所有行为都会基于错误配置,排查起来非常痛苦。
3.3 多路输出电源的启动顺序
如果 PCA9422 配了多路系统输出,比如一路给 MCU、一路给传感器、一路给无线模块,输出上电顺序会影响系统稳定性。我遇到过一次奇怪的问题:无线模块先于 MCU 上电,模块在上电瞬间把电源拉出很大的毛刺,导致 MCU 复位,然后一直循环复位,根本进不了主流程。
正确的做法是:MCU 所在的那一路必须最先上电,等 MCU 跑起来之后,再由 MCU 通过 I2C 或 GPIO 使能后续输出。也就是说,电源域的使能顺序本身就是一个"由软件掌控的上电时序"。我在配置里做了三个状态位,分别控制传感器电源、无线模块电源和外围接口电源,代码顺序是:
- 确认 MCU 电源域正常。
- 读取配置,决定是否需要电源。
- 使能传感器电源,等待电源良好。
- 再使能无线模块电源。
如果 PMIC 支持电源良好指示,我会在使能后读对应状态位,确认电压稳定才继续下一步。这一步可以用示波器验证,我实际测过的时序图上,三路输出的开启间隔大约是 2~3 毫秒,不会出现电压毛刺。
4. 充电管理:从寄存器配置到状态机轮询
4.1 充电参数怎么定:先看电池规格书
充电管理的第一步是确定电池参数。我这里用一块标称 3.7V、充电限制电压 4.2V、容量 1500mAh 的锂电池来举例。
充电电流一般按 0.5C 到 1C 设置。0.5C 就是 750mA,1C 就是 1500mA。在实际产品里,我会综合考虑充电时间、电池寿命和发热,通常选 0.5C~0.7C。如果是小容量纽扣电池或者软包电池,电流要更保守。
PCA9422 的充电目标电压和充电电流一般在寄存器里配置,常见做法是:
- 恒流阶段:以设定的充电电流给电池充电,电池电压逐渐升高。
- 恒压阶段:电池电压到达 4.2V 时,充电电压钳位,电流自然下降。
- 截止阶段:电流降到截止电流(一般是恒流电流的 1/10,比如 75mA),判定充满。
这里要提醒一句:电池的放电终止电压和充电限制电压,一定要按电池规格书设置。有些工程师喜欢把充电限制电压设高一点"多充一点电",短时间内问题不大,但长期循环会加速电池容量衰减,甚至引起鼓包。我自己的习惯是严格按规格书设置,并且留 20mV~30mV 的裕量,比如规格书说 4.2V,我实际配置 4.18V,安全性更好。
4.2 MCU 侧充电状态机
充电期间,MCU 不能一直轮询 I2C,浪费功耗;也不能完全不管,因为有些异常状态需要处理。我用的方案是中断触发 + 定时状态机:
- PMIC 在以下时刻拉低 /INT:充电开始、充电完成、适配器拔出、故障保护。
- MCU 收到中断后,设置一个
charging_event标志。 - 主循环里每 1 秒检查一次标志,如果置位,则读充电状态寄存器,更新本地充电阶段记录,并关闭中断标志。
充电状态机的跳转逻辑我写得很简单:
IDLE → CHARGING:检测到 VBUS 且电池未满 CHARGING → FULL:充电状态寄存器显示充满 CHARGING → FAULT:读取到保护事件 FULL → IDLE:适配器拔出,或者电池放电到低于再充电阈值我还在状态机里加了一个"再充电阈值"的判断。很多 PMIC 在充满后并不会一直停在满电状态,当电池电压回落到某个阈值时会自动重新充电,或者需要 MCU 重新使能。设计者要明确这个行为,否则会出现"充满之后又自动充电、充满之后又自动充电"的循环,对电池寿命不友好。
4.3 充电保护事件的处理优先级
充电过程中的异常保护,我的处理原则是:先安全,后服务。碰到过温、过压这类保护事件,MCU 第一时间停止充电相关操作,然后通过 PMIC 寄存器读取具体原因,记录下来,再决定是否需要关机还是降额重试。
温度保护是锂电池系统里最容易被轻视的点。如果板子上有 NTC 接到电池附近,一定要把 NTC 配置进 PMIC 的温控逻辑里。我曾经做样品时为了省一个电阻,没接 NTC,结果长时间充电时电池表面温度能到 45°C 以上,虽然没出安全问题,但电池衰减明显变快。加了 NTC 之后,系统在高温环境下会自动降低充电电流,实测电池温度能压在 40°C 以内。
5. 系统低功耗设计与电量监测
5.1 先把功耗预算做出来,再谈省电
低功耗不是靠某一行代码实现的,而是先从纸面计算开始。我每做一块电池板,都会先列一张功耗表:
| 模块 | 工作电流 | 睡眠电流 | 工作时长 | 睡眠时长 | 平均电流 |
|---|---|---|---|---|---|
| MCU | 3.0 mA | 2 µA | 10 ms/s | 1000 ms/s | 32 µA |
| 传感器 | 1.5 mA | 1 µA | 50 ms/s | 1000 ms/s | 76 µA |
| 无线模块 | 120 mA | 5 µA | 20 ms/s | 5000 ms/s | 480 µA |
| PMIC 静态损耗 | - | 15 µA | 全时 | 全时 | 15 µA |
平均电流的计算方法是:平均电流 = 工作电流 × 工作时长占比 + 睡眠电流 × 睡眠时长占比。比如 MCU 这一行:3mA × 10/1010 + 2µA × 1000/1010 ≈ 32µA。把所有模块加起来,就能算出系统的总平均电流,然后除以电池容量,得到理论续航时间。
这一步最重要的作用是帮你知道省电的核心在哪。如果无线模块每秒要发一次数据,那它占了平均电流的大头,纠结 MCU 睡眠电流从 2µA 降到 1µA 意义不大。先集中精力优化无线模块的发送频率和发送时长,效果立竿见影。
5.2 让 PMIC 和 MCU 一起"睡觉"
系统的深度休眠,核心是 PMIC 进入待机态 + MCU 进入 SLEEP。我的操作顺序是:
- 通过 I2C 配置 PMIC,关闭所有非必要输出通道。
- 将 PMIC 设置为待机模式,只保留唤醒监测。
- 等待一个 I2C 总线空闲时间,确保 SDA/SCL 都被释放。
- 将唤醒源配置好(外部中断引脚、定时器等)。
- MCU 执行
SLEEP指令。
这里有个非常隐蔽的问题:PMIC 的 I2C 地址和数据线在休眠时可能不再保持有效。如果 MCU 在进入休眠前没有把 I2C 总线释放干净,SDA 被拉低,而 PMIC 在休眠模式下不再响应,总线就会锁死,唤醒后通信失败。我的解决办法是在休眠前把 I2C 模块禁用,并且把 SCL/SDA 引脚重新配置为普通输入或带上拉输入模式,确保总线处于空闲电平。
唤醒时,PMIC 通过 EN_SYS 引脚或者中断引脚把 MCU 从睡眠中拉起来。我实测过整套流程:从外部事件发生到 MCU 跑起第一行代码,大约需要 3~5 毫秒,大部分时间消耗在 PMIC 内部电源路径重新建立电压上。如果你的系统对唤醒延迟很敏感,需要在 PMIC 选型和配置时提前规划。
5.3 电量监测:电压法和补偿曲线的取舍
PCA9422 这类 PMIC 不一定内置高精度库仑计,所以我在能量监测上主要用电压法:通过 ADC 读取电池电压,再根据放电曲线估算剩余电量。这里有个关键点:不能直接测空载电压去查表,因为电池在负载下电压会跌,而在充电状态下电压会虚高。
我的做法是采样三种电压:
- 静置电压:休眠前或唤醒后立刻采一次,此时负载最小,最接近开路电压。
- 负载电压:无线模块发送瞬间采一次,用于判断电池内阻是否过大、是否需要提前保护。
- 恢复电压:大电流负载结束 100ms 左右再采一次,用来修正之前负载下的电压跌落幅度。
然后我建立一张"电压-容量"查表,比如 4.2V→100%、3.9V→70%、3.7V→40%、3.5V→15%、3.3V→5%。温度对电池曲线影响很大,低温下满电电压也会偏低,所以我根据当前温度选不同的查表系数。这个方案精度大约在 ±10% 左右,对大多数消费级产品够用了。如果产品需要高精度电量显示,就得换带库仑计燃料表的 PMIC,或者外接电量计 IC,但那会显著增加成本和软件复杂度,按需选择。
6. 实测踩过的坑和调试建议
6.1 寄存器写不进去,不是 I2C 的问题
有一段时间,我在样机上发现 PCA9422 的某个寄存器怎么都写不进去。逻辑分析仪上波形完全正常,ACK 也正常,但写完后回读还是默认值。查了很久才发现,这颗 PMIC 的某些配置寄存器只在特定状态下允许写入,比如正在充电时不能修改充电电流,或者 PMIC 处于保护状态时寄存器被锁定。
遇到这种情况,正确流程是:先读当前状态寄存器,确认处于什么模式;如果是保护状态,先清除保护锁存,或者等故障消除;然后再写入配置。数据手册里一般会有一张表,标明哪些寄存器可读写、哪些只读、哪些在什么状态下可写。我建议把这部分内容整理成自己的寄存器速查表,哪怕是手写在纸上,也会让调试快很多。
6.2 充电纹波大,先看 PCB 布局再看配置
第二次做板子时,我遇到充电电流抬升时系统电压有明显的开关纹波,无线模块的误码率也跟着升高。一开始怀疑是 PMIC 配置问题,改了好几组电容参数都没用。后来用示波器探头直接点 PMIC 输入和输出的陶瓷电容引脚,才发现问题出在布局:输入电容离芯片引脚太远,走线绕了一圈,等效串联电感把开关毛刺放大了。
PCB 布局上,我总结了几条硬性规则:
- 输入电容、功率电感和输出电容要尽量靠近 PMIC 引脚,走线短而粗。
- 电池线和充电线要和其他信号线拉开距离,尤其是 I2C 线。
- 地回路要单独走,避免功率地电流流过信号地。
- 调试时探针要用短弹簧地线,不要用长的鳄鱼夹地线,否则测出来的波形全是振铃。
改版之后,同样配置下纹波实测从 80mV 降到了 20mV 以内。这个案例说明,电源问题很多时候不是软件能解决的,layout 的优先级应该放在配置参数之前。
6.3 唤醒失败的隐蔽原因:外设电源域没关干净
深度休眠流程跑通之后,我遇到过一次偶发唤醒失败:有时候一睡就醒不来,只有按复位键才能恢复。排查了很久,发现问题出在传感器没断电。传感器虽然处于睡眠模式,但它的 I2C 接口没有完全释放,SDA 引脚一直把总线拉低。PMIC 在休眠态下即使唤醒逻辑收到事件,也因为这个下拉把唤醒信号干扰了。
后面我把休眠流程改成:先通过 PMIC 关闭传感器电源域,再让 MCU 进入睡眠,就再没出现过唤醒失败。这里的关键点是,外设的电源域一定要由 PMIC 物理断开,而不是依赖外设自己的软件睡眠。软件睡眠只是让芯片内部时钟停了,但 IO 口状态不可控,极容易在共享总线上制造问题。
6.4 量产前的电源系统回归清单
最后分享一份我自己常用的电源系统验证清单,每次改板或者改配置之后都会跑一遍:
- 上电时序:用示波器记录各输出电源域的上电顺序和斜率,确认无毛刺。
- 静态功耗:整个系统进入休眠后测平均电流,和功耗预算表对比。
- 充电曲线:从低电量充到满,记录恒流、恒压、截止三段时间,确认与设定一致。
- 热插拔测试:反复插入拔出适配器,确认系统不掉电、PMIC 中断正常。
- 低电压触发:让电池电压降到保护值以下,确认系统能安全关机而不是反复重启。
- 唤醒延迟:外部事件触发到 MCU 恢复运行的时间,确认在应用可接受范围内。
- I2C 稳定性:长时间运行后校验寄存器读写,确认没有总线锁死。
我自己的体会是,PMIC 加 MCU 的组合,真正的难点不是把每一颗芯片跑起来,而是把两者的状态机对齐。PMIC 是一个以硬件为主的实时系统,MCU 是一个以软件为主的逻辑系统,二者之间靠中断和寄存器同步。只要把接口信号、状态跳转和异常处理设计清楚,这套方案在可靠性和灵活性上都会明显优于全分立方案。如果你是第一次做电池供电产品,强烈建议先从这套"硬件状态机 + 软件决策"的框架入手,先把电源路径画明白,再去写代码,会少走很多弯路。