☰
PCA9422与PIC18F4525打造便携设备电源管理方案
2026/10/10 5:05:20 网站建设 项目流程

做便携设备这些年,我踩得最深的坑基本都集中在电源链路。MCU逻辑再清楚,电池一掉压、USB一拔、负载一拉,各种怪问题就跟着来:莫名复位、充电充满识别不到、电池放久了直接过放。后来做一台便携式环境监测终端,我索性把电源管理整体交给PCA9422这颗可I2C配置的PMIC,由PIC18F4525单片机在系统层负责充电管理、状态监控和低功耗切换。这套组合跑通以后,我才算把“电源管理”从一个调试痛点,变成了真正可控的功能模块。

这篇文章按项目推进的顺序来写,把硬件连接、I2C驱动、充电状态机、低功耗优化和实测中踩到的几个典型问题完整拆开。适合正在做锂电供电小设备、或者想给现有MCU项目补上充电管理和多路电源轨控制的开发者参考,代码和参数都是可以直接拿来改的那种。

1. 一块MCU管不住整个电源链路:先把职责拆清楚

1.1 锂电池供电为何“不听话”

锂电池本质上不算一个稳定的电源。满电时有4.2V,放到截止大概3.0V,中间平台期在3.7V上下波动,而且带载瞬间电压会塌一截。如果你直接拿电池经过LDO给MCU供电,负载一加上去,电压可能从3.7V瞬态掉到3.2V,ADC采样跟着抖,MCU的掉电复位门限一被触发,系统就重启。

更麻烦的是充电。锂电池充电需要恒流、恒压两段式,接近充满时电流要逐步往下降,降到截止电流才能判断充满,过充、过放、过温都需要及时切断。这些动作靠MCU配合分立器件去做不是不行,但意味着你要自己搭充电回路、自己写保护逻辑,还要处理电池内阻、温度、USB输入限流这些变量。一套搞下来,复杂度远超一个普通应用代码。

1.2 电源管理的三件事:充电、变换、监控

我把整套电源链路拆成三件事,这也是接入PMIC之后最清晰的分工方式。

第一件是充电管理。从USB进来5V,经过PMIC内部的充电回路给单节锂电充电,MCU只需要通过I2C读状态,就能知道当前是预充、恒流充、恒压充还是已经充满。第二件是电源变换。电池电压范围摆在那里,系统需要稳定的3.3V、1.8V这些轨,PMIC内置Buck和LDO把电压转好,MCU不用自己去控制功耗电路,只需要配置输出目标值。第三件是状态监控。PMIC把VBUS是否插入、电池是否欠压、充电是否异常、是否有故障上报到寄存器,MCU定期读取,再决定系统进正常运行还是进休眠、关机。

这三件事如果全压在MCU头上,每一件都得靠外部电路补,每一处补出来的电路又都是新的故障点。

1.3 为什么是PCA9422和PIC18F4525这个组合

PCA9422是一颗面向单节锂电应用的PMIC,内部集成了充电路径、主Buck输出、几路LDO,而且电源轨的电压和开关控制都可以通过I2C动态配置。这意味着同一个硬件设计,软件上改一个寄存器就能把某路输出从3.3V切到2.8V,调试阶段非常省事。

PIC18F4525这边,48KB的Flash和接近4KB的SRAM对电源管理逻辑来说绰绰有余,自带的MSSP模块可以直接做I2C主站,ADC可以独立采样电池电压,BOR掉电检测能作为最后一道防线。8位机的优势在这种场景下反而明显:启动快、行为确定、没有操作系统层面的不确定性,做电源状态机很合适。

这套组合的本质,就是让PMIC管好功率级的事情,MCU只管读状态、做决策、控时序。

2. 硬件设计:PCA9422外围电路与PCB上的取舍

2.1 从VBUS到SYS的功率路径

先看功率流。VBUS进来之后,PMIC内部有一个电源路径管理逻辑:当USB插入时,VBUS优先给SYS系统轨供电,同时用剩下的电流给电池充电;USB拔掉瞬间,内部开关切到电池侧,由电池通过同一个SYS轨继续供电。MCU不需要干预这个切换,读状态寄存器就能知道当前供电来源。

我板子上的连接关系大致是这样:

  • VBUS进来接5V USB输入,输入脚旁边放了一颗2.2µF陶瓷电容,再串一颗几百毫欧的限流电阻做保护;
  • 电池正极接BAT脚,电池负极直接接功率地,注意不要在电池负端串采样电阻,除非你确认PMIC支持外部检流;
  • SYS轨作为系统主供电,接到后级负载,同时给MCU的3.3V电源输入供电;
  • I2C的SCL、SDA接到PIC18F4525的MSSP引脚,外部上拉到SYS轨;
  • 其余LDO输出分别给传感器、模拟前端等,具体电压由寄存器配置。

有一个细节我一开始忽略了:PMIC的I2C上拉如果接到某一路可关断的LDO上,而你又把这路LDO关了,总线就会跟着浮空,后续怎么读写都不对。所以上拉一定要接在SYS这种常供电的轨上。

2.2 电感电容选型与放置

PCA9422这类PMIC的主Buck输出通常需要一个功率电感。我选的是2.2µH、饱和电流大于2A的贴片功率电感。选择依据很简单:系统满载时峰值电流在1A上下,电感饱和电流至少留50%以上裕量,否则电流一大电感值塌掉,输出纹波会急剧恶化。

输出电容方面,SYS轨我放了10µF陶瓷电容靠近IC输出脚,再在负载端补了一颗22µF;LDO输出各放1µF到2.2µF。陶瓷电容选X5R或者X7R,别用高容量的Y5V,温度一变化容量掉得离谱。放的位置比容量更关键,所有去耦电容都要贴着对应引脚放,过孔尽量少,走线先经过电容再到平面。

给一组我验证过的参数参考:

元件选型说明
主Buck电感2.2µH,饱和电流≥2A靠近LX脚,功率回路面积最小化
SYS输出电容10µF + 22µF陶瓷10µF贴近IC,22µF放在负载供电入口
VBUS输入电容2.2µF陶瓷兼做热插拔缓冲
LDO输出电容1µF~2.2µF陶瓷每路LDO各一颗,靠近输出脚
I2C上拉电阻4.7kΩ100kHz速率,走线不长时很稳

2.3 地线布局与采样

电源板的地线设计是头等大事。功率路径的电流环路要短,VBUS、电池、SYS之间的地不要绕大圈,否则开关节点的高频噪声会把地电位打得到处乱跳。我的做法是PMIC下方的地平面连续铺铜,功率地和信号地在MCU附近单点汇合,避免让大电流回流穿过ADC采样参考地。

另外,如果板上有电池电压分压采样给MCU的ADC,分压电阻的地要单独引回PMIC地平面,不要和负载回流的强电地串在一起,不然采出来的电压会带几十毫伏的纹波,影响欠压判断。

3. I2C主站驱动:PIC18F4525与PCA9422的通信层

3.1 MSSP模块初始化:速率是怎么算出来的

PIC18F4525的MSSP模块配置成I2C主模式,核心就一个公式:SCL频率等于Fosc除以4倍的(SSPADD加1)。反过来,要得到目标频率,SSPADD就等于Fosc除以4倍目标频率再减1。

我主频用的8MHz,I2C跑100kHz,算出SSPADD是19,也就是0x13。如果跑400kHz,SSPADD就是4。实际项目里我把速率定在100kHz,不是400kHz做不到,而是PMIC寄存器操作本身没有高带宽需求,100kHz在长走线、差环境下的抗干扰能力明显更好。

初始化代码:

#include <stdint.h> #include <xc.h> #define _XTAL_FREQ 8000000UL void i2c_init(unsigned int fsck_khz) { // SSPADD = Fosc / (4 * FSCK) - 1 SSPADD = (uint8_t)((_XTAL_FREQ / (4ul * fsck_khz * 1000ul)) - 1); SSPSTATbits.SMP = 1; // 标准速率压摆率 SSPSTATbits.CKE = 0; SSPCON1 = 0x38; // SSPEN=1, CKP=1, SSPM=1000 -> I2C主模式 SSPCON2 = 0x00; TRISCbits.TRISC3 = 1; // SCL设为输入(开漏) TRISCbits.TRISC4 = 1; // SDA设为输入(开漏) }

注意SCL和SDA都要配置成输入方向,I2C是开漏总线,靠外部上拉电阻拉高,引脚方向必须写成输入,否则内部上拉和开漏逻辑会打架。

3.2 寄存器读写驱动

底层驱动就是标准的I2C主机流程:START、发器件地址、发寄存器地址、发数据、STOP,读操作中间还要插入一个RESTART。PIC18F4525的MSSP模块用硬件管这些时序,MCU只要往SSPCON2的相应位写1,再等硬件清掉这个位。

void i2c_start(void) { SSPCON2bits.SEN = 1; while (SSPCON2bits.SEN); } void i2c_stop(void) { SSPCON2bits.PEN = 1; while (SSPCON2bits.PEN); } void i2c_restart(void){ SSPCON2bits.RSEN = 1; while (SSPCON2bits.RSEN); } uint8_t i2c_write_byte(uint8_t byte) { PIR1bits.SSP1IF = 0; SSPBUF = byte; while (!PIR1bits.SSP1IF); // 等待发送完成 return SSPCON2bits.ACKSTAT; // 0表示器件应答,1表示NACK } uint8_t i2c_read_byte(uint8_t send_ack) { uint8_t data; SSPCON2bits.RCEN = 1; while (!SSPSTATbits.BF); // 等待接收缓冲满 data = SSPBUF; SSPCON2bits.ACKDT = send_ack ? 0 : 1; // 0发ACK,1发NACK SSPCON2bits.ACKEN = 1; while (SSPCON2bits.ACKEN); return data; }

对PCA9422的读写封装成这样,其中7位地址0x32是我这块板上芯片的地址,不同后缀型号可能不同,务必以数据手册和实际读回的ID寄存器为准:

#define PCA9422_ADDR_W 0x64 // (0x32 << 1) | 0 #define PCA9422_ADDR_R 0x65 // (0x32 << 1) | 1 void pca9422_write_reg(uint8_t reg, uint8_t val) { i2c_start(); i2c_write_byte(PCA9422_ADDR_W); i2c_write_byte(reg); i2c_write_byte(val); i2c_stop(); } uint8_t pca9422_read_reg(uint8_t reg) { uint8_t val; i2c_start(); i2c_write_byte(PCA9422_ADDR_W); i2c_write_byte(reg); i2c_restart(); i2c_write_byte(PCA9422_ADDR_R); val = i2c_read_byte(0); // 最后一字节回NACK i2c_stop(); return val; }

这里最容易出错的是读操作末尾的NACK。最后一个字节如果不回NACK,从机以为你还要继续读,会一直拉着总线,下一次START就会乱。

3.3 正常工作之外的边界情况

I2C通信不能只写顺滑路径,还要处理异常。PMIC在上电初期可能处于内部复位窗口,会拉伸时钟或者干脆不应答,此时MCU立刻发起通信就会卡住。我的处理办法是:系统上电后先延时几毫秒再初始化I2C和PMIC,所有读写操作都加超时保护,用主循环里的计数器控制等待上限,超时就复位I2C模块。

另外,PCA9422的寄存器操作最好做一次写后读回校验,尤其是充电电流这类关键参数。写进去是一回事,芯片是否正确接收是另一回事,读回确认能挡住绝大多数偶发通信错误。

4. 充电管理与系统状态机:从插上USB到电池欠压

4.1 上电初始化顺序

PMIC的寄存器初始化顺序有讲究。我习惯先读ID寄存器确认芯片版本,再配置各路输出电压,接着配置充电参数,最后使能输出。如果一上来就先使能输出再改电压,负载看到的可能是一段时间的错误电压,某些对外设器件来说这就是一次异常上电。

初始化流程示意:

uint8_t id = pca9422_read_reg(REG_DEVICE_ID); if (id != EXPECT_DEV_ID) { // 芯片不对或I2C异常,进入错误处理 return; } pca9422_write_reg(REG_BUCK_VOUT, BUCK_3V3); pca9422_write_reg(REG_LDO1_VOUT, LDO1_1V8); pca9422_write_reg(REG_LDO2_VOUT, LDO2_3V0); pca9422_write_reg(REG_CHG_CTRL, CHG_500MA | INPUT_LIMIT_1A); pca9422_write_reg(REG_MODE, BUCK_EN | LDO1_EN | LDO2_EN);

4.2 充电状态识别:恒流、恒压与充满判断

PCA9422的充电状态直接映射在状态寄存器里。充电过程分几个阶段:电池电压过低时先预充小电流,然后进入恒流充电,电压升到4.2V左右切到恒压段,电流逐渐下降,降到截止电流后充电完成。

实际代码里判断逻辑是这样:

#define ST_VBUS_PRESENT 0x80 #define ST_CHARGING 0x40 #define ST_CHARGE_DONE 0x20 #define ST_BAT_LOW 0x10 #define ST_FAULT 0x08 uint8_t sta = pca9422_read_reg(REG_STATUS); if (sta & ST_VBUS_PRESENT) { if (sta & ST_CHARGING) { // 正在充电,按充电状态更新UI或日志 } else if (sta & ST_CHARGE_DONE) { // 充满,可以停止充电相关的提示 } } if (sta & ST_FAULT) { // 读取故障寄存器并处理 }

这里有个经验:不要在“正在充电”状态下去读电池电压判断电量。充电时电池端电压是被充电回路抬高的,读出来偏高,拿这个做电量百分比会误导状态机。要么在充电电流为零的窗口采样,要么干脆信任PMIC的状态位,把电量估算放在非充电时段做。

4.3 系统状态机的完整流转

我把系统状态定义成一个枚举:

typedef enum { SYS_IDLE, SYS_RUNNING, SYS_CHARGING, SYS_LOW_POWER, SYS_POWER_OFF } sys_state_t;

主循环里先读一次PMIC状态寄存器,再根据当前状态做迁移。USB插入且未充满,从IDLE进CHARGING;USB拔出且电池电压正常,进RUNNING;电池电压低于欠压门限,进LOW_POWER,关闭不必要的外设负载,只保留RTC和唤醒源;如果继续掉到安全截止电压,直接进POWER_OFF,让PMIC进入低功耗关断模式。

这个状态机看起来简单,真正的复杂度在边界条件的防抖。USB插拔瞬间,状态寄存器会抖动,我做了10ms的软件防抖,连续两次读到同一个状态才切换。电池欠压判断也做了迟滞,电压回升到3.5V以上才允许从LOW_POWER回到RUNNING,避免临界电压来回横跳反复开关机。

5. 低功耗优化:待机电流从毫安压到实测微安级

5.1 PMIC侧的低功耗配置

待机电流的优化,一大半在PMIC侧。设备进入休眠前,先把那些只在运行期才用的LDO关掉,比如给传感器供电的LDO2,直接写寄存器关闭。主Buck如果负载很小,PMIC会自己进入PFM轻载模式,开关频率降下来,静态损耗自然小。

我踩过一个细节:PMIC有个“低功耗待机”相关的模式位,进入之前要先把唤醒相关的中断使能配置好,否则睡了就醒不来。这个位和LDO的开关控制是分开的,别只关了负载忘记让PMIC进入低功耗模式。

5.2 MCU侧睡眠策略与外设电流

PIC18F4525这边,进入Sleep之前要把ADC关掉,不用的模块全部禁用,所有IO口要么拉到固定电平、要么设为输出低,防止经过引脚上下拉的漏电流。尤其注意没用的模拟输入引脚,高阻态时容易通过内部电路漏电。

唤醒源方面,我保留了一个RTC定时唤醒和一个按键外部中断唤醒。定时唤醒后只读一次PMIC的充电状态,有充电事件就进充电状态机,没有就马上再睡回去。这套流程跑一次大概10ms,功耗摊下来可以忽略。

5.3 实测电流构成与测量方法

这是我在VBAT端串万用表实测的一组数据:

系统状态配置说明实测VBAT电流
深度休眠LDO全部关闭,PMIC低功耗,MCU Sleep9.6µA
待机LDO1保持1.8V,MCU Sleep54µA
空载运行主Buck和LDO全开,MCU Idle5.8mA
运行+外设传感器和通信模块工作取决于负载

9.6µA的深度休眠状态里,PMIC自身静态加MCU休眠加板级漏电占了绝大部分。想继续往下压,就得从漏电和分压电阻下手,比如电池电压分压采样电阻用大阻值,或者用MOSFET在休眠时把分压网络断开。

测量方法要注意,万用表毫安档内阻不算大,但微安档的内阻可能到几百欧,串进电池回路会引入额外压降,影响PMIC的充电判断。我常用的是在电池负端串一个1欧姆采样电阻,用示波器看在各种状态下的压降,再换算电流,数据比万用表准得多。

6. 调试中踩到的三个典型问题

6.1 I2C总线偶发“卡死”的教训

现象:系统跑一段时间后,PMIC的寄存器读回来全是0xFF,紧接着看门狗复位,复位后I2C通信仍然失败,要完全断电才能恢复。

排查过程:先用示波器抓SDA和SCL,发现总线停在SDA为低、SCL空闲的状态。这是典型的I2C“锁死”现象。进一步定位发现,问题出在MCU从休眠唤醒的时刻,正好撞上PMIC内部上电复位的准备窗口,PMIC此时会拉伸时钟或者处于不应答状态。MCU的MSSP模块没有超时机制,遇到这种状况会一直等下去,等看门狗复位之后,PMIC可能还处于错误状态,总线就一直被拉着。

解决办法分两层。第一层,所有I2C读写都加超时,等待标志位超过一定时间就放弃本次操作并复位I2C模块。第二层,预留一套GPIO模拟的“总线释放”子程序:把SCL和SDA临时切到普通IO,手动拉9个SCL脉冲,让挂在总线上的从机完成内部状态复位,再切回MSSP。这个函数在通信异常时调用,能救回绝大多数锁死场景。

6.2 充电电流怎么测都不对的真相

现象:寄存器配置充电电流500mA,万用表串在VBUS输入端测,稳定在接近700mA;再换到电池端串表测,又变成350mA。怎么看都和设定值对不上。

原因:PCA9422是电源路径管理架构,VBUS输入电流等于系统负载电流和电池充电电流之和。我板上的系统负载当时跑了将近200mA,500mA充电加200mA系统,VBUS侧自然看到700mA。而在电池端串万用表,微安档和毫安档的内阻不同,再加上接触电阻和线阻,会把充电回路的电压条件改变,PMIC检测到的电池侧电压和电流路径都变了,充电电流跟着缩水。

正确测量方法:想验证充电电流,应该直接在电池正极和PMIC的BAT脚之间串一个低阻采样电阻,用示波器看电阻两端的压差波形,换算得到瞬时充电电流。同时要注意输入电流限制的配置,如果输入限流设置得太低,PMIC会优先保证系统负载,剩下多少电流才给电池充电,充电电流自然达不到设定值。把输入电流限制设为系统峰值加充电设定值,再留出裕量,才能让充电电流稳定达到目标。

6.3 USB转电池瞬间的系统复位

现象:设备正用USB供电运行,拔掉USB的瞬间,MCU偶尔复位;示波器看SYS轨,在切换瞬间塌了一个接近VDD的缺口,持续时间只有几百微秒。

原因:VBUS断开后,PMIC内部的电源路径要从VBUS切到电池,这个切换过程里SYS轨的输出电容会先放电,Buck控制环路还没来得及接管。如果负载瞬时电流大,电容上的电压就会被拉穿,MCU的BOR一旦触发就复位。

处理办法我做了三件事。第一,在SYS轨出口增加了一颗22µF陶瓷电容,给切换过程多留电荷裕量。第二,把PMIC在轻载下的PFM模式调整成固定PWM模式,切换瞬间控制环路响应更快,代价是空载功耗稍高,只在必要阶段开。第三,MCU侧把BOR配置成较快的触发响应,与其让MCU在电压异常区间继续跑出脏数据,不如第一时间复位,软件启动时再根据状态寄存器判断要不要进入低功耗。加了这三条之后,USB反复插拔测试上百次,没有再触发复位。

这套方案做完,我最大的一点体会是:电源管理不该是MCU“顺便做做”的事,它应该像通信、存储一样,成为一个独立的、有清晰职责划分的模块。PCA9422负责把电压电流这些脏活干好,PIC18F4525负责看懂状态、做对决策,两边各司其职,后面再往上加功能或者换低功耗策略,都只动软件不动硬件。如果你手头正好有类似的锂电便携项目,建议也先画一张“谁管功率、谁管决策”的架构图,再动手画板写代码,能省下大量的联调时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询