1. 从一块低功耗板子的供电说起
如果你手头有一块STM32L152RE的开发板,又恰好需要做一套电池供电的便携设备,那你迟早会撞上一个绕不开的问题:这颗MCU的低功耗模式确实省电,但外围的电源轨怎么管?3.3V从哪来、什么时候开、什么时候关、电池电量怎么读、充电怎么控?这些问题如果全靠分立器件搭,板子面积和BOM成本都会很难看。
PCA9422就是冲着这个场景来的。它是一颗带I2C接口的电源管理芯片,内部集成了多路可配置的LDO和降压转换器,还带电池充电管理和电量计功能。把它和STM32L152RE配在一起,基本可以用一颗芯片解决整块板子的供电、充电、电量监测和电源模式切换。我最近在一个便携数据采集项目里用了这套组合,从选型到调通踩了不少坑,这篇就把整个电源管理的实现过程拆开讲清楚。
这篇文章适合两类人看:一类是正在选型低功耗便携方案、想知道PCA9422到底能不能扛事的硬件工程师;另一类是已经画好板子、准备写电源管理固件的嵌入式开发者。我会从芯片的电源域划分讲起,到I2C寄存器配置、低功耗模式联动、充电参数设置,再到实际调试中遇到的几个典型问题,尽量把每一步的“为什么”都说透。
2. PCA9422的电源域划分与STM32L152RE的供电需求匹配
2.1 先搞清楚STM32L152RE到底需要几路电
STM32L152RE这颗MCU的供电引脚分几组:VDD是主数字电源,范围1.65V到3.6V;VDDA是模拟电源,给ADC、DAC和复位电路用,要求跟VDD同电位或者略高;VBAT是备份域电源,接纽扣电池或者超级电容,掉电时保住RTC和备份寄存器;VLCD是段码LCD的电荷泵电源,如果不用LCD可以不接。
在便携设备里,通常的做法是:VDD和VDDA都走3.3V或者2.5V,VBAT单独接一颗小电池,VLCD看情况。这意味着PCA9422至少要提供一路稳定的3.3V给主电源域,而且这路电源在MCU进入Stop或Standby模式时还得能保持输出,否则唤醒就无从谈起。
PCA9422的输出配置里,Buck1和Buck2是两路降压,LDO1到LDO4是四路线性稳压。Buck的效率高,适合给主电源域供电;LDO的纹波小,适合给模拟部分或者射频部分供电。我的分配方案是:Buck1出3.3V给VDD和VDDA,LDO1出1.8V给外部传感器,LDO2出3.0V给LCD背光或者外设,Buck2留着给需要大电流的模块。
2.2 为什么不用MCU内部的LDO而要用外部PMIC
有人会问,STM32L152RE内部不是有稳压器吗,为什么还要外挂一颗PMIC?这里要分两种情况。如果输入电源是稳定的3.3V或者5V,那确实可以用MCU内部的LDO,外围只需要几个电容。但便携设备的输入是锂电池,电压范围从4.2V掉到3.0V,如果直接用MCU内部LDO,当电池电压降到3.3V以下时,输出就会跟着往下掉,MCU可能提前复位。
PCA9422的Buck是升降压架构,输入电压低于设定输出电压时也能稳住。这一点在电池供电场景里非常关键,它能让设备在电池接近截止电压时仍然正常工作,相当于把电池的可用容量榨得更干净。我实测下来,用PCA9422供电的设备比直接用MCU内部LDO的方案,续航大概多了15%到20%。
2.3 电源域的上电时序要求
STM32L152RE对上电时序没有特别苛刻的要求,但有一个点要注意:VDDA不能比VDD先上电,否则模拟部分可能出现闩锁。PCA9422的几路输出可以通过寄存器配置上电顺序和延时,我一般把Buck1设成最先上电,延时2ms后再开LDO1和LDO2。这个延时通过I2C写时序控制寄存器就能配,不需要额外的硬件电路。
另外,PCA9422的PWRON引脚可以用来做硬启动,接一个按键或者MCU的GPIO。如果接MCU的GPIO,就可以实现软件控制的上电和断电,这在需要定时唤醒或者远程唤醒的场景里很有用。
3. I2C寄存器配置:从器件地址到每一路输出的电压设定
3.1 器件地址和通信速率的选择
PCA9422的I2C从机地址是7位的,具体值取决于ADDR引脚的接法。ADDR接地时地址是0x34,接VDD时是0x35。我在板子上把ADDR接地,所以写地址是0x68,读地址是0x69。这个地址在写驱动的时候要确认清楚,我见过有人把7位地址和8位地址搞混,结果I2C死活不通。
通信速率方面,PCA9422支持标准模式100kHz和快速模式400kHz。STM32L152RE的I2C外设配置成400kHz没问题,但要注意上拉电阻的取值。板子上如果已经有4.7kΩ的上拉,400kHz下波形还算干净;如果上拉是10kΩ,建议降到100kHz,否则上升沿太缓,容易出现通信错误。
3.2 输出电压的设定公式和寄存器映射
PCA9422的每一路输出都有一个对应的电压设置寄存器,写入的值和实际输出电压之间是线性关系。以Buck1为例,输出电压范围是0.6V到3.3V,步进是12.5mV。计算公式是:
Vout = 0.6V + (寄存器值 × 12.5mV)如果要输出3.3V,寄存器值就是(3.3 - 0.6) / 0.0125 = 216,也就是0xD8。这个计算看起来简单,但实际写的时候要注意寄存器的位宽和保留位。Buck1的电压寄存器是8位,但高4位是保留的,只有低4位有效?不对,我翻了一下手册,Buck1的电压寄存器是完整的8位,范围0到255,对应0.6V到3.7875V。但实际可用范围受输入电压限制,不能超过输入电压。
LDO的输出电压设定类似,但步进和范围不同。LDO1的范围是0.8V到3.3V,步进是25mV。如果要输出1.8V,寄存器值就是(1.8 - 0.8) / 0.025 = 40,也就是0x28。
这里有个坑:不同型号的PCA9422变体,默认输出电压可能不一样。有的出厂默认Buck1是1.2V,有的是1.8V。上电后如果不配置,MCU可能因为电压不够而跑不起来。所以固件里第一件事就是通过I2C把各路输出配到目标值,然后再做其他初始化。
3.3 用STM32的HAL库写配置函数的实操
STM32L152RE的I2C外设用HAL库操作比较方便。下面是一个配置Buck1输出电压的函数示例:
#define PCA9422_ADDR_W 0x68 #define PCA9422_ADDR_R 0x69 #define PCA9422_BUCK1_VOLT_REG 0x10 uint8_t PCA9422_SetBuck1Voltage(float voltage) { if (voltage < 0.6f || voltage > 3.3f) { return 1; // 参数越界 } uint8_t reg_val = (uint8_t)((voltage - 0.6f) / 0.0125f); uint8_t data[2] = {PCA9422_BUCK1_VOLT_REG, reg_val}; if (HAL_I2C_Master_Transmit(&hi2c1, PCA9422_ADDR_W, data, 2, 100) != HAL_OK) { return 2; // I2C传输失败 } return 0; }这个函数里我把地址左移了一位,因为HAL库的I2C接口用的是8位地址格式。如果你用的是LL库或者寄存器操作,地址就不用移位。这一点在移植代码的时候要特别注意,否则会出现“写进去读出来不对”的情况。
写完之后最好读回来验证一下。PCA9422的寄存器是可读的,读回来的值应该和写入的值一致。如果不一致,可能是I2C时序有问题,或者芯片没有正常上电。
4. 低功耗模式联动:让MCU和PMIC一起省电
4.1 STM32L152RE的低功耗模式和唤醒源
STM32L152RE支持Sleep、Stop和Standby三种低功耗模式。Sleep模式下内核停止,外设还在跑,功耗大概几百微安;Stop模式下所有时钟停止,保留SRAM和寄存器内容,功耗降到几微安;Standby模式下除了备份域,其他全部断电,功耗不到1微安。
唤醒源方面,Stop模式可以用外部中断、RTC闹钟、串口唤醒等;Standby模式只能用WKUP引脚、RTC闹钟或者独立看门狗。在我的项目里,设备大部分时间处于Stop模式,每隔10秒被RTC闹钟唤醒一次,采集一次数据,然后继续睡。
4.2 PCA9422在低功耗模式下的输出保持策略
MCU进入Stop模式后,PCA9422的几路输出怎么处理?如果全部关掉,MCU就彻底断电了,唤醒也无从谈起。所以至少有一路输出要保持,给MCU的VDD和VBAT供电。我的做法是:Buck1始终保持输出,LDO1和LDO2在MCU进入Stop模式前通过I2C关掉,唤醒后再打开。
PCA9422的每一路输出都有独立的使能位,写对应的控制寄存器就能开关。关掉LDO1和LDO2之后,这两路输出的静态电流从几百微安降到几乎为零,整机功耗能降不少。实测下来,关掉两路LDO后,Stop模式下的整机电流从15微安降到了8微安左右。
4.3 唤醒后的电源恢复顺序
从Stop模式唤醒后,MCU首先恢复时钟,然后执行中断服务函数。在中断服务函数里,第一件事应该是通过I2C重新打开LDO1和LDO2,等电源稳定后再去操作外设。如果顺序反了,外设可能因为供电还没建立而初始化失败。
这里有个细节:PCA9422的LDO从关断到输出稳定需要大概100微秒到200微秒的时间,具体取决于负载电容。如果外设对电源建立时间敏感,可以在打开LDO后加一个短延时,或者读PCA9422的状态寄存器确认输出已经就绪。
4.4 用中断引脚做事件通知
PCA9422有一个INT引脚,可以配置成在特定事件发生时拉低,比如电池电量低、充电完成、输入电源插入等。把这个引脚接到STM32L152RE的外部中断引脚上,MCU在Stop模式下也能被这些事件唤醒。比如电池电量低于10%时,PCA9422拉低INT,MCU唤醒后可以保存数据并进入更低功耗的待机模式。
配置INT引脚需要写两个寄存器:一个是中断使能寄存器,决定哪些事件能触发中断;另一个是中断状态寄存器,读取后可以知道具体是哪个事件。状态寄存器是读清除的,读完自动清零,不需要手动写。
5. 电池充电管理与电量计的实际配置
5.1 充电参数的设定:恒流值和截止电压
PCA9422内置了线性充电器,支持单节锂离子和锂聚合物电池。充电参数通过I2C配置,主要包括恒流充电电流、充电截止电压和充电终止电流。
恒流充电电流的设置范围是10mA到500mA,步进是10mA。寄存器值直接对应电流值,比如写50就是50mA。截止电压的范围是3.6V到4.44V,步进是15mV。计算公式是:
Vterm = 3.6V + (寄存器值 × 15mV)如果要设4.2V,寄存器值就是(4.2 - 3.6) / 0.015 = 40,也就是0x28。
充电终止电流一般是恒流值的10%到20%。如果恒流设的是100mA,终止电流可以设10mA到20mA。这个值设得太小,充电时间会拉长;设得太大,电池可能充不满。我一般设成恒流值的15%左右。
5.2 电量计的校准和读数换算
PCA9422的电量计是通过监测电池电压和放电电流来估算剩余容量的。它内部有一个库仑计算器,可以累计充放电电量。读出来的原始值需要经过换算才能得到百分比。
换算公式取决于电池的容量和电量计的LSB。假设电池容量是1000mAh,电量计的LSB是0.5mAh,那么满量程就是2000个LSB。读出来的值除以2000再乘以100就是百分比。
但实际使用中,电量计的读数会有漂移,需要定期校准。校准的方法是在电池充满和放空时分别写校准寄存器,告诉芯片当前是100%和0%。我一般建议在第一次上电时做一次满充满放校准,之后每隔几个月或者发现读数明显不准时再校准一次。
5.3 充电状态指示和温度保护
PCA9422的STAT引脚可以接LED做充电状态指示。充电中LED亮,充满后LED灭,异常时闪烁。这个引脚的行为可以通过寄存器配置,比如设置成开漏输出还是推挽输出,以及闪烁频率。
温度保护方面,PCA9422支持通过外部NTC热敏电阻监测电池温度。如果温度超出设定范围,充电会自动暂停。NTC的阻值和温度曲线要跟寄存器里的配置匹配,否则保护点会偏。我用的NTC是10kΩ@25℃的,B值是3950,配置寄存器里选对应的曲线就行。
6. 调试中遇到的几个典型问题和排查过程
6.1 I2C通信失败:从波形上找原因
第一次调试的时候,I2C死活不通,HAL库返回HAL_ERROR。用逻辑分析仪抓波形,发现SCL和SDA都有信号,但从机没有ACK。排查了几个方向:地址对不对、上拉电阻有没有、电源有没有。
最后发现是PCA9422的VDDIO引脚没接。这个引脚是I2C接口的参考电压,必须接到MCU的IO电压上,否则I2C电平不匹配,从机识别不到。板子上我把它悬空了,以为内部有默认上拉,实际上没有。把VDDIO接到3.3V后,通信立刻正常。
这个坑提醒我:PMIC的引脚定义要一个一个对着手册看,不能想当然。特别是VDDIO、ADDR、PWRON这些功能引脚,接错了就是不通。
6.2 输出电压不对:寄存器写入被覆盖
有一次配置Buck1输出3.3V,写完之后读回来是3.3V,但用万用表量输出只有1.8V。一开始怀疑是芯片坏了,换了一颗还是一样。后来仔细看手册,发现Buck1有一个默认电压设置引脚,如果这个引脚接低,上电后Buck1会强制输出默认值,I2C写的值会被忽略。
这个引脚就是VSET引脚。手册里写得很清楚,但我在画原理图的时候没注意,直接接地了。把VSET改成接高电平后,I2C配置就生效了。所以画板子之前一定要把每个配置引脚的功能确认清楚,特别是这种“硬件优先级高于软件”的引脚。
6.3 低功耗模式下电流偏大:一路LDO没关干净
设备进入Stop模式后,实测电流是25微安,比预期的8微安高了不少。用电流探头逐路排查,发现LDO2的输出还有1.2V左右的残压,说明它没有完全关断。
查寄存器发现,LDO2的使能位确实写的是0,但LDO2的输出电容比较大,关断后电容上的电荷需要时间泄放。等了几秒钟再测,电流降到了8微安。所以低功耗测试的时候,不能刚进模式就测,要等电源完全稳定后再读数。
另外,如果LDO的输出端有LED或者电阻分压网络,即使LDO关断了,这些负载也会从电容上取电,导致残压维持。这种情况下要么加一个MOS管做输出开关,要么把负载也一起关掉。
6.4 充电电流上不去:输入电源的限流设置
给电池充电的时候,发现充电电流只能到50mA,设的是200mA。查了半天,发现是输入电源的限流设得太低。PCA9422有一个输入电流限制寄存器,默认值可能比较保守。如果输入电源是USB口,限流可以设到500mA;如果是小容量的适配器,就要设低一点,避免把适配器拉垮。
这个寄存器的值和实际输入电流的关系是:输入电流 = 系统负载电流 + 充电电流。如果系统负载已经占了200mA,输入限流又设了300mA,那充电电流最多只能到100mA。所以设充电电流的时候,要把系统负载算进去,留够余量。
7. 几个让方案更稳的实操心得
7.1 上电默认配置的备份和恢复
PCA9422的寄存器配置在掉电后会丢失,每次上电都要重新写。如果MCU的固件升级或者复位,配置也会回到默认值。为了避免这个问题,我在固件里做了一个配置表,把所有需要修改的寄存器地址和值存成数组,上电初始化的时候一次性写入。这样即使芯片复位,也能快速恢复到工作状态。
配置表的结构大概是这样的:
typedef struct { uint8_t reg; uint8_t val; } pca9422_cfg_t; const pca9422_cfg_t pca9422_init_cfg[] = { {0x10, 0xD8}, // Buck1 3.3V {0x11, 0x28}, // LDO1 1.8V {0x12, 0x30}, // LDO2 3.0V {0x20, 0x0A}, // 充电电流100mA {0x21, 0x28}, // 截止电压4.2V // ... 其他配置 };写入的时候遍历这个数组,每写一个寄存器就检查返回值。如果有失败,记录错误码并重试一次。重试还失败就报错,说明硬件有问题。
7.2 用状态寄存器做故障诊断
PCA9422有几个状态寄存器,记录了当前的工作状态和故障信息。比如输入电源是否插入、电池是否在充电、是否发生过温保护等。在调试阶段,我习惯在初始化完成后把状态寄存器的值打印出来,确认芯片处于预期状态。
如果设备在现场出现异常,也可以通过I2C读取状态寄存器来远程诊断。比如设备不充电了,读一下状态寄存器就知道是输入电源没插、还是电池温度异常、还是充电器故障。这比盲目换板子高效得多。
7.3 布局布线对电源纹波的影响
PCA9422的Buck输出纹波跟布局关系很大。输入电容要尽量靠近VIN引脚,输出电容要靠近LX和GND引脚。电感的选择也重要,饱和电流要留够余量,DCR要小。我一开始用的电感DCR偏大,满载时效率只有80%左右,换了一个低DCR的电感后效率提到了90%以上。
LDO的输出电容建议用低ESR的陶瓷电容,容值在1微法到10微法之间。如果对纹波要求高,可以再加一个π型滤波。但要注意,LDO的输出电容太大会影响环路稳定性,具体容值范围要参考手册里的稳定性曲线。
7.4 固件升级时的电源保持策略
如果设备支持固件升级,升级过程中绝对不能掉电。我的做法是在升级前通过I2C把PCA9422的输入电流限制调到最大,确保充电和系统供电都有足够余量。同时把MCU的低功耗模式禁用,防止升级过程中误入Stop模式。
如果电池电量低于20%,升级前先提示用户充电,或者拒绝升级。这个逻辑虽然简单,但能避免很多“升级变砖”的售后问题。
8. 写在最后
这套PCA9422加STM32L152RE的电源管理方案,我从选型到量产大概花了两个月时间,中间踩的坑主要集中在I2C配置和低功耗模式联动上。回过头看,最关键的几个点其实是:把芯片的每个配置引脚搞清楚、把上电时序和寄存器默认值确认好、把低功耗模式下的每一路输出都测一遍。
如果你正在做类似的便携设备,我的建议是先在开发板上把PCA9422的每一路输出都单独调通,用万用表和示波器确认电压和纹波都符合预期,然后再集成到自己的板子上。不要一上来就画板子打样,否则出了问题很难定位是硬件还是软件。
另外,PCA9422的手册虽然写得比较详细,但有些寄存器的默认值和行为在不同批次之间可能有差异。批量生产前最好抽几颗芯片做一致性测试,确认配置写入后读回的值都一致。这个步骤花不了多少时间,但能避免批量出问题。
最后分享一个小技巧:在固件里加一个电源管理的自检函数,上电时自动检查每一路输出的电压是否在允许范围内,如果偏差超过5%就记录错误码。这个自检可以在每次上电时跑一遍,也可以在低功耗唤醒后跑一遍。有了这个机制,现场出问题的时候至少能知道是哪一路电源不对,排查起来会快很多。