☰
基于PCA9422与STM32L496ZG的低功耗完整电源管理方案设计
2026/10/24 4:54:39 网站建设 项目流程

把“完整电源管理”做成一个能跑、能自检、能低功耗待机的系统,核心其实是两件事:先把电给对,再把电管好。这个项目里,我用 PCA9422 这款带 I2C 接口的集成电源管理芯片,配合 STM32L496ZG 的丰富低功耗模式,做了一个从电池充电、输出电压控制到系统关断都可由软件统一调度的电源管理方案。适合做手持设备、低功耗仪器、电池供电传感器的工程师参考,也适合第一次把 PMIC 集成进系统的朋友,文章里会讲清楚硬件连接、寄存器配置、状态机设计和调试时真正踩过的坑。

1. 整体方案与供电架构

1.1 先画电源树:从 USB 到每一个负载

任何电源管理项目,我习惯先画一张电源树,把每条路径的电压、电流、纹波需求列清楚,再决定 PMIC 承担什么、MCU 承担什么。

我这个项目的供电链路是这样的:USB 或者适配器接到 PCA9422 的 VBUS 输入,锂电池接 BAT 引脚,PCA9422 输出一个系统主电源 VSYS,默认设置成 3.3V。STM32L496ZG 的供电直接从 VSYS 取,但要注意,L496ZG 的 VDDA 引脚建议串一个磁珠再进,避免 PMIC 开关噪声直接耦合到 ADC 参考源上。传感器和无线模块则通过一个 P-MOS 开关由 GPIO 控制,这样才能做到在睡眠时彻底断电,而不是仅仅让软件“不用它”。

之所以选择这种结构,而不是让 MCU 直接控制一堆分离的 LDO、DCDC,是因为 PCA9422 这类 PMIC 把充电管理、电压监控、输出保护和 I2C 状态上报都做进去了。自己搭充电回路不是不行,但恒流恒压的切换、充电完成判断、电池过放保护,这些细节在量产阶段很容易成为返工重灾区,交给 PMIC 统一处理会省掉很多事。

项目里我定下的关键参数如下:

项目数值说明
电池类型单节锂离子 3.7V比较常见的 1S 方案
浮充电压4.2V常规三元材料,磷酸铁锂需改
充电电流500mA兼顾充电速度与发热
预充电流200mA电池电压低于 3.0V 时使用
系统输出电压3.3V供 MCU 和外设主轨
I2C 速率400kHzPMIC 与 MCU 通信,越快越好
控制周期100ms 一采集运行态状态刷新

把这些写成表格贴在工位旁,之后再写驱动逻辑,每个数值都能找到来源,不会出现“当初随便填了个充电电流”的窘境。

1.2 功耗模型决定管理模式

完整的电源管理不是把 PMIC 寄存器配好就结束了,还要把不同负载的功耗特性分清楚,才能在软件里做出合理的电源状态切换。

我当时把整板负载分成了三类。第一类是 MCU 核心,包括 STM32L496ZG 自身,它不能断电,但可以从 Run 模式降到 Stop2 模式,电流从几十毫安降到几个微安。第二类是传感器,1.8V 供电,工作平均电流 5mA,可以由 GPIO 控制电源开关,在不需要采集的时候完全切断。第三类是显示和无线模块,3.3V 供电,峰值电流可能到几百毫安,但只在很短的发送窗口内工作,必须用单独的 MOSFET 开关控制。

这样的分类直接决定了状态机的走向。因为 PMIC 本身无法判断什么时候该给传感器供电,它只知道输出 VOUT,具体谁能拿到电,是 MCU 根据自己运行状态决定的。反过来,MCU 也无法自己完成锂电池恒流转恒压的切换,必须靠 PMIC 的硬件闭环。所以二者之间的关系是:PMIC 负责电源域的底层控制,MCU 负责功耗策略的上层调度。

2. 硬件连接与 PCB 设计要点

2.1 STM32L496ZG 与 PCA9422 的核心引脚连接

先说连接关系,我用的 I2C 控制器是 STM32 的 I2C1,对应引脚 PB8 做 SCL、PB9 做 SDA。这两个引脚各接一个 4.7kΩ 上拉到 3.3V,这是 I2C 总线的基础要求,只要挂在总线上的器件不止一个,就必须上拉。

PCA9422 的中断输出 IRQ 接到 PA0,在 STM32 里 PA0 默认识别为 EXTI0,可以配置成下降沿唤醒。要注意,不同 PMIC 的中断极性可能是高有效也可能是低有效,我拿到板子后第一件事是查芯片手册确认,再决定触发沿,不能想当然。

电源好的信号 PG 接到 PC1,MCU 可以轮询这个引脚判断 PMIC 是否已经稳定输出。部分 PMIC 的使能引脚可以硬件拉高常开,但我的硬件上设计了 PC0 接到 EN,这样软件可以强制关闭输出,方便产线测试和低功耗调试。

电池电压采样和 NTC 温度采样没有直接用 PMIC 内部 ADC,而是分别引出到 STM32 的 ADC1_IN0 和 ADC1_IN1。原因是我想让 MCU 在 PMIC 没来得及上报的状态下也能独立知道电池电压,比如系统正在初始化、I2C 还没配好的那段时间。

引脚连接清单如下:

STM32L496ZG 引脚方向连接对象说明
PB8开漏输出PCA9422 SCLI2C 时钟,4.7kΩ 上拉
PB9开漏输出PCA9422 SDAI2C 数据,4.7kΩ 上拉
PA0输入PCA9422 IRQ下降沿唤醒事件
PC0输出PCA9422 EN高电平开启,低电平关机
PC1输入PCA9422 PG电源好状态
ADC1_IN0模拟输入VBAT_SENSE电池电压分压后采样
ADC1_IN1模拟输入NTC 电压电池温度采样

2.2 电源路径上容易被低估的元件

PMIC 周边的电感电容,直接影响纹波和负载响应。

电感我选了 2.2μH,饱和电流至少按输出电流的 1.5 倍留余量。项目里最高负载是 1A,所以电感饱和电流选了 2.5A。如果电感选小了,大电流时电感量掉得厉害,纹波会突然恶化,输出电压在示波器上看起来像锯齿波。

输入输出电容尽量选 X7R 或 X5R 材质,容量 10μF 起步,耐压 6.3V。如果后面要长期带载到 2A,建议输出侧再并一个 22μF 的陶瓷电容,两三颗并联有时候比一颗大电容效果更好,因为 ESR 更低,高频响应更好。

NTC 电阻我用的是 10kΩ B 值 3435 的贴片热敏电阻,放在电池包外面贴紧电池,接到 PMIC 的 NTC 引脚。这里有个细节,PCB 走线时 NTC 线路尽量短,远离开关节点,否则温度读数受噪声影响很大,PMIC 会误判电池温度过高而停止充电。

另外,电池两端不要忘记加一个 0.1μF 高频滤波电容,这是很多人会漏的,但高频噪声其实最容易从电池线辐射出来。

2.3 PCB 布局上先背下来的三条规则

第一,从 VBUS 输入到电感再到输出电容的功率环路,面积越小越好。我第一版板子就是因为把电感放得太远,回路面积大了几平方厘米,输出纹波直接飙到 300mV,后来把环路收紧后才压到 40mV 以下。

第二,反馈采样点必须从输出电容正端单独拉线,不能在电感和输出电容之间取。采样线要是走得太靠近开关节点,纹波噪声会被直接引入反馈回路,PMIC 会误以为输出电压高了或低了,导致工作频率抖动。

第三,PMIC 底下的地平面要保持完整,不要在芯片正下方打过孔,也不要在芯片下方布信号线。这是为了给大电流回流一个低阻抗路径,同时也避免数字噪声窜到模拟采样线上去。

3. 芯片初始化与寄存器配置

3.1 I2C 地址探测与前几行代码

PCA9422 的 I2C 地址一般跟 AD0 引脚的接线有关,不同硬件设计下可能是 0x60 也可能是 0x30。为了避免拿到一块板子后还要去猜地址,我写了一个探测函数,复位后先扫描一遍候选地址,能读到 ID 寄存器的那个就是实际地址。

static uint8_t pmic_addr = 0; uint8_t pmic_probe(void) { uint8_t tmp = 0; static const uint8_t candidates[] = {0x60, 0x30}; for (uint8_t i = 0; i < sizeof(candidates); i++) { if (HAL_I2C_Mem_Read(&hi2c1, candidates[i] << 1, REG_MANUFACTURER, 1, &tmp, 1, 10) == HAL_OK) { pmic_addr = candidates[i]; return tmp; } } return 0; }

注意,HAL 函数的设备参数是 8 位地址,需要把 7 位地址左移一位,也就是在参数里把读写的 bit 位留空给 HAL 自己补。我一开始没注意这个问题,把 7 位地址直接丢进去,结果读回来全是超时。

I2C 初始化时速率设成 400kHz,上升时间不要超过数据手册要求,如果总线上还挂了其他慢速器件,就统一降到 100kHz。I2C 通信失败时的重试次数,我会根据重启可能性调整,但至少重试 3 次以上,因为 PMIC 在上电瞬间有时候还没准备好,第一次访问失败是正常的。

3.2 初始化序列:可复用的写法

拿到芯片后,第一件事不是直接写一堆寄存器值,而是先看数据手册,把所有寄存器地址过一遍。不同批次的 PCA9422 可能有不同寄存器布局,我工程里用的寄存器宏定义如下,实际操作时建议对照手册核对。

#define REG_SYS_CTRL 0x00 #define REG_CHG_CTRL 0x01 #define REG_VOUT_CTRL 0x02 #define REG_STATUS 0x03 #define REG_IRQ_MASK 0x04 #define REG_ADC_DATA 0x05 #define REG_MANUFACTURER 0x06

初始化顺序的原则是:先让输出建立,再使能充电。如果反过来了,在电池电量很低时,PMIC 可能一边想充电一边又要给系统供电,输入限流分配冲突,导致系统供电不稳。

void pmic_init(void) { // 1. 退出运输模式,使能 SYS 输出 pmic_write(REG_SYS_CTRL, 0x0C); // 2. 先屏蔽所有中断,待基本配置完成后再统一打开 pmic_write(REG_IRQ_MASK, 0xFF); // 3. 设置输出电压 3.3V,强制 PWM 模式 pmic_write(REG_VOUT_CTRL, 0x1F); // 4. 设置充电参数:500mA CC,100mA 截至,4.2V CV pic_write(REG_CHG_CTRL, 0x2A); // 5. 打开需要的中断:充电完成、故障、VBUS 插入 pic_write(REG_IRQ_MASK, 0xF1); }

写每一条寄存器之后,建议做一次回读校验,确保写进去的值真的生效。上电之后 PMIC 可能还在做内部的校准,立刻写寄存器也许会被覆盖,所以初始化最好做成一个带重试的序列,超过 3 次失败就上报错误,不让系统带病运行。

这里需要特别提一下“强制 PWM 和自动 PFM”的选择。强制 PWM 模式在轻载时效率低一些,但纹波很小,适合对模拟采样精度要求高的阶段。自动 PFM 模式在轻载时更省电,但纹波会变大。我的做法是,MCU 运行在全速工作时切到强制 PWM,在进入低功耗前切回自动 PFM,把选择权交给用电器而不是让 PMIC 一个模式打天下。

3.3 状态读取的软件封装

电源管理最忌讳的就是把寄存器读取代码散落在各个业务模块里,一会儿读一次、一会儿又用不同的方式解析,后面维护起来特别痛苦。我习惯先把 PMIC 的状态快照封装成一个结构体。

typedef struct { uint16_t vbat_mv; uint16_t vbus_mv; uint16_t ichg_ma; uint16_t die_temp; uint8_t state; } pmic_snapshot_t;

读取时尽量用 I2C 的连续地址突发读,一次把所有需要的数据拿回来,避免多次单发读造成的总线开销。数据手册里的 ADC 寄存器原始值一般是 12 位或 16 位,换算成真实电压电流时会有对应的 LSB 系数,这个系数千万不能从网上抄,必须从手册上找。

void pmic_get_snapshot(pmic_snapshot_t *snap) { uint8_t buf[8]; HAL_I2C_Mem_Read(&hi2c1, pmic_addr << 1, REG_ADC_DATA, 1, buf, sizeof(buf), 20); snap->vbat_mv = buf[0] * 2 + buf[1] / 128; snap->vbus_mv = buf[2] * 5 + buf[3] / 256; snap->ichg_ma = (buf[4] << 2) + (buf[5] >> 6); snap->state = pmic_read(REG_STATUS); }

不要在一个状态读取里加入太多计算,能先把数据拷贝到结构体里,再在模块层做滤波和判断,会比每次都直接操作 I2C 总线安全得多。数据校验方面,连续两次读到的电压跳动超过 500mV 就直接丢弃,基本可以判断为一次 I2C 总线扰动而不是真的电压突变。

4. 系统级电源管理状态机

4.1 从充电曲线到状态机

锂电池充电是典型的恒流恒压过程。我借助 PMIC 的状态寄存器实现了下面这条状态迁移链:VBUS 插入前处于 IDLE,插入后进入 WAIT_VBUS,做输入源检测;电池电压低于 3.0V 时用预充电流 200mA 充到 3.0V,然后进入恒流充电 500mA,一直到电压逼近 4.2V;之后自动切换为恒压充电,电流逐渐下降,当充电电流降到 100mA 截止阈值后,PMIC 上报充电完成。

状态机最核心的代码是处理 PMIC 中断事件:

void pmic_irq_handler(void) { uint8_t st = pmic_read(REG_STATUS); if (st & ST_FAULT) { system_state = PMIC_STATE_FAULT; } if (st & ST_CHARGE_DONE) { system_state = PMIC_STATE_CHARGED; } if (st & ST_VBUS_PRESENT) { system_state = PMIC_STATE_CHARGING; } if (st & ST_BAT_LOW) { // 通知上层开始低功耗续航模式 power_request_low_battery(); } }

实现状态机时要注意,不要把状态判断放在中断里做完整数据解析,中断里只该做标志位更新,真正的状态迁移放到主循环调度里,否则中断服务函数会被 I2C 读操作拖得很长。I2C 读操作本身是有延时的,进入中断里进行的话,轻则拖慢系统响应,重则在低功耗唤醒时反复进中断。

4.2 配合 STM32L496ZG 的 Stop2 模式

用了 L496ZG 却不让它进入低功耗模式,等于买了跑车只用来上下班买菜。STM32L4 系列的 Stop2 模式可以把内核和大部分外设时钟停掉,但 SRAM 内容保持,唤醒速度也在微秒级,正好拿来配合定时采集。

进入 Stop2 之前的操作顺序是有讲究的。先把 GPIO 控制的传感器电源关断,再把 ADC 停掉,失能掉可能产生中断的外设,最后设置 PCA9422 的中断屏蔽,只保留充电完成、故障、VBUS 插入三个事件,然后执行 WFI 指令。

void enter_stop2(void) { // 1. 关断外设电源 HAL_GPIO_WritePin(SENSOR_PWR_EN_GPIO_Port, SENSOR_PWR_EN_Pin, GPIO_PIN_RESET); // 2. 关闭业务相关外设 HAL_ADC_Stop(&hadc1); HAL_I2C_MspDeInit(&hi2c1); // 3. 配置 PMIC 中断屏蔽:只留关键唤醒源 pmic_write(REG_IRQ_MASK, 0x0E); // 4. 进入低功耗模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWER_STOP2, PWR_STOPENTRY_WFI); // 5. 唤醒后重新初始化 SystemClockConfig_STOP(); HAL_I2C_MspInit(&hi2c1); pmic_init(); }

这套流程跑下来,实测整板平均功耗从运行态的 35mA 降到了 1.8mA,大概是因为传感器供电被关断、MCU 进入 Stop2、PMIC 切到自动 PFM 模式这三件事叠加的效果。如果想再往下压,可以连 I2C 上拉都关掉,但代价是唤醒后要重新初始化总线和 PMIC,启动时间会长一点。

低功耗模式下不要急着去读 PMIC 的状态,刚唤醒那一下 PMIC 的 I2C 可能还没重新建立时序,等时钟稳定、I2C 外设重新初始化完再读比较稳。

4.3 定期采集的参数与周期

采集周期设计成表格,方便调试时迅速确认参数来源:

参数周期触发方式用途
电池电压500ms定时采集低电量和电量估算
充电电流1s充电中定时采集确认充电状态正常
电池温度5s定时采集充电安全保护,了解发热趋势
系统电压100ms运行态定时采集判断 PMIC 输出是否稳定
充电完成事件触发IRQ 中断切换系统状态机

这些周期的选择不是拍脑袋。100ms 是为了在负载突变时能快速发现问题,但电池温度本身变化慢,5s 一次完全足够;充电电流变化也不快,1s 采样既能看到异常又不至于频繁唤醒 MCU。采集周期本质上是在 MCU 的工作量、电池消耗和故障响应速度之间做平衡。

5. 调试实录:五类典型故障和排查方法

5.1 I2C 读写失败,SDA 一直拉低

这个是我第一次上电就踩到的坑。PMIC 初始化函数一直返回超时,用逻辑分析仪看波形,SCL 有正常的时钟,但 SDA 在地址阶段之后就没有任何 ACK 信号。

排查发现原因有两个。第一,I2C 地址确实不对,我按照网上一份资料写的是 0x60,但实际板子 AD0 接地,7 位地址对应的是 0x30。第二,SDA 引脚开漏配置写成了推挽,导致总线释放不了,一直处于低电平。前者通过探测函数解决,后者则是把引脚模式改成了开漏输出。

以后的调试习惯是,先挂逻辑分析仪看波形,确认 SCL 正常、SDA 在第 9 个时钟周期有 ACK,再谈读寄存器。不要凭印象改代码。

5.2 充电不启动,充电电流始终为 0

这个问题的原因比较多,我用一个排查清单快速收敛:

检查项工具可能原因
VBUS 电压是否在阈值以上万用表输入源功率不足,电压被拉低
NTC 是否在温度窗口内看 PMIC 寄存器温度传感器接反或数值跳变
电池电压是否可读I2C 读 VBAT电池保护板断电,电压为 0
充电使能位有没有写进回读 CHG_CTRL初始化序列被后续写操作覆盖
预充到恒流切换是否完成观察电流曲线电池过放,需要更长时间的预充

实际那一次的原因是我把 NTC 接到了 GND,而不是电池包上的热敏电阻,导致 PMIC 认为电池温度低于 0 度,直接关断了充电。换过来之后电流立刻起来。

5.3 输出电压掉电时纹波变大

这个问题是在带载 1A 测试时发现的,本来 3.3V 的输出纹波正常不到 40mV,但峰值负载一起来,纹波变成 200mV 左右的毛刺。

原因是我的板子在 PMIC 附近只放了一颗 10μF 电容,动态响应不足,负载瞬态时电压被拉出一个很大的凹陷。后来在输出端并联了两颗 10μF 和一个 100nF 高频电容,同时在软件上把 PMIC 从自动 PFM 强制切换到 PWM 模式,纹波就恢复正常了。

这提醒我,布局时不要迷信“数据手册最小电容”,负载越猛,输出电容越要往大了加,最后以实测纹波为准。

5.4 低功耗电流总是降不下来

进入 Stop2 模式后,我期望整板平均电流到 2mA 以下,但实测还是 8mA 左右,怎么都降不下去。最后用排除法,把所有外设电源逐个断开,发现是一个调试串口芯片没断电,它一直挂在 VDD 上,光这一路就吃了 5mA。

另一个隐蔽的问题是 GPIO 悬空。进入低功耗前没有把某些在 PCB 上没有连接器件的引脚配置成模拟输入,导致引脚内部上拉或下拉电阻还在耗电。低功耗项目的 GPIO 状态设计必须逐脚确认,宁可多花十分钟把每个引脚该设成什么模式写清楚,也不要等电流表带着你做侦探。

5.5 采集到的电池电压跳变严重

电池电压采样一直不稳,前后两次竟然能差 300mV。查出来是 ADC 采样时间设置得太短,内部采样电容还没充好就开始转换了。把采样时间设置到最大档位之后,跳动降下来了,但还不够,我又在软件里对连续 8 次采样取了中值平均,稳定之后读数基本可以到 10mV 以内。

为了让电压值可信,我用高精度万用表在同一时刻做了一次两点校准,把分压电阻的误差修掉。电池电压这个量是整个电源管理的依据,如果它都不准,后面电量估算和低电量保护全是空中楼阁。

6. 一些有价值的习惯,留给你做参考

做完这个项目,我自己的体会是,电源管理方案最花时间的不是原理图,也不是寄存器配置,而是把各个电源事件之间的依赖关系理清楚。比如,充电需要 VBUS,VBUS 进来了要不要立刻唤醒 MCU,什么时候该让 MCU 进低功耗,什么时候又该把传感器供电断掉,这些问题只有自己画一遍完整状态图才会真正有底。

有个小技巧很值得分享:在设计阶段就把每个电源域测试点留出来,不要觉得测点占用面积就省掉。PCB 打样回来后,我用万用表量电压、示波器抓纹波,全都靠这些不起眼的测试点,省了很多飞线的麻烦。另一个建议是先完成最小系统验证:不要让整套软件都跑起来后再调电源,先把 PMIC 初始化、I2C 通信、充电状态机这三件事调通,后面的业务逻辑才有稳定的地基。

电源管理这个东西,平时不出问题的时候感觉不到存在,一旦出问题就是整块板子不工作。希望这篇记录能帮你在自己的项目里少踩几个坑,让每次上电都充满确定性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询