
做便携式设备这几年我越来越觉得嵌入式项目里最容易被低估的环节不是主控逻辑而是电源管理那一摊。电池充电要管、三路四路供电轨要按顺序上电、过流过热得盯着稍微不仔细整套系统就会在凌晨三点莫名其妙复位。这篇文章想完整拆一个我最近刚落地的方案用 PCA9422 这颗集成电源管理芯片搭配 PIC18LF46K80 这颗低功耗 8 位 MCU把充电、多路供电、上下电时序和保护收进一颗 I2C 从机里。适合正在做电池供电、可穿戴或传感器节点的朋友参考尤其是那种想把电源复杂度从主控身上挪走、但又不希望系统复杂到上 Linux 平台的项目。1. 选型思路为什么把电源管理交给 PCA9422而不是让 MCU 自己硬扛1.1 便携设备的电源域到底有多碎先说我上一个项目的痛处。单节锂电供电系统里至少有三到四种电压MCU 核心与 IO 要 1.8V 或 3.3V传感器模组要 2.8V射频前端要 1.8V显示和外部接口可能还要一路 3.3V。如果全部用分立 DCDC LDO 去搭主控至少得腾出五六个 GPIO 去做使能控制再加上每路输出的 RC 延时、上下电顺序的硬编码延时、各种保护阈值的比较电路整套模拟电路画下来占了板子三分之一面积软件上还得专门维护一套先开哪路、等多久、再开哪路的时序逻辑。这里最大的坑是掉电顺序。很多传感器在电压跌到某个区间时会锁死下次上电需要完整复位信号否则 I2C 一直 NACK。用分立方案做掉电顺序必须靠 MCU 在电池电压跌落检测到之后抢时间把该关的先关掉动作稍微慢一拍系统状态就乱了。而我手里这颗 PMIC 恰好把这些都做成了可配置的硬件行为各路输出使能顺序、延时、故障响应全部由芯片自己管理MCU 掉电了它也有能力按预设顺序收尾。这就像把物业的安保服务外包给了专业团队自己只需要在门口登记访客名单。1.2 PCA9422 与 PIC18LF46K80 的分工逻辑这个组合的分工其实很清晰。PCA9422 负责电源域的体力活单节锂电池充电管理、多路 SMPS 和 LDO 输出、上电/掉电时序、过压欠压过流过热保护这些能力都通过 I2C 寄存器暴露出来。PIC18LF46K80 负责脑力活上电后立刻通过 I2C 把 PMIC 配置成项目需要的电压和时序运行中定期读状态寄存器出现故障时通过中断线知道然后决定是复位负载还是进入低功耗待机。之所以选这个 8 位 MCU 而不是随手拿一颗更高性能的 32 位 MCU原因有三个。第一是静态功耗这颗芯片在 Deep Sleep 下的待机电流能做到很低的水平和 PMIC 自己的静态电流叠加后整板待机能压到几十微安级别这对电池供电项目是硬指标。第二是它自带两路 MSSP 模块配 I2C 非常省事不用软件模拟第三是项目后面计划挂 CAN 总线K80 系列自带 CAN 控制器正好用上。如果你不需要 CAN选同系列其他型号也没问题驱动逻辑基本一致。下面这张表是我们项目最终的供电轨分配你可以直接作为参考起点输出通道分配负载设定电压备注SMPS0PIC18LF46K80 及数字逻辑3.3V主负载电流需求最大SMPS1传感器模组1.8V轻载为主注意 PFM 模式LDO0实时时钟与保持域1.8V一直保持供电LDO1模拟前端参考2.8V纹波敏感选低噪声档LDO2备用 IO 电压域3.3V掉电时序中排在最后关表格里的分配思路是高频动态负载走 SMPS纹波不敏感模拟和保持域走 LDO简单干净且时序上可以单独控制。实际做的时候每个通道的电压档位一定要对着芯片手册里的 VOUT 码表确认不同批次可能寄存器的步进不同这个后面会细说。2. 硬件搭接电源域拓扑、I2C 通道与中断线的实际连接2.1 最小系统的供电链路先画一个我们板上的简化链路。电池正极直接进 PCA9422 的 VBAT 输入脚芯片内部完成充电管理和所有输出合成各路输出分别去 MCU、传感器和保持域。地线全部走同一层并单点回流到电池负极避免多个负载的回流路径交叉产生噪声。锂电池 BAT ────────────────────────── PCA9422 VBAT │ ├─ SMPS0 ─ 22µF 100nF ── PIC18LF46K80 VDD ├─ SMPS1 ─ 10µF 100nF ── 传感器模组 ├─ LDO0 ─ 1µF 10nF ── RTC 保持域 └─ LDO1 ─ 2.2µF 10nF ─ 模拟参考 锂电池 BAT- ────────────────────────── 地平面 ──────────────────── 所有 GND有几个细节是纯看原理图发现不了的。PCA9422 的输出电容虽然标了最小容量要求但我实测下来容量可以适当加大对负载瞬态有好处关键是 ESR 不能高我见过有人用普通电解电容去配开关电源输出结果是纹波大得把传感器 ADC 都淹了。另外 SMPS0 是给 MCU 供电的主路PCB 布局上一定要让 PIC 的电源脚尽量靠近 SMPS0 输出电容走线以粗短为主。PMIC 焊盘底下的散热地孔也要留足这芯片虽然不像大功率 DC-DC 那么烫但充电模式下长期工作热量还是需要泄掉的。输入侧同样别省。VBAT 引脚近端我放了一颗 10µF 陶瓷电容再加一颗 100nF 高频旁路作用是吸收充电开关切换和电池线束引入的振铃。电池连接器到 VBAT 的走线我特意加宽压降损失尽量控制在几十毫伏以内否则充电电流稍微一上去PMIC 的输入欠压保护就可能误触发。2.2 I2C、中断线与电池检测路径的接法PCA9422 的控制接口是 I2C我们这个项目里 SCL 和 SDA 直接接到了 PIC18LF46K80 的 MSSP 引脚上总线各自上拉 4.7kΩ 电阻。如果你把 PCB 走线做得比较长或者线路上挂了多个从机上拉电阻可以降到 2.2kΩ但注意不要低于这个值否则总线高电平驱动电流太大会把端口拉出问题。总线上我额外串了 100Ω 的小电阻做阻尼这个对抑制振铃有点帮助不是必须的。器件地址是通过 PMIC 的 ADDR 引脚选择的我把这个引脚接 GND地址落在 0x20 这一档。如果你的板子用另一档所有驱动代码里的地址宏改一下就行我习惯把地址写到一个头文件宏里换板子只改一处。中断线是这套系统里最容易被忽略的一环。PCA9422 的故障、充电完成、输入源插入这些事件都会通过 IRQ 引脚通知外部。我把 IRQ 接到了 PIC 的 INT0 引脚上并配置成下降沿触发。为什么要走中断而不是轮询因为待机时 MCU 要睡死轮询意味着要定期醒来功耗和响应速度两头不讨好。中断能让 MCU 在真正有事的时候才睁眼。电池温度的检测也很重要。PCA9422 的充电管理里有温度窗口判断我按手册要求把一颗 10kΩ NTC 热敏电阻接在电池附近通过分压网络送给芯片的 TS 引脚。这个引脚的分压配置参数必须按手册推荐值选我自己就在这里栽过跟头后面章节会展开说。3. 上电时序与寄存器配置从默认值到定制方案的落地3.1 先摸清默认输出再动手很多 PMIC 上电后不是全部输出关闭而是有一个出厂默认配置某个 rail 可能默认就开着电压也可能不是你想要的。我拿到 PCA9422 后的第一件事不是写配置而是上电后把全部寄存器回读一遍、记下来。这步非常重要因为后续调试时你会反复需要知道当前寄存器到底什么值有个基准才不会抓瞎。回读用最基础的 I2C 单字节读就行我写了这么一个小工具函数调试阶段靠它打印寄存器uint8_t pmic_read_reg(uint8_t reg) { uint8_t val 0; i2c_start(); i2c_write(0x20 1); // 写地址 i2c_write(reg); // 寄存器地址 i2c_start(); // 重启总线进入读状态 i2c_write((0x20 1) | 1); // 读地址 val i2c_read(0); // 最后一字节回 NACK i2c_stop(); return val; }实际项目里我把每个通道的默认电压、使能位、时序延时都整理到一张表里然后对照需求逐项修改。PCA9422 的上电时序寄存器里放的是各路输出的使能延时值单位通常是毫秒档位。我们要求的启动顺序是先 SMPS0MCU 起来再传感器 SMPS1最后才开模拟 LDO1掉电顺序则反过来。这样做的逻辑是主控必须先于一切外设就位否则外设可能收到来自还没配置好的主控的毛刺信号。3.2 用 I2C 写序列实现分步启动分步启动的软件实现要遵循一个原则先把所有电压档位写到位再置位使能位最后才碰主使能位通常叫 ME 或者类似名字。因为如果你先使能了某个 rail再去改它的目标电压输出会出现一个不期望的中间电压台阶严重时直接打坏后端负载。我贴一段当时写的初始化代码简版结构就是典型的改电压 → 改延时 → 置使能 → 拉主使能void pmic_power_config(void) { // 第 1 步配置各路电压目标值 pmic_write_reg(REG_SMPS0_VOUT, 0x??); // 3.3V具体码值查手册 pmic_write_reg(REG_SMPS1_VOUT, 0x??); // 1.8V pmic_write_reg(REG_LDO0_VOUT, 0x??); // 1.8V pmic_write_reg(REG_LDO1_VOUT, 0x??); // 2.8V // 第 2 步配置上下电时序延时 pmic_write_reg(REG_PWRUP_DELAY, 0x??); // SMPS0 先上 pmic_write_reg(REG_PWRUP_DELAY2, 0x??); // 间隔 10ms 再上 SMPS1 pmic_write_reg(REG_PWROFF_DELAY, 0x??); // 掉电顺序反向 // 第 3 步使能所需输出 pmic_write_reg(REG_ENABLE, 0x??); // 打开 SMPS0/1、LDO0/1 // 第 4 步拉高主使能位开始执行上电序列 pmic_write_reg(REG_CONTROL, 0x??); // ME 1 }第 4 步之后我不能立刻就去操作外设而是要在软件里等 PMIC 完成整个启动序列的时间或者更稳妥一点读回状态寄存器确认各路输出都 Ready 了再往下走。时序上保守一点没坏处我见过有同事上来就往下执行导致传感器冷启动期间就读寄存器读回来一坨垃圾数据。3.3 充电参数与保护阈值的配置充电参数这部分PCA9422 支持单节锂电的标准 CC/CV 充电流程。我当时的配置是恒流阶段电流设到 200mA截止电压 4.2V再充阈值设为 4.05V输入限流按适配器能力设到 500mA 档。需要强调一句这些数值一定要对照你电池规格书来定不同电池的充电倍率和截止电压规范不一样拿别人项目的参数硬套会出问题。调试充电参数有个从实践中来的经验先从小电流开始。我习惯第一版固件把充电电流和输入限流都设到最低档确认电压电流检测通路正常后再逐步调高。因为如果分压网络或采样电阻有问题你直接按大电流写进去轻则充电效率低重则把电池充出鼓包。另外充电状态寄存器里的 POR 位不要一直盯着判断是否充满有的芯片在全过程中 POR 会短暂翻转我都是连续三次读到充满标志才认为真正完成。保护阈值我在第一版基本保留了默认值只把输入过压和电池过压阈值重新确认了一遍确保和电池规格匹配。上电时序和 rail 使能可以放开了调但保护阈值这种和物理极限相关的参数没有充分测试依据前不建议为了够用就行而放宽。4. PIC18LF46K80 端软件设计I2C 驱动、状态机与低功耗联动4.1 MSSP 模块的 I2C 驱动写法PIC18LF46K80 的 MSSP 外设配置成 I2C 主模式其实很快。关键参数是串口时钟分频。我的外部晶振是 16MHzMSSP 外设时钟按 16MHz 计算I2C 总线想跑 100kHz 时SSPADD 配成 39想跑 400kHz 时SSPADD 配成 9。标准公式是F_SCL FOSC / (4 × (SSPADD 1))我用 100kHz 做默认总线速度。不是它跑不了 400kHz而是我们板子 SDA 线上还挂了一颗传感器传感器手册只保证到 400kHz而且长走线加上拉之后上升沿有点缓降一档速度换可靠性很划算。PMIC 本身对速度不挑剔100kHz 下读写完全够用。实际的驱动里我除了基本的起始、停止、发送字节、接收字节之外还会检查两个标志ACKSTAT从机是否应答和 BCLIF总线冲突。每次事务结束都查一遍一旦 BCLIF 被置位我会马上重新初始化 MSSP 模块而不是假装没看见继续往下跑。这个习惯在硬件链路有点脏的项目里救过我很多次。4.2 电源状态机的设计与低功耗联动这套系统的软件我并没有把 PMIC 操作散在业务逻辑里而是抽象成一个简单的电源状态机一共四个状态BOOT_CONFIG上电后立即执行 PMIC 初始化然后进入 FULL_ONFULL_ON正常工作周期性读 PMIC 状态遇到低电量告警就降载SLEEP进入待机前先通过寄存器关掉传感器 rail只保留保持域然后 MCU 进 Deep SleepFAULT_RECOVER中断触发后进入读故障标志、执行恢复动作再回到 FULL_ON 或 SLEEP。这个状态机的价值在于待机前调多少路输出、唤醒后要恢复哪些寄存器逻辑都集中在同一个文件里不会散落到各处造成某个功能模块自己关了 PMIC 输出却忘了恢复的悲剧。我用一个枚举保存当前状态在循环主程序里集中切换中断里只置事件位不做状态切换避免中断上下文里做重活。低功耗联动的关键在进 SLEEP 之前的那几步。我进待机前会先把 SMPS1 和 LDO1 关掉因为它们对应的传感器和模拟前端在待机时可以完全断电然后轮询一次 PMIC 状态寄存器确认没有挂起的故障最后才执行 PIC 的 Deep Sleep 指令。唤醒源有两个RTC 定时唤醒做周期采样以及 PMIC 的 IRQ比如充电完成、输入源拔出。唤醒后软件会根据保存的配置标志决定是完整重启 PMIC 还是增量恢复防止重复写配置造成额外电流毛刺。实测在 3.7V 锂电池下整板待机电流大概在几十微安级别其中大头是 PMIC 的静态电流和保持域的负载PIC 的 Deep Sleep 贡献很小。相比原来用分立 LDO 的方案待机电流下降了差不多一个数量级。5. 故障保护与实测排坑IRQ 处理链路、恢复策略和两个真实问题5.1 IRQ 处理链路的完整走法PCA9422 的 IRQ 是电平触发式输出故障发生时拉低需要主控通过 I2C 读取故障寄存器并清除对应标志后才会释放。我的处理链路是IRQ 下降沿触发 PIC 的 INT 中断 → 在中断里拉高一个事件标志 → 主循环里读取 PMIC 故障状态寄存器 → 把故障原因整理到软件结构体如充电过流/输入过压/芯片过温/低电量→ 根据故障类型做恢复 → 最后清掉 PMIC 的 IRQ 标志位。这里有个细节故障寄存器里的标志有些是锁存的即使故障已经消失标志还停在那里必须软件清零。所以你如果只在 ISR 里读一遍状态就去做恢复很可能会基于过期的标志操作。我花了点时间把故障信息打印到调试串口固化到一套先读后清再确认的流程里读故障寄存器保存快照按快照决定恢复动作关载、复位输出、重新充电写清除命令延时后回读确认标志确实被清除。常见故障的恢复动作我也整理成了表方便快速浏览故障类型触发条件我的处理输入过压适配器电压异常断开充电路径拉高告警等待输入恢复正常输出过流某 rail 负载短路或超限关闭该 rail尝试一次软启动失败则保持关闭芯片过温充电满负荷 环境温度高降低充电电流或暂停充电风道改善后重试电池低电量VBAT 跌到阈值以下降载只保保持域等待充电输入插入5.2 踩坑一充电温度窗口判断错误导致一直不充电这个坑让我排查了一整个下午。现象很直接插上 USB 后PMIC 的充电状态一直停留在挂起态充电电流为零充电指示灯不亮。我最初怀疑是 I2C 写入失败于是回读所有充电相关寄存器发现配置值都对充电使能位也是 1。这时开始怀疑硬件链路。我拿示波器量了 TS 引脚电压发现它落在了一个芯片判定为温度超窗的区间。再看板上的 NTC 分压配置问题就清楚了我用了手上现成的 100kΩ NTC而 PCA9422 的温度窗口参考电路是按 10kΩ NTC 设计的阻抗完全不匹配。换上 10kΩ NTC 并将分压电阻调到参考电路推荐值后充电状态立刻恢复电流也正常拉起来了。这个坑的教训是PMIC 这类高度集成的芯片外部辅助引脚配套电路一定要按手册参考设计来不能看着差不多就上。改一个 NTC 型号等于把整个温度窗口全改了不是简单换颗电阻的事。5.3 踩坑二掉电瞬间 I2C 总线锁死MCU 复位后一直无法通信第二个坑更隐蔽。现象是快速插拔电源几次之后MCU 单独供电时 I2C 读写 PMIC 全部无应答示波器量 SDA 发现一直是低电平总线像被什么东西拽死了一样。分析后发现问题出在掉电的先后顺序上PMIC 的供电先跌到不能维持输出电平此时它把自己的 SDA 拉到了低电平但 PIC 的 VDD 因为有大电容保持还在运行于是主控侧看到的总线状态始终保持被占用自然发不出任何有效事务。解决分两层。软件层我加了一个总线恢复函数检测到 SDA 被持续拉低超过设定的时间后手动把 SCL 翻转 9 个时钟周期同时把 SDA 释放让总线上的从机释放占用最后再发一个 START/STOP 组合把总线状态复位到空闲。这段代码现在固定放在 I2C 驱动初始化之前void i2c_bus_recover(void) { // 把 SCL/SDA 配置成普通 GPIO 输出 // SDA 保持释放SCL 翻转 9 次 for (int i 0; i 9; i) { scl_set(0); __delay_us(5); scl_set(1); __delay_us(5); } // 发一个 STOP 条件让总线回到空闲 i2c_start(); i2c_stop(); }硬件层我也做了改进让总线上的上拉电阻挂到 PCA9422 自己输出的保持域而不是挂在电池直通电压上。这样即使 PMIC 先于主控掉电总线上拉也随之变低从机不可能反向拖拽。这些动作加完之后电源快插快拔的极端测试通过了。5.4 看门狗复位后 PMIC 配置必须幂等恢复最后补一个和稳定运行强相关的设计。PIC 开了看门狗之后一旦发生意外死循环看门狗会把 MCU 复位但此时 PMIC 还在运行各路 rail 依然保持复位前的配置。MCU 重新执行初始化时如果直接把整套寄存器无脑写一遍就会出现短暂的电压台阶或重复使能动作很可能把正在工作的外设打断。我的做法是引入一个配置有效标志在非易失存储里保存一个魔数PMIC 配置成功后写入上电初始化时先读这个标志存在就直接进入工作流程不存在才执行完整 PMIC 配置。看门狗复位不会清这个标志于是 MCU 醒过来后不会重复配置 PMIC而是先读一遍状态寄存器确认 rail 还在再继续业务。需要说明的是具体到不同 PMIC 和 MCU这套方案可以微调但初始化必须幂等这个原则是通用的无论系统从哪里醒来执行的电源初始化动作都不应该造成状态跳变。最后补一个实测经验这个方案跑了一段时间后我最大的体会是调试 PMIC 加 MCU 的电源系统90% 的时间花在确认寄存器实际值上而不是花在想方案上。强烈建议把 I2C 回读练成肌肉记忆每次写完寄存器立刻读回来确认能省掉大量和示波器较劲的时间。另外一个细节是示波器探头的接地线一定要用最短的弹簧地线测 PMIC 输出纹波时用长地线夹子会看到一堆根本不存在的高频毛刺容易把自己带偏。如果后续你想给这套系统加无线模块完全可以沿用同样的 I2C 驱动框架只是把无线模块的供电轨挂到空闲的 SMPS/LDO 上再在状态机里补一个发射功率档位对应的输出电压切换就行扩展起来不需要动核心架构。