我最近把一个户外采集器的电源方案整个重做了一遍。核心思路不复杂:用 PCA9422 做前端电源管理,用 PIC32MX795F512L 做主控,把充电、多路输出、电压监测、休眠唤醒全部收到同一套 I2C 寄存器体系里。之前那版方案是分立 LDO 加独立充电模块,外围器件多,休眠电流一直压不下去,加功能就得加器件,系统越来越难维护。换到这两颗芯片之后,供电链路清晰了,固件侧的状态机也顺了。这篇文章不聊纸面参数,只讲我完成这套“完整电源管理”方案时怎么选型、怎么配寄存器、怎么调状态机,以及在实测里踩过的几个坑,对正在做低功耗电池设备的朋友应该有参考价值。
1. 项目起点:为什么把电源管理交给一对专用芯片
1.1 系统的真实需求:完整电源管理到底指什么
很多时候大家聊电源管理,以为就是选一个 DCDC 再加个 LDO,把电压供出来就行。真放到电池供电的产品里,会发现“把电供上”只是最表层的问题。我这边设备的需求大致是:电池输入范围 3.0V 到 4.2V;主控需要 3.3V 数字轨,模拟前端需要 1.8V 低噪声轨;要支持 USB 充电,还要实时知道充电状态;系统需要在没有任务时进入低功耗休眠,整机电流要压到几十微安级别;休眠之后还要能被外部事件或定时器唤醒,并且唤醒后要能快速恢复到正常工作。
这些需求叠在一起,就变成一套完整的电源管理问题:充电要不要由主控管理,是硬件自动充还是软件介入;多路输出怎么分配,是先上 3.3V 还是先上 1.8V,谁去使能;休眠时哪一路电源可以关掉,哪一路必须保持;电池电压掉到多少伏要开始保护数据,而不是等掉电瞬间才傻眼。之前我拿分离器件拼的方案,每个问题都要单独想办法,而这次直接用 PMIC 加 MCU 的组合,等于把充电、供电、监控这些杂事统一成一组寄存器读写,思路顺畅很多。
1.2 PCA9422 的定位:充电与多路供电的前端集成
PCA9422 是一颗面向便携设备的电源管理芯片,内部集成了线性电池充电管理以及两路可配置输出的稳压通道,还带 I2C 控制接口和中断输出。选它的主要原因有几点。第一,它把最容易被忽视的充电路径管理做进去了,USB 插入时系统优先走 USB,同时给电池充电;USB 拔掉后自动切到电池,外部不需要额外加 PMOS 切换电路。第二,两路输出都可以通过寄存器调整电压,我可以用一模一样的硬件板子,通过固件把电压改成 3.3V 或 1.8V,对调试和多版本硬件很友好。第三,它有一颗专门的中断输出脚,能上报 VBUS 插入、充电完成、电池低压等事件,配合 MCU 的外部中断就能实现真正的低功耗唤醒,而不是让主控反复轮询。
当然这颗芯片也有它的边界,比如充电电流上限不算高,所以它更适合中小容量电池的设备。做电动工具那种十几安培充电电流的场景,直接另选其他方案会更合适。但在我这个采集器项目里,电池容量在 2000mAh 左右,充电电流几百毫安完全够用。
1.3 为什么主控选 PIC32MX795F512L 而不选更低功耗的 MCU
很多人会问:既然做低功耗,为什么不直接用一颗 Cortex-M0+ 或者更小的 8 位 MCU?我选 PIC32MX795F512L 不是因为它功耗最低,而是因为这颗器件把 USB、多个 UART、多路 ADC、比较器、I2C/SPI 全塞进了一颗芯片,512KB Flash 和 128KB RAM 也让我不用抠着资源写代码。
实际做下来,PIC32MX795F512L 在 80MHz 全速运行时电流并不低,但低功耗产品通常不是让主控一直全速跑,而是“跑一下、睡很久”。所以真正重要的是它能进入合适的睡眠模式,并且能被中断快速唤醒。PCA9422 的中断线直接接到 RB0 外部中断,充电事件一发生,MCU 从深度睡眠醒来也就是几个微秒级别的时间,完全够用。
另外,PIC32 的 ADC 通道多,我可以同时监测电池电压、PMIC 输出电流采样信号和温度 NTC;这在做完整电源管理时特别方便,不需要再外挂一颗专门的 ADC 芯片,降低了不少系统成本和布局压力。
2. 电源树设计:从电池到每一路电压轨的实际走线
2.1 电池输入级:保护、路径管理与输入电容
电池输入不是直接把电池焊到 PMIC 就完事。我在这条链路上加了 TVS 管做 ESD 防护,后面接一颗 10μF 陶瓷电容和一颗 0.1μF 高频电容,靠近 VBAT 引脚放置。这颗 PMIC 的 VBUS 引脚接 USB 的 5V 输入,内部具备电源路径选择能力:检测到 VBUS 有效时,系统轨由 VBUS 供电,同时把多余电流送去充电;VBUS 断开后,系统轨自动切到电池。这块完全由硬件完成,不需要 MCU 介入,所以即便系统已经进入深度睡眠,插上 USB 充电线后设备也能安全供电和充电。
这里有个很实际的经验:输入电容不要只追求容量大,要注意陶瓷电容的直流偏压特性。一颗标称 10μF 的 0603 电容,在 5V 偏压下实际容量可能只有 3μF 左右,所以我选了 0805 封装、额定电压 16V 的规格,留足裕量,避免输入电压跌落时 PMIC 误判。
2.2 输出电压轨分配与去耦策略
我的电源树里,PCA9422 的 DCDC1 被配置成 3.3V,专门给 PIC32MX795F512L 数字电源、Flash、外围数字逻辑供电;DCDC2 被配置成 1.8V,给模拟传感器、ADC 参考电压、运放供电。把模拟轨和数字轨分开非常有必要,因为 DCDC 开关噪声会通过公共电源阻抗耦合到模拟部分,直接表现为 ADC 读数跳动。
去耦电容按通道布置:每个输出端放 10μF 陶瓷电容做主储能,再并一个 1μF 的低 ESR 电容,靠近 PMIC 输出引脚;到了负载端,每个 IC 旁边再放 0.1μF 到 1μF 不等的去耦电容。PCB 布局上,DCDC 的输入环路和输出环路要尽量短且粗,功率地回流的路径不要和模拟地重叠。我在中间最窄处只留了一个单点连接,也就是模拟地只通过一个 0 欧电阻或者直接窄铜皮在一点汇入系统地。
2.3 上电顺序与使能控制
虽然这颗 PMIC 的两路输出默认上电就会启动,但我在实际设计里把 DCDC2 的使能控制交给了 MCU 的一个 GPIO 管脚。也就是说,每次系统复位后,DCDC1 先给主控供电,PIC32 启动完成、端口初始化完毕之后,再去拉高 GPIO 使能 DCDC2,模拟部分才开始工作。这样做有两个好处:一是避免模拟传感器上电瞬间的浪涌电流拉低 3.3V,导致主控复位;二是休眠时可以通过软件把 DCDC2 关断,彻底断开模拟前端的供电,减少漏电通路。
2.4 测量电源质量的几个小技巧
想判断电源设计有没有问题,一定要学会看纹波。用示波器测纹波时,把带宽限制开到 20MHz,探头使用弹簧接地短线,把探头尖直接点在输出电容正极,接地环压在电容负极,这样测出来的数据才是真实的开关纹波。我之前用过普通长地线夹子,测出来有 80mV 峰峰值,一开始以为是 PCB 布局问题,换成弹簧地线后实际纹波只有 15mV,完全是虚惊一场。
3. I2C 寄存器配置:充电参数与输出电压的计算逻辑
3.1 先读懂寄存器地图:地址、默认值与写保护
使用 PCA9422 的第一步不是急着写代码,而是要把寄存器地图结构搞清楚。这类 PMIC 通常包含设备 ID 寄存器、充电控制寄存器、DCDC 输出电压配置寄存器、中断状态寄存器、模式控制寄存器。上电后 PMIC 会用内部默认配置输出一组电压和电流,MCU 需要在这一基础上做覆盖写入。
我的建议是先读 ID 寄存器做一次通信握手,确认 I2C 地址正确、器件活着,然后再开始配置。另外要注意有些寄存器是 write-once 的,误写后只能靠重新上电恢复调试起来很痛苦。为保险起见,每次写入后都立即读回对比,一旦不一致就进入错误上报分支。
3.2 充电电流和终止电压的计算过程
充电参数设计直接决定电池寿命和充电时长。我选了一块 2000mAh 的锂电池,终止电压 4.2V。充电电流我设置成了 320mA,大约 0.16C。对电池来说,这个倍率相当温和,充电发热很小,基本不会触发 PMIC 内部温度保护。充电时间可以用一个工程近似来估算:恒流恒压充电的总时长大约是容量除以充电电流,再乘 1.2 到 1.5 的经验系数。按 2000mAh / 320mA 计算,理论恒流段约 6.25 小时,加上恒压段收尾阶段电流逐步下降,完整充满大概在 7 到 8 小时。如果项目对充电速度有硬指标,就需要把电流上调,但这时要重新评估 PCB 铜宽和散热。
终止电压不能乱设。4.2V 是常规电池标准,但如果电池电芯本身支持 4.35V,可以在寄存器里改。我提醒一句:用 4.35V 满充确实能多出一些容量,代价是电池循环寿命下降,长期高温下鼓包风险也会增加。户外采集器这类设备我更看重可靠性,所以一直用 4.2V。
3.3 输出电压、限流与动态功耗调节
DCDC 输出电压寄存器通常按固定步进划分。在代码里我会把配置做成带注释的宏定义,而不是直接填数字。比如 DCDC1 目标 3.3V,对应某个字段值;DCDC2 目标 1.8V,对应另一个字段值。这里有个动态调压的玩法:PIC32 在 80MHz 全速跑时,3.3V 电压是必要的;但如果某个阶段只做定时采集,主频降到 10MHz,此时把 DCDC1 电压临时调到 3.0V 也能正常工作,这样可以省下一点动态功耗。
不过要小心:Flash 在较高频率读写时对电压有最低要求,降压过头会导致程序跑飞。我实测下来的可接受下限在 2.9V 左右,但为了稳,动态调压路径我暂时没有用到产品模式里,只在验证阶段玩过。数据手册给定的最小工作电压,才是最终约束。
3.4 初始化代码与 I2C 时序的实操细节
下面是一段精简的初始化代码骨架,用来说明写 PMIC 寄存器的基本套路。寄存器编号我会用功能宏代替,具体编号请以你手头的数据手册为准。
#define PMIC_I2C_ADDR 0xA8 // 7位地址,实际由硬件ADDR引脚决定 #define PMIC_REG_CHG 0x10 #define PMIC_REG_DCDC1 0x20 #define PMIC_REG_DCDC2 0x21 #define PMIC_REG_STATUS 0x30 uint8_t pmic_write(uint8_t reg, uint8_t val) { uint8_t tx[2] = {reg, val}; return i2c_master_write(PMIC_I2C_ADDR, tx, 2); } void pmic_init(void) { // 充电电流320mA,终止电压4.2V while (pmic_write(PMIC_REG_CHG, 0x0C) != 0); // DCDC1输出3.3V while (pmic_write(PMIC_REG_DCDC1, 0x1B) != 0); // DCDC2输出1.8V while (pmic_write(PMIC_REG_DCDC2, 0x06) != 0); }这里如果 I2C 驱动本身重试机制做得好,可以让pmic_write返回非零后自动重试。实测中我遇到过 PMIC 刚上电的前几百微秒内 I2C 外设还没完全稳定,连续写入会偶发 NACK,所以后来我在初始化等待里加了至少 5ms 的延时,之后通信就稳定了。
4. 固件状态机:充电、运行、休眠、唤醒的闭环
4.1 系统状态定义与迁移条件
电源管理不能散落在主循环的各个角落,我最后把它收敛成了一个显式状态机。状态包括:BOOT、RUN、CHARGE、SLEEP、BATLOW。BOOT 是上电初始化,完成 PMIC 寄存器的写入和内部数据恢复;RUN 是正常采集和通信,充电事件通过中断异步进入 CHARGE;SLEEP 是低功耗待机,可以定时醒来或由外设事件唤醒;BATLOW 是低压保护状态,只保留最低限度进程,比如保存关键数据到 Flash,然后进入深度睡眠。
状态迁移关系很直接:BOOT 完成后进 RUN;检测到 VBUS 插入进 CHARGE,充电完成后退回 RUN;运行中电量低进 BATLOW;收到休眠指令进 SLEEP;睡眠中被充电插拔或定时器唤醒后回到 RUN。
4.2 充电管理:状态轮询、充电完成与错误处理
PMIC 硬件会自动执行恒流恒压充电,但完整充电流程还是需要 MCU 参与管理状态。我在主循环里周期读取 PMIC 状态寄存器,识别充电中、充电完成、充电故障三类事件。充电完成通常发生在电流降到某个阈值时,PMIC 会相应置位状态位并拉低中断引脚。
错误处理要兜底:如果充电时电池温度过高,PMIC 可能有内部保护,也可能需要 MCU 通过 NTC 读取温度自行切断充电。我的做法是在状态机里保留一个“温度异常”分支,一旦检测到温度超阈值,直接通过寄存器停止充电,直到温度回落再恢复。户外设备夏天太阳直射下外壳温度可能很高,这个保护逻辑很实用。
4.3 休眠与唤醒:PCA9422 中断线如何接进 MCU
低功耗休眠的关键是把 PMIC 的中断输出接到 MCU 的外部中断引脚。PCA9422 的中断脚通常是开漏输出,低电平有效,我把它接到 PIC32MX795F512L 的 RB0/INT0,并在引脚上加了一个 10kΩ 上拉电阻。
休眠前需要做的准备工作比想象中多:先把 DCDC2 输出关闭,然后关掉传感器供电,再把 PMIC 配置成允许 VBUS 插入、充电完成、电池低压这几类事件触发中断,最后 MCU 执行睡眠指令。外部事件来时,PMIC 把 INT 拉低,MCU 从睡眠中被唤醒,进入中断服务函数去读取 PMIC 的中断状态寄存器,清标志后再恢复 DCDC2。
这里最容易被忽略的是中断标志清理顺序。我最初是先清 MCU 外部中断标志,再去读 PMIC 状态寄存器,结果下次睡眠前又发生了一次误触发,原因就是 PMIC 的 INT 引脚还没恢复高电平,MCU 再次被拉低唤醒。后来改成“先读 PMIC 状态、清 PMIC 中断标志、再清 MCU 外部中断标志”,整个系统才安静下来。
4.4 实现要点:不要在主循环里直接写 PMIC 寄存器
一个容易犯的错误是把 PMIC 操作直接塞进主循环的某个业务分支里。I2C 写入本质是阻塞操作,在等总线期间会耽误传感器采集和通信。我现在的做法是把 PMIC 相关操作包装成一个事件请求,比如“请求进入充电模式”“请求关闭 DCDC2”,然后由统一的状态机在合适的时候真正执行 I2C 写入。这样主循环永远保持非阻塞,PMIC 的寄存器读写只发生在状态切换点,调试时问题也好定位。
5. 实测排坑:三个典型问题的完整排查链路
5.1 ADC 采集噪声:从“数据跳动”到“DCDC 开关耦合”的定位
系统联调时,我发现 ADC 读到的电压值在每次系统唤醒后都会出现规律性跳动,幅度大约有 30mV,已经超出传感器手册给的噪声底线。我第一反应是传感器干扰,结果把传感器断开后,主控内部 ADC 采样依然有跳动。
于是我用示波器挂了 ADC 采样引脚的波形,发现采样保持期间有大约 80mV 的毛刺,频率恰好等于 PMIC DCDC1 的开关频率。这就基本锁定噪声是从 1.8V 电源轨上耦合进来的。因为模拟电源轨同样由 PMIC 的 DCDC2 输出,DCDC1 的开关噪声通过共用的输入电源或地平面耦合到了 DCDC2。最终我做了两处改动:一是让 ADC 采样延时到 DCDC 开关周期相对平稳的时间点;二是在 1.8V 输出到 ADC 参考引脚之间加了一级 RC 低通滤波,时间常数约 2μs。改完之后噪声降到了 5mV 以内,问题解决。
5.2 I2C 偶发 NACK:时钟速率与上拉电阻的联合排查
系统进入低功耗模式后,I2C 通信变得不太稳定,主要表现为初始化阶段偶发写失败,重试两三次后才能成功。我用示波器同时抓 SCL 和 SDA,发现 SDA 在时钟下降后建立时间明显不足,信号波形在门槛电压附近缓慢爬升,这是典型的上升沿过缓。
排查下来根因有两个:一个是 I2C 总线上的上拉电阻用了 10kΩ,对 400kHz 速率来说太大;另一个是 PMIC 和主控之间的走线穿过了两层板,总线上还挂了另一个传感器,等效电容比预想大。解决办法是把上拉电阻换成 2.2kΩ,同时把 I2C 速率从 400kHz 降到 100kHz。PMIC 的控制寄存器操作并不频繁,100kHz 完全够用,可靠性的优先级更高。改完后连续跑了两天,没有一次 NACK。
5.3 电池低压瞬间掉电引起的 MCU 复位
第一次户外试用时,电池电压低于 3.2V 后设备开始反复复位,最后一次数据没有保存。用电池监测脚看电压,发现负载一加重,电池端电压瞬间跌到 3.0V 以下,PMIC 内部欠压保护直接切断了输出,MCU 来不及保存数据就掉电了。
这不是单纯降低 PMIC 保护阈值能解决的事,因为电池有内阻,负载越大电压跌落越严重。我最后给出的方案是让 MCU 在 BATLOW 进入条件上做提前量:当电池电压低于 3.4V 时,立即停止采集,先把关键数据和文件系统日志保存好,再进入深度休眠。这样就算电池继续缓慢放电,PMIC 的硬件保护仍然在 MCU 后面兜底,数据不会再丢。
5.4 休眠电流测量误区:万用表串联导致的电压跌落
调试休眠电流那天,我把万用表电流档串进电源正极,读数一直不太对,而且 MCU 偶尔还会复位。后来发现万用表电流档内阻带来的压降接近 300mV,在系统从休眠唤醒瞬间,瞬间电流会让电压跌得更多,导致 PMIC 误判电池电压不足,直接把系统切断了。
准确做法是串一个 1Ω 精密采样电阻,用示波器差分档测电阻两端电压,再换算成电流。这样既能看见休眠平均电流,也能捕捉到唤醒瞬间的电流尖峰。我顺手把唤醒瞬间的 2ms 电流尖峰记录了下来,这组数据对后面优化低功耗启动逻辑非常有帮助。
6. 功耗数据与后续优化建议
6.1 实测功耗数据表
经过这一轮改造,整机功耗已经完全可接受。以下是实测数据,数据来自同批次三块板子的平均值。
| 工作模式 | 电流 | 说明 |
|---|---|---|
| 全速运行(80MHz,外设全开) | 38 mA | 主要用于调试和通信 |
| 普通运行(10MHz,周期采集) | 6.8 mA | 常规工作状态 |
| 休眠(RTC 定时唤醒) | 47 µA | DCDC2 关闭,主控睡眠 |
| 深度休眠(等待外部事件唤醒) | 12 µA | PMIC 低功耗模式,MCU 深度睡眠 |
| 充电中额外电流 | 320 mA | 充电电流占大头 |
这个数据比最初用分立方案时低了不止一个量级,之前仅休眠电路就有 800µA。对于一块 2000mAh 的电池,正常一天几次采集加无线上报,可以用到一个月以上。
6.2 进一步优化方向
如果后面要把功耗再往下压,我会优先做三件事。一是尝试在轻载时把 PMIC 的 DCDC 切换到 PFM 模式,让待机效率进一步提高;二是对模拟前端做更细的电源域拆分,把用不到的传感器轨彻底断电而不是只是关闭模块;三是把 PIC32MX795F512L 的时钟树重新调整,睡眠状态下只保留 32kHz 辅助振荡器给 RTC,其他所有高频振荡器全部关闭。
另外我还打算把充电状态和电池电压日志做成统一事件队列,配合无线模块上报到后台,这样室外设备即使无人值守,也能远程看到电池健康和充电异常。电源管理这件事做到这个程度,才算是真正闭环了。