如果你做过带锂电池的便携设备,一定被电源管理恶心过:充电保护要自己搭,过放检测要自己写,功耗一高电池就崩。我之前为一块低功耗传感器板做电源方案,一开始图省事用了 LDO 加独立充电模块的老套路,结果待机电流怎么压都压不到理想值,充放电过程基本是个黑盒,出了问题连根因都摸不着。最后狠狠心把方案推倒,换成了 PCA9422 加 PIC18F4620 的双芯片架构,把充电路径、系统供电、电量监测和异常保护统一交给 PMIC 和主控配合管理,才算真正把电源理顺。
这套组合负责的是小型电池设备里最难缠的三件事:充电管理、路径切换、电量监控。主控只需要通过 I2C 下发配置,剩下功率层面的脏活累活全部由 PMIC 承担。如果你正在做可穿戴设备、传感器节点、追踪器、手持终端这类产品,这篇文章里的选型逻辑、硬件接线、驱动实现和排障记录都可以直接参考,每一步都是实际验证过的东西,不是纸上谈兵。
1. 双芯片选型逻辑:为什么非要 PMIC 加 MCU 的组合
1.1 分立方案的三个死穴
LDO 加独立充电模块是很多人起步时喜欢用的做法。LDO 负责把电池电压稳下来给系统供电,充电模块负责给电池充电,看起来分工明确,实际用起来全是麻烦。
第一个死穴是效率。电池电压从满电 4.2V 一路降到 3.3V,LDO 把多余的压差以热量形式消耗掉,电池后半段电量基本是浪费的,系统跑着跑着就突然关机,剩余电量根本用不干净。第二个死穴是路径管理。插入 USB 之后充电模块给电池充电,系统负载同时又从电池取电,充电模块不会自动把系统切到 USB 侧,结果是电池一边充一边放,寿命和状态都变得难以预测。第三个死穴是保护。过流、过温、欠压这些保护全靠自己搭分立电路,搭多了成本上去,搭少了关键时刻不会动作,出了问题板子烧掉都不知道是谁的责任。
正是这些痛点让我彻底转向 PMIC 思路。把电源相关的功率部分交给专用芯片,主控只做策略和调度,系统的电源行为才真正变得可控。
1.2 PCA9422 的核心价值
PCA9422 这类小封装 PMIC,把电池管理需要的那一大堆功能合到了一起。充电管理、功率路径切换、系统输出电压、充放电保护、状态监测都在一颗芯片里完成,外部只需要很少的被动元件,不占板子面积。
最实用的接口是 I2C,充电电压、充电电流、输入限流这些参数全部可以用软件配置,而不是靠焊电阻去设定。对批量产品来说,改版只需要改代码,连 PCB 都不用动,这是非常大的灵活性。它还会把电池电压采样值、充电状态、故障标志通过寄存器反馈出来,主控拿到这些数据就能实时知道系统处于什么状态,该做什么决策。
这颗 PMIC 的任务是离电池最近的那一层。它不负责业务逻辑,负责功率变换、充电管理、保护,并且通过中断把异常第一时间汇报给主控。换句话说,它把电源从单纯的硬件变成了可以参与软件策略的一部分。
1.3 PIC18F4620 在系统里的角色
PIC18F4620 是一颗很成熟的 8 位单片机,64KB Flash、4KB RAM 的空间放这套电源管理代码外加应用逻辑非常够用。它内置的 MSSP 模块可以直接做 I2C 主模式,硬件时序比软件模拟稳定得多,CPU 占用也更低。自带的 13 路 10 位 ADC 可以完成电池电压冗余采集和温度采样,多个外部中断引脚则支持睡眠状态下被 PMIC 的中断信号唤醒,这是低功耗设备必需的能力。
在这套系统里,MCU 要做的是三件事。上电初始化时把 PCA9422 的寄存器配置写好,运行过程中根据读取到的电池状态执行充电、放电、待机、故障处理的状态机切换,再通过串口或无线模块把电源状态上报出去。
可以说 PMIC 管功率,MCU 管策略。两者配合,正好把一套小型电池系统的电源管理完整做下来。
1.4 适合这套方案的产品形态
不是所有项目都需要 PMIC 加 MCU 的架构。我用一张表说明判断依据:
| 产品类型 | 典型需求 | 是否适合 |
|---|---|---|
| 纽扣电池供电的小玩具 | 单节干电池,无需充电管理 | 不需要 |
| 锂电池供电的传感器节点 | 需要充电、低功耗、状态上报 | 非常适合 |
| 智能手环、耳机盒类 | 充电管理加电量监测,体积受限 | 非常适合 |
| 自带主控的电池产品 | 已有 MCU,只差充电和路径管理 | 非常适合 |
判断标准很简单:只要有锂电池、有充电需求、有需要上报的状态,这套架构就值得考虑。它省掉的是一堆离散保护电路和调试时间,换来的是原理图更简洁、BOM 更短、行为可配置。
2. 硬件搭建:电源树、I2C 和中断引脚的连接细节
2.1 电源树的整体结构
先看整体供电路径。USB 输入接到 PCA9422 的输入侧,电池接在电池端,PCA9422 的系统输出端作为整个设备的主供电轨,PIC18F4620 和后级负载都从这条轨取电。
我实际用的连接关系整理成表,方便对照自己的板子:
| PCA9422 引脚 | 功能作用 | 连接去向 |
|---|---|---|
| VBUS 输入 | USB 或适配器电源输入 | 5V 输入,对地放 0.1uF 陶瓷电容 |
| BAT | 锂电池正极 | 单节锂离子电池,靠近引脚放 10uF 电容 |
| SCL / SDA | I2C 总线 | 接 PIC18F4620 的 RC3/RC4,各接 4.7k 上拉 |
| INT | 中断输出,低电平有效 | 接 PIC18F4620 的 RB0/INT0 |
| EN | 芯片使能 | 接普通 GPIO,由系统控制 |
| GND | 地 | 系统地平面,单点连接功率地 |
PIC18F4620 的 MSSP 模块对应引脚就是 RC3(SCL)和 RC4(SDA),这两个引脚在 I2C 模式下保持开漏特性,配合外部上拉电阻工作。RB0/INT0 配置为外部中断,低电平触发,这样 PMIC 有异常时可以立刻把睡着的 MCU 叫醒。EN 引脚单独用 GPIO 控制,调试阶段可以在软件里直接关断整个 PMIC 做故障注入测试。
2.2 系统输出与后级稳压
PCA9422 的系统输出一般可以作为主电源轨,但不同配置下输出设置可能不一样。我当时的做法是让 PMIC 输出一个稳定的系统电压,然后加了一级低压差稳压器给 MCU 供电,这样即使 PMIC 在路径切换瞬间出现电压波动,MCU 也不会复位。
有人为了省电会跳过一级 LDO,直接让 MCU 吃系统轨,但我实测在 USB 插入的瞬间系统轨容易有跌落,稳妥起见还是加了一级。如果你的后级有无线模块,建议在模块电源脚附近放独立的滤波电容,无线发射时瞬时电流可能到几百毫安,这个瞬态处理不好会让电池电压读数出现假跳变,影响电量判断。
2.3 布局布线和测试点
布局方面有几个经验。第一,PCA9422 和电池之间的走线越短越好,充电电流回路的铜皮尽量加宽,别用细线走,否则充电时线上压降会直接影响满充电压判断。第二,芯片电源引脚附近的去耦电容要紧贴引脚放,我习惯是 10uF 和 0.1uF 成组,别只放一个。第三,SCL/SDA 上拉电阻放在靠近主控侧,上拉到和 PMIC 同一供电域,避免跨域漏电,这个在调试阶段坑过我一次,后面排障部分详细说。
测试点一定不要省。电池电压测试点、系统输出测试点、I2C 信号测试点都留出来,调试时可以随时挂示波器看波形,不用去捅芯片引脚。我第一版就吃了没留测试点的亏,量信号只能用镊子颤颤巍巍搭在芯片引脚上,既危险又容易短路。
3. PIC18F4620 侧 I2C 驱动:先把 PMIC 的通信打通
3.1 MSSP 模块初始化与时钟计算
PIC18F4620 的 MSSP 模块配置成 I2C 主模式很简单,关键是把 SSPCON1 的 SSPM 位设为 1000,再用 SSPADD 寄存器决定总线时钟。频率公式是 SCL = FOSC / (4 × (SSPADD + 1))。
我当时系统主频用 16MHz,目标总线速度 100kHz,反推出来 SSPADD = 16MHz / (4 × 100kHz) - 1 = 39。初始化代码写成这样:
void pmic_i2c_init(void) { // RC3/RC4 设为输入模式,I2C 总线靠外部上拉释放 TRISCbits.TRISC3 = 1; TRISCbits.TRISC4 = 1; // MSSP 模块使能,配置为 I2C Master 模式 SSPCON1 = 0b00101000; // SSPEN=1, SSPM=1000 SSPCON2 = 0x00; // 清空控制位 SSPSTAT = 0x00; // 标准 100kHz 模式,不使用 SMBus 特性 SSPADD = 39; // FOSC/(4*100kHz)-1 }TRIS 置 1 加外部上拉是最省事的做法,总线释放时由外部 4.7k 电阻拉高,主从机都靠这个机制维持通信。如果引脚支持内部弱上拉也可以试,但我建议外部独立上拉,阻值可控,信号边沿也更干净。
3.2 寄存器写和读的时序
I2C 驱动里最常用的两个函数是写寄存器和读寄存器。写寄存器流程是启动条件、器件地址加写位、寄存器地址、数据字节、停止条件,每个字节传输完之后检查 ACK。读寄存器要多一个重复起始条件:先写器件地址和寄存器地址,再用重复起始切换为读模式,主机收到数据后发 NACK 结束。
完整代码如下:
// 写一个寄存器字节 unsigned char pmic_write_reg(unsigned char reg, unsigned char val) { I2C_Start(); if (pmic_tx_byte(0x90) == 0) return 1; // 器件地址 0x48 加写位,NACK 则失败 if (pmic_tx_byte(reg) == 0) return 1; if (pmic_tx_byte(val) == 0) return 1; I2C_Stop(); return 0; } // 读一个寄存器字节 unsigned char pmic_read_reg(unsigned char reg) { unsigned char val; I2C_Start(); pmic_tx_byte(0x90); pmic_tx_byte(reg); I2C_Start(); // 重复起始 pmic_tx_byte(0x91); // 器件地址加读位 val = pmic_rx_byte(1); // 主机收 1 字节,最后给 NACK I2C_Stop(); return val; }底层发送和接收函数里,一定要对 ACKSTAT、总线冲突位做判断,超时后重试几次而不是傻等。I2C 总线上出问题很常见,一次通信失败不能直接判定硬件坏了,加个重试计数再用状态机处理,比一次失败就 panic 靠谱得多。
3.3 器件地址的一个关键提醒
PCA9422 的器件地址并不是固定的,通常和 ADDR 引脚的电平设置有关。我上面写的 0x90/0x91 是把 7 位地址 0x48 左移一位得到的,实际地址要以数据手册和你的 ADDR 接法为准。
建议在代码里用宏定义地址,把读取 ADDR 引脚电平的逻辑写进去,避免硬编码。不同 PCB 上 ADDR 接法不同,代码也要跟着改,做成宏之后只需要改一处。首次调板最值得做的是一段 I2C 扫描程序:遍历常见地址,向每个地址发一个写操作,能收到 ACK 的地址就是 PMIC 当前所在位置。把这个程序烧进去跑一遍,比对着示波器猜快非常多。
3.4 时钟选择与抗干扰
总线速度建议先从 100kHz 起步。400kHz 能跑,但对 PCB 布局和上拉阻值更敏感,信号质量差的时候偶发 NACK 会让人排查到崩溃。如果后面确实需要更高频率,再把上拉电阻换成 2.2k,同时检查波形上升沿是否陡峭。
抗干扰方面,I2C 线尽量短,不要绕远路。板上其他容易产生毛刺的信号线,比如 PWM 输出、无线模块的控制线,和 I2C 线保持距离,避免串扰导致伪启动或伪停止条件。这个问题在双层板上特别容易出现,布线阶段多花十分钟,调试阶段省十小时。
4. 电源策略配置:充电曲线、路径切换与系统状态机
4.1 上电默认值不一定能用
这是很容易踩的坑:PMIC 的寄存器配置在每次上电复位后会回到出厂默认值,并不具备掉电保存能力。如果板子一上电就直接让系统跑起来,PMIC 很可能停留在默认状态,充电参数不是你要的,系统输出也未必打开。所以 PIC18F4620 上电初始化阶段最重要的事,就是把关键寄存器全部重新写一遍,再读回来做校验。
我把这个过程叫配置下发,放在系统上电后的前 100ms 内完成。实际代码里是一个数组加循环,把寄存器地址和数值成对写进去,之后再逐条读回来比对。如果校验不过,说明 I2C 链路或 PMIC 本身有问题,直接进入故障状态,比稀里糊涂跑下去好得多。
4.2 关键充电参数的配置顺序
充电相关的参数决定了电池能不能安全、完整地充满。我常用的配置顺序是:先设充电电压上限,再设充电电流,然后设输入限流,最后使能充电功能。顺序本身很重要,如果先使能充电再写参数,PMIC 会先用默认参数开始充电,等软件再把参数写进去才切换过来,这个过程存在风险。
具体配置项整理成表,参考的是单节锂离子电池的常规方案:
| 配置项 | 典型值 | 说明 |
|---|---|---|
| 充电电压上限 | 4.2V | 常规三元锂电池,具体看电池规格书 |
| 恒流充电电流 | 500mA | 配合电池容量和散热设计 |
| 预充电流 | 50mA 左右 | 电池过放时先小电流恢复 |
| 终止充电电流 | 50-100mA | 设太低会导致久久不判充满 |
| 输入限流 | 900mA | 保护 USB 输入不被拉垮 |
| 系统输出 | 按后端需求 | 以数据手册配置范围为准 |
每个参数写入后立刻读回确认。寄存器写入失败不代表板子坏了,可能只是总线时序的瞬时抖动,重试一次就过去,但不读回校验,错误配置会影响整个充电过程,这种问题最难查。
4.3 系统状态机的设计与实现
电源管理不能只靠一堆 if 判断,功能一多逻辑准乱。我把它实现成一个小型状态机,用枚举定义:
typedef enum { PMIC_STATE_INIT, // 上电配置阶段 PMIC_STATE_STANDBY, // 待机 PMIC_STATE_CHARGING, // 充电中 PMIC_STATE_CHARGED, // 已充满 PMIC_STATE_RUNNING, // 正常放电运行 PMIC_STATE_FAULT // 故障 } pmic_state_t;状态切换由事件驱动。主循环里检查事件标志,比如 VBUS 插入、VBUS 拔出、充电完成、外部中断触发、电压阈值越限,然后调用对应的事件处理函数,在函数里判断当前状态、决定是否迁移到新状态。每个状态迁移时打印一条串口日志,调试时随时能看出系统在哪一步发生了什么。
这个状态机第一版代码里我就做了,后面调低功耗和充电逻辑时帮了大忙。电源问题最怕毫无征兆,状态日志能直接告诉你是配置问题、路径问题还是保护触发,排查时间至少砍半。
5. 电量与健康监测:ADC 与中断怎么配合
5.1 从 PMIC 读回的量化数据
PCA9422 内部带有 ADC,电池电压、充电电流、片上温度这些量会以数字形式放在寄存器里,PIC18F4620 通过 I2C 读取后换算成真实物理量。关键是把数据手册给出的换算关系写成代码里的宏,比如电压寄存器每个 LSB 对应多少毫伏,电流寄存器每个 LSB 对应多少毫安。直接使用寄存器原始值会导致后续所有阈值判断都出错,这个换算步骤不能省。
我当时的做法是做一个统一的读取函数,把 PMIC 的原始采样值转换成工程值,再用这些工程值去做状态判断。这样策略代码里写的都是 3.6V、350mA 这样直观的数字,可读性高,后面换电池模型时改阈值也方便。
5.2 主控 ADC 作为冗余备份
只依赖 PMIC 内部 ADC 有一个风险:如果 I2C 通信异常,主控就拿不到任何电池状态了。所以我额外用 PIC18F4620 自身的 ADC 采集电池电压,通过两个精密电阻组成的分压网络接入。正常情况下两套数据互相印证,异常情况下即使 I2C 彻底断开,MCU 也能凭分压值做一次紧急低压保护。
分压电阻用 1% 精度,阻值选大一些,尽量减少对电池的消耗。ADC 参考电压我接了外部基准,比内部参考更稳,电压判断的重复精度提高不少。如果只是做粗略保护,内部参考也够用,但要做精准的电量百分比估算,外部基准值得加。
5.3 用中断代替轮询
电源管理里最不建议的就是满节奏轮询。低功耗设备大部分时间在睡眠,如果每隔几百毫秒醒一次读 PMIC 状态,总线功耗和 MCU 唤醒功耗都会上去。正确做法是利用 PCA9422 的中断输出:充电完成、故障、输入接入这些事件发生时 INT 引脚拉低,PIC18F4620 的外部中断把 MCU 从睡眠唤醒,主循环再通过 I2C 读取具体状态。
中断服务程序里只做一件事,置标志位然后退出。真正处理逻辑放在主循环,因为在中断里直接做 I2C 通信非常容易卡死,还会拉长中断响应时间,影响系统稳定性。这个习惯我踩过教训后才彻底改过来,强烈建议从一开始就按这个规范写。
6. 实测排障:五个坑的完整排查记录
6.1 首版板子 I2C 完全没有 ACK
第一版板子回来,烧上 I2C 扫描程序,扫了一圈没有任何地址回应,总线上一片死寂。我的排查链路是这样的:先看示波器上 SCL 和 SDA 的静态电平,发现都拉高了,说明上拉没问题;再查器件供电,PMIC 供电正常;然后检查 I2C 引脚连接,结果发现我把 RC4 接到了板上一个调试 LED 的复用网络,数据线被 LED 驱动电路干扰,等效于地址对不上。重新改板把 LED 移到其他引脚后,扫描程序立刻找到了器件地址。
这次教训让我意识到,I2C 引脚在原理图阶段就要检查复用冲突。特别是带 LED、按键、调试接口的板子,这些引脚很容易在布线后期被挪用,最终受害者往往是 I2C 这种对时序敏感的接口。
6.2 插入 USB 瞬间系统复位
插上 USB 给电池充电时,系统偶尔会复位,空板带电池时尤其明显。排查发现输入浪涌电流瞬间把系统电压拉低,MCU 掉电复位。对策分两步:一是把 PMIC 的输入限流配置调低一点,让充电启动时对输入侧的冲击更平缓;二是在 USB 输入端增加 TVS 和大容量储能电容,吸收插拔时的瞬态能量。这样处理后,插拔动作对系统轨的扰动明显减小,复位问题消失。
这个坑提醒我,电源管理方案测试时一定要覆盖热插拔场景,很多问题只有在带载动态切换时才会暴露。只做静态功能测试,永远不知道真实使用场景下会出现什么。
6.3 充电充到 80% 就不走
有一版电池实测充到接近 80% 电量时充电就停了,看起来像是充满,实际上电池电压离 4.2V 还差很远。查下来是终止充电电流阈值被我设置得太高,电池内阻稍微大一点,恒流阶段结束后电压还达不到满电,终止判断就提前触发了。把终止电流调低、加了一个满充检测的迟滞窗口之后,电池可以正常充到满电。
这里也想提醒一下,老化和低温下电池内阻会变大,满充判断不能只看电压,要结合充电电流衰减曲线一起看。电池是化学器件,不是理想电压源,它的表现会随温度和使用时间变化。
6.4 待机电流 2mA 压不下去
低功耗模式调试时发现整机待机电流一直在 2mA 左右,怎么都降不下。逐路断开负载排查,最后定位到 I2C 上拉电阻被放在了跨电源域的路径上:上拉电源来自系统轨,而 PMIC 的 I2C 引脚参考的是另一个电域,形成一条稳定的漏电路径。把上拉电阻改到和 PMIC 同一个电源域之后,待机电流立刻降了一个数量级。
另外一个常见漏电来源是悬空的 GPIO。所有不用的引脚要么设输入加内部弱上拉,要么设输出固定电平,绝对不能放着不管。这类问题用电流钳逐路排查很有效,就是费时间,最好在原理图评审阶段就堵住。
6.5 配置下发之后又被覆盖
最后这个坑比较隐蔽:代码里明明把充电参数写进去了,跑一段时间后发现配置又变回默认值。排查后发现是器件在某次上电复位后重新走了默认配置流程,而我的下发代码只在初始化阶段执行过一次,后续复位后不会再执行。解决办法是把配置下发逻辑放进一个统一的电源初始化函数里,无论冷启动还是复位后都会被执行,并且下发后强制读回校验,确保关键配置始终落在预期值上。
这个坑的根因是 PMIC 配置不具备非易失特性,任何复位都会丢失。所以在架构设计上,配置下发必须和系统初始化绑定,而不是和应用启动绑定,这个区别很多人会忽略。
整个方案做下来,我的体会是电源管理难不在某个单点,而在整个系统要把功率流向和策略逻辑彻底理顺。PCA9422 承担了充电、路径、保护这些脏活累活,PIC18F4620 做好配置和决策,两者配合起来,小型电池设备的电源才算真正可控。这里分享一个小技巧:把 PCA9422 的寄存器映射整理成一张表格,标注每个字段的默认值、实际值、写入时机,开发时打印出来贴在工位上,调试效率提升非常明显。如果你正准备做类似的可穿戴或其他电池产品,这个组合值得纳入方案评估,尤其是需要定制充电行为和精细电量管理的项目。