做多路供电设备的时候,我一开始也是老老实实三片 DC-DC 加两片 LDO 分开布局。原理图看着简单,调起来真不简单:上电顺序、负载瞬态纹波、休眠功耗,哪一项都够折腾。后来我把电源部分整体换成 PCA9422 这颗 PMIC,再让 PIC18F86K90 通过 I2C 去配置它、监测它、切换它的工作模式,整套电源才算真正管起来。这篇就把这套组合的硬件接线、固件状态机、动态调压过程,以及我实际踩过的几个坑完整写出来。对正在做多轨供电、低功耗便携设备,或者好奇怎么用 MCU 管理 PMIC 的工程师来说,这应该是一份能直接上手的参考。
1. 为什么选 PCA9422 + PIC18F86K90 这个组合
1.1 PCA9422 的电源骨架
PCA9422 是一颗集成度比较高的电源管理芯片,内部集成了 3 路 DCDC 降压转换器和 2 路 LDO,可以同时输出多路电压,基本覆盖一个典型嵌入式系统的电源需求。DCDC 适合给 CPU 核心、DDR、外设这种电流需求比较大的负载,因为开关电源的转换效率高,在大电流下发热少;LDO 则适合给模拟前端、PLL、低速接口这类更在意输出噪声的电路,LDO 输出纹波小,对负载瞬态的响应也相对温和。
这颗芯片最吸引我的地方,是所有输出电压都由内部寄存器控制,不是靠外部电阻分压决定。这就意味着固件可以在系统运行过程中直接修改某一路输出电压,实现真正的动态调压(DVFS)。比如 CPU 降频时把核心电压从 1.0V 降到 0.9V,动态功耗和漏电流都能明显降下来;频率拉高之前再把电压补回去,保证时序裕量。除此之外,PCA9422 内部还集成了限流保护、过温保护、输入欠压检测等常见电源保护逻辑,并且有一个中断输出引脚。只要芯片内部检测到异常,就会主动通知 MCU,MCU 不需要一直轮询寄存器,处理故障的实时性会好很多。
这里要说明一点:具体的输入电压范围、输出电压范围和寄存器地址映射,每个封装批次都会有详细手册说明,我后面提到的数值都是基于常见配置,真正做项目时一定要以你手里那本数据手册为准。
1.2 PIC18F86K90 在系统里到底管什么
PIC18F86K90 在这里并不是系统的主处理器,而是一个电源管理协处理器,说白了就是整个系统的“电源管家”。它有多个 MSSP 模块,可以用硬件 I2C 和 PMIC 通信;有 12 位 ADC,可以检测输入电压、电池电压、温度;还有外部中断引脚,用来快速响应 PMIC 的告警。更关键的是,这颗 MCU 有很丰富的低功耗休眠模式,系统待机时它自己可以睡得非常深,同时保留 RTC 或者外部唤醒源,有事件发生再快速醒来。这一点在做电池供电设备时特别值钱。
另外,PIC18F86K90 本身还带一个片上 LCD 驱动控制器。如果设备上需要显示当前电压、工作模式、故障代码这些信息,不需要再额外接一颗显示驱动芯片,MCU 直接驱动 LCD 屏就行。这样整个电源管理子系统的成本很低,PCB 面积也被压得很小。
我更愿意把 PIC18F86K90 的角色理解成“决策者”:PCA9422 负责执行电压转换和保护动作,但什么时候调压、什么时候关电源轨、什么故障对应什么处理策略,这些决策都由 PIC 来做。这个分工是完整的、闭环的,比单纯把 PMIC 当稳压器用要高明得多。
1.3 分离器件方案和 PMIC 方案的实际差异
很多人会觉得“三路 DCDC + 两路 LDO”分开画也没什么问题,我一开始也是这么想。可真放在一个要量产的系统里,差别就出来了。我整理了一张对比表,基本说明了为什么我最终转向集成 PMIC 方案。
| 对比项 | 分离器件方案(多颗 DCDC/LDO) | 集成 PMIC 方案(PCA9422) |
|---|---|---|
| 上电时序控制 | 需要 MCU 用多个 GPIO 依次控制使能脚,软件控制时序,逻辑繁琐 | 芯片内部可编程时序,MCU 写入配置即可,上电自动执行 |
| 输出电压调整 | 靠外部电阻分压,改电压必须换电阻或者加 DAC | 通过 I2C 改寄存器,运行中可动态调压 |
| 静态功耗 | 每颗芯片都有自身静态电流,数量多时待机功耗明显偏高 | 单芯片集成,低功耗模式统一管理,待机电流容易压得低 |
| 保护功能 | 每路需要额外设计过流/过温保护电路 | 芯片内部集成,状态可读,异常可中断 |
| PCB 面积与 BOM | 器件数量多,布局分散,电感电容一大堆 | 单芯片加少量外围,面积和采购成本优势明显 |
先说结论:分离方案并不是不能用,它成本更低,选型灵活,适合电源轨很少、不需要频繁调压的设备。但如果你想做“完整电源管理”,也就是运行中动态调压、待机自动关断部分电源轨、故障主动上报、软关机不掉数据,分离方案会非常痛苦。PMIC 方案的优势不仅在于集成度高,更在于“所有状态都能被固件读到,所有输出都能被固件改”,这才是真正意义上的可管理电源。
当然,集成 PMIC 也有门槛,最大的门槛就是寄存器配置。刚开始接触时会觉得寄存器数量多,不知道从哪里下手。我的经验是:不要被手册吓到,先只关注 ID 寄存器、电压配置寄存器、使能寄存器、中断状态寄存器这几个关键块,跑通第一条 I2C 通路,后面都是查表填值的事情。
2. 硬件连接与电源拓扑设计
2.1 电源分配架构
我做的是一个便携式采集设备,外部输入 5V,系统主控需要多路电源轨。PCA9422 的输出分配如下:DCDC1 输出 0.9V 给系统主控核心,DCDC2 输出 1.8V 给逻辑和内存接口,DCDC3 输出 3.3V 给外部传感器和外设接口,LDO1 输出 2.8V 给模拟采集前端,LDO2 输出 1.2V 给对噪声敏感的锁相环和部分模拟电路。另外还需要一路备用电源域,用来在系统深度睡眠时给 RTC 和唤醒逻辑供电,这正好可以接 PCA9422 的保留输出域。
这样的分配思路是:大电流、电压变化频繁的负载走 DCDC,因为开关电源效率高;小电流、对噪声敏感的负载走 LDO,因为干净。启动顺序方面,先建立核心电源轨,再建立外围电源轨,最后启动模拟电源轨。如果系统有明确的上电先级需求,可以直接利用 PMIC 内部的时序控制来配置,不需要额外加迟延芯片。
PCB 布局上有一点要特别注意:DCDC 的电感要靠近芯片引脚,走线尽量短粗,输出电容放在电感后面。我习惯每一路输出放一个 10uF 陶瓷电容加一个 22uF 陶瓷电容,把高频和低频纹波都压一压。电感值通常在 2.2uH 到 4.7uH 之间,具体要看负载电流和开关频率,按手册推荐值选最稳妥,不要自己拍脑袋改。
2.2 I2C 控制链路与上拉设计
PCA9422 通过 I2C 接口和 PIC18F86K90 通信,我使用 MSSP1 模块。SCL 和 SDA 分别接 MCU 的对应引脚,再通过两根 2.2kΩ 电阻上拉到 3.3V 电源。上拉电阻的大小很讲究:太小会增大灌电流,功耗高;太大会导致信号上升沿慢,通信不稳。在 400kHz 快模式下,我一般选 2.2kΩ;如果板子上连接线比较长,可以降到 1.8kΩ 到 1.5kΩ。
I2C 速率建议先从 100kHz 起步。硬件 I2C 通信最怕总线上地址冲突,所以要确认总线上没有其他器件使用相同地址。PCA9422 的从机地址会受到 ADDR 引脚电平的影响,不同参考设计里的地址可能不一样。我踩过这个坑,后面专门讲。代码里要把地址做成宏,同时在上电初始化时读回芯片 ID 做确认,宁可在初始化时多花 10 毫秒校验,也不要等到配置写无效了才发现地址不对。
还有个细节:I2C 总线上如果同时挂了其他器件,比如 EEPROM,要注意它们之间的地址会不会撞车。建议画原理图时就把所有 I2C 设备的地址列一张表,逐位比对。
2.3 使能、中断和电压采样的引脚分配
PIC18F86K90 的引脚资源很充足,分配起来不紧张。我的分配大致是这样:
| PIC18F86K90 引脚 | 功能 | 说明 |
|---|---|---|
| MSSP1_SCL | 连接 PCA9422 SCL | I2C 时钟线,带上拉 |
| MSSP1_SDA | 连接 PCA9422 SDA | I2C 数据线,带上拉 |
| 外部中断引脚 | 连接 PCA9422 INTB | 低电平有效,故障触发中断 |
| AN0 | 输入电压采样 | 电阻分压后接 ADC |
| AN1 | 电池电压采样 | 分压加滤波电容 |
| AN2 | NTC 温度采样 | 用于系统温度监测 |
| GPIO 握手脚 | 连接主控复位/状态脚 | 软关机前通知主控保存数据 |
使能引脚这里多说一句:PCA9422 有硬件使能脚,但也有通过 I2C 寄存器关断输出的方式。我建议把硬件使能脚接固定上拉,始终允许芯片工作,具体每一路电源轨的开启和关断都交给 I2C 寄存器。原因是寄存器关断的时序更平滑,可以在关断前先通知主控做善后工作;直接拔硬件使能太粗暴,容易引起系统突然掉电。
电压采样电路要处理好分压电阻。输入电压如果是 12V 或者更高,分压电阻选高一点阻值,比如 100kΩ 对 10kΩ,避免采样电路自身消耗过多电流。ADC 引脚上并一个 100nF 到 1uF 的滤波电容,采样值会更稳定。
3. 固件侧的电源管理状态机
3.1 I2C 初始化与芯片 ID 校验
PIC18F86K90 的 MSSP1 模块配置成 I2C 主模式,7 位地址,速率先设成 100kHz。初始化完成后,第一件事不是写各种配置寄存器,而是读芯片 ID。只有 ID 读回来是正确的,才说明硬件链路没问题,后面写的所有寄存器才有意义。
/* 寄存器地址这里用逻辑命名,实际数值以 PCA9422 数据手册为准 */ #define PMIC_I2C_ADDR 0x30 #define PMIC_ID_REG 0x00 void mssp1_init_i2c(void) { /* Fosc = 16MHz 时,SSP1ADD = 39,得到 100kHz */ SSP1ADD = 39; SSP1STAT = 0x80; SSP1CON1 = 0x28; } uint8_t pmic_read_id(void) { uint8_t id = 0; i2c_read_byte(PMIC_I2C_ADDR, PMIC_ID_REG, &id); return id; }这段代码里 i2c_read_byte 是封装好的底层函数,不同编译器可能写法不同,但逻辑都差不多。我特别建议把“读 ID”做成一个独立函数,调试时可以直接调用。如果你在板上发现 ID 一直读不到,先看波形:用示波器抓 SCL 和 SDA,确认 START 条件有没有发出去、从机有没有回 ACK。如果从机回了 ACK 但数据是 0xFF,多半是地址不对;如果一直 NACK,要检查上拉电阻、电源和 ADDR 引脚。
3.2 输出通道配置的写入流程
硬件通路确认没问题之后,就可以开始配置各路输出了。我习惯把配置表设计成结构体数组,每一行代表一个通道的完整配置,包括通道号、输出电压、限流值、是否使能。这样后期要适配不同硬件版本,只需要改表,不需要改逻辑。
typedef struct { uint8_t channel; uint8_t vout; uint8_t ilim; uint8_t enable; } pmic_ch_cfg_t; const pmic_ch_cfg_t sys_power_cfg[] = { { PMIC_DCDC1, VOUT_0V90, ILIM_1500MA, EF_ENABLE }, { PMIC_DCDC2, VOUT_1V80, ILIM_1200MA, EF_ENABLE }, { PMIC_DCDC3, VOUT_3V30, ILIM_1000MA, EF_ENABLE }, { PMIC_LDO1, VOUT_2V80, ILIM_300MA, EF_ENABLE }, { PMIC_LDO2, VOUT_1V20, ILIM_200MA, EF_ENABLE }, }; void pmic_apply_configuration(void) { uint8_t i = 0; for (i = 0; i < sizeof(sys_power_cfg) / sizeof(sys_power_cfg[0]); i++) { i2c_write_byte(PMIC_I2C_ADDR, sys_power_cfg[i].channel, sys_power_cfg[i].vout); i2c_write_byte(PMIC_I2C_ADDR, sys_power_cfg[i].channel + 1, /* 假设限流寄存器紧随其后 */ sys_power_cfg[i].ilim); i2c_write_byte(PMIC_I2C_ADDR, sys_power_cfg[i].channel + 2, /* 假设使能寄存器紧随其后 */ sys_power_cfg[i].enable); } }注释里我用“假设”就是为了提醒:具体寄存器偏移要看手册,不要照抄。写配置的时候建议暂时关掉全局中断,因为 PIC 的硬件 I2C 模块在字节传输过程中被中断打断,容易出现未完成的事务。如果一定要开中断,至少 I2C 中断优先级要设成最高,并且主循环里别出现耗时太长的临界区。
配置写入完成后,PMIC 的输出电压会快速跳变到目标值,不需要额外等待。但如果你发现写入后输出电压没有变化,查两件事:一是有没有写错寄存器地址,二是这个芯片是否要求写入后额外置位一个 commit 位才生效。后者在带影子寄存器的 PMIC 中很常见,具体看手册的“寄存器更新模式”章节。
3.3 运行/待机/软关机状态切换逻辑
电源管理最核心的其实是状态机。我维护了三个主状态:运行态、待机态、软关机态。运行态下所有电源轨正常输出,PIC 定时轮询状态寄存器和 ADC 采样值;待机态下关闭大电流外设轨,保留核心和唤醒电路;软关机态则把所有主电源轨关掉,只保留备用电源域给 RTC 和唤醒逻辑。
| 状态 | 输出电压状态 | PIC 状态 | 唤醒源 |
|---|---|---|---|
| RUN | 全部输出正常 | 正常运行,ADC 轮询 | 按键、通信事件 |
| STANDBY | 关闭 DCDC3、LDO2,保留核心轨 | 进入低功耗休眠 | 定时唤醒、外部中断 |
| SOFT_OFF | 关闭全部主输出,只保留备用域 | 深度睡眠 | 按键、RTC 定时 |
状态切换的流程必须严谨。以软关机为例,PIC 先通过握手引脚通知主控保存数据,收到主控应答后再写 PMIC 关机寄存器,最后 PIC 自己进入深度睡眠。绝对不能跳过“通知主控”这一步直接切电源,否则主控可能正在写 Flash,突然断电轻则丢数据,重则文件系统损坏。状态切换之后,还要读回 PMIC 状态寄存器,确认目标状态已经达到了。如果超时没达到,要记录故障状态并回滚到上一个有效状态,而不是卡在中间状态。
待机态的逻辑有点意思。我把 DCDC3 和 LDO2 关掉之后,系统功耗能降一大截,但保留的 DCDC1 仍需要持续给主控核心供电,否则主控没法在外部事件到来时快速唤醒。PIC 自己进入休眠之前,把唤醒源配置好,比如按键边缘中断、定时器溢出、主控握手引脚跳变。这样整个系统在典型待机场景下,板级功耗可以压到几十微安级别。
4. 动态调压与故障处理
4.1 在线电压调整的实现
动态调压是这套组合里最有意思的功能。系统运行时,PIC 根据主控的工作频率请求,通过 I2C 实时修改 DCDC1 的输出电压。调压的原则是“升频先升压,降频后降压”:主控频率准备提高前,先把电压写高,等电压稳定后再提高频率;频率降下来之后,再把电压降回去。这个顺序如果反了,主控在电压不足的情况下高频运行,轻则跑飞,重则造成逻辑误翻转,非常危险。
uint8_t pmic_set_dcdc1_voltage(uint8_t vout_code) { uint8_t status = 0; status = i2c_write_byte(PMIC_I2C_ADDR, PMIC_DCDC1_VOUT, vout_code); return status; }实际调压时,我会先用示波器探头夹在输出电容两端,看着输出电压曲线改寄存器。步进幅度如果比较大,比如从 0.9V 直接调到 1.2V,建议分两次写,中间隔 1 到 2 毫秒,让 DCDC 环路有时间稳定,避免输出出现过冲或者跌落。过冲 100mV 对某些主控来说已经是极限了,一定不要图快。
DVFS 除了能省电,还可以做温度控制。当 NTC 温度偏高时,PIC 主动请求主控降频,同时把电压降下来,整机功耗和发热都能明显缓解。这在便携设备上非常实用,相当于用电源管理做了热管理的第一道防线。
4.2 中断与故障联动
PCA9422 的 INTB 引脚是低电平有效,我把它接到 PIC 的外部中断引脚。芯片检测到输入欠压、过温、输出过流等异常时,会主动把 INTB 拉低。PIC 在中断里不要做完整的 I2C 读操作,正确做法是只置一个故障标志位,然后立刻退出中断,回到主循环后再去读中断状态寄存器。这样做是为了避免 I2C 通信过程本身被其它中断打断,导致读到的状态不完整。
volatile uint8_t pmic_int_flag = 0; void __interrupt() high_isr(void) { if (INT0IF) { pmic_int_flag = 1; INT0IF = 0; } }主循环里发现 pmic_int_flag 被置位,就调用故障处理函数:读中断状态寄存器,确认具体故障类型,然后按策略处理。不同故障对应不同动作。输入欠压时,先通知主控保存数据,然后执行软关机;过温时,先降频降负载,看温度能不能回落;过流时,关掉故障电源轨,保留其它关键轨,同时记录故障码到非易失存储。故障码很重要,设备返修时可以通过读取故障码快速定位问题。
刚开始做故障处理时容易贪多,想在 ISR 里把状态一次读清楚。我的建议是千万不要这样做。中断里只置标志,主循环里慢慢处理,哪怕处理延迟几十毫秒,对整个电源管理子系统来说也完全可以接受。稳定可靠比实时响应更重要。
4.3 状态监测与安全关断链路
PIC 的 12 位 ADC 会定期采样输入电压、电池电压和 NTC 温度。采样周期我放在主循环里,每 100 毫秒采一组,换算成真实电压和温度后和阈值比较。输入电压如果缓慢下降,说明外部电源可能即将断开,PIC 可以在低压保护触发之前就主动发起安全关断,给系统多留一部分保存时间。
安全关断链路是这样的:PIC 检测到输入电压低于阈值后,先通过握手引脚通知主控进入保存模式,主控保存完成后会给出应答信号;PIC 收到应答后,写 PMIC 关机寄存器,把全部主输出关闭;确认 PMIC 进入关闭状态后,PIC 自己再进入深度睡眠。这个链路里任何一步超时都要有故障兜底,我设了 500 毫秒超时,超时后不管主控有没有应答,都强制进行后续动作,保证系统不会无限期停在半死状态。
状态监测的另一个用处是记录电源历史。PIC 把每次异常事件的类型、时间戳、当前电压值写到 EEPROM,长年累月之后可以画出电源健康状况曲线,对分析现场问题非常有帮助。这也许不算产品必需功能,但做过现场维护的人都知道这套数据有多省心。
5. 实测结果与踩坑记录
5.1 关键电性能实测数据
硬件和固件都调通之后,我专门对电源部分做了一轮比较完整的实测。测试条件是:输入 5V,输出端并接 10uF 陶瓷电容,示波器探头使用 20MHz 带宽限制,测量点尽量靠近负载端。一组典型数据如下:
| 输出通道 | 目标电压 | 负载电流 | 实测纹波 |
|---|---|---|---|
| DCDC1 | 0.9V | 1.2A | 12mVpp |
| DCDC2 | 1.8V | 0.9A | 15mVpp |
| DCDC3 | 3.3V | 0.6A | 18mVpp |
| LDO1 | 2.8V | 180mA | 8mVpp |
| LDO2 | 1.2V | 120mA | 8mVpp |
待机功耗方面,整板在 STANDBY 状态下实测工作电流约 28 微安,SOFT_OFF 状态下约 15 微安,这个数据让我比较满意。DVFS 从 0.9V 升到 1.1V,输出电压稳定时间大约 200 微秒。整套方案在常温下连续运行了 72 小时,没有出现电源轨跌落或者看门狗误复位。
这里要说明一下,这些数据只是我手里这块样板在特定测试条件下的结果,不同批次芯片、不同 PCB 布局、不同负载特性都会带来差异。想拿到稳定数据,关键还是按照数据手册的推荐布局和外围参数来设计。
5.2 值得记录的三个坑和排查链路
第一个坑是 I2C 地址读不通。上电后我按参考设计写的 0x54 地址去读 ID,结果一直在等 ACK,波形显示主机发完从机地址后收到的是 NACK。排查时我先用示波器确认 SCL/SDA 电平正常,然后用总线扫描程序把所有 7 位地址都扫了一遍,才发现实际地址因为 ADDR 引脚接法不同变成了 0x30。解决办法很简单:地址宏改成实际值,同时保留读 ID 校验逻辑,以后换板子也能自动发现地址不对的问题。
第二个坑是上电瞬间输出电压不是配置值。示波器抓 DCDC1 的上电波形,发现电压先爬到 1.1V,再降到配置的 0.9V,中间还维持了大约 100 毫秒平台。这是因为 PMIC 在 PIC 初始化完成之前,用的是默认启动电压。如果负载不允许短暂过压,要么在硬件上把默认配置引脚设置成正确电压,要么增加一个负载开关,等配置完成后再接通负载。我这套系统的主控能容忍短时过压,所以只做了软件快速配置。
第三个坑是 DCDC 开关噪声串进 I2C。现象是系统在大电流负载瞬态时偶尔读到错误的寄存器值,不是每次都复现,很难抓。后来用逻辑分析仪记录完整帧,发现错误帧大多出现在 SCL 信号上,有毛刺把时钟沿多打了一下,导致收发的字节错位。解决措施有三条:SCL/SDA 走线远离电感,总线对地加 100pF 滤波电容,PMIC 的功率地和模拟地单点连接。改完之后跑了很久都没有再复现。
5.3 给后来者的三条实操建议
第一条,先跑通 I2C 回环再画板。拿到 PCA9422 的测试板后,先用杜邦线把它接到 PIC 最小系统上,写一个最简单的读 ID 程序,确认能稳定读回 ID 再开始画正式 PCB。这套预研工作不需要花很多时间,但能帮你省掉后面大半的调试时间。
第二条,寄存器配置一定要表驱动。实际项目里硬件版本会迭代,某一路电压从 0.9V 改成 0.8V,或者某个通道限流值变了,如果配置散落在主循环里,改一个参数要找半天;做成配置表之后,只改一行数据就够了。代码可维护性差距很大。
第三条,状态机一定要有超时和故障回滚。电源模块最怕卡在中间状态:说关没关彻底,说开没开完整。给每个状态切换都加超时判断,超时就回滚到上一个已知状态并记录错误码。这样系统就算真的出了异常,你也能从错误码里快速找到方向,而不是对着示波器猜。
实际做这套方案下来,我最大的体会是:电源管理的复杂度不在硬件,而在状态。硬件把 PMIC 接好之后,剩下的工作几乎全是“如何在正确的时刻做正确的寄存器操作”。PCA9422 和 PIC18F86K90 的组合让我可以随时调压、随时读状态、随时切换工作模式,这在分离方案里是不敢想的。最后再分享一个小技巧:如果你刚拿到板子,先把 I2C 地址封装好,写一个最简单的读 ID 函数,能在十分钟内确认通信没问题,再谈后面的动态调压和故障处理,这一定是最快的路径。