做低功耗物联网设备的朋友,应该都对电源管理这块又爱又恨。我前阵子用 PCA9422 和 STM32F100ZE 这两颗料,完整搭过一套电源管理方案,把锂电池充电、多路供电、电量监测、低功耗切换这几件事全收了进去。PCA9422 作为 PMIC 负责底层电源变换和充电控制,STM32F100ZE 作为主控负责配置、监控和策略调度,两端配合下来,整个板子的电源部分非常干净。这篇文章就围绕这个组合,把整体架构、硬件设计、固件实现和调试过程里比较关键的东西完整讲一遍,适合正准备做类似低功耗产品、或者想搞明白 PMIC 如何跟 MCU 协作的朋友参考。
1. 整体设计思路与方案选型
1.1 这里说的“完整电源管理”到底要管什么
很多人一提电源管理,第一反应就是“能开机、电压对就行”,但真正做产品就会发现远没那么简单。我当时做的项目是一个低功耗温湿度标签,一节锂电池供电,板上同时挂了主控、无线通信模块、传感器、LED 指示灯,还有一块很小的屏幕。仔细一列需求,发现电源这块要管的事特别多:电池要能充,充电电流要能根据电源适配器能力调整;系统需要多路电压,3.3V 给主控和传感器,1.8V 给无线模块和部分外设,LED 驱动还需要一个独立可关断的供电轨;待机功耗要压到微安级,否则标签放半个月就没电;另外还要实时知道电池还剩多少电、有没有过温、充电有没有异常。
如果这些事儿全用分离器件去拼,电路会变得非常庞大。一路 LDO 加一路 DCDC,再加一个充电管理 IC,最后接一堆电平转换、MOS 管开关,硬件改版一次,软件也跟着遭罪。所以这类需求下,最自然的选择就是把“电源管理”这件事交给一颗专门的 PMIC,把“策略和控制”交给 MCU。PMC 管的不是某一路电,而是一个完整的电源子系统。
1.2 为什么选 PCA9422 搭配 STM32F100ZE
先说 PCA9422。这颗 PMIC 吸引我的点主要有三个。第一,它把充电回路和多路 DC-DC 整合在一起,外围只需要很少的阻容和电感,BOM 很干净。第二,关键参数全部可以通过 I2C 改写,输出电压、充电电流、中断使能这些都不用改电阻,调试阶段特别省事。第三,它内部带了 ADC 和放电监测相关电路,电池电压、电流、温度这类信息可以直接从寄存器里读,省掉了外部分压采样网络。
再说为什么主控选了 STM32F100ZE。这颗芯片虽然是 Cortex-M3 内核、主频 24MHz,性能不算强,但做电源管理策略绰绰有余。而且它的外设资源非常齐全,I2C、USART、ADC、定时器、丰富 GPIO 全都有,144 引脚封装给扩展留了很大余量。我当时手上正好有这颗料,加上它价格实在,就直接定了。这里有个经验:选主控不要只看算力,要看接口数量够不够、低功耗模式好不好用、调试工具熟不熟。F100ZE 的低功耗模式在 Stop 下能保持 RAM 内容,配合 RTC 唤醒做低功耗标签项目完全够用。
这类组合的另一个优势是,芯片之间的协作关系非常清晰。PCA9422 负责“把电池和外部输入变成各路稳定的电源”,STM32F100ZE 负责“告诉 PMIC 怎么配置、读取状态、在合适时机切到合适的电源策略”。相当于 PMIC 是执行层,MCU 是管理层,职责边界清楚,出了问题也容易定位。
1.3 系统电源树设计与启动顺序
电源树是整个设计的骨架,我一般先画清楚再动电路。这个项目的电源树大致是这样的:
| 电源通道 | 输出电压 | 主要负载 | 时序/开关策略 |
|---|---|---|---|
| 输入 VBAT | 3.0V~4.2V | 电池直接通路 | 始终在线 |
| BUCK 通道 0 | 3.3V | MCU、传感器、指示灯 | 常开,系统启动后输出 |
| BUCK 通道 1 | 1.8V | 无线模块、电平转换 | 由 GPIO 控制使能 |
| LDO 通道 | 3.0V | LCD 背光/模拟电路 | 按需开,低功耗时关断 |
| 充电输入 | 5V USB | 充电回路 | 检测到 VBus 才启用 |
这个划分的原则有两条。一是主控供电必须最可靠,所以 3.3V 这一路放在常开通道上,上电先建立,确保 MCU 第一时间跑起来。二是耗电大或不需要一直供电的外设,尽量放在可独立关断的通道上,这样进入低功耗模式时可以直接把对应输出切掉,把待机电流压下去。启动顺序方面,PCA9422 上电后先进入默认安全状态,由 MCU 初始化完成后写入新的配置,再逐个使能输出通道。这样就避免了外设在主控还没初始化时就进入未知状态。
这套设计下来,比原来的分离器件方案简洁很多。硬件上少了一堆 LDO、MOS 管和分压电阻,软件上也统一成一个 I2C 读写的问题,整个项目后续迭代都轻松不少。
2. 硬件设计要点与关键参数
2.1 PCA9422 的功能拆解
PCA9422 这一类 PMIC 的寄存器结构虽然各家有差异,但功能模块大致是固定的。我按功能拆开讲,方便大家对照手册快速定位。
第一个模块是充电控制。它支持对单节锂电池进行线性充电,输入一般接 5V USB。需要关注的寄存器主要是充电电流设定、充电截止电压、充电超时时间、充电状态标志。充电电流建议根据电池容量来定,我实际用的是 0.5C 左右,也就是 1000mAh 的电池按 500mA 充,既能保证速度,又不会让电池温升太快。如果输入电源能力有限,还需要把输入电流限制配小一点,防止把适配器拉垮。
第二个模块是降压输出通道。PCA9422 上有多个功率输出通道,我用到的有降压通道和 LDO 通道。降压通道效率高,适合电流大的负载;LDO 通道纹波小,适合模拟电路,但压差大时功耗也会大。输出通道的电压值、软启动时间、使能控制都在寄存器里配置。
第三个模块是监测与中断。内部 ADC 可以采电池电压、充电电流、芯片温度等参数,状态寄存器里也会有过压、欠压、过温、充电完成、充电异常这些标志。中断引脚 INT 可以配置成事件触发,MCU 通过外部中断来感知电源状态变化,不用一直轮询。
使用 PCA9422 时最重要的一点是,上电默认值不一定适合你的系统。有些输出通道可能默认关闭,有些中断默认屏蔽,所以初始化代码里第一件事就是完整配置一遍,而不是假设“上电就能用”。
2.2 STM32F100ZE 的资源分配与引脚规划
STM32F100ZE 的资源在这个项目里不算紧张,但引脚规划我还是提前做了表,避免画板时乱了。
| 功能 | STM32F100ZE 引脚/外设 | 说明 |
|---|---|---|
| I2C 总线 | I2C1,SCL/SDA 接到 PCA9422 | 400kHz 快速模式,加 4.7k 上拉 |
| 电源中断输入 | GPIO 外部中断,如 PA0 | 下拉配置,上升沿/低电平有效需按手册 |
| 充电状态 LED | PB0 推挽输出 | 接 LED 限流电阻 |
| 无线模块使能 | PB1 推挽输出 | 控制 BUCK 输出通道使能 |
| RTC 唤醒 | 内部 RTC | 用于周期性唤醒低功耗状态 |
| SWD 调试口 | PA13/PA14 | 保留,方便调试 |
I2C 这块要特别注意。PCA9422 是标准的 I2C 从机,地址由硬件或寄存器决定,上电后建议先读一次芯片 ID 确认总线没问题。STM32 的 I2C 引脚默认是开漏模式,所以外部必须有上拉电阻,我用的是 4.7kΩ,400kHz 下波形很好。如果板上还有其他 I2C 设备,要注意地址冲突和总线电容,总线过长或挂载设备过多时要适当降低速率。
引脚分配上,另一个容易忽视的点是中断引脚。PCA9422 的 INT 脚要接到 STM32 上带外部中断能力的引脚,并且要配置为上拉或下拉的输入模式,防止悬空误触发。我在早期调试时就是没注意这个,充电器一插上就触发中断,查了半天才发现是引脚配置问题。
2.3 PCB 布局与滤波的细节
电源电路的 PCB 布局,直接影响纹波和稳定性。PCA9422 的降压通道用的电感,我建议选屏蔽式功率电感,型号按输出电流和纹波要求来。电容方面,输入输出都要放足够的陶瓷电容,特别要注意靠近芯片引脚放置,缩短回路路径。
有两种比较容易踩的坑。一是地回路处理不当,大电流的功率地和小信号的模拟地混在一起,导致 ADC 采样值乱跳。二是电感底下的铺铜处理,大面积铺铜会改变电感特性,反而可能让噪声变大。比较好的做法是,按照芯片手册里的参考布局来画,功率回路尽量短粗,反馈走线远离电感,保持 PCB 布局整洁。
另外,如果板上同时有无线模块,电源噪声会直接影响射频性能。我在布局时尽量让无线模块的供电线远离电感,并在靠近无线模块的位置额外加了一级小电容滤波,实测对射频灵敏度有帮助。
3. 固件实现与核心流程
3.1 I2C 驱动编写与寄存器读写封装
固件开发的第一步是把 I2C 驱动跑通。STM32F100ZE 的 I2C1 用标准外设库或 HAL 都可以,我这边用的是 HAL 库,先把初始化写好:
#include "stm32f1xx_hal.h" I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 400000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1); }初始化完成后,读写封装成两个函数:
#define PCA9422_I2C_ADDR 0x28 // 实际地址以芯片手册为准 uint8_t pca9422_read_reg(uint8_t reg) { uint8_t val = 0; HAL_I2C_Mem_Read(&hi2c1, PCA9422_I2C_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); return val; } void pca9422_write_reg(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(&hi2c1, PCA9422_I2C_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); }寄存器地址和具体位域定义,建议在头文件里做成宏,方便维护。封装层面我习惯再加一层“读改写”函数,因为很多寄存器只有某些位是可写的,直接整片覆盖容易误清状态位。比如先读回寄存器值,改完目标位段再写回去,这样能减少因为不了解保留位含义而踩的坑。
3.2 上电初始化流程与关键配置
系统上电以后,STM32F100ZE 先执行基础时钟配置和 I2C 初始化,然后就进入 PCA9422 的初始化流程。我的实际顺序如下:
void pca9422_init(void) { uint8_t id = 0; // 第 1 步:检查 I2C 通信是否正常 id = pca9422_read_reg(REG_CHIP_ID); if (id != EXPECT_CHIP_ID) { error_handler(); return; } // 第 2 步:设置各路输出电压 pca9422_write_reg(REG_BUCK0_CFG, 0x1A); // 3.3V pca9422_write_reg(REG_BUCK1_CFG, 0x0F); // 1.8V pca9422_write_reg(REG_LDO_CFG, 0x14); // 3.0V // 第 3 步:配置充电电流与截止电压 pca9422_write_reg(REG_CHG_CFG, 0x32); // 500mA pca9422_write_reg(REG_CHG_VLIM, 0x04); // 4.2V // 第 4 步:使能需要的中断 pca9422_write_reg(REG_INT_MASK, 0x00); // 开启所有电源事件中断 // 第 5 步:使能输出通道 pca9422_write_reg(REG_CTRL, 0x07); }每步为什么要这么做,我简单说一下。第 1 步读芯片 ID 是最重要的一次握手,它同时验证了地址、上拉、电平转换、I2C 时序,如果这里不过,后面全都不用谈。第 2 步必须在使能输出之前完成,否则通道已经输出默认电压,再改电压会有短暂波动,可能引起负载复位。第 3 步充电参数放到输出使能之后配置,是因为充电回路通常在检测到 VBus 以后才真正工作,不会跟主电源输出抢初始化资源。第 4 步和第 5 步打开中断和输出,顺序上先配中断再使能输出,可以保证输出建立过程中的异常事件第一时间被捕获。
需要提醒的是,芯片上电默认的 ID 核对完成以后,不要着急把整段初始化封装成一个“看不见细节”的黑盒函数。调试阶段尽量把每一步的寄存器值通过串口打印出来,或者用逻辑分析仪抓 I2C 波形确认。曾经有一次我改了输出电压配置,但代码里少写了一行寄存器使能,结果输出还是默认值,抓波形才发现写进去的值根本没生效。
3.3 电量监测与低功耗状态切换
电量监测这块,我主要是通过 PCA9422 内部 ADC 读电池电压寄存器来实现,配合软件做简单的电量百分比映射。比如读回电压值经过换算得到毫伏,再按电池放电曲线分几个区间,显示成电量百分比。不要求非常精确的话,这个方案性价比很高。
uint16_t get_battery_mv(void) { uint8_t adc_h = pca9422_read_reg(REG_VBAT_ADC_H); uint8_t adc_l = pca9422_read_reg(REG_VBAT_ADC_L); uint16_t raw = (adc_h << 8) | adc_l; return (uint16_t)((float)raw * ADC_LSB_MV); }低功耗状态切换是整个项目里比较关键的部分。我的做法是,STM32F100ZE 需要进入 Stop 模式前,先把外设关掉,然后把 PCA9422 不需要的输出通道关闭,再设置 RTC 唤醒时间,最后执行 STOP 指令:
void enter_low_power_mode(void) { // 关闭无线模块供电通道 pca9422_write_reg(REG_CTRL, 0x03); // 仅保留 3.3V 通道 // 关闭无关外设时钟 __HAL_RCC_GPIOB_CLK_DISABLE(); __HAL_RCC_GPIOA_CLK_DISABLE(); // 关闭 I2C,省掉总线静态功耗 HAL_I2C_DeInit(&hi2c1); // 配置 RTC 唤醒 HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_ALARM_A); // 进入 Stop 模式 HAL_SuspendTick(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); }唤醒以后,重新初始化 I2C,把 PCA9422 的输出通道重新打开,再恢复外设配置。这中间要注意一个细节:从 Stop 模式唤醒后,外设时钟状态可能已经改变,不能直接继续原先的操作,而是要重新做一遍初始化。我的代码里把“电源管理初始化”单独抽成了一个函数,唤醒和上电都调用它,减少重复代码。
3.4 中断处理与故障恢复
PCA9422 的 INT 引脚接到 STM32 的外部中断,中断回调里读状态寄存器,判断具体是什么事件:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == INT_PIN) { uint8_t status = pca9422_read_reg(REG_INT_STATUS); if (status & FLAG_CHG_DONE) { // 充电完成,更新电池状态 } if (status & FLAG_CHG_FAULT) { // 充电异常,进入保护逻辑 pca9422_write_reg(REG_CTRL, 0x00); } if (status & FLAG_OVERTEMP) { // 过温保护,关闭非必要输出 pca9422_write_reg(REG_CTRL, 0x01); } } }中断处理的第一原则是快进快出。中断里不要做 I2C 长阻塞操作,尤其是 400kHz 通信在中断里等应答这种事,会拖慢整个系统。我一般是在中断标志位置位后,只做最少的处理,具体逻辑放到主循环的任务里执行。如果某些状态必须立即响应,那也要设置超时保护,防止读状态寄存器卡死在中断里。
故障恢复方面,比较有效的做法是维护一个“电源状态机”,把正常运行、低功耗、充电中、故障保护这些状态管理起来。每个中断事件都触发状态机迁移,避免出现多套逻辑互相打架。我早期没做状态机时,出现过充电完成中断和低功耗切换同时发生导致输出混乱的情况,后来统一用一个状态机管理,问题就没了。
4. 调试实战与问题排查记录
4.1 I2C 通信异常的排查顺序
这个项目里我遇到最多的就是 I2C 通信问题。整理一下排查顺序,基本能解决九成的情况。
第一,检查地址。PCA9422 的 7 位地址写没写错,发送时有没有正确左移一位变成 8 位地址。我第一次就犯过这个错,直接把 7 位地址传给了 HAL 库,结果总线 ACK 一直收不到。第二,检查上拉电阻。I2C 开漏总线没有上拉啥都白搭,4.7k 是常用值,但总线过长、挂载设备多时可以换 2.2k 试试。第三,抓波形。用逻辑分析仪抓 SCL/SDA,看起始条件、地址帧、数据帧的时序对不对。很多“偶发错误”其实是上拉太弱导致上升沿过缓。第四,看电平匹配。如果 PCA9422 的电源域和 STM32 的 I2C 引脚电平不一致,需要加电平转换,否则读到的数据会乱码。
4.2 电源纹波超标的处理
有段时间无线模块的发射距离明显不够,排查到最后发现是 1.8V 电源轨纹波超标。改动主要有三处:把降压电感的额定电流提高了一档,避免大电流时电感饱和导致纹波变大;在输出端增加了一颗 10µF 陶瓷电容,同时保证它尽量靠近负载端;把反馈走线从电感下方绕开。改完以后纹波从 60mV 降到了 20mV 以内,问题解决。
4.3 充电管理上的典型 Bug
充电这块最开始有个问题:插上 USB 后,充电状态一直显示异常。后来发现是充电电流设得太大,而 USB 输入端的限流保护被触发了。把充电电流从 800mA 降到 500mA 以后,问题消失。还有个问题是电池电压采样不稳定,最后确认是 PCB 上采样走线太靠近电感,在地回路里出现了电压偏置,调整走线位置后数据就稳定了。
4.4 实测数据与优化记录
| 项目 | 优化前 | 优化后 |
|---|---|---|
| 待机电流 | 35µA | 8.5µA |
| 无线发射时系统电流 | 67mA | 61mA |
| 1.8V 纹波 | 60mV | 18mV |
| 电池电量误差 | ±20% | ±8% |
待机电流从 35µA 降到 8.5µA,主要是把 LDO 通道彻底关断、I2C 总线释放、并且把 INT 引脚的中断配置成了仅在事件发生时唤醒,避免每 10 秒就会被唤醒一次去轮询状态。电量误差减小是因为在软件里做了电压补偿,并且过滤了瞬时负载变化引起的电压跌落。
这几个问题排查下来,我最大的体会是电源调试不能靠猜。每次改动只动一个变量,改完以后用示波器或电流表记录数据,前后对比,比凭感觉试要高效得多。尤其是 PMIC 这种 I2C 配置的设备,寄存器状态和硬件电路是绑在一起的,错误的寄存器值会让硬件表现得很像一个“硬件故障”,这时候一定要先回到寄存器层面验证配置。
最后分享一个小技巧:在做这类电源管理项目时,板上一定预留一个独立的调试串口和一对 I2C 测试点。调试阶段把初始化过程、状态切换、中断触发都通过串口打印出来,I2C 测试点用来夹逻辑分析仪探头,很多看似玄学的问题,抓到波形和日志以后,基本都能定位到具体原因。这套 PCA9422 加 STM32F100ZE 的方案,我后续在低功耗传感类产品上还会继续用,整体来说是省心且可控的组合。