从去年开始,我一直在调试一块低功耗电池设备的主控板,MCU 选了 STM32F071VB,电源侧最初是几颗独立的 DC-DC 加 LDO,加上一颗充电管理芯片,电路倒是简单,但到了联调阶段问题全冒出来了:不同负载模式下电压切换要靠 GPIO 去拨引脚,充电状态只能靠 LED 猜,电池电压一低整个系统就直接睡死。后来整板改版,换成了 PCA9422 这颗 PMIC,和 STM32F071VB 通过 I2C 对接,才算是把电源管理真正“管”了起来。这篇东西就是把我在这个项目里从选型到调通的全过程记录下来,包括寄存器怎么设置、驱动怎么封装、状态机怎么设计,以及那些数据手册里不会写的坑。如果你也在做类似的电池供电小设备,并且想用一颗 PMIC 配合 MCU 实现充电、升降压输出、动态电压调节和低功耗联动,这篇文章应该能让你少走不少弯路。
1. 为什么选 PCA9422 + STM32F071VB 这套组合
1.1 项目负载侧的需求拆解
先还原一下我当时的设备需求:整体是一个随身佩戴的传感器节点,主控是 STM32F071VB,板载一颗 3.7V 锂电池,外部负载包括一个 1.8V 的 MCU 核心逻辑区(这里其实是板内电压轨,不是 MCU 本身)、一个 3.3V 的外设区(传感器、屏幕背光)、一个 2.8V 的模拟前端,另外还希望有一个可调电压轨,给外接模块在 1.2V~3.3V 之间动态配置。
设备一共有四种工作模式:运行、空闲、休眠、关断。运行模式下系统总电流约 120mA,空闲模式约 30mA,休眠模式要求整机电流小于 100uA。这组需求看起来不算夸张,但把它拆开看,难点至少在三个地方:第一,四种模式的电压轨数量不完全一样,休眠时希望把多数外设的电源直接切掉;第二,电池电压会从 4.2V 一路降到 3.0V,需要一路 buck-boost 保证在所有电池电压下都能稳定输出 3.3V;第三,不同工作模式切换时,电压轨的上下电顺序必须可控,否则传感器或模拟前端会被反灌电流打坏。
这些需求用分立方案也能凑合,但会引入一个很麻烦的问题:每一次模式切换都要由 MCU 同时操作好几路电源芯片的 EN 引脚,而且这些引脚的时序完全依赖软件延时去凑,软件一改动,时序就漂,排查起来非常痛苦。我后来换成 PCA9422,就是因为它的核心电压轨、LDO 和充电管理全部集成在一起,并且都挂在同一个 I2C 寄存器空间下面,MCU 写一个寄存器就能完成输出电压切换或关断,时序由芯片内部逻辑保证,软件压力一下轻了一个数量级。
1.2 PMIC 选型对比:为什么不是分立方案或普通 DC-DC
在做选型对比时,我认真评估过三条路线。路线 A 是分立方案,也就是“充电 IC + 升降压 DC-DC + LDO + 负载开关”各一颗。优点是物料通用、每一级的效率都能单独优化;缺点的核心是“系统复杂度被摊到 PCB 和软件里”,每一路电源的分立电阻电容都要单独计算,每个 EN 引脚都要单独管理,而且多颗芯片之间的上电时序要人工设计 RC 延时或 MCU 引脚配合,板子面积和研发成本并不低。
路线 B 是普通的 DC-DC 模块加串口控制,类似数字电源模块。这类模块通常控制接口是 PMBus/SMBus,对于小系统来说驱动代码更重,而且多数数字电源模块强调大电流性能,对电池小电流场景反而不够精细。PCA9422 这类面向可穿戴设备的 PMIC 则是路线 C:降压、升压、升降压、LDO、线性充电管理一起封装,控制接口用 I2C,寄存器数量适中,MCU 用两三个 GPIO 配合 I2C 就能接管所有电源行为。
从最终指标看,PCA9422 的静态电流大约在微安级别,这一点非常关键。如果一颗 PMIC 自己待机就吃掉 50uA,那么设备 100uA 的休眠目标几乎不可能实现。我在选型时专门对比了多款同类 PMIC 的 Iq 参数,最后确认这颗在轻载模式下能进入节能工作方式。另外它的升降压拓扑能在输入电压高于或低于输出电压时平滑切换,这对于 3.7V 电池系统里稳定输出 3.3V 来说是最直接的匹配。还有一点是它的充电电流可配置范围覆盖了我需要的 50mA 到 500mA,而且充电截止电流也能通过寄存器设置,这就让我不用再为“涓流多少毫安”去换采样电阻。
1.3 主控与 PMIC 的分工边界
STM32F071VB 和 PCA9422 在系统里的角色一定要分清楚。PCA9422 负责的是“电源转换和基础保护”,比如把电池电压转换为多路输出、执行充电、检测过温过压;STM32F071VB 负责的是“电源策略和用户交互”,也就是根据当前工作模式、电池电量、传感器事件来决定让哪一路输出什么电压、要不要停止充电、什么时候进入休眠。
这里最容易犯的错是让 MCU 去做太多底层保护。比如电池低压保护,我在最初的项目里是让 MCU 每 100ms 读一次 ADC 电压,低于阈值就关输出。后来发现这样做有两个问题:一是 MCU 本身可能已经因为电压过低而无法稳定运行,二是 ADC 的采样点和真实瞬间电压尖峰之间有延迟,极端情况下过放保护根本来不及。PCA9422 内部自带比较器,可以在硬件层面直接切断输出,我只需要把阈值配置好,MCU 的角色就退到“事后读取中断寄存器,把故障状态发给上层”而已。
也就是说,硬件保护环路尽量让 PMIC 自己闭环,MCU 只做状态管理和动态配置。这个原则看起来简单,但真的能显著减少系统死机率。在后期的低功耗设计中,我把所有电源相关中断都接到 STM32F071VB 的 EXTI 引脚上,MCU 在休眠模式下能通过 PMIC 的中断唤醒,这比 MCU 定时醒来查寄存器方案要省电得多。
2. PCA9422 核心资源与寄存器地图
2.1 内部架构的快速理解
PCA9422 的内部结构可以简化成四块。第一块是电池充电管理,包含恒流/恒压/涓流三个阶段,充电状态寄存器可以读到正在充电、充电完成、充电错误等标志。第二块是主升降压转换器,负责从电池电压产生核心系统电压,比如 3.3V,输出电流能力按应用场景不同有一个上限,我选型时确认过最大负载电流超过我的 120mA 运行电流两倍以上,留足余量。第三块是一路 LDO,通常用来给模拟部分或低噪声数字部分供电。第四块是 I2C 接口、中断控制器和寄存器组,芯片的所有控制都从这里走。
这里要特别说明升降压拓扑。电池供电系统最尴尬的一点是电池电压在 4.2V 到 3.0V 之间变化,而很多时候我们需要一个稳定的 3.3V 输出。如果只用 buck,那么当电池电压只有 3.0V 时,buck 电路输出不可能高过输入;如果只用 boost,那么当电池电压 4.2V 时,boost 输出会被强制抬高,白白浪费效率。buck-boost 电路会在输入高于输出时按 buck 方式工作,输入低于输出时按 boost 方式工作。PCA9422 在这个转换过程中会自动切换模式,软件不需要参与,这一点在动态调节电压时非常重要。如果人工切换拓扑,切换瞬间大概率会出现电压跌落或过冲。
2.2 关键寄存器位段说明(以实际数据手册为准)
我不打算把所有寄存器都抄一遍,数据手册里都有。我只讲几个项目里真正用到的、而且容易配错的位段。先说充电控制寄存器,里面有几个关键字段:充电使能、恒流电流档位、截止电流档位、充电电压目标。我当时的配置是充电电流 300mA,截止电流 30mA,充电电压 4.2V。注意截止电流不是指充满后停止,而是指 CV 阶段电流下降到该值后视为充满。设置太小会延长充电尾部时间,设置太大则会过早判断充满,导致电池容量只有九成。经验上取恒流值的 10% 左右比较合理。
然后是主输出配置寄存器,这个寄存器的核心是输出电压选择位和输出使能位。PCA9422 支持动态调节电压,寄存器里有一个电压选择字段,比如从 1.2V 到 3.3V 分成若干个档位。我的应用里把默认输出配成 3.3V,运行模式不动,休眠模式下切换成 1.8V 给保持电路供电,其他外设直接关。切换电压时不能只看写入结果,还要读回确认。芯片在改变输出电压时会有内部软启动,大概几十微秒级。我当时做了一套切换流程:先读当前电压档位,确认不是目标值再写新值,写完后延时 1ms,再读当前电压目标核对,同时读取状态寄存器里的输出良好标志,这样才算一次成功的电压切换。
中断寄存器也是必须理解的。PCA9422 的每一个中断标志位都有独立的屏蔽位,默认很多中断是屏蔽的,我踩过这个坑,以为芯片没工作,其实是中断没打开。我在项目里只打开了几只必要的中断:充电完成、充电错误、输入电源插入/移除、输出欠压。这些中断全部映射到一个 INT 引脚,MCU 侧用 EXTI 触发。比较坑的是中断读取后要“读清零”,有些寄存器读一次就自动清除,有些需要写 1 清除,务必看手册,不然中断标志永远挂着,导致 MCU 一直醒来。
2.3 I2C 地址与读写时序注意事项
PCA9422 的 I2C 地址通常在手册里给出,一般有一个引脚或配置位可以选地址。我的板子上没有接地址选择功能,直接使用默认地址,比如 0x38(这个值以实物为准)。读写时序遵循标准 I2C,支持 Standard 和 Fast 模式,我用的是 400kbps Fast 模式,与 STM32F071VB 的硬件 I2C 对接没有问题。
真正要小心的不是时序本身,而是 I2C 总线上的电源隔离。当 PMIC 的某一路输出被关闭,或者系统进入某个特殊状态时,如果 I2C 上拉电源和 PMIC 内部逻辑电源不一致,就可能出现总线电平不稳,甚至向芯片倒灌电流。我在板子设计时把所有 I2C 相关引脚的上拉电源统一接到 PMIC 的内部 LDO 输出上,而不是直接接系统 3.3V,这样只要 PMIC 处于工作状态,I2C 引脚的参考电源就一直是稳定的。另外,由于 STM32F071VB 的 I2C 引脚是开漏,需要外部上拉,我选了 2.2k 上拉电阻。在 400kbps 下,这个阻值匹配良好,总线波形上升沿大约 100ns 左右,没有明显振铃。
还有一个容易忽略的地方:PMIC 复位后,I2C 寄存器会恢复默认值,而默认值不一定是应用需要的值。因此 MCU 上电后不能急着操作 PMIC,必须先等待 PMIC 内部上电复位完成。我的做法是:STM32 复位后延时至少 10ms,再依次读取 PMIC 的设备 ID 寄存器,确认 I2C 通信正常,然后才执行完整初始化流程。如果读取设备 ID 失败,我会让设备进入一个错误状态,点一个故障灯,绝不继续往下跑。
3. STM32F071VB 端的软件实现
3.1 硬件连接与引脚分配
我的板子硬件连接如下:STM32F071VB 的 I2C1 用作控制总线,SCL 接到 PB6,SDA 接到 PB7,这是该 MCU 的硬件 I2C1 默认映射之一。PMIC 的 INT 中断脚接到 PB1,并配置为外部中断输入,下降沿触发。此外还有两个普通 GPIO,PC0 连接 PMIC 的使能脚,PC1 用于读取电池存在检测信号。硬件设计上,INT 引脚要加上拉电阻,PMIC 内部通常是开漏输出,不过我实际测量时发现芯片已经内置了上拉,外部再加一颗 10k 上拉也能保证低电平可靠,问题不大。
电源方面,STM32F071VB 的 VDDA 和 VDD 分别用磁珠和电容隔离,模拟地和数字地单点连接。PCA9422 的输入侧接电池,输出侧接系统电源轨,MCU 的供电优先从 PMIC 的输出取电。这里有一个设计原则:不要让 MCU 直接控制自己的供电电源,也就是说 PMIC 的使能脚如果由 MCU 来控制,一旦 MCU 挂掉可能导致 PMIC 被锁定在关断状态,所以我的方案里 PMIC 的主输出在硬件上有默认使能,MCU 的 GPIO 只控制“软件关断”的附加条件。这样即使 MCU 死机,PMIC 依然保有默认输出,保证 MCU 可以复位恢复,而不是整机砖掉。
3.2 驱动层封装:读写函数与错误处理
我习惯把 PMIC 驱动封装成两层:底层是 I2C 字节读写,上层是寄存器读写接口和功能函数。底层函数基于 STM32F071VB 的标准外设库或 HAL 库实现,这里我展示的是基于 HAL 的简化写法。
#define PMIC_I2C_ADDR (0x38 << 1) // 7-bit地址左移1位 #define PMIC_I2C_TIMEOUT 100 static HAL_StatusTypeDef pmic_write_reg(uint8_t reg, uint8_t val) { return HAL_I2C_Mem_Write(&hi2c1, PMIC_I2C_ADDR, reg, I2C_MEMSIZE_8BIT, &val, 1, PMIC_I2C_TIMEOUT); } static HAL_StatusTypeDef pmic_read_reg(uint8_t reg, uint8_t *val) { return HAL_I2C_Mem_Read(&hi2c1, PMIC_I2C_ADDR, reg, I2C_MEMSIZE_8BIT, val, 1, PMIC_I2C_TIMEOUT); }调用 HAL 的 Mem_Write/Mem_Read 函数时,芯片会自动发送寄存器地址,简单可靠。但在产品级代码里,不能只看返回值,还需要做寄存器回读校验。特别是 PMIC 这种对电源配置影响极大的器件,读回校验能及时发现总线写错位或芯片正处于复位状态的问题。我封装了一层“写后读回”的接口,只有回读值与写入值一致才认为操作成功,如果连续三次失败,就调用错误处理函数,把设备切换到安全模式并上报。
3.3 初始化序列样例
初始化顺序很讲究。我的完整序列是:先读设备 ID,确认访问到的是 PCA9422;然后将所有中断屏蔽位设置为 1,避免初始化过程中产生意外中断;再依次配置充电参数、主输出参数、LDO 参数、中断使能;最后读取状态寄存器,确认无异常告警,最后才打开 PMIC 的主输出使能。下面给出一段关键初始化代码,实际寄存器地址已根据常见芯片结构做了抽象,具体数值应以你手上的 PCA9422 数据手册为准。
void pmic_init(void) { uint8_t id = 0; if (pmic_read_reg(PMIC_REG_DEVICE_ID, &id) != HAL_OK) { system_safe_stop(); return; } // 先屏蔽所有中断,防止初始化期间误触发 pmic_write_reg(PMIC_REG_INT_MASK, 0xFF); // 充电参数:目标电压4.2V,恒流300mA,截止30mA pmic_write_reg(PMIC_REG_CHG_CTRL, (CHG_TARGET_4V2 | CHG_ICURRENT_300MA | CHG_TTERM_30MA)); // 主输出:默认3.3V,使能 pmic_write_reg(PMIC_REG_MAIN_OUT, (MAIN_MODE_BUCKBOOST | MAIN_VOLT_3V3 | MAIN_ENABLE)); // LDO:2.8V用于模拟前端,使能 pmic_write_reg(PMIC_REG_LDO_OUT, (LDO_VOLT_2V8 | LDO_ENABLE)); // 打开需要的中断:充电完成、充电错误、电源插入、输出欠压 pmic_write_reg(PMIC_REG_INT_MASK, ~(INT_CHG_DONE | INT_CHG_ERROR | INT_POWER_INSERT | INT_UV_FAULT)); // 启动后延时,等待电压稳定 HAL_Delay(5); pmic_read_reg(PMIC_REG_STATUS, &status); if ((status & STATUS_UV_FAULT) != 0) { system_safe_stop(); } }初始化后我习惯打印或记录一次所有关键寄存器的值,作为后续故障排查的基线。如果设备运行一段时间异常,我可以通过通信接口把当前寄存器全部 dump 出来,和基线对比,往往能很快定位是哪个环路的保护被触发。
3.4 动态电压调节与状态读取
动态电压调节是这个系统的核心功能之一。一个典型场景是:设备进入休眠模式前,MCU 先把传感器电源关掉,把主输出从 3.3V 降到 1.8V,然后 MCU 进入 Stop 模式。等到唤醒事件通过外部中断触发时,MCU 先恢复 PMIC 主输出到 3.3V,再开启传感器电源,故意延迟大约 2ms 等电压稳定,然后才开始采集数据。
动态电压调节的代码不能只是“写一个寄存器”,还要处理切换后的确认。下面是我封装的函数:
int pmic_set_main_voltage(uint8_t target_volt_sel) { uint8_t cur_ctrl = 0; uint8_t status = 0; pmic_read_reg(PMIC_REG_MAIN_OUT, &cur_ctrl); cur_ctrl = (cur_ctrl & ~MAIN_VOLT_MASK) | target_volt_sel; pmic_write_reg(PMIC_REG_MAIN_OUT, cur_ctrl); // 等待输出稳定,并检查电源良好标志 for (int i = 0; i < 10; i++) { HAL_Delay(1); pmic_read_reg(PMIC_REG_STATUS, &status); if ((status & STATUS_PWR_GOOD) != 0 && (status & STATUS_UV_FAULT) == 0) { return 0; } } return -1; }这里的 STATUS_PWR_GOOD 标志非常有用。只有它置位,才能说明当前输出已经稳定存在于目标电压范围内。有些 PMIC 在用 I2C 切换电压时,如果负载是纯容性或者大动态负载,会在切换瞬间陷入欠压保护,然后重新软启动,整个过程可能达到几毫秒。软件里不能只做“写完就算成功”,否则后面模块直接工作就会吃到不稳定的电压。
状态读取方面,我不仅读取电压相关寄存器,还会周期性检查充电状态和电池电压。不过要注意,电池电压通过 PMIC 内部 ADC 读到的值会受负载影响。如果设备正在发射射频或驱动电机,电池电压读数会瞬间掉下去,这不能直接作为低电量的判断依据。我在代码里对 ADC 采样做多次平均,并且在采样前停掉大电流外设,或者采样时标记为“非稳态”,避免误报警。
4. 完整电源管理策略与低功耗联动
4.1 充电管理:恒流/恒压/截止电流的设置
充电管理部分看起来简单,实际上有若干细节。PCA9422 内部充电回路包含三个状态:涓流充电、恒流充电、恒压充电。当电池电压很低,比如低于 2.9V 时,会先以较小电流充到安全电压,再进入恒流阶段;恒流阶段电流固定为设定值;电压达到目标值后转入恒压阶段,电流逐渐下降,直到低于截止电流,充电完成。
我在初始化代码里设定恒流 300mA,目标电压 4.2V,截止电流 30mA。使用中发现一个问题:在涓流状态下,充电电流会明显低于设定值,此时如果软件只看“充电状态”标志,无法区分“正在涓流充电”和“充电异常电流太小”。幸好 PMIC 有独立的充电阶段寄存器,我通过读取该寄存器判断当前处于哪个阶段。调试充电逻辑时,我在日志里把充电阶段、电流设定、电压目标都打出来,观察设备从空电到满电的完整曲线。
另外,充电完成会让 PMIC 停止充电,但电池电压会自然回落,之后 PMIC 不会自动重新充电,除非有新的插入事件或寄存器重新使能。这会导致一个现象:设备插着充电器,电池充到 4.2V 后,电压慢慢回落到 4.15V,但 PMIC 依然显示充电完成,不补电。我在应用层做了处理:周期性读取电池电压,如果发现电压低于 4.1V 且充电完成标志存在,就重新触发一次充电启动,这样能保证设备即使在长时间插电状态下也能保持接近满电。当然这会多消耗一点电量,项目里我把它设成 10 分钟检查一次。
4.2 系统级电源状态机
电源管理不是只靠 PMIC 寄存器堆砌,它需要一套清晰的状态机。我的设备定义了几个状态:运行、空闲、休眠、充电中、故障。状态转换的触发条件由 MCU 判断,但每个状态对应的 PMIC 配置是确定的。我用一个表来管理状态与 PMIC 设置的对应关系。
| 系统状态 | 主输出 | LDO | 传感器电 | 充电使能 | MCU功耗模式 |
|---|---|---|---|---|---|
| 运行 | 3.3V | 2.8V | 开 | 按需 | 运行 |
| 空闲 | 3.3V | 2.8V | 开 | 按需 | Sleep |
| 休眠 | 1.8V | 关 | 关 | 关 | Stop |
| 关断 | 0V | 关 | 关 | 关 | 掉电 |
状态转换的核心原则是“先供电后负载,先卸载后断电”。从运行切到休眠时,MCU 会先停止传感器采集,关掉传感器电源,再把主输出降到 1.8V,最后才执行低功耗指令。反过来从休眠唤醒时,先恢复主输出到 3.3V,再开传感器电源,等 5ms 后再开始采集。这个顺序不能反,否则传感器在上电瞬间如果还没得到稳定的 VDD,可能会跑出非法状态或者灌电流。
状态机的实现里,我特意加了一个“转换中”状态。因为每次电源切换都有毫秒级延时,如果系统在这一瞬间收到新的外部事件,不能立刻进入新状态。我在转换中状态把新事件挂起,等转换完成后再执行。这样会比单纯按事件驱动可靠得多,也更容易排查问题。
4.3 与 STM32 低功耗模式的配合
STM32F071VB 的低功耗模式包括 Sleep、Stop、Standby。Stop 模式配合 RTC 唤醒或外部中断唤醒,是我这个设备休眠的主要方式。把 PMIC 主输出降到 1.8V 后,MCU 在 1.8V 下运行频率会受限,但 Stop 模式本身不依赖主频,所以可以正常进入和唤醒。唤醒后,MCU 的第一段代码就会调用 pmic_set_main_voltage 把电压调回 3.3V,然后等待 PWR_GOOD 再恢复外设。
这里有一个容易踩的坑:有些低功耗模式会关闭 I2C 外设时钟,所以你在唤醒中断里直接调用 I2C 写函数会卡死。我的做法是:在进入 Stop 模式之前,把 PMIC 设置成休眠态的固定配置;唤醒后执行的第一个代码块里,先重新初始化 I2C 时钟,再操作 PMIC,最后恢复应用逻辑。另外,由于 PMIC 的 INT 引脚在充电完成或故障时会产生下降沿,而 MCU 在 Stop 模式下只有特定引脚能唤醒,我确认过 PB1 的 EXTI 通道在 Stop 模式下有效,这样充电完成事件也能直接唤醒 MCU 去更新状态标志,而不是一直靠 RTC 轮询。
还有一点需要提醒:不要在 PMIC 输出电压还没稳定时就让 MCU 进入低功耗。PMIC 在电压切换的瞬间可能有瞬态电流,如果 MCU 正处于 Stop 模式且内部 LDO 在临界状态,偶尔会出现唤醒失败。我的经验是,在状态转换的最后一个步骤加入“PWR_GOOD 读回 + 短延时”的确认,确认无误后才执行 WFI 或者 PWR_EnterSTOPMode 指令。
4.4 电量计量与电压补偿
PCA9422 不内置库仑计,所以电量估计主要靠电压查表。我通过 PMIC 的 ADC 读电池电压,然后映射到 SOC 百分比。但这个映射不能简单用一个固定表格,因为电池放电曲线受温度、负载和老化影响。我在项目里做了一个实用的两段式补偿:第一段补偿负载补偿,根据当前系统状态查表加上一个“负载压降”修正值;第二段补偿温度,采用一个简单的线性系数,低温时降低电压阈值。
这套方案虽然没有库仑计精确,但对于我的传感器节点来说足够了。关键是不要把电量判断放在中断里做,因为中断频繁唤醒会显著增加平均功耗。我把电量评估放在系统从空闲切到休眠之前执行一次,其余时间只是用 PMIC 的电压中断做粗略的低压告警。
关于低压告警,我给 PMIC 配置了输出欠压阈值和输入电池低压阈值。当电池电压低于 3.1V 时,PMIC 会产生一个中断,MCU 收到后把系统切到低功耗模式,并关闭充电以外的高耗电功能。如果电压继续跌到 2.9V,PMIC 的硬件保护会直接切断主输出,此时 MCU 可能直接掉电,数据来不及保存。为了应对这个情况,我在硬件上加了超级电容或大容量储能电容,MCU 在收到 3.1V 告警时立刻把关键数据写入备份寄存器,这样即使随后主输出被切断,数据也不会丢。
5. 调试实录:常见问题与排查方法
5.1 I2C 通信不稳定的罪魁祸首
这个项目里我遇到过三次 I2C 通信失败。第一次是上拉电阻太大,10k 上拉在 400kbps 下上升沿太慢,偶尔读写超时。第二次是 SDA/SCL 走线太长,跨过了电源区域,产生了干扰。第三次最隐蔽,是 PMIC 的 INT 引脚在触发中断时,如果 MCU 没有及时读取中断寄存器,INT 引脚会一直拉低,而我在其他引脚上误把该端口配成了 I2C 复用,两者冲突导致总线错乱。
排查 I2C 问题我的思路是先看波形。用示波器抓 SCL 和 SDA,确认起始条件、地址字节、寄存器地址字节都有正确的 ACK。如果地址字节没 ACK,就先查板子上的 I2C 地址和供电;如果 ACK 有但后续数据 NACK,大概率是寄存器地址越界或芯片处于保护状态。也可以把 I2C 速率临时降到 100kbps,很多时候把速率降低后波形会恢复正常,这能快速分辨是电阻问题还是干扰问题。
另外要记得给 I2C 加超时保护。MCU 的硬件 I2C 在某些异常情况下会锁住总线,我的代码在调用 HAL_I2C_Mem_Write 之后检查返回值,超时后先复位 I2C 外设,再尝试重新初始化。如果复位外设还失败,那就执行一次 PMIC 的硬件复位,通过控制使能脚断电重启。
5.2 输出电压纹波异常
电源调试时,示波器上最容易看到的是输出电压纹波。我的设备运行模式电流 120mA,理论上 PMIC 的输出纹波应该很小,但实际测量时发现 3.3V 上有大约 60mV 的纹波,频率接近开关频率。排查过程是:先检查电感值是否合适,如果是升降压拓扑,电感值太大会导致动态响应慢,太小则电流纹波大;再检查输出电容容量和 ESR,我最初只放了一颗 22uF 陶瓷电容,后来并联了一颗 10uF 和一颗 100nF 高频电容,纹波立刻降到 20mV 以下。
还有一个很意外的因素:PMIC 的反馈采样点位置。PCB 上输出走线先经过了一个过孔再回到 PMIC,反馈路径过长导致负载瞬态时采样到错误电压,进而影响占空比。我后来把反馈采样电容直接放在 PMIC 输出电压引脚附近,把负载电源线从电容远端引出,纹波和瞬态响应都好了很多。如果你遇到类似的纹波问题,请先按“输出电容足够、反馈采样靠近芯片、地回路尽量短”三件事排查。
5.3 充电电流达不到设定值
我设置恒流 300mA,实际用电流表量只有 180mA。我一开始怀疑芯片有问题,后来发现是 PCB 上电池检测电阻引起的压降。PMIC 通过外部电阻或内部 FET 来检测充电电流,如果外部电流检测电阻阻值偏大,那么反馈回路会认为电流已经很大,从而降低充电电流。我的板子上电池和 PMIC 之间串联了一颗 0.2 欧的防护电阻,这颗电阻上会多产生压降,导致 PMIC 输入电压降低,充电电流自然上不去。
解决办法有两个:一个是减小串联电阻值,另一个是在软件里把恒流档位相应调大一点来补偿。我最终选择了前者,把串联电阻换成了 0.05 欧,并且保证 PCB 上的电流检测点尽量贴近电池端子,避免走线电阻也被算进采样回路。另外,不要在电池温度较低时强行设置大的充电电流,锂电池低温充电有安全风险,PMIC 通常会提供温度保护引脚,我把热敏电阻接好后,低温时芯片会自动降低充电电流,这是硬件保护,软件不需要干预。
5.4 复位与掉电时序问题
MCU 和 PMIC 的上电时序如果处理不好,会出现“MCU 已经跑起来了但 PMIC 还没准备好”的情况。我在调试中发现,STM32 复位之后访问 PMIC,设备 ID 读回来是 0xFF 或者 I2C 总线一直忙,就是因为 MCU 上电比 PMIC 快。PMIC 内部的 LDO 和参考电压需要一点启动时间,在启动期间 I2C 接口可能还没准备好。
解决方式是在 MCU 主函数最开始加入一个电源稳定等待循环,占用时间约为 20ms。不要只用 HAL_Delay,还要同时轮询 PMIC 设备 ID 寄存器,只有读到正确 ID 才继续初始化。如果等太久读不到,就进入故障模式,避免在一个不完整的电源环境下执行复杂逻辑。
另外,系统掉电时也要注意。当电池电压快速下降到保护阈值,PMIC 会切断主输出,此时 MCU 可能正在执行 Flash 写操作,这会很危险。我的设计是,把关键数据的保存触发条件放在电池低压中断里,而不是放在主循环里检查。低压中断一旦触发,MCU 立即进入一个精简保存流程,禁止一切高耗时操作,保存完就进入低功耗等待,不允许再写 Flash。这个流程我在多次掉电测试中没有出现过数据损坏。
5.5 调试工具与调试流程
最后说下我实际使用的调试工具。示波器必须有,四通道最佳,一个通道抓电池电压,一个通道抓主输出 3.3V,一个通道抓 PMIC 的 INT 引脚,另一个通道抓 MCU 的 GPIO 事件,这样可以完整观测电源事件和软件事件的对应关系。逻辑分析仪在调 I2C 时序时很有用,能解析写入的每笔寄存器地址和值,特别是在排查“我明明写的是 0x03,芯片状态却是 0x00”这类问题时,逻辑分析仪的记录是铁证。
我还用了一个 I2C 转 USB 工具,把它并联在总线上,通过上位机软件实时读写 PMIC 寄存器。这样可以不用改固件就能临时修改配置,尤其在现场调参时非常方便。但要注意,调试工具并联期间会引入额外电容,在高频下可能影响总线波形,所以我调试时把 I2C 速率降到 100kbps,等调完再改回 400kbps。
开发过程中我养成了一个习惯:在工程里保留一个调试信息接口,通过 UART 把 PMIC 状态机的变化、关键寄存器读回值、中断标志都打印出来。一开始担心 UART 打印会影响时序,实际发现只要放在状态转换完成之后,影响很小。这些日志在最后整机测试时帮了大忙,比如发现休眠唤醒偶发出问题,回看日志发现是某次 PMIC 电压切换后 PWR_GOOD 迟迟没置位,从而锁定了是输出电容充放电时间的问题。
这个项目做完,我的体会是:PCA9422 加 STM32F071VB 这套组合最大的价值不在于某一颗芯片多强,而在于把电源管理这个容易失控的环节变成了一个可读可写的系统。你用 I2C 能拿到所有电源状态,能用寄存器控制所有电压轨,再加上几个中断引脚,MCU 就能在极低功耗下把电源管得明明白白。最后分享一个小技巧:在生产测试阶段,可以写一段简单的测试固件,只初始化 PMIC,然后依次切换每一档输出电压,同时用 ADC 去读回实际电压,判断与目标值的偏差是否在允许范围内。这个测试五分钟就能跑一遍,比人工用万用表点几十个测试点要高效得多,而且能自动生成测试报告。如果你也想做类似的项目,建议从一开始就把这段测试固件考虑进去,后面量产会省掉大量重复劳动。