电池供电产品里,电源管理从来不是“把电压接对”这么简单。手头这块板子用 PCA9422 搭配 PIC18LF4620 做了整套电源管理,PCA9422 负责电池充放电、多路电压输出和状态上报,PIC18LF4620 负责系统逻辑、I2C 配置和低功耗切换。这套组合非常适合便携仪表、智能手环、遥控器、小型传感器节点这类电池容量不大、对体积和待机电流都很敏感的设备。本文会把硬件连接、寄存器初始化顺序、充电状态读取、低功耗切换和调试方法整体复盘一遍,尤其是 PMIC 里那些容易被忽略的状态位和控制时序。
1. 为什么要单独做电源管理
1.1 PIC18LF4620 的低功耗并非万能
PIC18LF4620 确实是一颗很省电的单片机,同一系列里,LF 后缀版本把工作电压下探到 1.8V,Sleep 模式下典型电流可以拉到个位数微安级别,配合 WDT 和中断唤醒,做低功耗产品的底子很好。但 MCU 省电不代表整机省电,产品里往往还有显示屏、传感器、无线模块、运放电路,这些外设的供电不是一颗 MCU 能管好的。
几个实际场景就能说明问题:第一,系统里需要多个电压域,模拟部分用 3.3V,数字内核用 1.8V,传感器可能还要单独断电;第二,电池充电需要限压限流,充满后还要检测终止电流,靠分立元件搭充电电路非常占板面积,也很难把充电状态上报给 MCU;第三,睡眠和唤醒时,各路电源需要一个明确的先后顺序,我用 GPIO 控制 MOS 管也能实现,但时序一旦做错,MCU 可能还没初始化完就看门狗复位。这些问题集中在一起,就用到了我们今天的主角 PCA9422。
1.2 PCA9422 在这里的角色
PCA9422 是一颗面向电池设备的 PMIC,典型的资源包括电池充电管理、内部升压或降压转换、多路输出、I2C 配置接口、状态寄存器和中断引脚。用它之后,电源策略从硬件电路搬到了软件里,输出电压、充电电流、开关时序、低功耗模式都可以通过 I2C 寄存器动态调整。
我在这块板子上没有把 PMIC 当成一个单纯的电源芯片用,而是让它承担四件事:
- 把 USB 5V 和电池供电统筹起来,做输入路径管理
- 给系统提供两路主输出,一路 3.3V 给外设,一路 1.8V 给数字部分
- 对电池做完整的充电流程控制,包括预充、恒流、恒压、截止判断
- 把充电状态、输入电源状态、各路输出状态通过 I2C 暴露给 PIC18LF4620
这样做的好处是,MCU 不需要用 ADC 去频繁测电池电压来判断充电阶段,直接读状态寄存器就能知道当前处于哪一种阶段。硬件少了一大块,调试交互区也从示波器看波形变成了逻辑分析仪看 I2C 报文,可控性提升非常明显。PIC18LF4620 本身有 MSSP 模块,硬件 I2C 直接可用,不需要软件模拟 I2C,CPU 占用率也很低。
2. 硬件连接与系统拓扑
2.1 整机电源树设计
先看一下完整的电源结构。这板子的输入有两个来源:USB 5V 和单节锂电池,两者通过 PCA9422 内部的路径管理切换。PMIC 输出端分别生成 3.3V、1.8V 两路,另外留一路直接给 PIC18LF4620 供电,电压用 3.3V。
为什么要分两路而不是统一 3.3V?因为板上有一个 1.8V 的数字逻辑器件,如果从 3.3V 经过一颗 LDO 降压到 1.8V,LDO 的静态电流在低功耗模式下会成为负担,而且 LDO 压差大时效率很低。PMIC 直接输出 1.8V,在几种负载电流区间里都能保持较高的转换效率。更重要的是,1.8V 这路负载在睡眠时可以单独关闭,不需要 3.3V 的外设供电跟着一起受影响。
PIC18LF4620 的 VDD 也连接在 3.3V 上,但 MCU 单独加了一颗 0.1uF 的退耦电容。在布线时,PMIC 输出的主电容放在电源入口,小容值电容靠近各负载引脚。这点别省,输出纹波和瞬间压降很多时候就是少了这些小电容造成的。
2.2 电池充电路径
电池充电部分,我们用的是一颗单节锂离子电池,标称 3.7V,充电限制电压 4.2V。PCA9422 的充电配置里需要设定预充电压阈值、预充电流、恒流充电电流、恒压充电电压和终止电流。这些参数不是靠数据手册随便填的,得根据电池规格书来。
我这边电池容量是 500mAh,按照 0.5C 左右的常规充电率,恒流阶段设置 250mA。预充阶段设置在 2.9V 以下,预充电流 30mA,防止过放的电池在恒流大电流下损坏。恒压 4.2V,终止电流设置 25mA,也就是当恒压阶段电流掉到 25mA 时就认为电池已经充满。
这里有一个很多人忽略的点:充电回路上的 PCB 走线电阻、电池连接器接触电阻和采样电阻误差,都会影响实际到达电池的电压。理论设置 4.2V 结束时,电池端可能只有 4.18V,长期下来容量损耗能到几个百分点。我看着数据手册上的充电电压精度,再用万用表实测了电池接口处的电压,把恒压值向上修正了大约 20mV,这才保证电池端实际能到 4.2V。
2.3 I2C 与中断链路
PCA9422 作为 I2C 从机,PIC18LF4620 作为主机。SCL、SDA 两根线都接了 4.7k 欧姆上拉电阻到 3.3V,因为 PMIC 的 I2C 引脚通常是开漏结构,必须外部上拉才能拉高。这个电阻值在 400kbps 速率下没问题,如果只跑 100kbps,可以适当地换成 10k 甚至更大,但上拉太弱会导致上升沿变缓,极端情况下通信不稳定。
还有一个 RESET 引脚,我用 PIC18LF4620 的一个 GPIO 去控制,用来在做低功耗唤醒时让 PMIC 重新启动,避免状态机里出现奇奇怪怪的残留配置。中断引脚 INT 则接到 MCU 的外部中断输入,当充饱、充电异常、输入插入、输出掉电等事件发生时,PMIC 会拉低 INT 通知 MCU,MCU 在中断里通过 I2C 读 STATUS 寄存器确认具体原因。
PIC18LF4620 的 MSSP 引脚默认可能是输入模式,需要先把 TRIS 配好。另外注意上电时序,PIC 的 VDD 还没稳定时,GPIO 可能是高阻或不定状态,这会干扰 PMIC 的 EN 和 I2C 总线。好在我的系统里 PIC18LF4620 和 PMIC 同时上电,PMIC 的默认输出状态可以先用。如果必须要求 MCU 先跑起来再开 PMIC,就要加上 EN 控制,并把 MCU 的几个关键 GPIO 初始化为确定电平。
3. 固件实现的初始化与状态读取
3.1 I2C 读写底层函数
PIC18LF4620 使用 MSSP 模块做硬件 I2C,初始化时把 I2C 模式设置为 100kbps 或 400kbps,地址长度 7 位。实际项目里我直接把速率设成 400kbps,原因很简单:PMIC 配置过程只发生在开机瞬间和模式切换瞬间,数据量不大,400kbps 足够快,而且 PMIC 的 I2C 时序经过逻辑分析仪验证能稳定工作在这个速率下。
底层写函数核心就是 start、发送地址、发送寄存器地址、发送数据、stop 这几步。为了减少重复代码,我封装成下面这样:
void pmic_write(uint8_t reg, uint8_t dat) { i2c_start(); i2c_write(PMIC_ADDR_W); i2c_write(reg); i2c_write(dat); i2c_stop(); }读函数从 PMIC 读取一个字节时,需要注意“读寄存器地址”和“读数据”之间要插入 restart 信号,不能直接用 stop 再加 start,否则某些从机芯片在 stop 后会释放总线,重新 start 后又需要重复发送设备地址,整个流程变复杂且容易出错。标准流程如下:
uint8_t pmic_read(uint8_t reg) { i2c_start(); i2c_write(PMIC_ADDR_W); i2c_write(reg); i2c_restart(); i2c_write(PMIC_ADDR_R); uint8_t dat = i2c_read(ACK); i2c_stop(); return dat; }i2c_read 的参数表示主机读完这一字节后是回 ACK 还是 NACK。因为这里只读单个字节,所以读完一字节后回 NACK,告诉从机不需要继续发送了。如果一次要连续读取多个寄存器的值,可以在最后一个字节读完后返回 NACK。寄存器地址寄存器会自动递增的话,非常适合批量读取,PCA9422 这类 PMIC 大多支持连续读。
3.2 上电配置的顺序
PMIC 上电后会按默认配置工作,但默认配置不一定符合产品要求,所以 PIC18LF4620 要在初始化阶段把整套配置写进去。顺序很关键,不要上来就改输出寄存器,先把输入电源检测状态看一遍,再配置充电参数,最后配置各路输出电压并使能中断。
我写的初始化流程是这样:
- 等待 PMIC 完成内部上电复位
- 读取设备 ID 寄存器,确认 I2C 通信正常,防止后续配置全做了无用功
- 读取输入电源状态寄存器,确认是 USB 供电还是电池供电,并判断电压是否在合法范围
- 配置充电电压、电流、终止电流和预充参数
- 配置各路输出电压目标值
- 按系统需要的顺序使能各路输出
- 清除所有挂起的中断状态位,打开需要上报的中断屏蔽位
- 使能 PMIC 中断引脚
设备 ID 校验这一步非常建议保留。有次我换了 PCB 样品,芯片焊错方向,I2C 地址全对不上,如果没有 ID 校验环节,那一套初始化代码会在完全未知的设备上执行,很难排查。ID 读出来不对最好直接卡住并输出错误,而不是继续往下跑。
输出电压的配置是控制 PMIC 内部 DCDC 或 LDO 的目标值。我这边把输出 1 配成 3.3V,输出 2 配成 1.8V,在配置寄存器里写入对应档位编码。代码中用宏定义包一层,不要直接手写十六进制值:
#define VOUT1_3V3 0x16 #define VOUT2_1V8 0x0C pmic_write(REG_VOUT1, VOUT1_3V3); pmic_write(REG_VOUT2, VOUT2_1V8);如果配置寄存器变更后输出电压没立刻变化,还需要单独发送一个“电压变化使能”命令或触发寄存器更新位。很多 PMIC 为了避免输出在配置过程中抖动,会把新的目标电压存到影子寄存器,等使能位更新后才真正生效。这时候不要反复写电压寄存器,否则会一直停留在旧的输出电压上。
3.3 电池状态与中断处理
充电状态读取是最能体现 PMIC 价值的地方。PCA9422 的 STATUS 寄存器会给出一个状态组合,区分预充阶段、恒流充电阶段、恒压充电阶段、充电完成、无电池、充电异常等状态。PIC18LF4620 只需要周期性地读一个寄存器,就能知道充电进行到哪一步。
我在代码里用了一个简单的状态映射表:
switch (charge_status) { case CHG_PRE_CHARGE: update_led(CHG_LED_SLOW); break; case CHG_FAST_CHARGE: update_led(CHG_LED_ON); break; case CHG_DONE: update_led(CHG_LED_DIM); break; default: update_led(CHG_LED_ERROR); break; }那个充电完成的判断,完全依赖 PMIC 终止电流检测,比 MCU 用 ADC 量电压判断要准得多。AD 采样误差、分压电阻温漂、参考电压精度都会影响判断,而 PMIC 芯片内部检测不到万用表级别,但重复性和一致性很好,产品量产时不易出现不同批次充电判定不一致的问题。
另外要注意,每当中断引脚拉低,MCU 进入中断服务函数后,第一件事是读取 STATUS 或 INT 寄存器,把中断标志读掉。只读不写寄存器通常也能自动清标志。有一些 PMIC 如果不清标志,INT 引脚会一直保持低电平,导致 MCU 反复进中断,整机功耗瞬间高出几毫安。这种问题用示波器看 INT 引脚会非常明显,拉低后没有恢复高。
4. 低功耗状态切换与续航估算
4.1 系统功耗状态划分
低功耗产品不能只有一个运行模式和睡眠模式,我把整机状态分成了四档:
- 工作状态:所有功能开启,PMIC 各路输出正常,PIC18LF4620 运行主循环
- 空闲状态:显示关闭,传感器停止采样,PMIC 保持输出电压,但关闭非必要外设供电轨
- 睡眠状态:PIC18LF4620 进入 Sleep,PMIC 切换到低功耗模式,保留一待机电压轨,其余输出关闭
- 关机状态:PMIC 输出基本关闭,仅保留充电检测和唤醒检测
这种分档比单一种类 Sleep 要实用,因为休眠意味着产品还能随时被按键、传感器中断或定时器唤醒;而关机则可能要求只有插上 USB 才能重新启动,两者对 PMIC 的配置完全不同。
PIC18LF4620 的 Sleep 模式可以让 CPU 停止,但 MCU 本身还挂在 3.3V 上,静态功耗已经压得很低。更大的收益在于 PMIC 关闭那些外设供电轨。比如一个图像传感器在 enable 引脚悬空或上电但没工作时,电流能到几百微安到毫安级,这在整机睡眠时是不可接受的,必须把整个电源域切断。
4.2 通过寄存器切换输出轨
睡眠状态切换时,PIC18LF4620 会先对使用中的外设完成一次停车,比如说把传感器配置保存好、把射频模块进入复位状态,然后才把 PMIC 输出 2 关掉或降低电压。这一步顺序反了,外设还没停稳就断电,唤醒后可能初始化失败。
我的睡眠流程里把 1.8V 轨直接关断,3.3V 轨保留,同时把 3.3V 上的外设供电在 PMIC 内部切换到较低功耗模式。这样,PIC18LF4620 的唤醒源仍然能工作,但绝大多数负载已经断电,整机睡眠电流从原来的 6.8mA 降到 48uA,效果非常明显。
代码里关键就是三个操作:
pmic_write(REG_PWR_CTRL, RUN_MODE_CONFIG); // 进入睡眠前先配置 PMIC 输出 pmic_write(REG_VOUT2, VOUT2_SLEEP_VALUE); pmic_write(REG_SLEEP_MODE, SLEEP_ENABLE); PIC_Sleep(); // 唤醒后恢复工作模式 pmic_write(REG_SLEEP_MODE, SLEEP_DISABLE); pmic_write(REG_VOUT2, VOUT2_1V8); pmic_write(REG_PWR_CTRL, RUN_MODE_CONFIG);这些寄存器具体定义要查 PMIC 数据手册,但思路是一致的。睡眠时不直接关断 PMIC 主电源,而是把输出电压降到刚好维持 RAM 不掉电的水平,或者直接关闭某一路输出,让 MCU 通过中断唤醒后再把配置写回来。PIC18LF4620 的 BOR(掉电检测)在电压跌落时要能扛得住,所以保留 3.3V 火线是底线。
4.3 一组续航实测数据
用这套配置跑下来的实测数据,贴出来供参考:
- 工作状态平均电流 42mA,屏幕开启,传感器以 10Hz 采样
- 空闲状态平均电流 4.5mA,屏幕关闭,传感器待机
- 睡眠状态平均电流 48uA,仅保留 RTC 定时唤醒功能
- 关机状态平均电流 5uA
拿 500mAh 电池算续航:假设一天里工作 2 小时,空闲 8 小时,睡眠 14 小时,那么一天的耗电量约等于 42mA x 2h + 4.5mA x 8h + 48uA x 14h,大约 120.7mAh。500mAh 电池理论可以撑 4 天左右。如果睡眠电流没调好,比如 PMIC 多路输出全部保持开启,睡眠电流到了 500uA,那 14 小时就会多消耗 6.3mAh,占比不大,但当你希望几个月不充电时,每一微安都是命。所以低功耗产品的核心,不一定在主芯片选型,而是电源域怎么切。
5. 调试中常见的坑与排查
5.1 I2C 不 ACK 的奇怪问题
I2C 通信失败是我遇到最多的坑。现象是 MCU 发送完设备地址后,SCL 上有正常的时钟,但 SDA 在应答位时始终为高,也就是从机没有应答。排查顺序建议这样:
先看地址对不对。PMIC 的 I2C 地址会根据地址引脚的电平变化,不能只对照数据手册的默认值,还要确认硬件上引脚是接地还是接高。我有一次就是把地址引脚默认悬空,以为悬空等于接地,结果和实际地址差了一位。
再看电源是否正常。PMIC 的 I2C 模块如果没有得到内部供电,从机根本不会拉低 SDA 应答。此时用示波器看 PMIC 的供电引脚纹波,如果电压在阈值附近反复抖动,也会出现间歇性无应答。
最后上拉电阻很关键。如果上拉电阻太大,或者 SCL、SDA 走线过长,通信速率又开到了 400kbps,上升沿会拉不动,逻辑分析仪看起来波形类似斜坡而不是方波。这种情况从机芯片还能勉强工作,但误码率升高。减少速率、换更小阻值的上拉、缩短走线,三个办法都能解决。
5.2 输出电压偏差与纹波
PMIC 内部 DCDC 的输出电压和设定值差了 100mV 以上,不一定是芯片坏了,先看反馈采样点。很多 PMIC 是从芯片引脚上采样输出电压,如果 PCB 上输出走线太长,负载端的电压就会明显低于芯片检测点电压,因为走线上有欧姆级的寄生电阻。解决办法是让采样走线直接连接到负载端的电容上,形成开尔文连接,避免大电流走线经过采样点。
纹波过大的原因,大多是电感选型不合适。输出电感的值决定了纹波电流的大小,电感量太小纹波大,太大又会影响瞬态响应。选型时按数据手册的推荐电感值范围,不要为了省体积用远低于推荐值的封装。DCR 过高也会在重负载下出现明显压降,所以电感参数不能只看感值,DCR、饱和电流都要检查。
如果加了输出电容后纹波依旧很大,可以看看周围是否有其他高频开关信号耦合,比如同一块板上的无线模块。这种干扰不是 PMIC 自身问题,而是布局问题,把负载走线远离天线和射频走线可以改善。
5.3 充电电流始终不对
设置 250mA 恒流充电,实际测量发现只有 150mA。原因可能有两个方向,一是 USB 输入限流设置太低,PMIC 在输入总功率受限时主动降低了充电电流;二是充电检测电阻附近有其他负载,抢走了电流。我检查后是输入限流寄存器没配,默认值把输入功率限制得很小,导致系统负载一大就无法提供足够的充电电流。
如果你遇到充电电流小于设定值,先不要怀疑 PMIC 的精度,先把所有负载电流估算一遍,再看输入电压是不是被拉低。USB 供电时如果线材电阻大,PMIC 的输入电压下降后会自动减小充电占空比,从而降低电流。重新设置更高输入电流限制、更换更粗的 USB 线和连接器,问题就消失了。
还有一次是充电状态一直卡在预充阶段,电池电压已经 3.8V 了,状态还在预充不跳恒流。查了半天发现是预充到恒流的电压阈值配置被我写错了,预充电压阈值设成了比电池电压高的值,系统认为电池一直处于预充状态。这种问题完全靠示波器看不出来,必须读 STATUS 寄存器才能察觉。
5.4 整机功耗降不下去
睡眠电流比估算值高出几百微安时,第一个要看的就是 PMIC 各路输出是否真的关了。有些 GPIO 控制的负载,看起来供电轨已经关闭,但负载芯片的某些引脚通过 MCU 的 GPIO 灌回了电流,形成寄生供电回路。
我在实际板子上遇到过传感器芯片的 I2C 引脚靠 MCU 的 SCL、SDA 高电平供电,即使 PMIC 那一路电源关了,芯片内部仍然有微弱电流通路。解决方案是在睡眠前把相关 GPIO 全部设为输入,或者输出低电平,不能只想着切断主供电。电源域能关是一回事,信号路径上的漏电也要堵住。
另外一个隐蔽来源是 PMIC 反馈电阻网络。有些 PMIC 的反馈分压电阻一直在消耗电流,虽然单颗电阻有几十微安级别,但整机睡眠目标做到 50uA 时,这一点就非常致命。极低功耗产品可以考虑带负载断开功能的 PMIC 版本,或者让 PMIC 在睡眠模式下断开反馈网络。不过 PCA9422 这类芯片内部设计已经考虑过此场景,重点还是检查外部有没有多余的电阻路径。
6. 一些可以继续做的方向
这套电源管理架构做稳定之后,我顺手做了几个扩展,也建议你可以试试。第一是加入电量统计。PCA9422 提供了充电状态和电池电压信息,但如果没有库仑计,很难精确知道剩余电量。可以外接一颗电流检测电路,让 PIC18LF4620 的 ADC 周期性采样电流,在软件里做积分。虽然不如独立电量计 IC 精度高,但对大多数产品来说已经足够好用。
第二是动态电压调节。把 PMIC 的输出轨和系统运行状态深度绑定,比如 CPU 全速跑时 3.3V 保持稳定,进入轻负载后把电压降到 3.0V,配合 PIC18LF4620 的代码执行频率调整,可以有效降低系统功耗。
第三是参数配置的固件升级预留。因为 PMIC 所有寄存器都能通过 I2C 配置,我建议在固件里预留一个配置表区域,把充电电压、充电电流、各路输出电压、睡眠模式配置都放在统一的数据结构里。这样后续修改电池规格或者更换显示屏时,只需要更新配置表,不用改主程序逻辑,维护成本低很多。
最后提醒一点,做电源管理调试的时候,一定要把寄存器读回来确认,不要只凭示波器看到波形正常就当配置成功。很多 PMIC 支持回读校验,这个功能能帮你少走弯路。我每次写完配置之后,都会顺手把关键寄存器读回来比对一遍,和配置值不一致时直接报错提示,这比出了问题再去翻逻辑分析仪要省事得多。