做便携设备的朋友应该都有同感:电源管理看起来简单,真正做起来却特别容易翻车。最近我们在做一款某手持式工业数据记录仪,核心方案定的是 PCA9422 这颗电源管理 IC,搭配 PIC32MX695F512L 这款 32 位 MCU,由它们共同完成整套电源管理链路。从硬件接口到充电流程,从动态调压到低功耗睡眠,再到各种保护逻辑,整个项目走下来踩了不少坑,也沉淀了不少可以直接抄作业的经验。这篇文章就把这套完整方案的设计过程、关键代码、实测数据和排查技巧一次性整理出来,给正在选型和做电源管理的朋友一个参考。
这篇文章适合谁看?如果你正在做便携设备、电池供电的仪器仪表、手持终端或者低功耗 IoT 设备,手头刚好在纠结“充电怎么管、多路电压怎么出、待机功耗怎么压”,那这篇应该能帮上忙。我会尽量说人话,有参数有代码,也把为什么这么做的逻辑讲透。
1. 整体方案设计:为什么选 PMIC + MCU 的组合
1.1 需求拆解:一套“完整电源管理”到底要管什么
在定方案之前,先要把需求写清楚。我们这台手持记录仪的目标不算复杂,但每一项都绕不开:
- 供电来源只有一节 3.7V 的锂电池,电压范围大概在 2.8V 到 4.2V 之间,充满 4.2V,放电到 2.8V 就必须停。
- 系统需要稳定的 3.3V 给 MCU 和数字传感器,还需要一路 1.8V 给低功耗外设,另外模拟前端需要干净的 3.3V 电源轨。
- 需要完整充电逻辑,包括预充、恒流、恒压、充满截止、电池温度保护,不能只靠一个“电池充放电板”糊弄。
- 待机电流目标在 50μA 以下,这意味着 MCU 和 PMIC 都必须能进低功耗模式,而且能互相唤醒。
- 要能做动态电压调节,比如外设不需要满速时,把主电源轨电压降下来省电。
- 故障保护不能只靠硬件 Pin 脚,最好还能通过软件实时读状态、做分级处理。
这些需求单独看没什么,但放在一起,如果全部用分立器件拼,电路会非常臃肿。这也决定了后面方案选型的方向。
1.2 方案选型:分立方案、SoC 方案和 PMIC+MCU 方案怎么权衡
一开始我们其实考虑过两种方案:一种是完全用分立 DCDC + LDO + 充电 IC 拼,另一种是干脆选一颗带内部 PMIC 的 SoC。
分立方案看起来每颗芯片都不贵,但真摆起来很麻烦。充电管理不是只给电池接一个恒流源就行,还要处理 USB 插入检测、预充、恒流恒压切换、截止电流判断、NTC 温度保护,这些状态机自己写可以,但硬件器件一多,保护逻辑很难做完整。而且便携设备强调的是小体积和低静态电流,分立器件叠起来静态功耗很难压下去。
SoC 自带 PMIC 的方案确实省事,但问题在灵活性。项目里不同的外设供电需求、不同版本的电池规格、不同的功耗策略,都只能受制于 SoC 内置 PMIC 那一亩三分地。真到了后期调功耗,想改个 LDO 电压或者加一路输出,基本没有余地。
最后定的方案是独立 PMIC + MCU:PCA9422 负责充电、升降压、LDO 输出和硬件保护,PIC32MX695F512L 作为主控制器,通过 I2C 和中断引脚管理整个电源状态。这个组合最大的好处是,把需要硬实时处理和保护的东西交给 PMIC,把需要智能决策的东西交给 MCU,两边各干各擅长的事。
1.3 电源树规划与目标指标
我画电源树的时候习惯先把每一路电压、来源、负载和备注列清楚,方便后面检查有没有重叠或者遗漏。
| 电源轨 | 来源 | 典型电压 | 主要负载 | 说明 |
|---|---|---|---|---|
| VBAT | 锂电池直供 | 2.8V~4.2V | PMIC 输入、电池电压采样 | 不能直接给 MCU 供电,电压范围超出 3.3V 系统要求 |
| VSYS_3V3 | PCA9422 Buck-Boost 输出 | 3.3V | MCU、数字传感器、存储卡 | 主电源轨,软件可调电压 |
| VDD_1V8 | PCA9422 LDO1 输出 | 1.8V | 低功耗外设、电平转换 | 待机时可关闭 |
| AVDD_3V3 | PCA9422 LDO2 输出 | 3.3V | 模拟前端、ADC 参考源 | 对纹波敏感,LDO 供电更干净 |
性能指标方面,我们给自己定的硬指标是:
- 整机待机电流:低于 50μA。
- 系统满载平均功耗:不超过 0.5W。
- 充电电流:默认 500mA,可根据电池容量调整,最大到 1A。
- 电池充满截止电压:4.2V ± 0.5%。
- 主电源轨动态调压范围:1.8V 到 3.3V。
这些指标不是拍脑袋定的,而是根据电池容量、外壳散热能力和使用场景算出来的。比如 500mA 充电电流对 2000mAh 电池来说是 0.25C,兼顾了充电时间和电池寿命,后面软件里设参数时也按这个逻辑来。
2. 硬件接口设计:把两颗芯片可靠地连起来
2.1 PCA9422 的功能梳理:不只是一颗“充电芯片”
PCA9422 这类 PMIC 很容易被理解成“充电芯片”,其实它是集成度相当高的电源管理单元。从我们实际用到的功能来看,至少包含这几块:
- 电池充电管理:支持锂电池预充、恒流、恒压、截止、充电安全定时器,还有 NTC 温度检测,插 USB 时的充电协议检测能力也在里面。
- 高效率 Buck-Boost 转换器:这是最重要的一路输出,因为要应对锂电池从 4.2V 放到 2.8V 的完整区间,输出始终稳定在 3.3V。如果只用 Buck(降压),电池电压低于 3.3V 时输出就顶不住了;只用 Boost(升压),电池高于 3.3V 时效率又难看。升降压方案在这个场景里是最稳的选择。
- 两路 LDO:给对纹波敏感的模拟电路或低压数字电路供电,比直接用 Buck-Boost 再滤波省事得多,静态电流也更低。
- I2C 接口和控制寄存器:几乎所有电压、电流、保护阈值、工作模式都能通过寄存器配置,这为软件动态管理留了很大余地。
- 中断输出:充电完成、电池欠压、温度异常、输入电源插拔等事件都能通过中断引脚通知 MCU,MCU 不需要一直轮询。
2.2 PIC32MX695F512L 侧的接口配置
PIC32MX695F512L 是 MIPS 内核的 32 位 MCU,主频 80MHz,512KB Flash、64KB RAM,做电源管理控制绰绰有余。它内部带了多个 UART、I2C、SPI 和 ADC,其中 I2C 和外部中断正是我们需要的。
这颗 MCU 本身工作在 3.3V,所以接口电平上不用做转换,直接和 PCA9422 连接就行。我把连接关系整理成了下面的表格,照着接基本不会错:
| PCA9422 引脚 | PIC32MX695F512L 引脚 | 说明 |
|---|---|---|
| SDA | I2C1 SDA | 双向数据线,需要上拉电阻 |
| SCL | I2C1 SCL | 时钟线,需要上拉电阻 |
| IRQ | 某个支持外部中断的 GPIO | 下降沿触发,用于电源事件通知 |
| EN/SYS_EN | 普通 GPIO | 控制 PMIC 主输出使能,软件可关断 |
| STAT 状态指示 | 普通 GPIO(可选) | 读取充电状态指示灯信号 |
这里必须提醒一个细节:SDA 和 SCL 的上拉电阻不能省。I2C 是开漏结构,没有上拉根本拉不高电平。我们 100kHz 速率下用了 4.7kΩ 上拉到 3.3V,如果跑 400kHz,建议换成 2.2kΩ,否则上升沿会偏缓,时序要求严格的设备可能出现通信偶发失败。
2.3 电源时序、去耦与 PCB 布线要点
硬件上最容易翻车的不是原理图连线,而是时序和布局。
上电时序上,PCA9422 的 Buck-Boost 输出建立需要一点点时间,而 MCU 复位释放之后会立刻跑程序。如果 MCU 一开始就去读 PMIC 寄存器,很可能读到的是“脏”数据。我们做的处理是:硬件上用 Buck-Boost 输出接到 MCU 的供电,MCU 天然等电压起来才上电;软件上则再加一道握手,MCU 启动后先等 10ms,再读 PMIC 的器件版本寄存器,读到正确值才继续往下走。
去耦电容这里不能省。Buck-Boost 输出端我放了 22μF 陶瓷电容加 0.1μF 高频电容,靠近负载端再放一组小电容。LDO 输出端至少 1μF,不然动态响应会差。电池输入端需要一个大容量电容,我们用了 47μF,用来吸收负载瞬态电流,防止电源拔插瞬间电压跌落导致系统复位。
PCB 布局上,PMIC 的功率电感和输入输出电容要尽量靠近芯片,走线要短粗。模拟电路用的 AVDD_3V3 供电要单独走线,从 LDO 输出端拉出去,不要在数字电路底下绕一圈。我见过有人把模拟电源和数字电源在 PCB 上直接共享一段走线,结果 ADC 采样噪声比数据手册标称值差了一倍多。
3. 软件实现:从 I2C 驱动到充电状态机
3.1 先打好 I2C 读写底子:寄存器驱动与调试命令
硬件连通之后,第一步不是写业务逻辑,而是把 I2C 读写函数跑通。所有电源管理功能都建立在寄存器读写之上,底子不稳后面全是空中楼阁。
我习惯先把所有寄存器地址用宏定义管理,不要每次直接写魔数。这里只展示框架,实际地址以 PCA9422 datasheet 为准,因为不同版本可能存在差异:
#define PCA9422_I2C_ADDR 0x68 #define REG_DEVICE_ID 0x00 #define REG_BUCK_VOUT 0x10 #define REG_CHG_CTRL 0x20 #define REG_CHG_STATUS 0x25 #define REG_INT_STATUS 0x30 #define REG_SYS_CTRL 0x40 static uint8_t pmic_read_reg(uint8_t reg) { uint8_t val = 0; i2c1_start(); i2c1_write((PCA9422_I2C_ADDR << 1) | 0); // 地址 + 写 i2c1_write(reg); // 寄存器地址 i2c1_repeated_start(); i2c1_write((PCA9422_I2C_ADDR << 1) | 1); // 地址 + 读 val = i2c1_read(); i2c1_stop(); return val; } static void pmic_write_reg(uint8_t reg, uint8_t val) { i2c1_start(); i2c1_write((PCA9422_I2C_ADDR << 1) | 0); i2c1_write(reg); i2c1_write(val); i2c1_stop(); }这些 i2c1_start()、i2c1_write() 函数内部封装了 PIC32 的 I2C 外设操作,项目里可以换成 Harmony PLIB 或者自己写寄存器。关键是流程要对:写寄存器一定先发器件地址,再发寄存器地址,最后发数据;读寄存器要用 repeated start,而不是简单 stop 再 start,否则多卡设备容易出时序问题。
驱动写完别急着上业务逻辑,先写一个调试命令行,能手动读写任意寄存器。这一步非常重要,后面排查问题全靠它。比如怀疑充电配置没生效,直接命令行读 REG_CHG_CTRL,一眼就能看出寄存器值对不对,比反复改代码猜问题快十倍。
3.2 充电流程实现:从插电到充满的完整状态机
充电是电源管理里最需要耐心的部分。PCA9422 本身能完成大量底层工作,但 MCU 还是要负责配置参数和监控状态。
先算充电参数。我们用 2000mAh 电池,充电电流设 500mA,也就是 0.25C,既不会让电池发热太厉害,又能在 4 到 5 小时内充满。截止电流(EOC)设 50mA,大约是 0.025C,这个值很关键:设太大电池充不满,设太小充电时间会拖得很长。CC 到 CV 切换由 PMIC 自动完成,MCU 只需要把寄存器配置好:
void battery_charge_config(void) { // 设置恒流 500mA pmic_write_reg(REG_CHG_CTRL, 0x30); // 设置截止电流 50mA pmic_write_reg(REG_CHG_CTRL + 1, 0x20); // 设置 CV 电压 4.2V pmic_write_reg(REG_CHG_CTRL + 2, 0x2A); // 使能充电,同时开启安全定时器 pmic_write_reg(REG_CHG_CTRL + 3, 0xC1); }这里寄存器地址我故意用偏移量表示,正式项目里建议直接展开成独立宏。使能充电放在最后一步,因为有些 PMIC 在使能瞬间就会开始采样电压电流,如果前面参数都还没写就被触发,容易把错误参数锁进去。
充电状态监控用轮询 + 中断结合。插电瞬间 PMIC 通过 IRQ 通知 MCU,MCU 进入充电配置流程;之后周期性读 REG_CHG_STATUS,解析当前状态:
typedef enum { CHG_OFF = 0, CHG_PRE_CHARGE, CHG_CC_CHARGE, CHG_CV_CHARGE, CHG_DONE, CHG_FAULT } chg_state_t; chg_state_t battery_get_charge_state(void) { uint8_t st = pmic_read_reg(REG_CHG_STATUS) & 0x07; switch (st) { case 0: return CHG_OFF; case 1: return CHG_PRE_CHARGE; case 2: return CHG_CC_CHARGE; case 3: return CHG_CV_CHARGE; case 4: return CHG_DONE; default: return CHG_FAULT; } }有几个点一定要提醒:
- 电池电压低于预充阈值时,PMIC 会用小电流预充。这个阶段充电电流只有恒流阶段的十分之一左右,目的是先把电压拉起来,防止大电流冲击损坏电池。如果长时间停在预充状态,大概率是电池过放或者电池连接电阻太大。
- 温度保护是硬性的。NTC 引脚不接或短路,PMIC 可能会直接禁止充电。我们调试时遇到过手上静电导致 NTC 采样异常,结果充电一直不启动,查了一个下午才发现是 NTC 没接对。
- 充电完成判断不是看电压到了 4.2V,而是看电流降到了 EOC 阈值以下。所以充满后的状态标志比电压值更可信。
3.3 动态电压调整:用 I2C 写寄存器实现 DVS
PCA9422 的 Buck-Boost 输出电压是可以软件改的,这是整套电源管理里比较出彩的一个功能。场景是这样的:设备正常工作时主电源轨 3.3V,但当系统进入低负载模式,比如数据记录仪夜间只保存传感器数据不干活时,其实不需要 3.3V 这么高的电压,降到 2.5V 甚至 1.8V,整机功耗能明显下降。
动态电压调整实现起来很简单,核心就是一两条寄存器写:
void pmic_set_buck_voltage(uint8_t voltage_code) { pmic_write_reg(REG_BUCK_VOUT, voltage_code); }具体电压码值和输出电压的对应关系要查 datasheet 里的映射表,不同批次芯片可能略有差异,正式代码里我建议做一张常量表,按电压值查表,避免把转换逻辑散落在业务代码里。
但这里有个容易忽略的坑:切换电压太快,负载端供电瞬间掉得太多,MCU 自己可能会死机。因为 MCU 自己就挂在这条电源轨上。我们实测直接 3.3V 跳到 1.8V,MCU 复位概率很高。解决办法是分级跳变,比如 3.3V -> 2.8V -> 2.5V,每级间隔 5ms,同时把 Flash 写操作、ADC 采样这类关键任务避开电压切换窗口。这一点要在项目初期就设计进去,后期再补会很痛苦。
3.4 低功耗模式:让整板真正睡下去
低功耗是整个电源管理里最考验细节的部分。MCU 进 Sleep 之前,要把 PMIC 也调到合适模式,同时要保证唤醒链路不出问题。
我们采用的方式是:
- MCU 先把不需要的外设关闭,I2C 外设时钟也关掉。
- 通过 REG_SYS_CTRL 把 PCA9422 的 Buck-Boost 设置为待机模式(或者按需关闭一路 LDO)。
- 配置 IRQ 为唤醒源,MCU 进入 Sleep。
- 插拔 USB、充电完成、电池欠压等事件都会触发 IRQ,把 MCU 拉起来。
代码骨架大概是:
void system_enter_sleep(void) { // 关外设 disable_sensor_power(); adc1_disable(); // 进入前确保 I2C 通信干净 pmic_write_reg(REG_SYS_CTRL, 0x02); // PMIC standby i2c1_disable(); // 配置唤醒中断 set_external_irq_falling(IRQ_PIN); sleep_mode_enable(); }实测下来,整机待机电流做到约 42μA,其中 MCU Sleep 大概占 27μA,PMIC 待机加电路损耗占 8μA,剩下的主要是电源分压电阻和泄漏电流。如果还想再压,可以把电池电压采样分压电阻换成大阻值或者用 MOS 管在待机时切掉分压通路,能再省几个微安。
这里有个特别容易踩的坑:只要 MCU 的 GPIO 还挂在 PMIC 的某个输出脚上,哪怕这个 GPIO 处于输入模式,也可能通过内部保护二极管漏电。解决办法是进入睡眠前把不用的 GPIO 全部配置为高阻输入,或者显式拉到固定电平。
4. 实测踩坑记录:那些文档里不会写的问题
4.1 上电后 I2C 通信失败:一上来就卡在 ACK
第一个大坑出现在第一版样机的软件调通阶段。上电后 MCU 死活读不到 PMIC 的 ACK,示波器看 SDA、SCL 都有波形,但返回就是 NACK。
排查下来发现是 I2C 地址的问题。PCA9422 的器件地址是 7 位地址,代码里左移一位后是 8 位总线地址,但我在宏定义里把 8 位地址直接当 7 位用了,地址错了一位,自然收不到 ACK。这里建议大家写驱动时统一用 7 位地址存储,在发送时再左移,降低出错的概率。
另外一个隐蔽问题是上拉电阻供电没对上。I2C 上拉接到了一个 5V 电源上,而 MCU 是 3.3V IO,虽然看起来能通信,但长期运行有电平风险,而且 PMIC 侧如果内部钳位,低电平释放会出现怪异的波形。最后全部统一到 3.3V 上拉,问题消失。
4.2 充电电流设了 500mA,实测只有 100mA
这个问题困扰了我们将近两天。寄存器配置确认没问题,充电状态也显示正常,但用电流钳实测充电电流就是起不来。
仔细看数据手册才发现,充电电流的实际值还取决于输入电源的限流能力。开发者板上 USB 口被上游限流到 100mA,PMIC 检测到输入电流受限,会自动降低充电电流。这是 PMIC 的保护机制在起作用,不是故障。
解决方法是把 USB 口配置成充电口模式,或者把输入电流限制寄存器的值调高。另外,有些场景下需要主动设置输入限流值,这和充电电流是两个独立变量,很多人都搞混。输入限流决定“能从适配器吃多少电”,充电电流决定“给电池灌多少电”,在电池同时给系统供电时,两者要一起规划。
4.3 电池电压采样误差大:ADC 参考源选择问题
记录仪需要定时上报电池电压,但第一版读数比万用表低了 0.15V 左右。这个误差对显示电量百分比来说有点大,而且会直接影响低电量关机判断。
排查过程发现,问题不在分压电阻,而在 ADC 参考源。PIC32 内部的 ADC 参考电压如果直接用电源电压 VDD,而 VDD 本身会随负载波动,那么采样结果当然不准。解决方法是把 ADC 参考源改成内部带隙参考或者外接基准源,同时在软件里做两点校准。校准方法也不复杂:先采集一个已知电压点,再采集一个接近满量程的电压点,算出斜率和偏移,之后每次采样都套公式换算。
如果有条件,分压电阻用 0.1% 精度的,温度系数选低一点的。误差预算里电阻精度占大头,尤其是高阻值电阻,温漂不容小觑。
4.4 唤醒后系统复位:低功耗“睡死”
还有个低级但隐蔽的问题:设备进入低功耗后,外部中断一触发,MCU 起来了,但跑了两秒又复位。抓日志发现是看门狗超时。
原因是我们进了 Sleep 之前忘记喂狗,Sleep 期间看门狗还在跑,时间一长直接复位。这个问题的解法很简单,标准做法是进 Sleep 之前刷新看门狗,并且把看门狗超时时间配置成大于最大睡眠时间;如果想在 Sleep 期间完全停掉看门狗,要确认器件是否支持。就结论而言,看门狗是保护程序跑飞的,不是用来惩罚睡眠的,该停就停,否则低功耗就永远做不好。
顺便说一句,MCU 唤醒以后不要马上去访问外设。PMIC 从 standby 恢复需要一小段时间,如果唤醒中断刚触发就立刻读寄存器,可能拿到异常值。我们后来在中断处理函数末尾加了 5ms 延时,再在主循环里重新初始化 PMIC 状态,问题才彻底解决。
4.5 常见问题速查表
| 现象 | 可能原因 | 排查与处理 |
|---|---|---|
| MCU 上电不启动 | Buck-Boost 输出未建立、EN 脚没拉高 | 量 VSYS 电压,检查 EN 配置 |
| I2C 无 ACK | 器件地址错误、上拉缺失、PMIC 未供电 | 先确认地址,再看上拉电阻和供电 |
| 充电不启动 | USB 未插、输入限流太小、NTC 异常 | 读充电状态寄存器,逐一排除 |
| 充不满 | EOC 阈值偏大、电池老化、连接电阻过大 | 调小 EOC,检查电池连接 |
| ADC 电池电压不准 | 分压电阻精度差、参考源不稳定 | 换 0.1% 电阻,改用内部参考并校准 |
| 睡眠后唤醒跑飞 | 唤醒后外设访问太早、PMIC 未恢复 | 加延时,重新初始化 PMIC |
| 动态调压导致复位 | 电压跳变太快 | 多级跳变,避开关键任务窗口 |
5. 后续扩展与个人体会
5.1 从电压估算到库仑计:电量精度升级路线
目前我们用的是电压查表法来估算剩余电量,优点是简单,不需要额外硬件,缺点是电池带载时电压会偏低,导致剩余电量估算偏保守。频繁充放电之后,电池老化也会让容量映射关系漂移。
如果后续项目对电量精度要求高,可以考虑外接库仑计方案,用低阻采样电阻配合电量计量芯片做库仑积分。这个方案能实时计算进出电量,精度比电压法高不少,代价是 BOM 成本和 PCB 面积都会增加。还有一种折中做法:在 PMIC 的电池电压监测寄存器基础上,叠加电压变化率 dV/dt 判断,能在不增加硬件的情况下改善部分场景的估算精度。
5.2 保护策略的层次化设计
最后再说保护逻辑。电源管理的保护不能只靠 PMIC 硬件,也不能只靠 MCU 软件,而是要做成层次化的。第一层是 PMIC 的硬件保护,过压、过流、过温、短路这些必须由 PMIC 在微秒级别内响应;第二层是 MCU 中断处理,比如充电状态异常、电池欠压警告,MCU 收到 IRQ 后做记录和状态切换;第三层才是应用层策略,比如连续多次欠压告警后强制关机、温度异常时降低充电电流等。
实测下来,把这种层次化保护思路落地到代码里之后,系统稳定性明显提升。之前所有保护逻辑都想塞进 MCU 主循环,结果主循环一卡壳,保护就失效。现在硬件负责“硬保护”,软件负责“策略”,各管各的,非常清晰。
这次项目做下来,我个人最大的体会是:电源管理不能只盯着芯片 datasheet 里的电气参数,还要把它当成一个完整的“状态机系统”来设计。充电有充电的状态机,低功耗有唤醒的状态机,保护有保护的状态机,这些状态之间的转换时序比单个寄存器的值重要得多。还有一点是调试工具要趁早配齐,一支能测小电流的电流钳、一台能抓 I2C 波形的示波器,再加上一个能手动读写寄存器的调试命令行,这三样东西能在后期的电源调优里帮你省下一半的时间。
如果你也在做类似 PMIC + MCU 的电源方案,希望这篇能帮你少走几步弯路。把寄存器映射表啃透、把状态机画清楚、把异常路径都想一遍,这套系统的稳定性会让你很省心。