最近在帮一个低功耗采集终端挑电源方案,最后把PCA9422和PIC24FJ256GB110组合在一起用了一轮。整套系统做下来,充电、三路输出、电量监控、休眠唤醒都放在一颗PMIC和一颗MCU上搞定,整体功耗和稳定性比我预想的要好。这篇文章就把完整的设计思路、软件状态机、寄存器配置流程,以及我踩过的坑都整理出来,给后面要碰这两颗料的朋友做个参考。
如果你正在做电池供电的物联网设备、手持仪器、可穿戴设备,或者只是想把板子上的多路电源理顺,这篇内容基本能覆盖你从选型到调试的全过程。PCA9422 负责电源转换和充电这类“脏活累活”,PIC24FJ256GB110 负责状态管理、I2C 配置、电量估算和中断处理,各管一段,逻辑非常清晰。
1. 这套组合要解决什么问题
1.1 为什么需要独立 PMIC
很多工程师习惯用一个 MCU 内部的 LDO 或者几颗分立 LDO 直接把电池电压降到 3.3V,再想办法让外设工作。这种方案在功能简单的板子上确实没问题,但一旦面对“锂电池供电 + 需要充电管理 + 多路电压输出 + 超低待机功耗”这几个需求同时出现,分立方案就会变得很难受。
- LDO 效率不高,电池从 4.2V 降到 3.3V,压差 0.9V,负载稍大一点,发热明显,电池续航也白白浪费。
- 充电管理不是一个 LDO 能解决的,需要充电电流控制、CC/CV 切换、截止电流判断、温度保护,这些逻辑如果全用 MCU 和分立器件去拼,软件复杂度和板子面积都受不了。
- 待机功耗难做,MCU 进了 deep sleep,但那些外设和没有完全断电的模块还在漏电,整机待机电流很难压到 10µA 以下。
所以更合理的做法是:电源链路交给一颗专用 PMIC,它内部集成 charger、Buck、ADC、中断管理、低压检测,MCU 只通过 I2C 和它对话,根据运行状态动态调整电压和电流。PCA9422 正是这类面向低功耗便携设备的 PMIC,而且它和 PIC24FJ 同属一个生态,软硬件协同上更省心。
1.2 系统架构与电源路径
整个系统的电源拓扑可以画成下面这条路:USB 或者直流适配器进来,直接接到 PCA9422 的输入脚。PCA9422 内部有电源路径管理,会自动决定用外部电源还是电池给系统供电。电池电压和系统电压经过内部路径选择后,给到内部几个 Buck 的输入。
Buck 的输出再分别送到 MCU、传感器、无线模块、显示屏等负载。PIC24FJ256GB110 通过 I2C 读取 PMIC 的状态寄存器,配置充电电流、Buck 电压、中断屏蔽,同时也能直接读电池电压和电流的 ADC 结果。
这套架构最大的好处是,MCU 只管“决策”,不用去理会 Mosfet 开关、电感电流、电池电压检测这些底层细节。电源路径切换、充电状态跳变、低压保护这些事,PMIC 自己就用硬件电路处理掉了。软件要做的,只是根据应用场景决定让系统处于哪个电源状态,再对 PMIC 写相应的配置。
2. PCA9422 的关键资源与选型思路
2.1 充电管理
PCA9422 集成了一路电池充电器,支持锂电池的 CC/CV 充电。输入电压范围适合 USB 5V 供电,电池电压和终止电压可以通过寄存器配置。对于大多数小容量电池,典型终止电压是 4.2V,充电电流则根据电池容量选。
这里有一个很重要的选型逻辑:充电电流不是越大越好。我曾经见过有人直接把充电电流配成 500mA,用的却是一颗 300mAh 的小电池,结果电池温度上升很快,寿命也受到明显影响。一般按照 0.5C 到 1C 来配比较合理,比如 500mAh 电池用 250mA 充电,既不会让电池太热,充电时间也还能接受。
充电过程还涉及截止电流和再充电阈值。锂电池快充满时,电流会逐渐减小,当电流小于截止电流(比如 10% 或某个固定值),充电器会判定充满并停止。之后电池电压掉到再充电阈值以下,充电器又会自动恢复充电。这个过程如果全靠 MCU 定时器去猜,肯定不如 PMIC 内部硬件自动处理来得可靠。
强烈建议把电池温度保护用起来。如果 PMIC 支持 NTC 引脚,一定要接一个热敏电阻,或者按照数据手册要求的分压网络把该引脚配置到合理电平。锂电池在 0°C 以下充电容易产生安全隐患,在高温下充电也会加速老化。PCA9422 如果因为温度异常停充,MCU 至少要知道这个状态,并在界面上提示,而不能让用户以为充电器坏了。
2.2 三路 Buck 与电压调节
PCA9422 内部有三路高效 Buck 转换器,每一路输出电压都可以通过 I2C 独立配置。这对多电压系统太有用了,比如 3.3V 给 MCU 和数字逻辑,1.8V 给存储器和低电压接口,1.2V 给外部射频芯片的内核,三路全部由一颗芯片搞定。
提到 Buck,工程师最关心的就是效率。实测下来,在中轻负载区间,这类集成 PMIC 的 Buck 效率通常在 80% 到 90% 以上,轻负载时还能自动进入省电模式。这在电池供电设备里价值非常大,因为大部分时间系统不是在满负载跑,而是处于空闲和浅休眠。
除了固定电压输出,还要注意 PMIC 的 DVS,也就是动态电压调节能力。如果数据手册里写着支持 DVS 引脚或者 DVS 寄存器切挡,设计时可以预留几组常用电压。比如运行状态用 3.3V,休眠状态降到 2.5V,由 GPIO 切换,完全不经过 I2C 通信。这样做不仅省电,还能避免在唤醒时因为 I2C 时序太长导致电压切换不够及时。
不过,动态调压有一个容易翻车的点:电压不能跳变太大。如果直接从 3.3V 一次性切到 2.0V,输出电容放电方向可能产生负压毛刺,或者让后级负载瞬间“掉压”复位。安全做法是分级切换,或者让 MCU 先把工作频率降下来,再逐步降电压。
2.3 ADC、中断和 Ship Mode
PCA9422 内置 ADC,可以直接读取电池电压和电流。这个特性非常实用,否则我得在板子上单独加一个电流采样电阻、运放和 ADC 芯片。它自身的 ADC 精度虽然比不上专业电量计,但用来做低压告警、粗略 SOC 估算、充电状态监控已经足够了。
电池电压读取的典型流程是,先把 ADC 输入切换到电池电压通道,触发一次转换,等转换结束后读结果,再根据芯片手册给出的比例系数换算实际电压。电流读取类似。要注意的是,如果芯片手册里写的 ADC 参考电压是内部基准,那么换算公式里会有一个固定系数,不能想当然地认为 1 LSB 就是 1mV。
中断系统是低功耗设计里必不可少的一环。电池电压过低、充电完成、Buck 故障、按键事件,这些 PMIC 都会通过 INT 引脚通知 MCU。MCU 平时可以在 deep sleep 里蹲着,等 PMIC 把 INT 拉低再去处理,省掉了一大堆轮询。
Ship Mode,也就是船运模式,是一个特别值得强调的功能。正式量产发货时,如果设备还接着电池,就算系统处于低功耗状态,电池也会被慢慢放干。Ship Mode 会把电池和系统之间的通路彻底断开,让待机电流降到几乎可以忽略。设备上电后,通过 USB 插入或者按键唤醒。这个功能如果不提前在固件里加上,产品在仓库里放两个月电池就废了。
3. PIC24FJ256GB110 承担的角色
3.1 电源状态机
PIC24FJ256GB110 是一颗 16 位 MCU,256KB Flash、16KB RAM,跑一个电源管理状态机绰绰有余。它的优势不只是性能,更在于低功耗模式种类多、外部中断引脚充足、I2C 硬件模块稳定。做电源管理,MCU 侧其实不需要太多计算资源,真正重要的是状态切换的严谨性。
我会把电源管理分成这几个状态:
- POWER_OFF:系统真正断电,只有按键或者 USB 插入能唤醒。
- BOOT:上电启动,MCU 初始化外设,配置 PMIC,把需要的 Buck 打开。
- RUN:正常运行,所有模块都在工作。
- SLEEP:关闭不必要的外设,降低主频,把部分 Buck 电压调低。
- DEEP_SLEEP:只保留 RTC 或者 PMIC 中断唤醒能力,其他全部关闭。
状态机的核心价值,是把“什么时候降频”“什么时候降压”“什么时候关外设”这些决策集中在一起管理。否则代码写久了,到处都会有临时加的 delay 和 set_buck_voltage 调用,最终变成一团乱麻,而且谁也说不清当前板子到底处于什么功耗档位。
3.2 I2C 控制链路
PIC24FJ 和 PCA9422 之间的控制链路就是 I2C。PIC24FJ 的 MSSP 模块可以工作在 I2C 主机模式,通过 SCL 和 SDA 两根线访问 PMIC 的寄存器。
需要注意,I2C 的地址不是随便定的,PCA9422 的 I2C 地址通常通过一个外部引脚的电平状态来选择。我在调试时就遇到过,因为地址配置引脚没有正确上拉,导致 MCU 一直找不到 PMIC。这种情况不是芯片坏了,而是地址不对。
I2C 总线的上拉电阻也值得检查。400kHz 模式下一般用 2.2kΩ 到 4.7kΩ 左右。如果你的 I2C 走线很长,或者挂了多个从设备,上拉电阻太小会让信号边沿变慢,上拉太大会导致功耗偏高。这个是在实际布线时就要想清楚的。
3.3 休眠与动态调压
PIC24FJ 进入休眠并不是简单写一条 Sleep 指令就完事。休眠之前,要明确哪些外设保持供电,哪些外设断电,以及系统时钟是否已经切到低频模式。
PIC24FJ256GB110 的最低工作电压和运行频率有关。如果主频跑到 32MHz,电压可能要求 3.0V 以上,但如果你把主频降到 31kHz,最低工作电压就可以降到 2.0V 甚至更低。利用这个关系,休眠流程可以这样设计:
- MCU 把系统时钟切到低速内部振荡器。
- 通过 I2C 把 PCA9422 的 Buck1 输出电压调低,例如从 3.3V 降到 2.2V。
- 关掉不用的外设和传感器电源。
- 执行 Sleep 指令,进入休眠。
唤醒流程反过来,PMIC 中断先把 MCU 唤醒,MCU 第一件事就是把 Buck1 电压调回 3.3V,再把系统时钟切回高速,然后继续跑应用。这个过程中最容易被忽视的是:电压还没抬起来时,MCU 运行的寄存器操作如果要求高电压,就可能出问题。所以唤醒代码最好放在 RAM 里,并且先执行电压恢复,再做其他操作。
4. 软件实现细节
4.1 初始化时序与寄存器配置
MCU 上电之后,PMIC 其实已经按默认配置工作了。默认配置不一定是你想要的,所以软件必须在尽可能短的时间内完成对 PMIC 的重新配置,避免后级电路在错误电压下工作太久。
我的初始化流程大概是这样的:
- 延时 50ms 左右,等 PMIC 内部上电时序完成。
- 通过 I2C 读取 PMIC 的 ID 寄存器,确认芯片在线,并且版本符合预期。
- 配置系统控制寄存器,使能需要的 Buck 和充电器。
- 分别配置 Buck1、Buck2、Buck3 的目标电压。
- 配置充电电流、终止电压、再充电阈值。
- 设置中断屏蔽寄存器,只允许关心的事件触发 INT。
- 读取一次中断状态寄存器并清除,避免残留中断标志导致 MCU 误唤醒。
下面是一个示意代码,寄存器名用通用名称代替,实际名称要按数据手册翻译。
#define PMIC_I2C_ADDR 0x60 #define REG_SYS_CTRL 0x01 #define REG_CHG_CTRL 0x02 #define REG_BUCK1_CTRL 0x05 #define REG_BUCK2_CTRL 0x06 #define REG_BUCK3_CTRL 0x07 #define REG_INT_STATUS 0x20 #define REG_INT_MASK 0x21 #define REG_BAT_VOLTAGE 0x30 #define REG_BAT_CURRENT 0x32 void pmic_write_reg(uint8_t reg, uint8_t val) { // 启动I2C,发送从机地址+写位,发送寄存器地址,发送数据,停止 } uint8_t pmic_read_reg(uint8_t reg) { // 发送寄存器地址,重发START,读取数据 return 0; } void pmic_init(void) { delay_ms(50); uint8_t id = pmic_read_reg(REG_ID); if (id != EXPECTED_PMIC_ID) { // 记录错误并进入恢复流程 } pmic_write_reg(REG_SYS_CTRL, EN_BUCK1 | EN_BUCK2 | EN_BUCK3 | EN_CHARGER); pmic_write_reg(REG_BUCK1_CTRL, BUCK1_3V3); pmic_write_reg(REG_BUCK2_CTRL, BUCK2_1V8); pmic_write_reg(REG_BUCK3_CTRL, BUCK3_1V2); pmic_write_reg(REG_CHG_CTRL, CHG_250MA | CHG_4V2 | CHG_EN); pmic_write_reg(REG_INT_MASK, INT_BAT_LOW | INT_CHG_DONE | INT_BUCK_FAIL); pmic_read_reg(REG_INT_STATUS); // 清中断 }初始化完成后,MCU 会进入主循环,后续所有对 PMIC 的修改都必须基于一个原则:先想清楚当前状态是否允许这次修改,再做写操作。
4.2 电量读取与 SOC 估算
PCA9422 的 ADC 读到的是一串原始值,要换成电压和电流才能用。不同芯片的换算系数不一样,但流程是一样的。
uint16_t raw = pmic_read_adc(ADC_BAT_VOLTAGE_CH); uint32_t voltage_mv = (uint32_t)raw * ADC_STEP_MV / 1000;读取完成后不要立刻拿这个电压去估 SOC,因为负载下的电池电压会比开路电压低很多。如果系统负载比较稳定,可以直接用带载电压做粗略区间判断。如果负载波动很大,最好在某个空闲窗口把负载关掉几十毫秒,再读一次电压,那才是比较接近 OCV 的值。
SOC 的粗略映射表可以参考:
| 电池电压 | 大致 SOC |
|---|---|
| 4.0V 以上 | 80% - 100% |
| 3.8V 左右 | 50% - 80% |
| 3.6V 左右 | 20% - 50% |
| 3.4V 左右 | 5% - 20% |
| 3.3V 以下 | 接近耗尽 |
这种方法的精度有限,但作为低功耗设备足够用了。重要的是加迟滞,不能让设备在 3.4V 附近反复报警和恢复。
4.3 中断唤醒处理
把 PCA9422 的 INT 引脚连到 PIC24FJ 的一个外部中断引脚上。INT 通常是低电平有效,而且很多 PMIC 是开漏输出,所以 MCU 侧要有一个上拉电阻,或者使能内部上拉。
中断处理的顺序是:
- 外部中断触发,MCU 从休眠中苏醒。
- 读取 PMIC 的中断状态寄存器,搞清楚发生了什么事。
- 清掉中断标志。
- 根据事件类型执行对应动作,比如充电完成就更新 UI 指示,电池低压就准备保存数据并关机。
- 重新配置低功耗状态,再次进入休眠。
这里有个很容易犯的错,读完中断状态后忘了清标志,导致 INT 一直拉低,MCU 就会不断被唤醒,功耗直接爆炸。所以我在代码里规定,所有中断服务函数只置一个标志位,具体的读状态和清标志放在主循环里统一处理,这样不容易漏。
4.4 代码框架示例
volatile bool pmic_int_flag = false; void __attribute__((interrupt, no_auto_psv)) _INT1Interrupt(void) { pmic_int_flag = true; _INT1IF = 0; } int main(void) { system_init(); pmic_init(); while (1) { if (pmic_int_flag) { pmic_int_flag = false; handle_pmic_interrupt(); } if (button_pressed()) { handle_button(); } if (usb_connected()) { handle_usb(); } // 根据运行时间或事件,进入低功耗 enter_low_power_if_needed(); } }这种超级循环加中断标志的做法,在低功耗系统里很实用,代码简单,行为可预期,也好调试。
5. 实战:低功耗无线传感节点
5.1 硬件电源架构
假设要做一块无线传感器节点,功能包含 MCU、BLE 模块、温湿度传感器、小容量锂电池、USB 充电口。用 PCA9422 供电,架构可以这样定:
- Buck1:3.3V,给 MCU、传感器、BLE 模块。
- Buck2:1.8V,给 SD 卡或者外部 Flash,运行时再打开。
- Buck3:当作备用或者完全关闭。
- 充电器:500mAh 锂电池,充电电流 250mA,终止电压 4.2V。
这颗 MCU 有很多外设接口,BLE 模块用 UART 连接,传感器用 I2C 连接,按键用普通 GPIO,USB 可以用来充电和数据通信。
5.2 工作状态定义
运行状态可以这样定义:
- IDLE:传感器 1s 采集一次,BLE 处于低占空比广播,屏幕关闭。
- SLEEP:MCU 进入 31kHz 低功耗模式,Buck2 关闭,Buck1 从 3.3V 降到 2.5V,只保留 PMIC 中断唤醒。
- DEEP_SLEEP:所有 Buck 都关闭或进入最低功耗模式,MCU 直接深度睡眠,只有 USB 插入或者按键唤醒。
实际测试的时候,IDLE 态整机电流大概还能接受,但真正省电的是 SLEEP 态。因为 MCU 在这个状态下不需要高频时钟,电压降到 2.5V 之后,动态功耗和漏电都会明显下降。
5.3 电池续航估算
如果整机在 SLEEP 态是 10µA,IDLE 态是 50µA,那么 500mAh 的电池,理论续航非常可观。当然实际还要考虑电池自放电、Buck 静态电流、PMIC 静态电流和温度影响,但整体下来,一套功耗设计正常的板子续航会比用 LDO 的方案高出很多。
这里有个经验值:如果整机待机电流能做到 10µA 级别,说明主电源路径上的静态漏电已经控制得不错。如果待机电流还在 100µA 以上,那多半有某个外设没完全断电,或者 PMIC 的某个 Buck 还在空载运行。
5.4 硬件设计注意事项
PCB 布局直接决定这套方案能不能稳定工作。PMIC 要尽量靠近电池座和 USB 座,输入电容放在 PMIC 输入引脚旁边,输出电容放在 Buck 输出引脚附近。电池线不要走太细,尽量避免让电流路径绕远路,否则电池低压时内阻加上线阻,系统容易误判掉电。
I2C 走线要短,两个上拉电阻最好放在同一个位置,避免形成复杂的星型拓扑。PIC24FJ 的电源脚旁边放 100nF 陶瓷电容和 4.7µF 电容组合,VDD 和 VSS 之间的回流路径要短。如果板子上有射频模块,电源地和射频地的切割方式也要谨慎,不要为了“看起来干净”把地平面乱切。
6. 常见问题与调试记录
6.1 充电不动作
充电不动作是我被问得最多的问题。排查思路很简单,先看输入电压是否足够,再看充电使能寄存器有没有写进去,最后看温度引脚的电平是不是在合法范围。
很多充电 IC 的 NTC 引脚如果悬空,会被判定为温度异常,充电直接禁止。如果你的板子没有接 NTC,就要按照数据手册要求,用固定电阻分压把这个引脚偏置到“正常温度”区间。这个问题我踩过,当时还以为是芯片坏了。
6.2 I2C 不稳定
I2C 不稳定最常见的原因是上拉电阻不合适、总线过长、或者设备地址配错。用逻辑分析仪抓一下波形,基本上立刻就能定位。另一个容易忽略的问题是,MCU 和 PMIC 的 I/O 电压是否匹配。如果 MCU 用 3.3V 供电,PMIC 的上拉电平却来自 VIOUT 或其他电源,两边高电平不对齐,通信就是时好时坏。
6.3 Buck 输出异常
Buck 输出电压要么不对,要么开机瞬间有毛刺,通常先查配置寄存器,再查输出电容。电容太小会导致环路不稳定,输出纹波变大;软启动时间太短会导致上电瞬间电流冲击,甚至把前级电源拉垮。
如果发现 Buck 使能瞬间 MCU 复位,大概率是输入电压瞬间跌落。解决办法包括加大输入电容、把软启动时间调长一点、以及确保电池不是已经严重亏电。
6.4 低功耗的坑
低功耗做不好的根本原因,往往不是 MCU 进不去 sleep,而是某个外设没有断电,或者 PMIC 的某个 Buck 还在供着一个不该工作的模块。建议在硬件上把每个负载的供电都单独留一个磁珠或者 0Ω 电阻,调试低功耗时一个一个拆掉,用电流表看整机电流变化,用排除法找到漏电路径。
另外,MCU 有些 GPIO 在悬空状态下会通过内部保护二极管漏电。休眠之前把不用的 GPIO 配置成输出低,或者设置为禁止模拟输入的数字输入并接外部上拉/下拉,是很有用的手段。
6.5 其他实用建议
有一件事特别提醒:PCA9422 这种 PMIC 的默认寄存器值一定要看数据手册。早期我因为想当然地认为默认电压就是 3.3V,结果板子一上电某个 1.8V 器件就直接冒烟了。后来养成一个习惯,每次拿到新 PMIC,第一件事就是把默认寄存器表打印出来,对着原理图把默认电压逐一核对。
调试阶段强烈建议先买官方评估板,把 PCA9422 和 PIC24FJ 通过杜邦线或者软排线连起来,先把软件调通,再画集成板。这样做的好处是,软硬件问题能分开定位,不用一上来就面对焊错的电容和无故复位的 MCU。
最后再分享一个小技巧:在 I2C 驱动里加一个重试机制,读取 PMIC 状态失败的时候自动复位 I2C 模块并重新初始化。这套方案在实际设备上运行,偶尔因为电池电压暴跌或者强干扰导致 PMIC 暂时不响应,有重试机制能避免整个系统卡死在一个状态里。按照我个人的经验,电源管理这类代码,多花一点时间把异常路径写全,比把正常路径写得再华丽都值得。