☰
电池供电多路电源轨设计与PMU+I2C实现详解
2026/10/24 7:52:08 网站建设 项目流程

前阵子调电池供电的便携采集设备,电源轨从1.8V拉到3.3V一共四条,还要兼顾休眠电流和上电时序。一开始图省事想用几个LDO凑合,结果面积、静态电流、上电顺序三座大山全压过来,最后定了PCA9422这颗PMU做功率级,用PIC18F4585做电源管理主控,靠I2C一条总线把所有电压通道管起来。这套组合跑下来,整机休眠电流、动态调压、故障保护都达到了预期。这篇文章把整套实现思路、代码模板和实际踩坑记录一起整理出来,给正在做电池供电或者多路电源产品的朋友一个可直接参考的落地案例。涉及PCA9422寄存器细节的部分,不同批次和封装会有差异,凡是我标了“参数入口”的地方,你们对着自己手里的最新数据手册填一遍就行。

1. 多路电源轨的三大麻烦,和这套方案的整体思路

1.1 分立方案为什么先输一半

很多人做板子第一步就是把各路电源用独立LDO拼起来:3.3V一颗、2.5V一颗、1.8V一颗,看起来简单,实际上有三个很难绕开的麻烦。

第一是面积和BOM。每颗LDO要输入输出电容,有些还要配使能上拉、反馈分压电阻,四路电源下来板子正面背面都是器件。降压DCDC更夸张,电感、续流二极管、肖特基、补偿网络全上来,Layout稍一紧张就容易出问题。第二是静态电流叠加。手持设备对休眠电流极其敏感,一颗LDO自己的静态电流几微安到几十微安,四颗叠一起,整机休眠指标直接崩掉。第三才是最重要的,上电时序根本没法统一管理。多路LDO靠使能脚和RC延时拼时序,精度差、温度漂移大,一旦某路负载挂了或者电容充得慢,顺序就乱了。

PMU方案之所以值得做,是因为它把多路Buck/LDO、时序控制、保护功能全部收进一颗芯片。以PCA9422这颗PMU为例,我实际用到三路Buck加一路LDO,四路输出都通过I2C寄存器配置,电压、使能、限流、软启动、放电全部可编程。上电顺序可以靠主控按代码走,也可以预烧进PMU自己的配置。器件少了,静态电流从几十微安降到微安级,调试的时候也能随时改配置,不用换电阻电容。

1.2 系统架构与“完整电源管理”的定义

这套板子的供电拓扑并不复杂:一颗3.7V锂电池进PMU,由PCA9422分出几路电压,PIC18F4585作为主控负责全局电源调度。我这边实际分配是这样:

通道电压负责的负载
Buck11.2V低功耗无线SoC核心电源,支持动态调压
Buck22.5V模拟前端AFE供电,低纹波要求高
Buck33.3V传感器、数字接口逻辑
LDO3.3V常开PIC18F4585、RTC唤醒逻辑、配置保持

这里要说明一下“完整电源管理”的定义,我做下来觉得它至少包含四件事:输出电压可配置、上下电时序可控、运行状态可监控、低功耗策略可切换。不是说PMU通电后有几路电压出来就算完成,而是要让它整个生命周期都知道自己在干什么。PIC18F4585在这个系统里的角色更像一个电源管家:上电时按顺序打开通道,运行中根据负载状态动态调电压,休眠前关掉大电流通道,同时用内置ADC实时盯着电池电压和温度。

1.3 为什么主控选 PIC18F4585

很多方案喜欢在电源管理上直接上32位MCU,甚至拿一颗Linux级别的主控去跑调压逻辑。我的看法是,电源管理这个任务本身逻辑不复杂,难点在稳定性和低功耗,杀鸡不用牛刀。PIC18F4585是8位MCU,优势非常明确:

一是自带硬件I2C模块,主从模式都支持,配置成I2C主机后发给PCA9422的命令都是硬件收发,不占CPU轮询资源。二是内置多路ADC,采集电池电压、电流采样电阻压降、NTC温度完全够用。三是自带数据EEPROM,可以把校准参数、寄存器配置表、设备序列号存下来,断电不丢。四是低功耗模式完善,Sleep电流很低,配合PMU的待机模式,整机休眠可以做到非常小。五是开发工具链成熟,我用MCC图形化配置完外设,生成初始化代码再改业务逻辑,整个工程搭建时间大概只花了一个下午。

2. 先啃 PCA9422:通道分配与电压配置

2.1 把通道映射到真实负载

拿到PCA9422的第一件事不是写代码,而是先把芯片手册里的内部框图看明白,然后把每路通道和实际负载对号入座。我见过不少人上来就试寄存器,结果后面电源轨交叉干扰,排查半天才发现是输出电容分配错了。

以我手里的配置为例,Buck1接无线SoC核心,这路负载动态变化大,需要支持DVS动态调压,所以要选VFBS反馈引脚对应的通道;Buck2接模拟前端,纹波要求高,输出电容选大一点,开关频率也要避开AFE的采样频段;Buck3接传感器和数字接口,电流不大但瞬态要求高;LDO接常开域,给PIC18F4585和唤醒逻辑供电,要求低静态电流,不要求多大负载能力。

通道分配确定之后,在PMU里把每路初始电压配置好。PCA9422上电后会有默认输出,但如果默认值和负载不匹配,轻则系统启动失败,重则烧后级芯片。我建议拿到芯片后先在空板上把每路输出实测一遍,用示波器确认波形干净,再继续接线调试。

2.2 I2C 配置流程的本质

PCA9422的电压调整本质上是写寄存器:每路通道有独立的电压选择寄存器位段,通过I2C写入目标值,内部DAC或反馈网络就会把输出调整到对应电压。理解这一点后,配置代码就变得非常朴素。

第一步使能通道,第二步选择输出电压,第三步配置限流和软启动,第四步读回寄存器值确认写入成功。这里要给一个代码模板,实际寄存器地址请大家对着自己的手册填:

/* 参数入口:以下寄存器地址按实际手册替换 */ #define PMU_I2C_ADDR 0x58 #define REG_CH1_CTRL 0x02 // Buck1 控制/使能 #define REG_CH1_VSEL 0x03 // Buck1 电压选择 void pmu_enable_channel(uint8_t ch) { uint8_t wbuf[2]; wbuf[0] = REG_CH1_CTRL; wbuf[1] = 0x80; // EN位拉高,具体位段看手册 i2c_write_bytes(PMU_I2C_ADDR, wbuf, 2); } void pmu_set_ch1_voltage(uint8_t vsel) { uint8_t wbuf[2]; wbuf[0] = REG_CH1_VSEL; wbuf[1] = vsel; i2c_write_bytes(PMU_I2C_ADDR, wbuf, 2); }

很多朋友会问,为什么不能直接用固定的默认电压,非要每路都单独配置?原因很简单,同一颗PMU在不同板子上接的负载不一样,板载LDO和DCDC的输出要给后级芯片明确且正确的供电电压。PCA9422的可编程意义就在于,一套硬件设计通过改代码就能适配不同负载需求,不用重新画板子。

2.3 读回与状态确认

写入电压之后一定要读回来确认。I2C总线偶尔会因为干扰或者时序问题丢bit,尤其在电源板这种开关噪声大的环境里,一次写失败如果没被发现,后面的代码可能跑在一个错误的电压上,表现就是各种奇怪的偶发复位。

读函数模板:

uint8_t pmu_read_reg(uint8_t reg) { uint8_t val = 0; i2c_start(PMU_I2C_ADDR | 0); // 写方向 i2c_write_byte(reg); i2c_restart(PMU_I2C_ADDR | 1); // 切读方向 val = i2c_read_byte(I2C_LAST_BYTE); i2c_stop(); return val; }

我习惯把每次写寄存器后都加一次读回比对,不一致就重试。不要怕这点开销,和一次莫名奇妙的产品故障比起来,浪费的这几毫秒根本不值一提。

3. PIC18F4585 初始化与 I2C 通信模板

3.1 最小系统配置

PIC18F4585这边我用了内部振荡器,省掉外部晶振和两颗负载电容,板子稍微干净一点。主频这里不需要跑太高,8MHz绰绰有余,因为电源管理任务本身不重,大量时间在延时和轮询。

引脚规划最好一开始就定死,我这次是这样分配的:

引脚功能说明
RC3SCLI2C时钟,接上拉电阻到3.3V
RC4SDAI2C数据,接上拉电阻到3.3V
RA0VBAT_SENSE电池电压分压采样
RA1NTC_ADC电池/板温NTC采样
RB0PWR_ON_KEY按键开机唤醒信号
RB1LOAD_SW_EN外部负载开关控制

注意I2C的SDA和SCL必须接上拉电阻,这个很多人会漏。PCA9422是标准I2C从机,总线要靠上拉电阻把电平拉高,没有上拉或者上拉电阻太大,通信就会变成时好时坏。一般用2.2k到4.7k,具体看总线电容和I2C速率。

3.2 MCC 配置 I2C 主机的步骤

我用MCC生成工程,配置I2C时只需要做几步:选择MSSP模块、模式设为I2C Master、波特率填100kHz、开启中断或者轮询。建议第一次先用100kHz,等所有功能跑通再考虑提400kHz,能减少很多调试干扰。

MCC生成的核心API是I2C_MasterWrite和I2C_MasterRead,它们在底层已经处理了起始、停止、应答信号。实际工程里我封装了一层寄存器读写函数,调用方不需要关心I2C细节。

3.3 寄存器读写函数模板

封装后的代码如下:

#include "mcc.h" bool I2C_WriteReg(uint8_t devAddr, uint8_t reg, uint8_t val) { uint8_t wbuf[2] = { reg, val }; uint8_t retry = 3; while (retry--) { I2C_Open(); if (I2C_MasterWrite(devAddr, wbuf, 2) == 0) { I2C_Close(); return true; } I2C_Close(); } return false; } uint8_t I2C_ReadReg(uint8_t devAddr, uint8_t reg) { uint8_t rbuf[1] = {0}; I2C_Open(); I2C_MasterWrite(devAddr, &reg, 1); // 发送寄存器地址 I2C_MasterRead(devAddr, rbuf, 1); // 读取1字节 I2C_Close(); return rbuf[0]; }

注意这里每次通信前后都加I2C_Open和I2C_Close,是为了保证起始/停止时序完整。MCC生成的驱动在异常结束后,内部状态机可能仍停在奇怪状态,重新打开一次能让模块回到空闲。

3.4 总线故障排查

I2C调试遇到最多的问题是NACK和总线挂死。

NACK最常见的原因是设备地址不对。PMU从机地址取决于芯片配置脚和手册,不同封装、不同后缀可能不同。我习惯先在代码里写一个扫描函数,把七个地址都发一遍,看哪个有ACK,确认之后再固定下来。

总线挂死的表现是SDA一直被拉低,后续通信全部失败。这种现象在刚焊好的板子上很常见,通常是某个从设备的上电顺序没满足,内部逻辑异常,把总线卡住了。解决办法是给SDA和SCL加几毫秒的低电平脉冲,模拟总线复位时序,让所有从机从异常状态退出。我在量产自检程序里加了这么一段:

void i2c_bus_recover(void) { /* 将RC3/RC4临时配置为GPIO输出 */ TRISCbits.TRISC3 = 0; TRISCbits.TRISC4 = 0; LATCbits.LATC3 = 0; LATCbits.LATC4 = 1; for (uint8_t i = 0; i < 8; i++) { LATCbits.LATC3 = 1; __delay_us(5); LATCbits.LATC3 = 0; __delay_us(5); } LATCbits.LATC4 = 0; __delay_us(5); LATCbits.LATC4 = 1; /* 交还给I2C外设 */ TRISCbits.TRISC3 = 1; TRISCbits.TRISC4 = 1; }

这段代码不要放在正式流程里频繁执行,只在自检或者疑似总线异常时用。

4. 上电时序设计与软启动控制

4.1 为什么要顺序上电

提到电源管理,很多人第一反应是“有多少电压、电流够不够”,却忽略了上电时序。实际上,数字控制器、模拟前端、接口电平都有明确的先后依赖。我这次板上有一条规则:先3.3V数字域,再2.5V模拟域,最后1.8V低速接口域。原因很简单,后级芯片在供电不完整时如果先被拉高到不确定电平,内部逻辑可能乱掉,轻则配置加载失败,重则闩锁。

上下电顺序和供电电压是强相关的,顺序写错最典型的现象是,示波器上各路电压都正常,但板子一上电就复位或者某个外设怎么枚举都不成功。这时候别急着怀疑固件,先用四通道示波器把四路电源的上升沿抓出来,看看是不是同时跳起来。

4.2 基于主控的软时序实现

PCA9422通道的默认使能状态可以烧进芯片配置,也可以由PIC18F4585上电后逐个打开。我建议量产阶段两种方式结合:把常开域和第一组电压配置成上电默认输出,保证主控自己能跑起来;后续通道由主控代码按延时表打开。

#define T_3V3_TO_2V5_MS 10 #define T_2V5_TO_1V8_MS 20 void power_sequence_start(void) { pmu_enable_channel(PMU_CH_LDO); // 3.3V常开域,先跑起来 __delay_ms(T_3V3_TO_2V5_MS); pmu_enable_channel(PMU_CH_BUCK2); // 2.5V模拟域 __delay_ms(T_2V5_TO_1V8_MS); pmu_enable_channel(PMU_CH_BUCK3); // 后级接口域 pmu_enable_channel(PMU_CH_BUCK1); // 核心电压最后拉起来 }

这里有个很重要的点,Buck1核心电压放在最后,是因为外部无线SoC有自己的上电要求,如果核心电压先起来而IO还没供电,芯片引脚可能进入未定义状态。每种芯片的上电要求不一样,一定要仔细看负载芯片手册里的Power-Up Sequence章节。

4.3 软启动与限流参数

除了顺序,每路通道上电瞬间的浪涌电流也需要管。PCA9422的通道基本都有软启动时间和限流阈值配置,我这边遇到的实际问题是,Buck3挂了两个大容量钽电容,软启动时间设置太短,上电瞬间电流直接顶到限流阈值,电压爬不起来,示波器看到的就是一个阶梯状斜坡。

解决办法是把软启动时间从1ms调整到4ms,斜坡变缓,限流不再触发,之后每次都稳定上电。经验是,按后级电容总容量估算启动电流,如果启动瞬间电流可能超过限流的80%,就把软启动时间放宽到估算值的两倍以上。

4.4 用示波器验证时序

写代码的人一定要自己学会看示波器。我把四路输出分别接到四通道,触发源设成PIC18F4585的某个GPIO,这个GPIO在上电时序开始时拉高。这样每次都能稳定抓到完整的时序波形,然后记录下来,确认每路电压都在目标范围内,上升沿先后符合设计。

还有一点容易被忽略,就是下电时序。很多系统只关注上电,下电时如果顺序反了,同样可能把后级芯片打死。我这边给PCA9422的通道关了之后,会再多等几十毫秒再关LDO常开域,让后级负载先消耗完电容储能,避免掉电过程中出现反向偏置。

5. 动态电压调节与低功耗策略联动

5.1 DVS 值得做的场景

动态电压调节听起来高端,其实本质很简单:负载在不同工作状态下,不需要一直吃满电压,把电压降下来,动态功耗能明显下降。板子上那颗无线SoC就是典型的受益者,满负荷传输时核心电压需要1.2V,进入低功耗监听模式后降到0.9V也完全能工作,一次状态切换能省下不少电流。

PIC18F4585作为电源管理主控,最适合承担DVS决策:它知道当前系统在做什么,该给SoC多少电压。实现路径就是通过I2C改写PCA9422对应通道的VSEL寄存器。

5.2 调压代码实现

我定义了三档电压等级,对应SoC的空闲、低负载、强调度三个模式:

typedef enum { CORE_VOLT_0V9 = 0, // 低频休眠 CORE_VOLT_1V1, // 中等负载 CORE_VOLT_1V2 // 满速运行 } core_volt_level_t; static const uint8_t vsel_table[3] = { 0x0A, 0x18, 0x20 }; void pwr_set_core_voltage(core_volt_level_t level) { if (level > CORE_VOLT_1V2) { return; } uint8_t target = vsel_table[level]; I2C_WriteReg(PMU_I2C_ADDR, REG_BUCK1_VSEL, target); /* 读回确认 */ uint8_t rd = I2C_ReadReg(PMU_I2C_ADDR, REG_BUCK1_VSEL); if (rd != target) { I2C_WriteReg(PMU_I2C_ADDR, REG_BUCK1_VSEL, target); } }

切换电压时不要把每次调压都直接跳到目标值,尤其大电压差场景。我的经验是让系统软件在任务层分几步走,比如从1.2V降到0.9V,先切到1.1V稳定几十毫秒,再降0.9V。虽然多一步操作,但能避开瞬态过冲和负载响应问题。

5.3 与休眠唤醒联动

PIC18F4585进入Sleep之前,我会做三件事:把Buck1电压降到最低档,关闭Buck2和Buck3这类不在休眠期工作的通道,保留LDO常开域继续维持唤醒逻辑。唤醒之后,先把各路通道打开、电压恢复到运行档位,再继续执行主流程。

这套联动做下来,整机电流数据基本符合预期:

工作状态整机电流
满负荷运行18mA
空闲监听1.2mA
深度休眠35uA

35uA里包含LDO的静态损耗、电压分压电阻的漏电、以及唤醒逻辑的保持电流。如果想把休眠电流再往下压,需要做两件事:把电池电压分压电阻换成MOS管控制采样通路,以及确认PMU有没有真正常开域待机模式。

6. 实测、波形与踩坑清单

6.1 纹波与瞬态波形

Buck输出要稳定,电感选型和输出电容很关键。我用的电感饱和电流比实际最大负载高至少1.5倍,工作频率对应的纹波电流控制在额定值的30%到40%之间。输出电容位置尽量靠近负载芯片引脚,这一点比单纯加大容值更管用。

示波器实测纹波数据,Buck2作为模拟电源轨表现最好,纹波大概8mV左右;Buck1因为电流比较大,纹波偏高,在15mV附近。这个数据对后端SoC供电来说可以接受,但如果还要接高精度ADC,建议在负载端再加一级LC滤波。

6.2 I2C 被电源噪声干扰的坑

整个调试过程中最折磨人的一个问题,是I2C读回来的寄存器数据偶尔不对。现象很随机,配置的电压明明是1.2V,读回来变成0x00,重试几次又正常。我一度怀疑芯片坏了,后来用示波器抓SDA波形,发现毛刺出现的时间点和Buck开关频率完全同步。

原因是PMU的功率开关动作在地平面上产生噪声,I2C走线离开关节点太近,高频dv/dt耦合到了SDA。解决措施有三条:调整I2C走线,远离电感和开关节点;把I2C上拉电阻从4.7k改到2.2k,提升总线抗干扰能力;在PMU输入脚加一颗100nF高频电容,把开关噪声的地回路封住。改完再跑一整天压力测试,再没出现过读回异常。

6.3 地平面和布局

电源管理板的地设计要守规矩:功率地和信号地单点连接,所有功率地电流不要穿过信号地。我的做法是电池负极直接进PMU的PGND,PGND和模拟/数字AGND之间用一个0欧电阻单点汇合,AD采样参考点取AGND。

很多板子出怪问题都是地没处理好,比如测量出来的电池电压忽高忽低、NTC温度读数乱跳,最先怀疑的应该是地平面。把采样回路缩短,确保采样点旁边没有高频电流路径,一般都能恢复正常。

6.4 电量监测与最终检查单

PIC18F4585的ADC承担了电量监测任务。电池电压通过两个电阻分压到ADC量程内,因为分压电阻本身有漏电,休眠前我切断了分压通路的供电,只在需要采样时打开。NTC温度采样用来做低温充电保护和过热降载,阈值写到EEPROM里,生产时逐台标定。

最后给一张自己的检查清单,每次改完板子都过一遍:

检查项通过标准
上电顺序各路电压上升沿时间差符合设计
I2C枚举每次上电都能扫描到PMU地址
寄存器读回写100次读100次,全部一致
调压切换1.2V/1.1V/0.9V切换无异常复位
休眠唤醒唤醒后电压恢复,任务正常继续
温度保护模拟高温时能触发降载或关机

做完这些项,整机的电源管理才算真正闭环。

最后分享一个我个人很受用的经验:做电源管理,别急着优化代码,先把每个寄存器配置用读回方式确认一遍,确认硬件链路是干净的,再往上叠加功能。DVS电压切换宁可慢一点,也不要让瞬态过冲把后端芯片打复位。一次写满所有寄存器,多半比不过先把基础配置跑稳。

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

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

立即咨询