ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

PCA9422与PIC18F46K42协同实现精细电源管理与低功耗设计

PCA9422与PIC18F46K42协同实现精细电源管理与低功耗设计 1. 项目整体设计PCA9422 和 PIC18F46K42 各自干什么活1.1 PCA9422 内部模块拆解先说一个容易忽略的事实PCA9422 不是一颗“简单的电源芯片”。它本质上是把一堆常用的电源管理功能塞进了一颗小封装里我在这套方案里主要用到了五类模块。第一是充电管理。它内置了线性充电回路可以对接单节锂电池或锂聚合物电池充电电压、截止电流都可以通过 I2C 配置。实际项目里我把充电电压设成 4.2V截止电流 100mA 左右USB 输入限流单独打开防止电脑 USB 口被拉挂。第二是两路 DC-DC。PCA9422 内部有两路 buck 降压输出我习惯把第一路设成 1.8V 内核主轨第二路设成 3.3V 外设轨。DC-DC 的效率比 LDO 高不少负载大的场景必须用 DC-DC。第三是两路 LDO。低噪声模拟轨适合用 LDO比如传感器供电、运放供电。我这边 LDO1 给 1.2V 模拟内核LDO2 给 1.8V 接口电平噪声和纹波都比 DC-DC 好控制。第四是系统状态监测。包括输入电压检测、电池电压采样、芯片温度、中断事件标志等。这些功能不是锦上添花是电源管理真正能“闭环”的基础。没有状态反馈PMIC 就是一个遥控开关谈不上“管理”。第五是OTP 配置空间。芯片上电后的默认寄存器值来自内部 OTPI2C 写寄存器只能临时改 RAM 配置掉电就恢复默认。这个特性影响非常大后面我会专门说。1.2 PIC18F46K42 在系统中的角色PIC18F46K42 在这套方案里当主控干的事情比“初始化 PMIC”多得多。它首先要负责I2C 通信。PCA9422 所有寄存器配置、状态读取、中断处理都走这条总线。I2C 初始化一旦慢半拍PMIC 就按默认 OTP 配置跑可能带不动负载。它还要处理中断联动。PCA9422 会把 VBUS 插入、充电完成、电池低电压、过温等事件通过 INT 引脚通知 MCU。PIC18F46K42 的外部中断唤醒配合 PMIC 的 INT 输出可以让系统在大部分时间里停在睡眠态事件来了再唤醒干活。这一条对电池设备来说很重要省下来的电流直接体现在续航上。另外PIC18F46K42 的 ADCC 模块还能做电池电压监测。它支持自动扫描和结果计算不需要 CPU 全程参与。我实际把电池电压通过电阻分压接到 AN0 上用 ADCC 的自动触发功能周期性采样然后和 PCA9422 内部测到的电压值互相校验防止某一个通道因分压电阻精度问题出现偏差。选这颗 MCU 还有一个现实原因它本身就有多种低功耗模式从几微安的休眠到几十微安的运行都有。电源管理和低功耗从来是分不开的主控自己耗电量太大PMIC 再省电也白搭。1.3 选型取舍为什么不是“DCDCLDOADC”传统方案早期我做类似项目用的是“独立 DCDC 两个 LDO MCU 内置 ADC”方案。便宜是便宜但问题很明显充电管理要单独一颗 IC电池保护和电量监测又要加电路最后 PCB 上铺了一堆电源芯片上下电时序还得用 RC 延时硬凑。换到 PCA9422 之后最大的变化是供电链路被集成化了。输入检测、充电、DC-DC、LDO、状态中断全在 I2C 总线上MCU 可以通过寄存器随时切换状态。硬件上省了外围器件软件上可以把电源策略做得很细比如充电时自动提高 buck 工作频率拔掉 USB 后切到轻载模式。代价也明显学习和调试成本变高。寄存器初始化顺序不对PMIC 可能直接进入未知状态OTP 配置一旦烧错芯片就废了。所以我对这套组合的定位是适合产品化程度较高、需要精细化电源控制的项目不适合“点个灯就能跑”的快速 demo。2. 硬件实现电源树设计、关键引脚与 PCB 要点2.1 电源树怎么搭从 USB/电池到各负载轨这套方案里电源输入有两种可能来源USB 的 5V或者电池的 3.7V 左右电压。PCA9422 内部有电源路径管理USB 插入时优先用外部供电并给电池充电拔掉 USB 后自动切到电池供电。我做了一个比较典型的电源树供电对象PMIC 模块目标电压典型负载电流说明MCU 数字内核DC-DC11.8V约 20mA轻载时自动切 PFM 模式外设传感器DC-DC23.3V约 50mA给传感器和电平转换供电模拟前端LDO11.2V约 10mA低噪声几乎没有纹波IO 接口LDO21.8V约 5mA匹配外部逻辑电平这个表是个参考结构具体电压必须按自己的负载需求改。比如某些传感器必须 2.8V 供电那就得调整 DC-DC2 的分压或寄存器配置。需要注意的是电池电压直接进了 PMIC 的电池脚这个引脚对走线阻抗很敏感。我在这块板子上把电池到 PMIC 的走线加宽到 2mm 以上并且靠近芯片端放了 10uF 陶瓷电容加 0.1uF 高频去耦。很多人觉得充电电流不大就随意走线结果带载时电压跌落很多其实是 PCB 走线和电容布局的问题不是 PMIC 的问题。2.2 关键引脚处理和布线提醒PCA9422 的关键引脚我逐个说下INT 脚必须接 MCU 的外部中断引脚。我第一次做的时候把 INT 接到了普通 GPIO 上用轮询方式读状态结果芯片在充电完成瞬间产生中断我根本没及时发现导致系统一直认为还在充电。后来改成外部中断事件响应延迟从几百毫秒降到了微秒级。I2C 的 SDA/SCL 上拉电阻要接而且阻值不能乱选。400kHz 模式下我用了 4.7k 上拉总线上挂两颗芯片波形没什么问题。如果总线电容比较大建议先量一下上升沿如果太缓就换 2.2k 试试但注意上拉太强会增加灌电流需要看芯片手册的绝对最大值。EN 使能脚和PG 电源正常指示脚也建议接出来。EN 可以用 MCU GPIO 控制强制 PMIC 进入关断模式PG 接一个 GPIO 做电源状态监控多一层保护总没有坏处。PCB 布局上PMIC 尽量靠近电池座和主负载反馈采样点要放在负载端而不是 PMIC 输出端。我踩过这样的坑把反馈网络放在芯片旁边结果负载端到反馈点的走线压降造成了 50mV 的电压误差数字模块偶尔跑飞。后来把采样线单独拉出去问题就消失了。2.3 MCU 低功耗硬件准备PIC18F46K42 的低功耗不是只靠软件睡眠硬件上要有配套措施。首先外部唤醒引脚必须配置正确。PMIC 的 INT 输出接到 MCU 的外部中断输入优先级设置成最高保证任何电源事件都能唤醒 CPU。其次时钟策略要想好。我在休眠前把主时钟切到内部低频振荡器唤醒后再切回外部晶振。如果一直让外部晶振跑着休眠电流会多出几十微安做低功耗就不划算了。还有一点容易被忽略未使用的 GPIO 不能悬空。悬空引脚会通过内部保护二极管产生漏电流。我在休眠前把没用的引脚全部配置成输出低电平实测休眠电流比之前少了大约 30%。这个优化看起来小放到电池产品里就是十几个小时的续航差异。3. I2C 配置实操寄存器读写、初始化顺序与状态机3.1 I2C 外设初始化400kHzPIC18F46K42 的 MSSP 外设支持 I2C 主机模式。我用的是 400kHz 速率因为 PMIC 事件响应要求比较快100kHz 虽然更稳定但读一串状态寄存器会明显变慢。下面这段初始化代码是参考 MCC 生成的思路写的核心是设置主模式、波特率和使能。#define I2C_CLK_FREQ 400000UL #define FOSC 16000000UL void I2C_Master_Init(void) { // 配置引脚为 I2C 功能 TRISAbits.TRISA0 1; // SCL1 TRISAbits.TRISA1 1; // SDA1 ANSELAbits.ANSELA0 0; ANSELAbits.ANSELA1 0; // MSSP1 主模式 SSP1STAT 0x00; SSP1CON1bits.SSPEN 0; SSP1CON1bits.SSPM 0b1000; // 主模式时钟 FOSC / (4 * SSP1ADD) SSP1ADD (FOSC / (4 * I2C_CLK_FREQ)) - 1; SSP1CON1bits.SSPEN 1; }初始化之后可以做一次总线自检比如读一个固定寄存器看看返回值是否合理。PMIC 的 I2C 地址我这边用了 7-bit 地址数据手册里一般会给一个默认值和 ADDR 脚选择位。地址这里每个项目不一样我强烈建议在代码开头加一个版本宏方便后续换料号时修改。3.2 PMIC 寄存器配置流程与示例表PCA9422 的寄存器较多但初始化的时候不需要全部写一遍。我按功能把配置分成四步读 CHIP_ID确认芯片型号和 I2C 地址正确。配置中断屏蔽寄存器打开需要响应的事件。配置 DC-DC 和 LDO 输出电压。配置充电参数包括充电电压、充电电流、输入限流。这里给一张我在工程里实际使用的配置表示例。寄存器名和取值以芯片数据手册为准不同批次的默认地址可能不一样千万不要拿着某一版的表直接套。配置项示例寄存器写值说明CHIP_ID 校验REG_CHIP_ID读回 0x51数据手册给出的示例 ID中断屏蔽REG_INT_MASK00x00全部打开再按需关闭DC-DC1 电压REG_DCDC1_CFG0x1C示例1.8V 输出DC-DC2 电压REG_DCDC2_CFG0x2C示例3.3V 输出LDO1 电压REG_LDO1_CFG0x0C示例1.2V 输出LDO2 电压REG_LDO2_CFG0x1C示例1.8V 输出充电配置REG_CHG_CFG0x31充电使能 500mA系统配置REG_SYS_CFG0x10自动电源路径管理初始化代码大致长这样void PMIC_Init(void) { uint8_t chip_id ReadReg(REG_CHIP_ID); if (chip_id ! 0x51) { // 芯片 ID 不对不要继续往下配置 while (1); } WriteReg(REG_INT_MASK0, 0x00); WriteReg(REG_INT_MASK1, 0x00); WriteReg(REG_DCDC1_CFG, 0x1C); WriteReg(REG_DCDC2_CFG, 0x2C); WriteReg(REG_LDO1_CFG, 0x0C); WriteReg(REG_LDO2_CFG, 0x1C); WriteReg(REG_CHG_CFG, 0x31); WriteReg(REG_SYS_CFG, 0x10); }为什么先读 CHIP_ID因为这一步可以同时验证 I2C 通信、硬件连接、供电是否正常。如果这一步读不到值后面全都不用看先查硬件再说。3.3 电源状态机设计电源管理之所以叫“管理”是因为系统要跟着输入状态和负载状态来回切换。我给这套方案设计了一个简单的状态机。POR 上电初始化MCU 先起来初始化 I2C读 PMIC CHIP_ID写默认配置。BATTERY 电池供电模式没有 USB 插入PMIC 由电池供电输出各轨。MCU 可以进入低功耗依靠中断唤醒。CHARGING 充电模式USB 插入PMIC 对外供电并给电池充电。MCU 点亮充电指示灯周期性读充电状态寄存器充满后进入 CHARGE_DONE。CHARGE_DONE 充电完成状态充电结束但 USB 还插着。此时系统继续由外部供电运行电池回路切断。SAFE_OFF 保护关断模式检测到过温、过压、电池电压太低时MCU 通过 I2C 关闭部分输出轨或者直接把 PMIC 的 EN 拉低。状态转移的核心是事件驱动。每次事件来了MCU 先读 PMIC 中断状态寄存器再决定跳到哪个状态。不要在主循环里反复读寄存器判断状态那样费电又迟钝。3.4 OTP 烧写的风险控制这是这套方案里最需要小心的地方。PCA9422 的 OTP 决定了芯片默认上电配置。如果你的产品量产时需要固定配置比如 DC-DC1 固定 1.8V可以把配置固化到 OTP 里这样 MCU 不需要在每次开机时重新配置。听起来很方便但 OTP 是一次性编程写错一个 bit 整颗芯片就废了。我的建议是开发阶段绝对不要碰 OTP全部用 I2C RAM 配置。等到代码稳定、硬件验证完成、负载测试也过了再把配置值整理出来重新评估。即使用到 OTP也先选一颗样品烧录测试确认无误后再大批量操作。还有一些 PMIC 支持 OTP 写保护位一旦使能保护后连芯片供应商都无法恢复。我见过有人把充电电压上限写高了电池鼓包之后才意识到问题出在 OTP那块板子的电源芯片最后只能拆下来换掉。4. 中断联动与低功耗切换4.1 为什么必须用中断而不是轮询电源事件的特点是“突发”而且“频繁度低”。USB 插入可能几个小时才一次充电完成也可能很久才发生。如果 MCU 每几十毫秒去问一次 PMIC“有事吗”I2C 总线一直忙CPU 也没法睡觉电流自然压不下去。所以 PMIC 的 INT 输出必须接外部中断MCU 平时进入休眠等 INT 拉低再唤醒。这一句话做对了系统待机电流能少几百微安。需要注意中断触发方式。我给 PMIC 的 INT 配置成低电平输出MCU 外部中断设为下降沿触发。但中断服务函数里不能立刻去 I2C 读寄存器因为在低功耗唤醒瞬间时钟和总线还没完全稳定。先置一个事件标志回到主循环之后再做寄存器读取。4.2 中断处理代码与事件解析下面是我的中断处理思路。不要在 ISR 里做太多事只置标志和清除中断标志位。volatile uint8_t pca_int_flag 0; void __interrupt() myISR(void) { if (PIR3bits.INT1IF) { pca_int_flag 1; PIR3bits.INT1IF 0; } } void PMIC_Event_Handler(void) { uint8_t irq0, irq1, irq2; if (!pca_int_flag) return; pca_int_flag 0; irq0 ReadReg(REG_IRQ0); irq1 ReadReg(REG_IRQ1); irq2 ReadReg(REG_IRQ2); if (irq0 IRQ0_VBUS_PLUG) { system_state STATE_CHARGING; } if (irq1 IRQ1_CHG_DONE) { system_state STATE_CHARGE_DONE; } if (irq2 IRQ2_BAT_LOW) { system_state STATE_SAFE_OFF; } // 清中断标志写 1 清 0 或写 0 清 1 依芯片而定 WriteReg(REG_IRQ0, 0xFF); WriteReg(REG_IRQ1, 0xFF); WriteReg(REG_IRQ2, 0xFF); }有一点需要反复核对清中断标志的写法。有的 PMIC 是写 1 清位有的是写 0这和普通 GPIO 中断逻辑不一样。我第一次用这颗芯片的时候想当然写了 WriteReg(REG_IRQ0, 0x00)结果中断标志没清掉事件循环触发系统根本停不下来。后来看数据手册才发现要写 0xFF差点把一个原型项目搭进去。4.3 低功耗模式切换和电流预期硬件没问题之后软件低功耗策略才能顺利落地。我的系统有两个主要状态正常模式MCU 8MHz 运行PMIC 工作在正常开关模式DC-DC1 和 DC-DC2 都输出整机电流约 2mA 左右这个值主要被传感器和逻辑电路吃掉了。休眠模式MCU 进入休眠PMIC 切到轻载模式DC-DC1 停掉或者强制 PFMLDO 按需关闭。整机电流可以降到 50uA 以下。如果系统里没有常供电的传感器这个数字还能继续压低。不同模式的电流其实跟你外接负载强相关不能照抄别人数值。我一般会在板子上预留一个 10mR 的取样电阻用来量整机电流。还有一点经验低功耗测试要测平均电流而不是峰值电流。有些设备每秒钟唤醒一次发数据休眠时 20uA唤醒时 30mA持续时间只有几毫秒。用万用表看瞬时值只会看到乱跳的数字得用示波器或者积分法算平均电流。5. 常见问题与排查实录5.1 故障现象速查表我把这段时间遇到过的典型问题整理成一张速查表方便大家排查。现象可能原因排查方法I2C 通信失败SDA 一直被拉低上拉电阻没接或阻值不对PMIC 处于复位状态用示波器看 SCL/SDA先测量 I2C 引脚电平DC-DC 带载后电压跌落走线太细反馈采样点位置不对PFM/PWM 配置不合理检查负载端电压核对反馈网络布局中断反复触发中断标志未正确清除总线读到的是残留数据先读一次 IRQ 寄存器再做清除操作充电电流上不去输入限流配置偏低NTC 引脚检测异常读充电状态寄存器检查 USB 输入电压休眠电流偏大GPIO 悬空外部晶振未关PMIC 输出轨未关闭逐个外设确认工作状态用排除法定位LDO 输出纹波大负载电流变化剧烈去耦电容离负载太远加输出电容调整布局5.2 一次 I2C 总线死锁的排查过程我说一次印象很深的调试经历。设备在实验室跑了大半天突然整机死机MCU 看门狗复位后仍然卡在 I2C 等待应答的地方。示波器一量SDA 被拉死SCL 还在正常跑。这种情况下单纯复位 MCU 没用因为 I2C 总线上的从机PMIC可能陷入了一个异常状态而总线没有收到合法的停止位。我当时的解决思路分三步第一步手动拉 PGD持续九个时钟把总线上的残留状态清掉第二步用 GPIO 模拟 I2C 时序把 PMIC 的寄存器读一遍确认它是否还在正常工作第三步重新初始化 MSSP 外设再恢复主程序的正常 I2C 流程。后来排查到根因是 PMIC 在某个故障保护条件下启动了 clock stretching而我的代码在等待应答时没有设计超时。MCU 一直在等总线就卡死了。解决方法是给 I2C 的 ACK 等待加上超时机制不能死等。这也是为什么我后来把所有 I2C 交互函数都加了超时参数宁可报一次失败重试也不能无限等下去。5.3 低功耗电流读数“虚高”的坑低功耗调试特别容易让新手自闭因为万用表量出来的值和数据手册完全对不上。问题在于数字设备的工作电流不是稳定的。一个无线模块休眠时电流只有几微安发射瞬间冲到几十毫安。普通万用表串联电流档的采样率不够那个读数可能是平均值也可能是随机抓到的某个瞬时值而且串联电流档本身的分压电阻也会影响电路供电状态。我的做法是在供电回路里串联一个 10mR 精密采样电阻用示波器差分探头测电阻两端电压换算成电流。如果要测平均电流就记录一分钟波形然后算平均值或者在电池供电回路上并联一个大电容让系统从电容取电用万用表量电池端的平均电流这样读数稳定很多。低功耗测不准很多时候不是电路设计问题而是测量方式问题。先把测量方法搞对再去纠结代码逻辑。还有一个细节PMIC 和 MCU 的搭配中启动时序不能忽略。PCA9422 上电后芯片内部要初始化MCU 不能立刻去读寄存器否则大概率读到 0xFF 或者应答异常。我在项目里加了一个固定延时 50ms把 MCU 启动和 PMIC 稳定之间的窗口避开这个延时虽然不起眼但能省掉大量“偶发性通信失败”的排查时间。我个人在实际项目里体会最深的一点是电源管理这类功能硬件问题往往在软件配置之前就已经埋下了。PCB 布局、反馈走线、电容摆放、I2C 上拉任何一个环节偷懒后面都要花数倍时间去找毛病。PCA9422 和 PIC18F46K42 这套组合的软件配置并不复杂真正的复杂度在“你把硬件做成什么样”和“你打算怎么从异常里恢复”这两个问题上。先把这两个问题想明白再去写初始化代码你会轻松非常多。
RELATED READING

延伸阅读

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