做电池供电的设备,最难的不是把电压稳住,而是把“电”算明白。去年我搭一套低功耗采集终端,MCU选了 STM32F091RC,电源侧一开始图省事,直接用一颗 DCDC 加一颗 LDO 拼,结果充电逻辑、动态调压、断电保护这些全得自己画,代码越写越重。最后换了集成 PMIC 的方案,用 PCA9422 搭配 STM32F091RC,一套下来才把电源管理这件事理顺。
这篇文章就把这套方案的完整链路梳理一遍:为什么选这两颗料、PCA9422 内部架构怎么理解、硬件上要注意哪些布局细节、固件端初始化到动态调压怎么写,最后是调试中让我折腾很久的几个真实问题。写出来的东西适合两类人看:正在给电池设备选型 PMIC 的硬件工程师,以及想把低功耗 MCU 电源逻辑做扎实的固件开发。内容偏工程实操,不摆原理堆,每个坑都标注了我当时的处理方式。
1. 为什么选 PCA9422:拼板方案和集成 PMIC 的差距比想象中大
1.1 常见拼板方案的真实痛点
很多低功耗项目起步时都这样:MCU 选好,电源这边按“线性充电 IC + 同步 Buck + LDO”三件套来拼。小批量做样机没问题,一上量问题就来了——BOM 数量变多,每个物料都要自己的 datasheet、封装、layout 规则,电源轨之间的上下电时序必须靠 MCU GPIO 软件控制,一旦代码里时序没写对,轻则外设初始化失败,重则 MCU 都起不来。
更烦的是动态调压。低功耗设备经常需要“干活时把电压拉高,闲着时把电压降下来”,拼板方案里每个 DCDC 的反馈电阻是死的,想调电压只能换电阻,根本没法和 MCU 联动。我当时的做法是用一个数字电位器去搭可调反馈电阻,结果电位器本身精度和温漂就够喝一壶,测下来整条电源轨的纹波和稳定性都不理想。
还有电池充电管理。锂聚合物电池充电不是“给个恒压就行”,预充、恒流、恒压、截止、温度保护、充电完成判定,这些状态机自己用分立器件搭非常痛苦,而且充电电流采样、NTC 检测全都要额外电路。
1.2 集成 PMIC 的核心价值
PCA9422 这类集成 PMIC 解决的就是上面这些事。一颗芯片里做好了几件事:输入路径管理、锂电充电控制、多路 DCDC/LDO 输出、I2C 动态调压、NTC 保护、各种过压过流标志。MCU 只需要通过 I2C 去配置它,剩下的硬件状态机由 PMIC 自己处理。
用一颗 PMIC 替换“线性充电 + 双路 DCDC + LDO + 数字电位器”的方案,BOM 数量大幅减少,layout 面积也小一圈。对便携设备来说,这两点比单个器件的性价比更关键。
我实际对比过两种方案的启动行为:拼板方案在电池电压偏低时,MCU 上电波形有明显台阶;换 PCA9422 之后,只要系统输出轨起来,供电就很干脆。原因在于 PMIC 内部的充电路径和放电路径切换是硬件自动完成的,不依赖 MCU 这个下游“还没醒”的器件。硬件接管关键时序,这是它最大的价值。
1.3 为什么搭配 STM32F091RC
STM32F091RC 在 F0 系列里属于配置比较全的一颗,Cortex-M0+ 内核,48MHz 主频,256KB Flash,32KB SRAM,LQFP64 封装,外设齐全,尤其是 I2C 外设支持快速模式 Plus,总线速率可以跑到 1MHz。做电源管理的从机控制端,这个外设能力完全够用,实际配置 PMIC 时我甚至只用 400kHz。
选它而不是大功率 MCU 的核心原因是低功耗表现。F0 系列是 M0+ 内核,支持 Sleep、Stop、Standby 多种低功耗模式,Stop 模式下微安级电流对电池设备非常重要。配上一颗能通过外部中断把 MCU 从 Stop 模式拉醒的 PMIC,整套休眠唤醒链路就闭环了。另外 256KB Flash 对完整电源管理固件、按键扫描、传感器采集、通信协议栈来说也绰绰有余,不会出现“写一半没地方放”的尴尬。
用一张表直接看拼板方案和集成方案的差异:
| 对比项 | 拼板方案(充电IC+DC-DC+LDO) | 集成PMIC方案(PCA9422) |
|---|---|---|
| BOM数量 | 多,供电链路分散 | 少,一颗芯片覆盖 |
| 上下电时序 | MCU软件控制,容易出Bug | 硬件自动处理 |
| 动态调压 | 需额外电路,精度差 | I2C寄存器直接调 |
| 充电状态机 | 自己搭或靠充电IC简化实现 | 内置完整充电逻辑 |
| 故障检测 | 分散在各器件 | 统一中断标志 |
| 低功耗联动 | 需外部逻辑配合 | PMIC中断直接唤醒MCU |
2. 先把 PCA9422 的电源通道摸清楚,再上板不迟
2.1 充电路径和放电路径的自动切换逻辑
PCA9422 这类 PMIC 的输入端通常分两条路:一条来自 USB/适配器的 VBUS,一条来自电池 BAT。芯片内部会自动判断优先级——VBUS 在时,优先让适配器给系统供电,同时给电池充电;VBUS 拿掉后,自动切到电池路径,整个过程不需要 MCU 干预。
我刚开始有个误解,以为系统供电在两种模式下电压会跳变。实际设计里,PMIC 的输出是“系统总线”轨,VBUS 和 BAT 之间的切换是内部 FET 完成的,切换时输出轨不会瞬间掉到零,而是有一个很短的过渡。这个过渡时间虽然短,但如果下游负载对电压敏感,还是要靠输出电容扛一下。我在这套设计里输出端放了两颗 22uF 陶瓷电容加一颗 47uF 钽电容,实测切换时输出轨的毛刺能控制在几十毫秒以内。具体容量建议按负载瞬态要求去算,不是越大越好,大电容会拖慢 DVS 调压的响应速度。
这里要特别提醒:电池充电过程不是简单的“插上就充”,它分预充、恒流、恒压几个阶段,不同阶段的电流电压限制都不一样。PMIC 内部有这个状态机,MCU 要做的只是把参数配好,然后读状态寄存器判断当前处于哪个阶段。千万不要在固件里自己去重复实现充电判断逻辑,那是给自己挖坑。
2.2 I2C 从机地址与中断机制
PCA9422 的 I2C 从机地址由外部引脚电平决定,通常有一两个地址配置引脚,接高接低组合出几个可用地址,具体那几个地址值要以原厂数据手册的寄存器映射表为准。设计原理图时我直接把地址配置引脚用电阻固定到确定电平,不让它在运行时浮动,这样固件里地址宏写死即可,省得每次上电还要探测。
这里必须提一个 I2C 地址的经典陷阱:数据手册可能同时给出 7 位地址和 8 位写地址两种格式。7 位地址比如 0x35,那么 I2C 发送时左移一位变成 0x6A,低字节加写位。很多人在传输函数里直接填手册上的 8 位地址,结果左移后完全对不上,总线一直 NACK。我建议工程里统一用 7 位地址宏,代码中左移交给传输函数处理,别把两种格式混在一个工程里。
中断脚 INTB 也值得说。它是开漏输出,低有效,任何重要事件都会拉低。我把它接到 STM32F091RC 的一个通用 IO 上并启用外部中断,这样 MCU 在 Stop 模式下也能被 PMIC 拉醒。实际验证下来这个路径非常可靠,前提是 INTB 外部必须有上拉电阻,开漏输出不带上拉是拉不起高电平的,我第一次测试时差点以为是芯片坏了。
2.3 各路输出与保护功能
PCA9422 的输出轨数量、每路能否调节,都要以你实际拿到的物料版本和手册为准,我以当时项目用的功能配置来讲。芯片可以提供多路输出,其中有支持 I2C 动态调压的轨道(数据手册里通常叫 DVS 轨,Dynamic Voltage Scaling),也有固定输出的 LDO 轨。
动态调压轨的意义在哪里?拿一个简单的户外传感器节点举例,主控在 48MHz 跑满时,外设可能需要 3.3V 逻辑电平;进入低功耗待机后,外设关掉,逻辑轨再维持 3.3V 就是纯浪费。如果某个负载本身可以工作在更低的电压域,就可以在固件里把它切到 1.8V 甚至更低,这部分功耗直接按电压线性降,省下的电流相当可观。
充放电保护和温度检测也是 PMIC 的强项。电池端通常接一个 NTC 热敏电阻,PMIC 通过检测温度自动暂停充电或降低充电电流,防止电池在高温环境里被大电流充爆。我一开始把 NTC 布在板边,离充电回路比较远,结果测到的温度比实际电池温度低不少;后来把 NTC 焊盘尽量贴近电池连接器放在同一块覆铜区域,温度回读才正常。做低功耗设备的人容易忽略这层,但它在实际项目里很关键。
3. STM32F091RC 侧硬件设计:供电树、I2C 接法和 PCB 避雷区
3.1 供电树怎么搭:一轨一轨理清楚
上板之前,先把整块板的供电树画出来。拿我那个采集终端举例,电池就是单节锂聚合物,标称 3.7V,满电 4.2V,容量 1200mAh;输入是 USB 5V。PCA9422 的系统输出轨作为主电源,直接给 STM32F091RC 和板上的主要负载供电,我配置成 3.3V;一路 DVS 输出给传感器数字逻辑,设定为 1.8V;另一路动态可调轨留给需要运行中调压的外设模块,档位由固件按工作负载切换。
| 电源轨 | 电压 | 负载 | 说明 |
|---|---|---|---|
| VOUT_SYS | 3.3V | STM32F091RC VDD、VDDA、接口电平 | 主电源轨 |
| DVS1 | 1.8V | 传感器数字端、板上逻辑芯片 | 固定低压轨 |
| DVS2 | 动态0.9~1.8V | 可调压外设模块 | 运行中I2C切换 |
| LDO输出 | 3.3V/模拟用 | 模拟前端或基准 | 按需分配 |
STM32F091RC 的 VDD 和 VDDA 我建议从同一条主电源轨取,中间加磁珠或小电感隔离模拟噪声,VREF+ 直接接到 VDDA。F091 的 VDD 范围是 2.0V 到 3.6V,3.3V 是非常标准的选择。每个电源引脚旁边都放一颗 100nF 去耦电容,靠近引脚放置,这是 MCU 稳定运行的底线。
整个系统的工作电流分布:MCU 跑满时大概几十毫安,传感器工作瞬间可能有几十毫安脉冲,通信模块瞬间电流最大,能到几百毫安甚至更高。PMIC 的每路输出到底能带多少电流,要看手册的带载能力表,尤其要注意 DVS 低压轨,电压越低能带的电流通常越小,选型时把这个账先算明白。
3.2 I2C 上拉电阻和中断引脚的连接细节
PCA9422 的 I2C 从机接口和 STM32 的 I2C 主机接口连接看起来很简单,SCL/SDA 对上就行,但有几个细节决定稳定性。
第一是电平域。一定要查数据手册里 PMIC 的 I2C 引脚高电平阈值,确保它和 MCU 的 3.3V 逻辑电压兼容。很多 PMIC 的电平参考是系统输出轨,系统轨设 3.3V,两边都是 3.3V 域,那就不用加电平转换。如果发现 PMIC 的电平域比 MCU 低,SDA/SCL 都要加电平转换电路,不能直接拉一条线过去。我建议大家在原理图阶段就确认这一步,别等板子回来再补救。
第二是上拉电阻。I2C 是开漏总线,SCL/SDA 都必须有上拉。上拉阻值的选择决定边沿上升时间和功耗的平衡:阻值太大,总线电容在 400kHz 下充电慢,波形边沿圆滑,容易误码;阻值太小,漏极电流过大,低电平容易被拉过高。我这边总线负载大概三四个器件,用 2.2k 上拉到 3.3V,400kHz 下波形非常干净。做高速总线或者总线电容特别大的时候,可以用公式估算最大上拉电阻,根据总线的上升时间要求去算。
第三是中断引脚配置。INTB 开漏输出,我在原理图上给它也加了 10k 上拉,接到 MCU 的通用输入端,并启用下拉内部触发沿。启动阶段如果 PMIC 还没完成初始化,INTB 可能一直处于低电平,这个状态如果不是真正的故障事件,要注意在固件里做去抖和初始化屏蔽,否则 MCU 一启动就误入中断处理。
3.3 PCB 布局:DC-DC 开关节点的规矩不能破
PCA9422 内部包含开关电源,这就决定了 PCB 布局必须遵守开关电源的规矩。开关节点(SW)是高频方波,它的环路面积越大,辐射越强纹波越差。电感应尽量靠近 PMIC 的 SW 引脚,电感到输出电容的走线要短而宽,输出电容再回到 PMIC 的地,这段环路必须最小化。
我在 layout 时把 PMIC 摆在电池连接器旁边,USB 输入走线从一侧进,电池走线从另一侧出,目的就是把充电路径和放电路径的铜皮都做短。PMIC 的地是模拟地和功率地的混合,数据手册一般会给出参考布局,我按它的建议把功率地单独走一小段再和模拟地在底层单点汇合,实测纹波改善很明显。
MCU 和 PMIC 的走线关系也值得注意。I2C 总线属于低速信号,但不要把 SDA/SCL 走在电感或 SW 节点的正下方;电源开关噪声会耦合进信号线,导致通信偶发错误。整板我只用了一个完整地平面,各电源轨的过孔都打在对应芯片的附近,没有让大电流横穿 MCU 区域。
还有一个细节是 NTC 热敏电阻的布局。NTC 要采的是电池温度,走线就一定要从电池连接器附近延伸出去,中间不要跨越功率器件、电感这些发热源;功率路径能绕开尽量绕开,否则温度读数会混杂板温,充电保护的判断就不准了。
4. 固件实现:I2C 初始化到动态调压的整条链路
4.1 按顺序初始化 PMIC,别跳步
固件端第一件事不是直接写各种寄存器,而是先验证 I2C 链路是否真的通了。我常用的办法是读 PMIC 的芯片 ID 寄存器,返回值和手册一致再继续,不一致就重试或者报错。这一步能挡住大批硬件问题:地址配错、上拉没焊、SDA 被拉死,全部会在这里体现。
验证通过后按这个顺序配置:先配充电参数,再配输出电压档位,最后使能各路输出。充电参数里要设的是充电电流、截止电压、充电终止阈值,这些值要和电池规格书匹配。我当时电池是 1200mAh,按 0.5C 取充电电流 600mA,截止电压 4.2V,终止电流设 100mA 左右。太小会延长充电时间,太大可能提前判断充满。
输出档位配置要等充电配置完成后再做,原因是避免在输出未稳定时去切换 DVS 档位。所有写操作我都做了回读校验:写完一个寄存器马上读回,和写入值不一致就重试三次,三次都失败就进入错误状态。这一步帮我挡过一次比较隐蔽的时序问题,后面排坑部分会说。
以下是一段基于 HAL 库的参考代码,实际工程里请把寄存器地址和值按数据手册替换:
/* PCA9422 I2C读写参考实现 */ #define PMIC_SLAVE_ADDR (0x00) /* 按实际ADDR引脚电平查手册填写 */ #define PMIC_REG_ID (0x00) /* 占位:ID寄存器 */ static void pmic_write_reg(uint8_t reg, uint8_t val) { uint8_t buf[2] = { reg, val }; HAL_StatusTypeDef ret = HAL_I2C_Master_Transmit(&hi2c1, PMIC_SLAVE_ADDR << 1, buf, 2, 200); if (ret != HAL_OK) { /* 记录并重试或报错 */ } } static uint8_t pmic_read_reg(uint8_t reg) { uint8_t val = 0; HAL_I2C_Master_Transmit(&hi2c1, PMIC_SLAVE_ADDR << 1, ®, 1, 200); HAL_I2C_Master_Receive(&hi2c1, PMIC_SLAVE_ADDR << 1, &val, 1, 200); return val; } static void pmic_write_reg_verify(uint8_t reg, uint8_t val) { pmic_write_reg(reg, val); for (int i = 0; i < 3; i++) { if (pmic_read_reg(reg) == val) return; pmic_write_reg(reg, val); } error_handler(ERROR_PMIC_WRITE_FAIL); } void pmic_init(void) { if (pmic_read_reg(PMIC_REG_ID) != PMIC_EXPECTED_ID) { error_handler(ERROR_PMIC_COMM); } pmic_write_reg_verify(PMIC_REG_CHARGE_CTRL, 0x00); /* 配置充电参数 */ pmic_write_reg_verify(PMIC_REG_DVS1, 0x00); /* 配置DVS1电压 */ pmic_write_reg_verify(PMIC_REG_DVS2, 0x00); /* 配置DVS2电压 */ /* 最后使能各路输出 */ pmic_write_reg_verify(PMIC_REG_ENABLE, 0x00); }4.2 状态回读和中断处理,电量的“仪表盘”
PMIC 上电之后不是放着不管,MCU 还要持续了解它的状态:当前是否在充电、充电是否完成、温度是不是过高、输入是否插拔、各路输出有没有过流保护动作。这些信息都在状态寄存器里,固件按需读取即可。不要轮询得太勤,状态寄存器读取本身就消耗总线时间,也占 MCU 运行时间。我是在关键事件转移时读取,比如检测到 INTB 中断后立刻读状态寄存器,把事件源搞清楚再决定后续动作。
INTB 中断的处理逻辑我建议做成事件循环:外部中断来了,置一个标志位;主循环里看到标志位,读状态寄存器,判断是充电完成还是温度保护还是输入插拔,然后分别处理。不要直接在中断服务函数里做复杂处理,I2C 通信本身有阻塞时间,放在中断里会卡住其他任务。
为了确保读到的状态是准的,我做了两个辅助手段:一是所有状态寄存器读取都连着读两次,只有两次完全一致才认为有效,避免 I2C 偶发噪声带来错误判断;二是读状态后延时一小段时间再返回,让 PMIC 内部的状态机稳定。听起来有点玄,但实际项目里这两个手段帮我挡掉过好几次假故障提示。
固件里我还会保留一份 PMIC 上次正常工作状态的镜像,一旦通信中断恢复,先用这一份镜像重新把关键寄存器灌一遍,让 PMIC 恢复到故障前的工作状态。这样做的好处是即使 PMIC 因为过流保护把所有输出关断了,MCU 重启后还能把系统“救回来”。
4.3 动态调压和低功耗联动:一个完整的切换流程
动态调压的真正价值要在系统层面体现出来。我的做法是把设备的工作状态分成几个档:全速运行态、浅睡眠态、深度休眠态。每个状态对应一套 DVS 电压配置。
进入浅睡眠时,先把 DVS2 从 1.8V 切到 1.2V,让可调压外设进入低压待机,然后再让 MCU 进入 Stop 模式。整个过程 I2C 只执行两条写指令,功耗却能降一大截。这里有一个顺序问题必须注意:一定要先把外设的电压降下来,再让 MCU 进入低功耗模式,反过来你睡下了外设还在高电压空转,等于没省。反过来唤醒时,MCU 先恢复时钟,再把 DVS2 拉回 1.8V 让外设跑满,顺序不能乱。
实际切换 DVS 电压时,PMIC 内部不是瞬间跳变,电压会以一个斜率过渡。这个过渡时间我测量过,大概在百微秒到几百微秒量级,具体看输出电容和负载。MCU 固件不要认为写了寄存器电压马上就到位,最好在切换后加一个短延时,或者通过读状态寄存器确认输出已稳定再继续。特别是如果 DVS 轨上有 ADC 采样,采样点最好避开电压过渡区,否则会采到中间电压的假数据。
void app_enter_low_power(void) { /* 先降外设电压,再睡MCU */ pmic_write_reg(PMIC_REG_DVS2, DVS2_1V2); HAL_Delay(5); /* 等电压过渡稳定 */ clear_pmic_pending_int(); disable_unused_pmic_irq(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); SystemClock_Config(); pmic_write_reg(PMIC_REG_DVS2, DVS2_1V8); HAL_Delay(5); }这套流程跑起来之后,整板不带屏不带无线模块时,Sleep 模式电流能压到微安级。低功耗数字本身不重要,重要的是“每个小项目的当前状态是否真的把该关的东西关了”。PMIC 和 MCU 的低功耗联动,本质上是一个状态机对另一个状态机的配合,两边都要把自己该做的顺序做对。
5. 实测与排坑:几个让我折腾很久的真实问题
5.1 I2C 总是不应答,问题出在地址格式上
这轮调试我印象最深,因为现象特别崩溃:PMIC 上电后第一次读 ID 就失败,SDA 没有 ACK。我以为芯片虚焊,重新风枪吹了一遍,还是不通;用逻辑分析仪抓 SDA/SCL,波形看起来完全正常,地址发送也符合 I2C 协议,但 PMIC 就是不响应。
后来我把示波器探头点到 PMIC 侧的 SDA 引脚和 MCU 侧的 SDA 引脚对比波形,发现两者高电平幅度差了 0.5V 左右。原来我把上拉电阻一端接在了系统输出电压轨上,系统轨在 PMIC 内部还没完全起来之前是低电平,导致总线上拉电压不足。解决办法是把 I2C 上拉单独接一个已稳定的 3.3V 电源轨,或者明确确认 PMIC 的系统输出轨在上电完成后才对外供电。
顺便说一句,I2C 被拉死的恢复手段也值得记一下:如果某个从机把 SDA 拉低不放,主机怎么发都发不出去,这时候可以手动切换 SCL 连续翻转 9 个周期,把总线状态机复位。我在调试时专门写过一个恢复函数,总线上出现异常超过一定时间就自动发 9 个脉冲,之后再重新初始化地址,这个手段救过我好几次。
5.2 DVS 切换瞬间 MCU 掉电复位,问题在负载电容和时序
发生在一个雨天测试:外设模块开始工作,我通过 I2C 把 DVS2 从 1.2V 拉回 1.8V,结果整板瞬间复位,还以为是 PMIC 过流触发了保护。用示波器看 DVS2 输出波形,发现电压上升过程中出现一个明显下凹,随后 MCU 的复位时电路也跟着波动了一下。
原因有两层:一是 DVS2 输出端的负载电容偏小,DVS 电压快速上升时,外设模块的大电容突然要充电,PMIC 输出端瞬时被拉低;二是 MCU 的复位引脚没有做足够的去耦,电源轨上这个下凹直接传到了复位输入。
处理办法分两步。硬件上我在 DVS2 输出端补了一颗 22uF 电容,同时把 MCU 复位脚对地加了一颗 100nF 电容。固件上把 DVS 切换后的延时从 2ms 提到 10ms,确保电压完全稳定再做后续操作。实际验证后,即使外设满负载切换也不复位了。
这里想提醒的是:DVS 切压不能当作“写个寄存器就结束”来对待,它本质是一个电源瞬态事件,输出电容、负载电流、切换斜率都会影响下游。做硬件方案时就要把输出端的储能电容按最大瞬态需求去估,别按最小规格来。
5.3 充电状态误判,NTC 位置导致的温度偏差
还有一次是充电状态反复跳动,充电器一会儿报“充电中”一会儿报“暂停充电”,我一开始怀疑 PMIC 芯片有问题,查了数据手册再量波形都正常。后来把电池连接器拔下来,用万用表量 NTC 引脚对地电阻,发现读到的阻值和我手边温度计的读数对不上,偏高不少。
问题就是 2.3 节提到的 NTC 布局。我把 NTC 放在了板边缘,离充电回路功率器件只有几毫米,板子在充电时局部温升明显,NTC 测到的并不是电池温度,而是“板温+电池温度”混合值。充电管理器一看到温度超过阈值,就暂停充电,温度降下来又恢复,于是反复跳。
解决起来也直接:把 NTC 焊盘移到电池连接器旁边,走线加粗,周围铺地隔离热源。同时我在固件里给温度阈值留了滞回区间,温度降到 45 度恢复充电、超过 55 度暂停充电,中间带死区,不会再因为温度抖动导致状态来回跳。NTC 这个细节看起来很小,但它是充电可靠性的重要一环。
5.4 进入低功耗后 PMIC 一直唤醒 MCU,互相拉扯
最后这个坑是低功耗联动的经典问题:我把 MCU 调到 Stop 模式之后,板子电流不但没降下来,反而比之前高。逐个排查发现 PMIC 的 INTB 引脚一直在触发,MCU 每次刚睡下就被拉醒,醒来读状态又发现不是关键事件,然后继续睡,再被拉醒,形成一个耗电循环。
用示波器抓 INTB 引脚,果然看到周期性的低脉冲。根因是进入 Stop 模式之前,我没有屏蔽那些“非关键”的 PMIC 中断源,比如输入电压的小幅波动、充电状态的细微变化,都会让 PMIC 拉低 INTB。MCU 在 Stop 模式下只要外部中断引脚有边沿就会被唤醒,于是反复跳。
解决方式是在进入 Stop 模式前,把 PMIC 中断使能寄存器配置成只有“真正需要唤醒”的事件才能触发中断,比如充电完成、输入插入/拔出、温度保护。其余所有状态轮询都放在唤醒后处理。这套做法改完,整个低功耗模式才算真正稳定。低功耗调试时,如果发现电流往下掉但始终掉不到预期值,先用示波器看所有可能触发唤醒的引脚,大概率有隐藏的“假唤醒”源头在捣乱。
回头再看这套 PCA9422 + STM32F091RC 方案,我觉得最值钱的不是某一个功能实现得多好,而是把电源管理从“MCU 外围一堆芯片靠软件硬撑”变成了“PMIC 负责硬件状态机,MCU 负责策略”。如果你也在做电池供电设备,我的建议是先把 PMIC 的输出通道和中断机制完全吃透,再写固件;把每个 DVS 切换都当成一次电源瞬态事件去设计,不要当成寄存器操作。这样整套方案跑下来会顺利很多。