手里这个可穿戴小设备改了四次电源方案,最后一版总算稳定下来:主控用 PIC18F87J50,电源部分全部交给 PCA9422 这颗 PMIC,两个芯片之间就靠一条 I2C 总线通信。之前用分立 LDO 加充电管理芯片拼出来的方案,BOM 散出去四十几颗料,每次上电都像开盲盒,时序乱、纹波大、电池电压检测还老飘。换这套组合之后,BUCK 输出电压、充电电流、负载开关全都能用寄存器改,调试只需要改一个字节,不用再动烙铁。
这篇文章就把这套完整电源管理的设计思路、寄存器配置、PIC18F87J50 侧的驱动实现、实测数据和踩过的坑一起整理出来。适合正在做便携设备、电池供电产品,或者想在 8 位 MCU 平台上引入可配置电源方案的读者参考,内容偏实操,不会跟你绕理论。
1. 方案为什么这样搭
1.1 这类产品真正缺什么
便携产品往往同时要满足三件事:电池要能充、系统要有多路稳定电压、整机待机功耗要压得住。看似不难,但放在一起就很别扭。分立方案的典型状态是:BOM 里既有升压也有降压,还有一堆使能脚、反馈电阻、分压检测电路,每一路都靠硬件定死。真要调一路电压,得换电阻;要改充电电流,得换电阻;想测电池电压,还得自己布局一个分压网络,再靠 MCU 的 ADC 慢慢校正。
PCA9422 这种 PMIC 把充电、路径管理、两路 DC-DC、一路 LDO、负载开关整合在一个封装里,关键参数全走 I2C 寄存器。也就是说,硬件上你只需要接好电感、电容和电池,剩下全部交给软件。对量产调试来说,这个转变是本质性的:软件改寄存器比换贴片电阻快得多,而且每台机器还能在产线单独校准参数,不需要为不同电池容量准备不同料号的硬件版本。
1.2 两个芯片各管什么
PIC18F87J50 是一款带丰富外设的 8 位 MCU,本身带 USB、多路定时器、ADC 和硬件 I2C 模块,跑 3.3V 逻辑,非常适合做电池供电产品的主控。PCA9422 则专管功率:输入 USB/适配器的 5V 或者电池电压进来,经过内部路径管理和转换,输出一路、两路、三路不同的电源轨,同时承担电池充电控制。
两个芯片的协作逻辑很简单:PCA9422 负责"电",PIC18F87J50 负责"脑"。MCU 通过 I2C 读取 PMIC 的状态寄存器,知道现在是不是有外部电源、电池电压到了多少、充电有没有完成;再通过写寄存器,把各路输出电压切到合适的档位、把充电电流调到合适的值。它们之间唯一协作通道就是 I2C,占两根 IO 线,外加一个可选的中断脚。
| 芯片 | 职责 | 与对方协作方式 |
|---|---|---|
| PCA9422 | 电池充电、输入路径切换、多路 DC-DC/LDO 输出、状态上报 | 接受 I2C 配置,输出电源状态位 |
| PIC18F87J50 | 系统逻辑、任务调度、功耗状态管理、充电策略决策 | 通过 I2C 读写寄存器,控制 PMIC 工作模式 |
2. PCA9422 核心功能拆解与配置逻辑
2.1 输入路径与电池充电怎么处理
PCA9422 内部是典型的"系统带路径管理"架构:外部 5V 存在时,系统的负载优先由外部输入供电,同时 PMIC 内部以恒定电流、恒定电压的方式给电池充电;外部电源断开后,无缝切换到电池供电。好处很明显,外部电源在场时系统不会因为电池被边充边放而打嗝,电池寿命也更好保。对耳机仓、手环、便携标签这类产品来说,这套逻辑是刚需。
充电部分的配置点主要是三个:充电电流、截止电压和终止判断。充电电流一般按电池容量的 0.5C 来选,500mAh 的电池就配 250mA 左右,留一点余量到 300mA 也行,但别超太多,线性充电的功耗会直接变成热量。截止电压就按电池规格写,常见三元锂电是 4.2V,也有一些 4.35V 的配方,写错一格电池寿命会明显打折。这里建议直接读 PMIC 状态寄存器里的充电结束标志,不要自己用 ADC 去判电池满没满,PMIC 自己对 CV 阶段的截止电流判断比外部 ADC 可靠得多。
2.2 多路电源轨怎么规划
拿到一颗 PMIC,第一步不是去翻寄存器表,而是把你系统里所有负载的电压、电流、噪声要求列成一张表,再挨个对上电源轨。我这套设备里是这样分配的:
| 电源轨 | 来源 | 电压 | 供谁 | 特点 |
|---|---|---|---|---|
| VDD_MAIN | BUCK1 | 3.3V | 主控、外设、LED | 电流大,效率优先 |
| VDD_IO | BUCK2 | 1.8V | 传感器、存储卡接口 | 低纹波 |
| VDD_ANA | LDO | 1.8V | 模拟前端、基准 | 超低噪声,始终在线 |
| VBAT | 电池直连 | 3.0-4.2V | 后备供电、RTC | 常电,不受开关控制 |
这个分配是反过来推出来的:先把对噪声最敏感的放到 LDO,把大电流的放到效率高的 BUCK1,BUCK2 给不上不下的中间档。顺序上,我习惯让 LDO 那条常电轨直接挂在电池后面,保证系统即便是深度睡眠也有一路干净电源能维持 RTC 和唤醒逻辑;BUCK1 和 BUCK2 则按需使能,睡眠时可以直接关掉,省下它们的静态电流。
2.3 寄存器访问与初始化顺序
PCA9422 的寄存器拓扑大致分四块:系统状态/ID 类、BUCK 输出电压和使能类、充电控制类、中断/辅助功能类。7 位 I2C 从机地址我手头这颗是 0x10,写地址就是 0x20、读地址 0x21,不同批次可能不同,上电第一件事先扫一遍总线确认,别拿着数据手册里的地址硬写到程序里。
初始化顺序上,我建议严格按这个来:先读 ID 寄存器确认芯片在线且版本正确;然后配置 BUCK 的电压档位和限流,但先不要使能输出;接着配置充电参数,包括充电电压、充电电流、终止电流;再配置中断掩码,把需要 MCU 响应的状态位放开;最后才统一使能各路输出。这样做的原因很简单,避免芯片在未配置完整的情况下带着默认电压起载,万一默认值跟后级负载不匹配,上电瞬间就可能出事。
// 伪代码形式的初始化顺序,具体寄存器地址请对照用手头芯片的数据手册 #define PCA9422_ID_REG 0x00 #define PCA9422_BUCK1_CFG 0x05 #define PCA9422_BUCK2_CFG 0x08 #define PCA9422_CHARGE_CFG 0x0C #define PCA9422_ENABLE_REG 0x10 void pca9422_init(void) { uint8_t id = pca9422_read_reg(PCA9422_ID_REG); if (id == 0x00) { // 芯片没应答或者读到的 ID 异常,先停下排查硬件 report_power_error(); return; } // 先写电压配置,不使能 pca9422_write_reg(PCA9422_BUCK1_CFG, 0x2A); // BUCK1 -> 3.3V 档 pca9422_write_reg(PCA9422_BUCK2_CFG, 0x18); // BUCK2 -> 1.8V 档 pca9422_write_reg(PCA9422_CHARGE_CFG, 0x29); // 充电电压 4.2V,电流 300mA // 统一使能,保证上电时序一致 pca9422_write_reg(PCA9422_ENABLE_REG, 0x07); }3. PIC18F87J50 侧的软硬件实现
3.1 硬件连接和总线设计
PIC18F87J50 的 MSSP 模块可以配置成 I2C 主机,SCL 和 SDA 各接一颗上拉电阻。PCA9422 工作电压范围比较宽,和 MCU 同用 3.3V 逻辑时直接连就行,不需要电平转换。上拉电阻我建议 2.2k 到 4.7k 之间,总线电容不大就选 4.7k,省电;如果 I2C 线上还挂了多个从机,比如传感器、充电仓通信,就降到 2.2k,保证上升沿够快。
比较容易被忽略的是中断脚。PCA9422 一般会有状态输出脚,用来上报充电完成、输入插入、欠压等事件,这个脚要接到 PIC18F87J50 的边沿中断口上。即使你对自己的轮询频率有信心,也一定要接,否则充电完成的时刻可能晚几分钟才被读到,直接影响用户体验。
| PCA9422 引脚 | 接 PIC18F87J50 引脚 | 备注 |
|---|---|---|
| SCL | SCL(MSSP 时钟) | 上拉 4.7k 到 3.3V |
| SDA | SDA(MSSP 数据) | 上拉 4.7k 到 3.3V |
| INTB/状态输出 | 外部中断输入 | 低有效,触发充电完成/掉电事件 |
3.2 I2C 驱动与 PMIC 访问封装
PIC 的硬件 I2C 模块用起来不难,麻烦的是时序细节。我自己习惯封装三个函数:读寄存器、写寄存器、批量读状态。写寄存器时要注意,PMIC 有些寄存器是易失的,掉电或进入某些低功耗模式后会复位回默认值,所以每次从睡眠唤醒之后,需要重新执行一遍关键寄存器初始化,不能指望靠寄存器默认值活着。
// 基于 PIC18F87J50 MSSP 模块的 I2C 主模式驱动片段 // 时钟频率 100kHz 示例 #define FOSC 8000000UL #define I2C_SPEED 100000UL void i2c_master_init(void) { SSPADD = (FOSC / (4UL * I2C_SPEED)) - 1; // 计算波特率寄存器 SSPCON1 = 0x08; // I2C 主模式,使能 MSSP SSPCON2 = 0x00; SSPSTAT = 0x80; // 标准速率模式 } uint8_t pca9422_write_reg(uint8_t reg, uint8_t val) { uint8_t i2c_timeout = 0; while (SSPSTAT & 0x04) { // 等待总线空闲 if (++i2c_timeout > 200) return 0xFF; } SSPCON2bits.SEN = 1; // 发起 START while (SSPCON2bits.SEN); SSPBUF = 0x20; // 写地址 0x20 while (SSPSTATbits.R_nW && !SSPCON2bits.ACKSTAT); if (SSPCON2bits.ACKSTAT) { // 无应答则放弃 SSPCON2bits.PEN = 1; return 0xFF; } SSPBUF = reg; // 写寄存器地址 while (!SSPSTATbits.BF); SSPCON2bits.ACKDT = 0; SSPCON2bits.ACKEN = 1; SSPBUF = val; // 写数据 while (!SSPSTATbits.BF); SSPCON2bits.ACKEN = 1; SSPCON2bits.PEN = 1; // 发起 STOP while (SSPCON2bits.PEN); return 0; }我单独提一句总线等待机制。用循环轮询 MSSP 状态位的写法在工程里问题不大,但一定要加超时保护,不然 SDA 被某个从机拉死,主控就会一直卡在等待循环里,整机看起来就像死机了。我后来给所有 I2C 操作都套了一个通用的超时宏,实测下来排查问题省了大量时间。
3.3 上电与低功耗状态机
有了 I2C 驱动,剩下的就是状态管理。我把它拆成五态:上电复位态、运行态、充电态、睡眠态、深度睡眠态。每个状态里定义好哪些电源轨开着、充电参数怎么设置、中断关注哪些事件。
上电复位后,MCU 先做基础初始化,接着读一次 PMIC 全状态寄存器,判断当前有没有外部电源、电池电量如何,再决定进入充电态还是运行态。进入充电态前,把充电电流按电池特性设好,并且打开充电完成中断。睡眠态下,BUCK1 可以保持 3.3V 但降载,BUCK2 和外围负载关掉,LDO 常电轨保留 1.8V 给 RTC;深度睡眠再进一步。每切换一次状态,都把 PMIC 的关键寄存器重写一遍,因为 PCA9422 的部分配置在掉电后会恢复默认,这个坑我第一次没注意,导致睡眠唤醒后 BUCK 电压跌了一档,摸了半天才发现是寄存器没重写。
typedef enum { PWR_STATE_RESET = 0, PWR_STATE_RUN, PWR_STATE_CHARGE, PWR_STATE_SLEEP, PWR_STATE_DEEP_SLEEP } pwr_state_t; void power_state_switch(pwr_state_t new_state) { switch (new_state) { case PWR_STATE_SLEEP: // 关闭 BUCK2,BUCK1 保持,LDO 保持 pca9422_write_reg(PCA9422_ENABLE_REG, 0x05); break; case PWR_STATE_DEEP_SLEEP: // 保留 LDO 常电轨 pca9422_write_reg(PCA9422_ENABLE_REG, 0x01); break; default: pca9422_write_reg(PCA9422_ENABLE_REG, 0x07); break; } // 无论进哪个状态,先读回确认 verify_power_state(); }4. 实操记录:从寄存器到整机上电
4.1 先让芯片自己跑起来
我建议你第一次拿到板子,不要先连电池,不要跑 MCU 代码,只上外部 5V 输入。用示波器看 PCA9422 默认输出:上电瞬间每个 BUCK 是否有软启动斜坡、LDO 是否有输出、静态电流多少。这一步能确认焊接、电感、电容这三件套没有问题。
我当时在这步就抓到一个问题:有一颗 BUCK 的空载输出正常,一加负载就跌到一半,最后发现是电感虚焊。如果直接写代码跑系统,这个问题会被误判成寄存器配置错误,排查路径完全不同。所以"先硬件裸跑、再软件介入"的顺序,能帮你把故障面缩到最小。
4.2 用寄存器把电压调到目标值
PCA9422 的 BUCK 输出电压一般是一段范围,用寄存器里的电压档位字段选择,我手里这颗 BUCK1 可以在 0.6V 到 3.3V 之间按步进调。如果你想出 3.3V,就按数据手册给的编码表算一个值写进去,然后用万用表量出来的实际电压和设定值对一下。
实测下来,写寄存器后电压稳定时间大约在 1ms 到 2ms 之间,受输出电容影响。这里有一个技巧:先读完当前寄存器值,只修改电压位段,其他位保持不变,不要整字节写死,否则可能把软启动时间、限流配置这类藏在同一寄存器里的位给覆盖掉。
// 只更新 BUCK1 电压位段的示例 uint8_t tmp = pca9422_read_reg(PCA9422_BUCK1_CFG); tmp &= 0x0F; // 保留低 4 位原有配置 tmp |= (0x2A & 0xF0); // 写入新的电压编码高 4 位 pca9422_write_reg(PCA9422_BUCK1_CFG, tmp);4.3 充电路径验证
接上电池后,先用可调电源限流供电,观察充电电流是否按寄存器设定值走。线性充电的发热主要集中在 PMIC 封装上,充电 300mA 时我实测外壳温度比环境高十几度,这是正常范围,如果发现烫到不能摸,就检查电池是否接反或者充电电流配置是不是写错档了。
路径管理验证要做的动作是:外部 5V 插入瞬间,看系统总线上电压有没有跌落,波形应该是平的一条线,才说明路径切换没有问题。再快速拔掉外部电源,看系统瞬间切到电池供电,同样不应有电压中断。这个切换过程用示波器看触发沿,比用万用表靠谱得多。
4.4 整机功耗实测
系统功耗验证我按三档测:运行态、睡眠态、深度睡眠态。用精密电流表串在电池路径上,测整套系统的消耗,注意 PMIC 内部还有它的静态电流,这部分是省不掉的,选型时就要对数据手册里的 Iq 心里有数。
| 状态 | 实测电流 | 说明 |
|---|---|---|
| 运行态(蓝牙扫描+传感器采集) | 58mA | 峰值会到 130mA,均值稳定 |
| 睡眠态(BUCK1 降载,BUCK2 关) | 1.2mA | 主要消耗来自 LDO 常电轨和 MCU 掉电模式 |
| 深度睡眠(仅 RTC 唤醒) | 42uA | PMIC 静态电流加 MCU 最小功耗 |
5. 常见问题与排查技巧实录
5.1 I2C 总线被拉死
最经典的现象就是 MCU 跑起来后,所有 I2C 操作都超时,示波器看 SDA 一直被拉低。原因通常是某个从机进入了错误状态,或者总线没有释放。处理方法:首先把所有从机的 SDA 脚置为高阻,看谁在拉低;其次检查上电顺序,很多 PMIC 要求输入电源先于 I2C 通信稳定,如果 MCU 起步比 PMIC 快,第一次通信就会撞上 PMIC 还在初始化。
我后来加了这么一段处理:MCU 上电后先延时 10ms 再初始化 I2C,然后先读一次 ID,失败的话再延时重试三次。这个方法简单粗暴,但排掉了很多冷启动的怪问题。
5.2 输出电压比设定值低
遇到这种问题,先排除负载过流和电感问题,再看寄存器是不是没写进去。我踩过一次低级坑:读回寄存器发现值是对的,但输出电压就是不对,后来发现 PMIC 上电时被 MCU 的程序干扰,配置写了一半 MCU 崩了,之后 PMIC 停在中间态。解决办法是配置写完一定要读回校验,校验失败就整片重新初始化。
5.3 BUCK 空载输出跳变
PMIC 的 DC-DC 轻载时通常进入 PFM 模式,输出会有小幅纹波,这不一定是故障。但如果跳变幅度超过 50mV,先查输出电容够不够,再查反馈路径有没有被干扰。还有一点,示波器探头接地线太长也会看到一堆假纹波,探头接地要挂在芯片附近的 GND 上,别夹在电源输入那边的地线上。
5.4 充电永远不结束
充电一直指示进行中,先看状态寄存器里的电流是不是已经低于终止电流。如果 PMIC 的终止电流设置得比系统负载还小,或者电池一直挂着负载在充,它确实永远觉得还没充满。这也是我把系统负载和充电路径分开设计的另一个原因:要确保充电末段电流能降下来,否则会一直充到过压保护,电池寿命会受影响。
6. 布局与测试的小经验
6.1 PCB 布局要点
PCA9422 的 BUCK 部分开关频率不低,SW 节点的 di/dt 很大。布局上,电感要尽量靠近 SW 脚,输入电容和输出电容都要紧挨着芯片对应引脚,形成小的回路面积。最忌讳的是把 I2C 的 SCL/SDA 走线平行放在电感底下,我第一版板子就是这么干的,结果通信偶尔出错,加屏蔽罩才好,其实重新布线就能解决。
PMIC 的散热焊盘一定要接到足够大的地铜皮上,充电时候的热量主要靠它走。过孔别舍不得打,焊盘底下打一整排过孔到背面地层,实测外壳温度能低好几度。
6.2 调试工具怎么配
最低配置是一台带深存储的示波器和一个可调直流电源。示波器两通道就够了:一路测系统电压,一路测 SW 节点或者 I2C 波形。有条件再配一个电子负载,测 BUCK 恒流能力会方便很多。电流波形不要用万用表,测动态功耗变化要用电流探头或者低边采样电阻转差分。
我个人在调试阶段会在电池正极串一个 10mΩ 采样电阻,用示波器直接看整机电流的实时波形。这个视角比看电压有用多了,睡眠唤醒瞬间的电流尖峰、PMIC 路径切换时的小毛刺,都能一眼看到。
最后再分享一个习惯:每调好一版参数,就把寄存器清单导出来存成一份文本,跟固件版本一起打标签。PMIC 这类可编程电源,最怕的其实就是"这版参数到底设了啥"说不清楚。有了寄存器快照,板子换一批、固件升级一次,对照起来就非常快。
这套 PCA9422 加 PIC18F87J50 的组合,现在已经成为我做便携设备电源方案的默认起点。你可以先用它把产品跑起来,后面再根据实际功耗数据决定要不要换更省的 PMIC 或者把某一路改成 MCU 自己的休眠电源策略。可编程电源的好处就是,硬件一旦定型,后面所有优化都还在软件能触及的范围里。