上周刚把一台低功耗可穿戴终端的电源管理方案调通,主控用的是 PIC18LF45K42,电源侧用的是 PCA9422。这颗 PMIC 负责电池充放、各路电压输出、过压过流保护,PIC 负责整体状态机、动态调压策略和故障恢复。两者通过 I2C 通信,配合得好的话,可以把整机待机电流控制在微安级别,同时该跑满性能时又能瞬时输出足够功率。
很多做嵌入式的人一提到电源管理,第一反应就是“用一颗带电源管理的 MCU 不就完事了吗”,或者“直接挂个 LDO + 锂电池保护芯片”。真要做带电池、带动态功耗切换、带长时间待机的产品级方案时,你会发现问题远不止电压够不够这么简单:电池电压会飘、负载会突变、充电要控温、过放要保护、休眠要低功耗、唤醒要快。这些问题杂糅在一起,就需要把“执行电源动作”和“制定电源策略”拆成两颗设备分管,这也是我最终选 PCA9422 + PIC18LF45K42 这套组合的原因。这篇文章把整个设计思路、硬件连接、固件流程和踩坑经验完整写出来,适合正在做便携设备、电池供电产品,或者对分离式电源管理架构感兴趣的人参考。
1. 先弄明白:主控和电源芯片各自该干什么活
1.1 电源管理为什么不能只靠一颗单片机搞定
有人会觉得“既然 PIC18LF45K42 自身工作电流已经够低,为什么还要外挂 PMIC”。这个质疑在样机阶段说得过去,当你开始往产品里加传感器、无线模块、屏幕背光之后,就完全站不住了。
电源管理真正难的不是把 3.3V 稳出来,而是处理动态过程。电池电压从满电 4.35V 掉到低压闩锁点之前,输出电压必须保持稳定;负载从一个 10mA 的休眠态瞬间跳到 300mA 的发射态,输出电压不能出现肉眼可见的塌陷;充电过程中电池温度升高了,需要降流;系统进入休眠,PMIC 自身消耗不能成为拖垮待机电流的短板。这些事如果拿通用单片机加离散 LDO 去拼,第一是 PCB 面积会膨胀,第二是硬件保护响应速度很难达到电源芯片那种微秒级水平。
电源芯片内部通常有比较器、参考源、控制环路,这些东西做在同一颗芯片里,能保证极快的过流、过温响应。MCU 哪怕跑得再快,从检测到异常到执行关断,中间隔着采样、处理、写寄存器,时间上根本来不及。所以我的原则很明确:快速硬实时动作归 PMIC,策略和决策归 MCU。
1.2 PCA9422 和 PIC18LF45K42 的分工清单
PCA9422 这类可穿戴 PMIC,内部基本把电池管理的外围都收进去了:充电通道、升降压变换器、低压差线性稳压器、负载开关,还有各种保护比较器。PIC18LF45K42 则是一个典型的低功耗 8 位单片机,工作电压范围很宽,峰值电流小,睡眠模式下能保持若干外设运行。
我习惯把任务画成一张职责表,避免开发过程中“谁负责什么”说不清。
| 任务 | 负责方 | 说明 |
|---|---|---|
| 锂电池恒流恒压充电 | PCA9422 | 包括充电截止、复充判断、充电状态上报 |
| 多路电压输出 | PCA9422 | DCDC/LDO 的使能、放电、电压选择 |
| 过压过流过温保护 | PCA9422 | 硬保护必须由 PMIC 内部完成,不等 MCU |
| 系统运行状态机 | PIC18LF45K42 | 决定当前应该运行、待机还是深度休眠 |
| 动态电压调节 | PIC18LF45K42 | 根据负载调整 PMIC 输出配置 |
| 故障记录与恢复 | PIC18LF45K42 | 读取 PMIC 状态寄存器,决定重启还是停机 |
| 电池温度监控 | PIC18LF45K42 | 通过外部 NTC 采样,配合充电策略降功率 |
一句话记住这套分工:PCA9422 是“手脚”,负责把电压电流这些粗活干到位;PIC18LF45K42 是“大脑”,负责判断当前该以什么姿态工作。两者之间通过 I2C 和一根中断线对话。
如果你做的是对功耗极敏感的项目,还要额外留意 PMIC 自身的静态功耗。选型时别只看输出电流能力, 要抓数据手册里“芯片自身工作时消耗电流”那一栏。PCA9422 这类低功耗 PMIC 在轻负载模式下自身消耗通常能压到很低,否则整机待机电流指标会直接被电源芯片吃掉。
2. 硬件连接与状态管理怎么搭
2.1 电源树:从电池到每一路负载的路径
设计电源树的时候,先把所有负载按工作特性分组,原则我总结成一句话:贵的负载用 DCDC,安静的负载用 LDO,带掉电要求的负载用始终导通轨。
按钮、传感器、RTC 这类需要 24 小时保持供能的模块,接到 PMIC 的常开电源轨上;主控核心、无线模块这类大电流负载,接到可由软件控制的 DCDC 输出上;射频前端、模拟采集这类对纹波敏感的负载,接到 LDO 上会更稳妥。
以我这次项目为例,简化的电源路径是:
- 电池正极接入 PCA9422 的 VBAT 输入脚,电池负极走检流电阻后接地。
- PCA9422 输出一路核心电压,给 PIC18LF45K42 的 VDD 供电。
- 另一路 DCDC 输出给无线模块和外设,软件可关断。
- 一路 LDO 留给模拟前端,保证低噪声。
- 常开域接按钮检测和唤醒逻辑,保证系统睡眠后随时能唤醒。
这样做的直接好处是,MCU 睡眠前只需要把不用的那一路 DCDC 关掉,把核心电压降下来,整机功耗立刻就能降一个数量级,而不是靠 MCU 把所有外设逐个哄睡。
2.2 I2C 总线的可靠性设计
PIC18LF45K42 和 PCA9422 之间走的就是标准 I2C,PIC 做主设备,PMIC 做从设备。电路上只需要两根线:SCL、SDA,各接一个上拉电阻到对应电压域。电阻阻值选几 k 欧姆即可,400kHz 模式下一句话经验是“2.2k 到 4.7k 大多数情况都能正常跑,走线长就取小一些”。
I2C 总线看着简单,实际出问题多半出在上电阶段。如果 PMIC 先于 MCU 上电,或者反过来,两边 I/O 电平没配合好,总线就可能被其中一个芯片钳住,导致后续所有通信失败。稳妥做法是给 I2C 两根线都加上拉,同时确保 PMIC 的 I2C 域电压和 PIC 的 VDD 在同一个电源轨上,不要跨域。
代码层面要为 I2C 加三样东西:通信超时、失败重试、总线复位。PIC18LF45K42 的 MSSP 外设,在检测到总线冲突时可以主动切换成 GPIO 模式,手动把 SCL 翻转几次释放总线。这个复位程序看起来土,但在板卡调试初期能救很大命。
2.3 中断线:让 PMIC 主动“喊”MCU
I2C 是问询模式,MCU 不主动读 PMIC 就不知道发生了什么。但电源事件往往需要及时响应,比如充电完成、低压告警、发生过流。所以 PCA9422 一般都会提供一个中断输出脚。把这一脚接到 PIC18LF45K42 的 INT 引脚,并且通过 PIC 的外设引脚选择功能映射好。
PIC18LF45K42 这块 MCU 有一个很有用的特性叫 PIN MAPPING,外设引脚可以灵活映射,布线时不用为了中断脚死磕某个固定引脚。在头文件里写一行映射配置,把外部中断源引到指定 IO 上,硬件布线就轻松不少。
中断触发后,MCU 在服务函数里尽快读取 PMIC 的状态寄存器,清掉中断标志,再做对应处理。注意 PMIC 的中断可能是电平触发,也可能是脉冲触发,要根据数据手册配置 MCU 的边沿检测,否则会出现中断风暴或者漏中断。
3. 固件实现:从启动到休眠的完整流程
3.1 上电默认配置:先让系统跑在一个安全姿态
MCU 自己也是由 PCA9422 供电的,所以存在一个先有鸡还是先有蛋的问题:谁先启动?实际上 PMIC 在主供电接入后先按默认寄存器的状态顺序上电,PIC 得电后运行固件,再由固件反过来“接管” PMIC 配置。
刚上电时,PMIC 提供的默认电压和默认使能状态可能不是为了你的负载量身定制的。因此 PIC 启动后第一件事,就是把电源配置脚本写进 PMIC,让系统进入期望的安全工作状态。我把这些配置写成一个表,放在 flash 里,启动时逐条下发。
typedef struct { uint8_t reg; uint8_t val; } pmic_reg_cfg_t; static const pmic_reg_cfg_t power_on_script[] = { { PMIC_REG_MAIN_CFG, 0x08 }, // 使能系统主控,关闭休眠 { PMIC_REG_DCDC1_VOUT, 0x2F }, // 核心电压设到 1.8V { PMIC_REG_DCDC2_VOUT, 0x3A }, // 外设供电设到 3.0V { PMIC_REG_LDO1_VOUT, 0x1E }, // 模拟LDO设到 2.8V { PMIC_REG_CHG_CFG, 0x4D }, // 充电电流 500mA,截止电压 4.2V { PMIC_REG_INT_MASK, 0x00 }, // 打开所有需要的中断 }; void pmic_apply_defaults(void) { for (uint8_t i = 0; i < ARRAY_SIZE(power_on_script); i++) { pmic_write_reg(power_on_script[i].reg, power_on_script[i].val); } }寄存器地址和位定义每个厂家数据手册都不完全一样,这里用宏做了一层抽象。好处是后续换料或换版本时,只改这一个表就行,不用满工程找寄存器号。顺带提醒一句,脚本下发顺序是有讲究的:先配电压,再使能输出。如果你先把输出使能了,再改目标电压,负载可能先吃到一个错误电压,轻则打嗝,重则损坏后端。
3.2 I2C 读写函数怎么写才稳
PMIC 的 I2C 读写和普通外设芯片没什么两样,但我在写驱动时要求自己加上应答检查、失败重试和错误计数。有一类很隐蔽的问题:系统正在高负载跑着,I2C 时序受干扰,写寄存器失败,PMIC 维持旧配置,系统却以为已经切到新配置,最终表现就是“明明写了低功耗模式,电流却下不来”。
我的 I2C 写函数会返回是否成功:
bool pmic_write_reg(uint8_t reg, uint8_t val) { uint8_t status; i2c_start(); status = i2c_write(PIC_ADDR(0x6C)); // PCD 地址,含写位 if (status) goto i2c_fail; status = i2c_write(reg); if (status) goto i2c_fail; status = i2c_write(val); if (status) goto i2c_fail; i2c_stop(); return true; i2c_fail: i2c_stop(); i2c_bus_recovery(); return false; }调用侧还要加一层限位重试。常见做法是写最多重试三次,连续失败就记录错误码并走故障处理流程,而不是无限循环卡死主程序。电源管理驱动和普通传感器驱动最大的区别在于,它写的每个值直接影响系统寿命和安全,失败不能静默吞掉。
3.3 动态电压调节:按负载切状态
硬件搭好后,真正的技术含量在状态切换的顺序和节奏上。我的系统定义了四个电源状态:运行、待机、浅睡眠、深度断电。
运行态时,PCA9422 全负荷输出,核心电压提到较高值,保证 PIC 跑最高主频且 ADC 采样稳定。切入待机时,先把外设 DCDC 降到仅够维持唤醒逻辑的电压,或者直接关闭。这里关键是切换顺序:先关负载,再降电压。如果先降电压,再关负载,那在电压下降窗口里,外设还在跑,很可能进入欠压复位。
写进固件里类似这样一个过渡函数:
void system_to_standby(void) { pmic_write_reg(PMIC_REG_GPIO_CTRL, 0x00); // 关掉负载开关 delay_ms(10); // 等解耦电容放掉 pmic_write_reg(PMIC_REG_DCDC2_VOUT, 0x0F); // 外设供电降到待机档 pmic_write_reg(PMIC_REG_DCDC1_VOUT, 0x18); // 核心电压降到 1.2V }这段代码里两个细节容易被忽略。第一,关闭负载开关和降电压之间要留时间,目的是让输出电容放电,避免后端设备在电容仍储能时出现反复上下电。第二,降电压要一级一级来,每一次改动后轻微延迟,让电源环路跟上,别一下子跳到极低压。对于负载有大电容的情况,突变电压容易让 PMIC 的反馈环路进入震荡区。
3.4 深度睡眠:把电流做到微安级
进入深度睡眠前,MCU 自己也要配合。PIC18LF45K42 有低功耗睡眠模式,睡眠前把不用的外设关掉,时钟切换到低频内部振荡器,看门狗按需开启。
这时候 PCA9422 那边保留两样东西:常开域的唤醒检测、PMIC 自身的低压保护。其它输出全部关掉。整个系统维持一个极小的静态电流,等着外部事件唤醒。
唤醒事件可以来自按键、充电插入、定时器溢出,也可以是 PMIC 的中断。以前我犯过一个错,总想让 PMIC 和 MCU 同时达到最低功耗,结果两边都在等对方,系统就再也醒不过来了。后来我把唤醒源分成两层:第一层是硬件唤醒,直接接在常开域上,不经过主控,保证有人按按键时系统一定醒;第二层才是 PMIC 中断唤醒,用来处理充电完成这类电源侧事件。两层各自独立,谁触发都能醒。
4. 做下来最容易踩的几个坑
4.1 I2C 通信偶尔不响应
这是我最先踩到的坑。现象很随机:跑几分钟或者几小时后,主控对 PMIC 读写开始返回失败。用示波器抓 SCL、SDA,发现 SDA 一直被拉低。这是典型的 I2C 总线锁死。起因大多是一次通信中途总线被异常打断,从设备处于半通信状态,持续等待后续数据。
解决办法分两层。硬件上 SCL、SDA 各加一个串联电阻和上拉,走线不要太细,且避免和开关电源的 di/dt 环路平行。软件上做总线复位:检测到失败后,把 SCL 和 SDA 都配置成普通 GPIO 输出,手动翻转 SCL 9 个周期,再把 SDA 释放,就能强制从设备复位掉状态。这个动作在连接充电器、插拔电池时特别重要,因为电源扰动可能打断正在进行的 I2C 通信。
4.2 上电瞬间负载过流导致系统反复重启
有一次调试,给系统上电后发现整机在反复重启。用电流探头看波形,每次上电瞬间都冲出一个很大的浪涌电流,触发了 PCA9422 的过流保护,输出被切断,MCU 掉电,然后保护复位又来一轮。
原因很典型:后端有大容量储能电容,PMIC 使能输出瞬间要对电容充电,充电电流超限。处理起来有两个方向。第一是拉长软启动时间,如果 PMIC 提供输出爬坡斜率配置,调节到较慢档位。第二是分时使能,先让 MCU 运行起来,MCU 再逐个打开外设供电,避免所有电容同时充电。我最后是两种方案一起用:PMIC 默认软启动调慢,外设部分由 MCU 脚本控制在开机后 50ms 才上电。
4.3 电池电压判断不准
用 PIC 的 ADC 去采样电池电压,如果不注意参考源和分压电阻,测出来的值和万用表差一大截。我一般不在 PMIC 内部做精准电量判断,而是依赖 PMIC 给的低压告警中断,再用 MCU 的 ADC 做二次确认。
ADC 采样有两点经验:一是分压电阻必须用低温漂的,阻值尽量大,避免分压网络自身把电池拉出额外电流;二是采样时序避开射频发射窗口。无线模块发射瞬间,电池内阻造成电压跌落,这时候采到的值会偏低,触发假的低压告警。固件里加一个小滤波,连续采样多次,去掉最大最小值取平均,就能有效滤掉由瞬态负载造成的波动。
4.4 看门狗把“待机”变成“反复重启”
低功耗模式下看门狗的设置特别容易出问题。PIC18LF45K42 的看门狗如果选得太短,MCU 进入睡眠后定时清零的任务也就不再执行,看门狗超时把系统强制复位,于是系统陷入“进入睡眠-被唤醒-重启-再进入睡眠”的死循环。
想让看门狗在睡眠时正常工作,必须用它在睡眠模式下也继续运行的配置,并且唤醒条件要设置得比看门狗周期快,或者在进入睡眠前暂停看门狗。我比较推荐的是保留看门狗,但把唤醒事件周期设置在看门狗超时周期的一半以下,这样睡眠中每隔一段时间醒来清狗,再继续睡,既不丢保护,也不被打扰。这块内容不同型号的配置位不一样,建议先拿最小工程确认实测电流波形没有周期性的毛刺,再往整机里集成。
4.5 中断标志没清掉造成连环响应
PMIC 中断脚通常是一个汇集事件源的开漏信号。只要内部任何一个事件标志位为 1,中断脚就可能一直保持有效状态。如果 MCU 读状态后不写清除位,中断服务函数会反复被触发,主程序永远走不下去。
我的习惯是:中断服务函数里一次性读取整个状态寄存器组,保存到全局变量,然后一次性写清除所有已读事件标志,最后做事件分发。不要在中断服务函数里做耗时的决策,只记录事件;真正动作放到主循环里执行。这样既不会漏事件,也不会因为中断占用太久导致主流程卡顿。
5. 工程化过程中值得继续打磨的几个方向
把基本流程跑通只是第一步,真正要交付给生产,还要做更多工程化的工作。比如把 PCA9422 的寄存器配置全部抽离成一个独立的配置表文件,产品和固件分离,产线烧录时可以直接写入不同机型配置,省得每次改版都重新编译固件。
再比如故障日志。我额外分配了一小块非易失存储,专门记录每次 PMIC 中断事件代码、时间戳和当前系统状态。返修时只需要读出日志,就能定位是过流、过温还是异常关断导致的死机,不用靠猜。这项工作投入产出比极高,强烈推荐。
另外一个细节是温度补偿。电池充电的截止电压会随温度变化,低温时充电电压过高对电池寿命影响很大。如果 PCA9422 支持通过外部 NTC 引脚读取温度,那么可以在固件里放一张温度补偿表,按区间动态调整充电参数。需要说清楚的是,NTC 摆放位置也有讲究,要贴在电池表面,而不是放在主板热源附近,否则采出来的是主板温度而非电池温度。
最后想提醒的是,电源管理调通一半的时候最容易自信过头,觉得“这么简单就完了”。实际上电池产品最容易在老化测试里暴露问题:长期充放、热循环、深度放电后重新充电。如果手头有条件,哪怕只做一台样机的 100 个循环充放测试,也能提前看到很多数据手册里不会写出来的问题,比如保护点漂移、充电终止电压偏差、低压闩锁后的恢复延迟。这些隐藏问题直接影响产品回修率,趁早排查比事后补救划算得多。
在我自己实际开发过程中,还有一个很小但很受用的习惯:每次修改 PMIC 配置后,都顺手打印一次配置回读值,确认写入和回读一致。电源芯片寄存器写失败不像程序跑飞那么明显,经常要等到现场设备出问题才暴露。多花的这几秒钟读数时间,能在调试阶段拦下大量诡异故障。这一点如果你也做电池产品,建议直接变成固件里的固定动作。