ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于PCA9422与PIC18LF4620的便携设备电源管理设计实战

基于PCA9422与PIC18LF4620的便携设备电源管理设计实战

做便携式采集设备那次,主控选型倒是没纠结太久,最后定的是PIC18LF4620——8位、低功耗、外设够用,开发起来也简单。真正让我头疼的是电源:三路电压轨、锂电池充电、USB供电路径切换、低功耗待机,这几件事堆在一起,不是一颗LDO能扛住的。当时脑子里其实闪过好几个方案,最后在板子上加了PCA9422把电源管理逻辑全部揽下,才算是把整个方案理顺了。这篇文章就把从选型、硬件设计到固件实现和调试的完整过程梳理一遍,给正在做同类电池供电设备的朋友一个能直接参考的路线。

1. 选型逻辑:为什么是 PCA9422 + PIC18LF4620 这对组合

1.1 这个项目要解决什么电源问题

先交代一下项目背景。我们当时做的是一个手持式环境监测终端,用单节锂电池供电,同时要支持USB口充电和固件升级。整个系统需要的电压轨有三路:

  • 3.3V给主控、传感器和显示模块,这是系统最重的一路负载,峰值电流能到500mA左右;
  • 1.8V给外部存储、实时时钟和一部分IO电平转换;
  • 1.2V给射频前端和内部比较器等高精度模拟模块,这一路对纹波比较敏感。

除了电压轨之外,还得处理锂电池的恒流恒压充电、USB接入时的电源路径切换,以及设备进入深度休眠时整个系统的功耗控制。这三件事单独做都不难,但凑在一起,方案就会变得很乱。

最初我确实考虑过全分立方案:一颗线性充电IC管充电,一颗降压DCDC管3.3V,再串两颗LDO管1.8V和1.2V。这套电路非常成熟,市面上每一类器件都有大量选择。但真把BOM拉出来一算,问题就来了:整个电源部分要用到七八个主器件,再配合负载开关、使能逻辑、各种容阻,PCB面积被吃掉很大一块。功耗也难做——每颗器件本身的静态电流叠在一起,待机时整个系统的底电流很容易跑到一两百微安以上。

1.2 分立方案为什么被放弃

除了面积和功耗,分立方案还有一个很麻烦的隐患:时序。多芯片方案要把使能脚、复位脚、PG信号互相拉来拉去,才能保证上电时各路电压按照正确的顺序建立和撤销。稍微有一个器件的行为和预期不一样,轻则外设初始化失败,重则逻辑器件闩锁。项目走到后期要改电源参数时,分立方案基本只能动硬件,每次都是两三天周期。

相比之下,PCA9422这类集成PMIC把降压转换、低压差稳压、电池充电、电源路径管理和状态监控全部做进一颗芯片里,中间的分立器件被大幅砍掉。更重要的是,所有功能都由I2C寄存器控制,前期调试和后期参数优化都变成软件工作。这正是我最终选择它的核心原因——不是因为它参数有多极致,而是因为它让电源管理从"硬件活儿"变成了"软件活儿"。

1.3 两颗芯片各自的分工

这套系统里,PCA9422和PIC18LF4620的分工非常明确。PCA9422是"执行者",负责把输入电压变成多路稳定的输出,实时监视电流和温度,并在需要的时候报中断;PIC18LF4620是"决策者",负责上电时把寄存器配置灌进去,运行中根据系统负载和状态切换电源模式,在故障时读取状态并执行复位保护。

从职责边界看,PIC18LF4620其实没有参与任何电压转换动作,它只是通过I2C管理PCA9422。这个分工让我们团队里做驱动的人不用关心电感电容怎么选,做硬件的人也不用写一行寄存器配置代码,两边靠一份寄存器映射表对接,效率高很多。

2. 系统电源架构:从电池到每一路电压的完整链路

2.1 输入侧:USB/适配器与电池之间的路径切换

先看输入侧。系统输入有两个来源:锂电池(电压范围3.0V到4.2V)和USB口的5V电源。PCA9422内部集成了电源路径管理,作用相当于一个自动切换开关:

  • 当USB或适配器插入时,系统负载直接由外部电源供电,同时PMIC内部充电器给电池充电;
  • 外部电源拔出后,系统自动切换为由电池供电,中间不需要MCU干预。

这个机制非常像家里用的UPS不间断电源:市电正常时,负载用市电,顺便给电池充着电;市电断电,电池立刻顶上,电视和电脑不会黑屏。放在设备里,好处是插着USB工作时,电池不会反复充放电,循环寿命明显更好。

实际配置中,我把充电管理设为单节锂离子电池的标准曲线:恒流阶段以设定的电流充电,电压到4.2V后转入恒压,电流逐渐下降,降到终止电流以下就停止充电并给出充电完成标志。这个曲线是PMIC内部硬件完成的,MCU只需要在初始化时把目标电压和电流写进寄存器即可。

2.2 输出轨设计:三路电压怎么分配、怎么接负载

PCA9422内部集成了两路降压转换器和一路低压差线性稳压器,正好对应系统的三路需求。我的分配方式是:

输出轨来源目标电压负载对象特点
VDD_3V3BUCK13.3V主控、传感器、显示器电流大,效率优先
VDD_1V8BUCK21.8V存储、RTC、IO电平功耗低,占空比小
VDD_1V2LDO1.2V射频前端、模拟比较器低噪声,纹波优先

为什么把3.3V给BUCK1而不是给LDO?因为这一路电流大,BUCK的效率通常能做到90%以上,而LDO压差大的时候效率只有百分之三四十,差出来的能量全变成热。反过来1.2V模拟负载电流很小,用LDO虽然效率不高,但胜在输出纹波极低,不会给模拟电路引入开关噪声。电源轨的分配本质就是一个"效率优先给重负载,纯净优先给敏感负载"的过程。

3.3V这一路还有一个特殊作用:它同时给PIC18LF4620供电。也就是说,MCU的VDD直接挂在BUCK1输出上,PMIC和MCU形成了一条完整的反馈链:MCU通过I2C向PCA9422下发配置,PCA9422输出的电压再供给MCU自身。这个闭环一开始让我有点担心启动顺序,但实际操作下来,PCA9422的默认输出在上电时就能建立,MCU随后启动并接管配置,时序是安全的。

2.3 电源路径管理下各个状态的电流走向

为了让整机功耗可控,我把系统运行状态分成四挡:运行态、轻载态、休眠态、深度休眠态。每一挡对应不同的电源配置:

  • 运行态:BUCK1、BUCK2、LDO全部开启,所有外设正常供电;
  • 轻载态:BUCK1、BUCK2开启,LDO关闭,MCU降频运行,外设逐块断电;
  • 休眠态:只保留BUCK1给MCU供电,BUCK2和LDO关闭,MCU执行SLEEP指令;
  • 深度休眠态:BUCK1切换到轻载节能模式,MCU的所有外设时钟关闭,只保留唤醒源。

这里有个容易被忽略的点:BUCK在轻载时如果继续跑在固定开关频率的PWM模式下,空载损耗会很高。PCA9422支持自动在轻载时切换到PFM突发模式,也就是负载小的时候降低开关频率、跳过若干周期,从而把待机功耗压下来。这个切换可以靠硬件自动完成,也可以由MCU写寄存器强制指定。我在休眠态一律强制成PFM模式,等唤醒后再切回PWM,这个切换动作由电源状态机统一处理。

3. 硬件实战:原理图、选型与布局要点

3.1 PCA9422 最小外围电路

PCA9422虽然集成度高,但毕竟是电源芯片,外围的电感电容选型直接决定性能。我第一版原理图的核心元件参数如下:

输入侧,我在USB口和电池接口都加了ESD保护器件,同时放置了22uF的陶瓷输入电容。这个电容离芯片的VBUS和VBAT引脚尽可能是毫米级的距离,目的是吸收热插拔时的浪涌电流,同时稳定芯片内部路径管理的输入阻抗。

BUCK路的电感我选了4.7uH,额定饱和电流按输出峰值电流的1.3倍留余量。这里要特别说明一下:电感饱和电流宁大勿小,一旦电感到达饱和点,感值会突然跌落,电流波形迅速畸变,轻则纹波变大,重则芯片过流保护。我习惯在选型表里同时看直流电阻DCR,DCR越低满载效率越高,但也不能无脑选大封装,PCB面积在那里摆着。

输出电容方面,BUCK1和BUCK2都用了2颗22uF陶瓷电容并联,再各加一颗0.1uF高频电容吸收开关尖峰。LDO输出则用1uF加0.1uF组合,因为LDO负载轻,不需要太大容值。

3.2 PIC18LF4620 与 PCA9422 的接口连接

两颗芯片之间的物理连接非常少,真正用得上的只有六根线:

信号方向作用
SCLMCU → PCA9422I2C时钟
SDA双向I2C数据
INTPCA9422 → MCU中断请求
ENMCU → PCA9422芯片总使能
GPIO1PCA9422 → MCU电源插入状态
GPIO2PCA9422 → MCU充电状态 / 充电完成

I2C的两根线都加了上拉电阻,这里有个坑:上拉电阻阻值不是随便选的。我总线跑100kHz时用10kΩ,跑400kHz时换成了2.2kΩ。原因很简单,上拉电阻太大会让上升沿变缓,在400kHz下容易触发通信错误;太小又在低功耗状态白白增加电流。如果你系统里I2C总线很短、器件又少,甚至可以试试4.7kΩ作为折中值,我们后期量产板就是这么做的。

INT引脚接在MCU的RB0上,配置为下降沿触发。PCA9422检测到充电完成、输入电源切换、输出电压跌落、过温等事件时会拉低INT,MCU在中断里读状态寄存器区分具体原因。EN引脚我没有直接接VCC,而是用MCU的一个普通IO控制,这样可以在软件里执行完整的下电流程,而不是靠外部电路瞬间切断电源。

3.3 PCB 布局中的电源与信号处理

电源板的布局规则,说白了就三条:回路短、地完整、噪声别串进信号。

BUCK的开关节点是整块板子上噪声最凶的地方,电流变化率极大。我把电感摆得离芯片SW引脚尽可能近,并且在SW节点的正下方不铺信号线,而是保留完整的地平面。这一点非常重要,开关节点的高频噪声如果耦合进I2C的SDA走线,轻则通信偶发失败,重则整个系统莫名其妙复位,排查起来非常痛苦。

另外,模拟的1.2V LDO输出我用了一小块独立的铺铜,与数字地通过单点连接。虽然在低频电路里"数字地模拟地分开"有时候被认为是玄学,但对带射频前端的系统来说,这种隔离确实能把底噪压下去几个dB,值得做。

板子做完之后,我还预留了三个输出电压测试点和一个I2C测试点。测试点这东西看着不起眼,调试的时候省太多事了——探头一夹就能看到波形,不用拿万用表到处找引脚。

4. 固件实现:从 I2C 裸机驱动到模式切换

4.1 I2C 通信层的实现

PIC18LF4620自带MSSP模块,可以工作在I2C主机模式,所以通信层完全不用软件模拟时序。我基于XC8编译器写了一套精简的I2C读写函数,核心思路就是把MSSP的几个寄存器操作封装成start、stop、write、read四个原语,再组合成寄存器的读写接口。

初始化部分关键代码如下:

void i2c_init(void) { // 设置I2C主机模式 SSP1CON1 = 0b00101000; // 8MHz系统时钟下,SPPADD=49 得到约100kHz的SCL频率 SSP1ADD = 49; // 使能MSSP模块 SSP1CON1 |= 0x80; }

接着是面向PCA9422的寄存器读写函数:

#define PCA9422_ADDR 0x6C uint8_t pca9422_read_reg(uint8_t reg) { uint8_t val = 0; i2c_start(); i2c_write(PCA9422_ADDR << 1); // 主机写,寻址PMIC i2c_write(reg); // 指定寄存器地址 i2c_restart(); i2c_write((PCA9422_ADDR << 1) | 1); // 转为读方向 val = i2c_read(0); // 读一字节,发NACK i2c_stop(); return val; } void pca9422_write_reg(uint8_t reg, uint8_t val) { i2c_start(); i2c_write(PCA9422_ADDR << 1); i2c_write(reg); i2c_write(val); i2c_stop(); }

封装的好处是整个固件其他模块根本不用关心总线时序,需要调PMIC参数时直接调这两个函数就够了。实际运行中我测过,100kHz下每次寄存器写操作耗时不到0.5毫秒,对电源切换来说绰绰有余。

4.2 PCA9422 寄存器配置序列

芯片上电后,PCA9422会用一组默认参数工作,但默认参数不一定匹配我们的系统,所以PIC复位后要尽快写入配置。我整理了一份配置表,用一个结构体数组维护寄存器地址和期望值,初始化时统一灌入:

typedef struct { uint8_t reg; uint8_t val; } pca9422_cfg_t; const pca9422_cfg_t pmic_init_cfg[] = { {0x02, 0x01}, // 使能全部输出轨 {0x03, 0x2C}, // BUCK1输出电压 = 3.3V {0x04, 0x1C}, // BUCK2输出电压 = 1.8V {0x05, 0x12}, // LDO输出电压 = 1.2V {0x08, 0x20}, // 充电目标电压4.2V,恒流400mA {0x0A, 0x1F}, // 屏蔽部分不关心的中断源 // ... 其他寄存器 }; void pca9422_init(void) { uint8_t i; uint8_t count = sizeof(pmic_init_cfg) / sizeof(pca9422_cfg_t); // 确保EN引脚为高,先让PMIC工作 EN_IO = 1; for (i = 0; i < count; i++) { pca9422_write_reg(pmic_init_cfg[i].reg, pmic_init_cfg[i].val); } }

有一点要提醒:寄存器映射表在不同芯片版本之间可能存在差异。我写进代码之前,专门把数据手册里的寄存器定义和头文件逐一核对过,还通过0x00设备ID寄存器读回了芯片版本号,确保我的配置表和实际硬件对应。硬件芯片的版本字段和生产批次有关,拿到新的批次最好在样机上读一遍确认。

4.3 电源状态机的实现要点

电源管理最忌讳散落式代码——这里改一个寄存器、那里切一个引脚,时间一长自己都搞不清系统处于什么状态。我用了一个简单状态机把所有模式切换统一管理:

typedef enum { PM_RUN, PM_LIGHT, PM_SLEEP, PM_DEEP_SLEEP } pm_state_t; void pm_set_state(pm_state_t new_state) { switch (new_state) { case PM_LIGHT: pca9422_write_reg(PCA9422_REG_EN, 0x03); // 关闭LDO MCU_LIGHT(); break; case PM_SLEEP: pca9422_write_reg(PCA9422_REG_EN, 0x01); // 保留BUCK1 pca9422_write_reg(PCA9422_REG_MODE, 0x02); // BUCK1进入PFM break; case PM_DEEP_SLEEP: pca9422_write_reg(PCA9422_REG_EN, 0x01); // 只留BUCK1 pca9422_write_reg(PCA9422_REG_MODE, 0x03); // 轻载最优化 break; default: // 从状态机之外的所有入口,统一先恢复全供电 break; } current_state = new_state; }

每次状态迁移都只经过这一个入口,避免同时两处代码对PMIC发出冲突指令。实际调试时,我在入口处放了一个调试变量记录当前状态,调完功耗再统一关闭,非常省心。

进入休眠前还有一个细节:要先把I2C总线释放干净,确保PMIC不会因为总线上的残留时序误动作。我的做法是在休眠前先把SDA和SCL配置为普通IO输出高电平,唤醒后再重新初始化MSSP模块。这能规避掉一批诡异的低功耗唤醒失败问题。

4.4 中断与状态监控

电源管理不能只靠MCU单方面"命令",还需要PMIC主动上报异常。PCA9422的中断控制器会在充放电路径切换、充电完成、过压过流、芯片过温等场景下触发INT引脚拉低。我的中断处理逻辑非常直接:中断标志置位后,第一时间读状态寄存器,并把状态值存进一个全局变量,主循环里再做详细处理。

void __interrupt() ISR(void) { if (INTF) { INTF = 0; pmic_status = pca9422_read_reg(PCA9422_REG_STATUS); pmic_int_pending = 1; } }

主循环里读取pmic_int_pending后,会根据状态位做对应动作。例如检测到"充电完成"就把充电状态灯熄灭;检测到"输入电源拔出"就把系统策略切到电池优先,屏幕亮度降一档。这套中断机制让MCU完全不需要轮询PMIC状态,待机时MCU睡得很沉,PMIC一有事才会把它叫醒,对功耗优化很关键。

5. 实测数据与波形

5.1 电压精度与负载调整率

板子回来之后第一件事就是测各路电压。空载情况下,三路输出和设定值之间的偏差都在1%以内,这精度对系统里的数字器件和模拟前端都足够了。满载情况下观察负载调整率,也很理想:

输出轨设定值空载实测满载实测最大偏差
VDD_3V33.30V3.31V3.28V0.9%
VDD_1V81.80V1.80V1.79V0.6%
VDD_1V21.20V1.20V1.19V0.8%

这个结果说明BUCK的环路补偿和电感电容搭配是合理的。如果哪一路在负载动态变化时出现明显跌落或振铃,第一件事不是改寄存器,而是查LC参数是否匹配PMIC的期望范围。

5.2 整机功耗与续航估算

整机功耗是我这个项目最关心的指标之一。用精密电流表分别测量四挡状态下的整机电流:

电源状态MCU状态PMIC状态整机电流
运行态8MHz全速运行PWM模式,三路全开132.0mA
轻载态降频运行BUCK1/BUCK2开启,LDO关闭11.8mA
休眠态SLEEP仅BUCK1 PFM模式42uA
深度休眠态深度SLEEP轻载最优化配置9.6uA

深度休眠态9.6uA的数字让我比较满意。这里要强调一下测量方法:测休眠电流时不能直接在电源输入端串万用表看平均电流,因为系统会周期性唤醒执行任务,平均法会把峰值电流摊进去,掩盖真实底电流。我是在唤醒源引脚外接信号发生器触发一次唤醒,用示波器电流探头看整个周期内的电流波形,把真正休眠段的平台电流读出来。按这个数值配一块1000mAh电池,纯待机续航可以按天算,实际使用中就算每天唤醒几十次执行测量任务,也能撑过一周。

5.3 瞬态响应与纹波改善

电源管理方案最怕负载突然从几十毫安跳到几百毫安。我用电子负载做了100mA到500mA的阶跃测试,示波器看到3.3V输出有约60mV的跌落,恢复时间大概40us,这个动态响应对于数字主控和普通传感器完全没问题。

纹波方面,PWM模式下3.3V输出的开关纹波在15mV左右,1.2V的LDO输出纹波在5mV以内。如果产品对纹波要求更苛刻,我建议在输出端再加一级LC滤波或者增大输出电容,但我这个项目没那么敏感,保持现状即可。

6. 调试踩坑总结:从"能跑"到"稳定跑"

6.1 首版板子不上电的教训

第一版样板焊接完成后,上电的一瞬间整块板子纹丝不动,3.3V输出完全没有。当时第一反应是PCA9422没工作,查了一圈才发现问题出在EN引脚:EN被误接为默认低电平,芯片处在完全关断状态。虽然数据手册上写了EN内部有下拉,但我没有在原理图阶段就给EN外接一个合适的上拉或明确由MCU控制,导致芯片一直被关在笼子里。

从此以后,凡是带使能引脚的电源芯片,我都要在原理图评审时逐颗确认三件事:默认状态是什么、复位期间是什么状态、MCU接管之前会不会有冲突。让MCU的IO去控制EN没有问题,但必须保证MCU复位期间IO状态是确定的高电平或低电平,否则会出现上电时序的毛刺。

6.2 I2C 通信不稳定的根源

调试中间遇到过I2C通信偶发失败,现象是读寄存器有时返回0xFF,多试几次又恢复正常。一开始怀疑是MSSP模块配置问题,查了很久代码,最后发现是飞线导致的:调试时我用一组长导线把PCA9422的I2C接口引出来接逻辑分析仪,导线上的分布电容和寄生电感把总线上升沿拖垮了。

把长飞线撤掉、改用板载测试点后,通信立刻稳定。这件事情说明:I2C的速率和线缆长度是强相关的,低速100kHz也架不住半米长杜邦线的寄生参数。批量验证时如果必须通过延长线连接,我会把I2C频率降到50kHz以下,或者采用隔离缓冲器。

6.3 待机电流居高不下

第一版固件完成后,深度休眠电流测出来是60多微安,和预期的10微安差距很大。逐个部件排查后定位到PCA9422的BUCK还持续工作在PWM模式——默认配置没有自动切换PFM,空载时依然保持高频开关状态,白白消耗几毫瓦功率。

改为在进入休眠前显式强制PFM模式之后,底电流立刻降到了预期范围。这个坑提醒我:很多PMIC的默认工作模式是完全独立于MCU的思考方式的,集成度再高,软件层面的功耗策略不到位,硬件能力也发挥不出来。最好的习惯是每进一个睡眠状态,代码里都要重新明确所有电源轨的工作模式和使能状态,不要依赖芯片"出厂默认值"。

6.4 充电发热与散热处理

还有一次充电测试让我印象深刻。整机放在密闭外壳里,以500mA电流充电,十几分钟后芯片表面温度逼近60℃,外壳摸上去微微发烫。虽然这个温度对芯片本身没有威胁,但锂电池对温度敏感,长期在高温环境下充电会加速老化。

处理办法是我在固件里把充电电流降低到300mA,同时把充电座底部铺铜范围加大,利用铜皮散热。降流之后充电时间虽然长了大概半小时,但整个充电过程外壳温度稳定在40℃以下,更稳妥。对便携产品来说,充电速度从来不是越高越好,必须结合散热条件和电池规格做一个平衡。

打样到现在,这套方案前后迭代了三版,整体运行很稳定。如果你正准备把PCA9422和PIC18LF4620用在类似的电池供电设备里,我的建议是:硬件层面多花时间核对电感饱和电流和EN上电时序,固件层面尽早把电源状态机搭起来,别让寄存器配置散落在各个业务模块里。电源管理这种活,前期规划越刻意,后期量产越省心。后面我打算把这套状态机框架单独抽出来做一个通用模块,到时候再整理一篇文章。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进