首页 / 资讯中心 / 文章详情

基于PCA9422与STM32F303RC的低功耗电源管理设计实践

基于PCA9422与STM32F303RC的低功耗电源管理设计实践 ★ FEATURED ARTICLE
1. 项目背景为什么我会用 PCA9422 STM32F303RC 做电源管理上个月接了一台便携式工业检测仪表的电源改造要求不换主控、把待机功耗尽量压低同时把充电、放电保护、系统供电统一管理起来。这台设备的主控是 STM32F303RC以前电源部分是老的锂电充电管理芯片加上两个 LDO、一个比较器和一堆离散 MOSFET。改完以后我换了 PCA9422整套电源管理变简了不少这里把选型、硬件接线和软件状态机一起整理出来。1.1 从原来分立方案的三个痛点说起说实话老方案并不是不能用但我在这个项目里遇到了三个很难绕开的点。第一是待机功耗不可控。原来的充电管理芯片和两个 LDO 一直挂在线性路径上系统浅睡眠时整板电流依然有 2mA 左右。里面天然有固定工作电流没法只靠关 MCU 外设砍下来。电流表一夹就能看到占了大头的反而不是 CPU而是电源链自己消耗的一部分。第二是状态切换太碎。冷启动时主控要先等充电芯片准备好再检查 LDO 输出稳定还要用 GPIO 去拉高一路逻辑电源使能。每一步都要软件去等而且时间裕量还很有限一旦电压建立的时序偏掉系统就有概率卡在启动过程里。我第一次排查时录过波形5V 输入上升到 3.3V 系统电源稳定中间隔了接近 18ms而且有明显的电压台阶。第三是在低压告警这类逻辑上过于粗糙。原来用电阻分压加比较器实现电池低电压检测精度只能到 ±50mV。在这个项目里电池曲线在平台期比较平50mV 对应的剩余电量误差很大要么提前关机要么触发太晚。用 PCA9422 的监测和中断寄存器之后这个阈值可以通过 I2C 配置而且读取精度明显比模拟比较器稳定。1.2 PCA9422 的能力拆解PCA9422 是一颗面向低功耗便携设备的电源管理 IC适用于单节锂离子电池应用内部整合了充电回路、电源路径管理、多路输出稳压器和配置逻辑。从电路图上看它省掉的不只是两颗 LDO还省掉了独立充电芯片、电池电压检测电阻分压、甚至一部分负载开关电路。在具体使用前我先把它的能力拆成了四块充电管理适配单节锂电池支持恒流恒压充电充电电流和截止电压可以通过寄存器配置。电源路径外部适配器接入时可以直接给系统供电并给电池充电断开适配器时自动切换到电池供电。输出电源轨芯片内部集成多路线性稳压器/低压差输出可供一路 3.3V 主电源和若干辅助电源。管理与状态输出通过 I2C 配置寄存器可实时读取充电状态、电压和温度状态中断输出引脚可以上报充电完成、电池电压低、过温等事件。这些话不是我拍脑袋写的而是最后逐个确认以后的分类。实际应用里真正需要的可能并不是它的每一路而是它的“可配置默认行为”和“状态上报能力”。比如系统启动时MCU 还没开始跑输出必须靠 PMIC 的默认配置先保持稳定主控起来以后再通过 I2C 改参数接管整个电源策略。1.3 保留 STM32F303RC 的理由很多人第一反应是做低功耗为什么不用超低功耗 MCU非要保留 STM32F303RC这台仪表现在已经有成熟的上层算法和 PWM 控制逻辑整机替换评估成本和风险都大。F303 最大的问题是自身功耗相对偏高但它有一个好处片内集成多个 ADC、比较器和运放用于采集电池电压、系统电流非常顺手。更关键的是完整的电源管理不一定要“所有器件都超低功耗”而是要有一个清晰的管理框架。把 PMIC 的低功耗模式和 F303 的 STOP 模式配合好系统可以从运行态的几十毫安降到休眠态的几百微安而不是靠换 CPU 来解决一切。对便携设备来说休眠时 PMIC 能否把不用的电源轨关掉、MCU 能否快速被中断唤醒往往比单纯压低 CPU 待机电流更实际。从项目推进角度说保留原有主控缩短了总体开发周期也降低了既有代码的风险尤其是那些对时序敏感的 PWM 模块完全不需要动。我只把电源核心替换掉整体风险反而可控。2. 硬件电路电池、PCA9422、主控三者怎么接拿到 PCA9422 之后我先没有铺 PCB而是把电源域重新画了一遍。这类 PMIC 接线看上去简单实际上布线布局决定后面的测量结果和稳定性。如果你也想用这颗芯片最好先在纸面上把“电池、PMIC、主控、外设负载”的关系理清。2.1 电源域划分与拓扑我最后定的拓扑是这样的电池和外部 5V 适配器都接到 PMIC 的输入侧PMIC 输出侧给出两个主要电源域和一个模拟电源域。主控 STM32F303RC 的 VDDA、VDD 和 VSS 引脚分开供电数字主电源用 3.3V模拟部分用一路独立 3.3V中间用电感和电容组成的简单滤波隔离。电源域具体划分如下表电源域电压主要负载备注VDD_SYS3.3VSTM32F303RC 数字部分及小功率外设PMIC 主输出VDD_ANA3.3VADC 参考、模拟前端加磁珠和去耦电容隔离VDD_AUX1.8V传感器和接口逻辑可通过寄存器控制开关VBAT_RAW电池电压断电保持电路、RTC经跳线接至主控 VBAT这里一个关键点是主控的复位引脚和启动引脚。复位不能直接挂在 PMIC 的电源良好信号上草草了事后面我会单独说。还有如果要测整板最低待机电流必须把 VBAT_RAW 到 MCU VBAT 的电流路径规划设计好否则休眠时电池会被主控内部 RTC 域一直消耗而这个电流又未必在 PMIC 的管理范围内。2.2 和 STM32F303RC 的接口与信号PCA9422 与 STM32F303RC 之间真正需要连接的信号数量并不多I2C 和中断几乎就够用但我还是加了几根控制线。STM32F303RC 引脚方向连接对象作用PB8/I2C1_SCL输出/开漏PCA9422 SCLI2C 时钟PB9/I2C1_SDA双向/开漏PCA9422 SDAI2C 数据PA0 或任意 EXTI 引脚输入PCA9422 INT中断信号触发状态读取PH3 或普通 GPIO输出PCA9422 使能/复位控制 PMIC 的软复位I2C 总线注意两点。一是速度我的设计里用了 400kHz 快速模式总线长度不超过几厘米这个速度没有任何问题。二是上拉电阻PMIC 的 I2C 引脚需要上拉至和主控一致的电平。我是把 PCA9422 的逻辑电源取自主控 3.3V 域上拉电阻选用 2.2kΩ 左右实测波形边沿够陡没有出现失真。中断引脚是完整电源管理的主力通道比如充电完成、低电压告警、过温这些事件优先用中断从休眠中唤醒主控而不是让主控一直轮询。我在 STM32F303RC 上把中断脚接到 PA0配置为 EXTI0上升沿和下降沿都要触发。PMIC 的中断行为通常分两组边沿触发后读取状态寄存器会清除中断标志这一点后面在软件部分会给出流程。2.3 电源 PCB 布局的几个细节硬件上最容易翻车的不是原理图而是板级布局。我画 PCB 时特别照顾了这几条输入电容要紧贴电池输入和 5V 输入引脚。不少 PMIC 对输入瞬态敏感电容放在稍远一点位置就可能导致输入电压跌落。输出电容尽量靠近对应电源引脚。PCA9422 输出侧我用了 1μF 和 100nF 并联去耦环要小。GND 平面分割不是越多越好。我把电池负极、PMIC 的功率地、主控数字地共用一块完整地避免跨分割导致环路噪声。NTC 温度检测走线要远离开关节点。如果 PMIC 支持电池温度采集热敏电阻的走线最好包地处理否则充电状态会误报过热。我在这块板上还在 PMIC 输出 3.3V 之后加了一个 0Ω 电阻作为测量隔离点。调试时拆掉 0Ω用电流表串联可以直接量系统消费电流不用飞线去刮焊。这虽然很简单但效率奇高建议在这个设计中保留。3. 驱动实现I2C 读写、初始化配置和复位流程硬件焊接完第一件事不是接电池而是先拿 MCU 的 I2C 去读设备 ID验证通信链路。PCA9422 这类 PMIC 通常有设备 ID 字段读出来以后再开始配置。我直接用 STM32F303RC 的 I2C1因为 F303 的 I2C1 引脚正好位于 PB8/PB9软件上并没有太多麻烦。3.1 底层 I2C 通信怎么封装底层函数我只封装了三个写 8 位寄存器、读 8 位寄存器、读 16 位寄存器。不封装过多因为 PMIC 寄存器操作并不复杂重点在错误重试和超时控制上。#define PMIC_I2C_ADDR 0x38 // 以数据手册实际从机地址为准 #define PMIC_I2C_TIMEOUT 100 static int pmic_i2c_write8(uint8_t reg, uint8_t val) { uint8_t buf[2] {reg, val}; return HAL_I2C_Master_Transmit(hi2c1, PMIC_I2C_ADDR, buf, 2, PMIC_I2C_TIMEOUT); } static int pmic_i2c_read8(uint8_t reg, uint8_t *val) { if (HAL_I2C_Master_Transmit(hi2c1, PMIC_I2C_ADDR, reg, 1, PMIC_I2C_TIMEOUT) ! HAL_OK) return -1; if (HAL_I2C_Master_Receive(hi2c1, PMIC_I2C_ADDR, val, 1, PMIC_I2C_TIMEOUT) ! HAL_OK) return -1; return 0; } static int pmic_i2c_read16(uint8_t reg, uint16_t *val) { uint8_t bytes[2] {0, 0}; if (HAL_I2C_Master_Transmit(hi2c1, PMIC_I2C_ADDR, reg, 1, PMIC_I2C_TIMEOUT) ! HAL_OK) return -1; if (HAL_I2C_Master_Receive(hi2c1, PMIC_I2C_ADDR, bytes, 2, PMIC_I2C_TIMEOUT) ! HAL_OK) return -1; *val (bytes[0] 8) | bytes[1]; return 0; }注意我写了一个不太起眼但重要的点所有 HAL 调用都检查返回值。PMIC 调试最怕的是 I2C 偶发失败后不去恢复后面整个状态机就悬空。实际测试下来PMIC I2C 侧偶发 NACK 的原因往往是寄存器地址错误或者访问被中断打断所以底层要坚持超时重试不能当它不存在。3.2 初始化配置的先后顺序PCA9422 上电后并不是空白的芯片它会先按 OTP 默认配置输出电源轨。MCU 通过 I2C 接管配置时顺序如果反了可能造成瞬间的输出扰动甚至直接触发负载复位。我总结的一套稳妥顺序是读设备 ID确认 I2C 通信正常且芯片不是异常器件。读取当前状态寄存器和中断寄存器记录上电事件防止旧事件干扰。把中断屏蔽寄存器统一配置先让所有中断源可上报。配置电源轨电压和使能状态此时主负载可能已经在跑改动要逐路进行。配置充电参数包括截止电压、充电电流、终止电流和温度阈值。再次读取状态确认配置生效随后使能主控侧中断。其中第四步很容易被忽略。有的 PMIC 输出电压配置是立即生效但负载电容大的情况下电压跳变会有斜坡时间。如果把主控自身供电从 3.3V 改成 3.0V 且幅度偏大主控可能直接发生掉电复位。所以我建议在改输出配置前先确保该电源域上没有关键负载或者把改动推迟到进入睡眠状态时做。3.3 复位与异常恢复路径PMIC 也有复位问题。常规运行中 I2C 总线偶尔被干扰或者软件状态跑飞配置寄存器可能会回到默认值。我在驱动里做了两个恢复路径。第一个是软复位PMIC 的配置寄存器里通常有一个软件复位位写 1 后芯片会回到复位态或者 OTP 默认态。软件流程是关中断源、写软复位、延时 10ms、重新执行初始化序列。这个操作不要把主控牵扯进去因为主控本身电源可能来自 PMIC软复位期间如果 PMIC 输出被拉低主控也应该按复位恢复而不是死等。第二个是硬件复位当 I2C 完全无法通信时用 GPIO 拉低 PMIC 的复位脚等待上电稳定后再重新初始化。硬件复位要控制好脉冲宽度太短的复位脉冲可能芯片不响应。我在调测时验证过脉冲建议不低于几百微秒之后等待内部上电时序通常要等 5ms 以上再初始化通信。异常恢复时特别注意不要把“PMIC 没反应”一律归为 I2C 错误。先拿示波器抓 I2C 波形查看 SCL 上是否只有单个脉冲、SDA 是否被拉死判断到底是总线锁定还是芯片复位异常。我在现场就遇到过 SDA 被拉低的情况最后定位是误把 PMIC 的 I2C 地址配错导致每次都访问了错误设备恢复路径其实很清晰。4. 电源状态机把运行、充电、休眠、异常管起来完整的电源管理说到底是一个状态机。PMIC 提供硬件资源和事件MCU 负责策略和执行。如果把电源策略散落在一个个 if 判断里后面加需求会非常痛苦。在我这个项目里我把状态机拆成了六个状态集中在一个电源管理模块里处理。4.1 状态定义与转换条件六个状态如下状态含义进入条件退出条件OFF关机PMIC 进入低功耗待机长按关机键检测到开机键、外部充电插入BOOT开机启动等待电压稳定开机事件初始化完成状态读回正常RUN正常运行BOOT 完成进入睡眠、故障、关机SLEEP浅睡眠主控 STOP 模式无任务且低功耗时间到外部中断、RTC 中断、充电事件CHARGE充电中可叠加于 RUN/SLEEP插入适配器且电池未满充满事件、适配器拔出FAULT硬件故障如过热、电池低压PMIC 上报过温/低压故障条件消失且软件确认设计时要注意 CHARGE 不是一个互斥状态。我单独把它看成事件而不是宿主状态。如果写成互斥状态RUN 状态下插入充电器就得从 RUN 切成 CHARGE还得保留 RUN 的任务上下文很别扭。我最后的做法是 CHARGE 作为叠加标志位状态机主状态仍是 RUN 或 SLEEP充电事件通过中断上报后只是写状态位。4.2 代码实现状态机核心代码如下它把一个通用事件队列和状态转换函数剥离开方便扩展typedef enum { PM_EVENT_NONE, PM_EVENT_POWER_ON, PM_EVENT_POWER_OFF, PM_EVENT_CHARGER_IN, PM_EVENT_CHARGER_OUT, PM_EVENT_CHARGE_DONE, PM_EVENT_BAT_LOW, PM_EVENT_OVERTEMP, PM_EVENT_TIMEOUT, } pm_event_t; static void pm_state_transit(pm_state_t new_state) { pm_state_t old g_pm_state; // 退出动作 switch (old) { case PM_STATE_RUN: pm_run_exit(); break; case PM_STATE_SLEEP: pm_sleep_exit(); break; default: break; } g_pm_state new_state; // 进入动作 switch (new_state) { case PM_STATE_BOOT: pm_boot_entry(); break; case PM_STATE_SLEEP: pm_sleep_entry(); break; case PM_STATE_FAULT: pm_fault_entry(); break; default: break; } } void pm_poll(void) { pm_event_t ev pm_event_get(); switch (g_pm_state) { case PM_STATE_OFF: if (ev PM_EVENT_POWER_ON) pm_state_transit(PM_STATE_BOOT); break; case PM_STATE_BOOT: if (pm_is_config_done()) pm_state_transit(PM_STATE_RUN); else if (ev PM_EVENT_FAULT) pm_state_transit(PM_STATE_FAULT); break; case PM_STATE_RUN: if (ev PM_EVENT_POWER_OFF) pm_state_transit(PM_STATE_OFF); else if (ev PM_EVENT_BAT_LOW) pm_state_transit(PM_STATE_SLEEP); else if (ev PM_EVENT_OVERTEMP) pm_state_transit(PM_STATE_FAULT); else if (pm_can_sleep()) pm_state_transit(PM_STATE_SLEEP); break; case PM_STATE_SLEEP: if (ev PM_EVENT_POWER_ON || ev PM_EVENT_CHARGER_IN) pm_state_transit(PM_STATE_RUN); else if (ev PM_EVENT_BAT_LOW) pm_state_transit(PM_STATE_OFF); break; case PM_STATE_FAULT: if (ev PM_EVENT_TIMEOUT pm_fault_clear()) pm_state_transit(PM_STATE_RUN); break; default: break; } }这段代码看起来不复杂但它是整套电源管理分析和优化的核心。真正上板时状态机的每个入口函数都要有原子性比如 pm_sleep_entry() 一定会配置 PMIC 的低功耗输出电压、关闭不用的电源轨、配置唤醒源然后再执行 __WFI()。使用者并不需要知道状态如何切换只要知道在触发条件下状态机会在毫秒级完成动作。4.3 中断与轮询的配合电源管理事件有两个来源PMIC 硬件中断和软件定时器轮询。我的做法是两者配合但明确划分职责。硬件中断负责快速响应充电完成、电池低压、过温、适配器插入/拔出。软件轮询负责慢速监测周期性读电池电压、充电电流、系统温度观察趋势而不是单点跳变。中断服务函数里只做标记不执行复杂逻辑。例如 PA0 外部中断回调中我只设置一个 volatile 标志pm_int_flag 1然后主循环的pm_poll()再去读 PMIC 状态寄存器确认是哪一路事件触发。这样状态机保持单一数据流也避免在中断上下文中调用 I2C 造成阻塞。实测下来中断触发到主循环处理延迟在几十微秒到几百微秒之间完全够用。5. 低功耗调优与实测坑位软件状态机跑通后才是真正让人头疼的低功耗调优。我先讲测量方法再给数据最后列几个我踩过的坑。5.1 实测方法我用的是最笨但也最可靠的方法在电池和 PMIC 输入之间串入电流通道用高精度电流表读取各状态电流。同时在 PMIC 输出端并联示波器电压探头观察切换瞬间的输出跌落。测量点建议放在电池正极到 PMIC 输入之间不要放在 PMIC 输出之后因为输出之后测不到 PMIC 本身消耗的电流。运行时 MCU 全速跑外设按照实际工作状态打开系统电流约 45mA。浅睡眠时F303 执行 STOP 模式PMIC 进入低功耗状态整板电流约 280μA。这个数据是六块板子测出来的平均值个别板子差异在几十微安左右。如果你也想做到类似量级需要把板子上所有未用 GPIO 配置成模拟输入或固定电平否则悬浮引脚可以把待机电流拉高不少。5.2 优化前后的数据对照状态优化前优化后说明RUN 正常运行58mA45mA关闭调试器、禁用不用的 ADC 通道SLEEP 浅睡眠1.8mA280μAPMIC 低功耗模式 MCU STOP GPIO 整理OFF 关机120μA21μA完全切断系统输出仅 VBAT 通路保留CHARGE 充电186mA186mA主要是充电电流优化空间不大OFF 状态从 120μA 降到 21μA这 21μA 来自主控 VBAT 域和 PMIC 的静态电流。如果你连这 21μA 都必须压掉只能考虑加 MOSFET 或采用外部负载开关彻底切断 VBAT不过会牺牲 RTC 和备份寄存器。对这个项目来说21μA 已经能满足产品要求。5.3 三个典型问题第一个问题是 PMIC 的 I2C 总线被拉死。现象是软件初始化时读完设备 ID 后再读状态寄存器就一直超时。用示波器一看SDA 已经低电平SCL 高电平。原因是 I2C 传输多字节数据时如果主机发送停止位前从机刚好检测到总线格式异常从机可能锁死总线。解决办法是初始化 I2C 前先对 PMIC 做一次总线恢复让 SCL 翻转九次再发一个 START 和 STOP 组合就能让 SDA 恢复高电平。之后把初始化流程里的读取动作全部改为带重试的封装函数问题没有再出现。第二个问题是休眠后一插适配器主控就立即复位。抓波形发现适配器插入瞬间 PMIC 的 3.3V 输出有一个下凹到约 2.5V 的毛刺导致主控复位。原因不是 PMIC 供电能力不够而是适配器插入时充电回路和系统电源路径切换产生的电流瞬间超过了输入电容储存的能量。我在 PMIC 输入侧再补了一个 10μF 陶瓷电容同时把适配器插入检测的中断优先级提高让主控先从 STOP 模式快速唤醒。改完以后插入毛刺明显减小没再触发复位。第三个问题比较隐蔽NTC 分压电阻把休眠电流拉高了。PMIC 的电池温度检测引脚通常需要一个 NTC 和一个固定电阻分压这个分压网络一直挂在电池上功耗约 50μA。我在原理图上看到时以为它最终只是接到 PMIC 内部 ADC 引脚电流很小结果实测才知道固定电阻每天都在耗电。最后我在分压网络串了一个 PMIC 的 GPIO 或主控控制的 MOSFET只在需要测温度时打开把这一路功耗彻底削减。这个细节如果不做实测只看数据手册很难发现。6. 后续还可以怎么扩展这套电源管理状态机和驱动框架搭好之后后续扩展其实很顺。我最常推荐的做法是把 PMIC 驱动继续往上层抽象让上层不直接看到 I2C 寄存器只提供几个语义化接口。接口设计可以长这样pm_err_t pm_power_on(void); pm_err_t pm_power_off(void); pm_err_t pm_enter_sleep(void); pm_err_t pm_exit_sleep(void); pm_err_t pm_set_charge_current(uint16_t ma); pm_err_t pm_get_battery_mv(uint16_t *mv); pm_err_t pm_get_charge_ma(uint16_t *ma); pm_err_t pm_get_temp_c(int16_t *temp);这样做的意义在于上层 HMI、协议栈或者业务逻辑完全不需要知道 PMIC 型号。哪怕后续把 PCA9422 换成另一款 PMIC只需要替换驱动实现状态机和业务代码不用跟着改。这个思路非常适合小团队和单人维护的项目改动集中在一层不容易把其它模块牵连坏。另外这套框架还支持我给设备增加电池健康度的功能通过读出充电曲线和电压变化用简单的一阶滤波估计当前可用电量百分比再把这个百分比通过协议上报给上位机。这些都属于在电源管理框架上的增值功能不需要动硬件软件迭代空间比原来大很多。最后我再分享一个很有用的调试习惯在 PMIC 的 I2C 通信线上加双通道逻辑分析仪同时抓取事件时序将适配器插入、中断引脚状态、I2C 配置写入三个信号对齐。这种三联判断对定位随机复位问题非常有效。我这次有几个问题正是这么定位的比起反复读数据手册真实波形才是真正的教科书。
阅读完成 · 觉得有帮助?
咨询建站