最近在跟进一个低功耗采集终端的电源部分,原来的方案其实是“散件堆叠”:一颗 LDO 稳 3.3V,一颗升降压给通信模块,再用 GPIO 控制负载开关。MCU 用的是 ATmega32,控制逻辑不算复杂。可真把板子拿回来实测,问题一个接一个——睡眠电流压不下去,供电时序只能靠 RC 延时硬凑,电池电量全靠电阻分压加 ADC 毛估。最后我把方案整个推掉,换成了 PCA9422 这颗 PMIC 配合 ATmega32 的组合,一颗芯片解决充放电、多路输出、电压监测、时序控制,MCU 只负责配置电源参数和跑低功耗状态机。整个过程从硬件到固件有不少值得记录的细节,这篇文章把完整设计和踩坑排查写出来,给正在做电池供电产品的嵌入式工程师做个参照。
1. 为什么我把分立电源方案推翻,换成 PMIC+MCU 双芯片组合
1.1 原方案的三处硬伤
第一处是静态电流。LDO 本身的地电流、负载开关的驱动电流、分压电阻的漏电流,这些单看都不大,叠在一起就成了睡眠电流的大头。我实测过那版板子,MCU 进入 Power-down 之后整板还有 1.2mA 左右,在电池供电产品里这个数非常难看。要说哪颗芯片有问题也说不上,就是那种“每个器件都合理,整体结果没法用”的典型情况。
第二处是上电时序。原来靠 RC 延时错开各路电源,本质是猜时间而不是定状态。环境温度一变、电容老化一点,时序就漂。更麻烦的是 GPIO 控制负载开关的时候,MCU 自己的供电还没稳定,程序跑飞的概率很高,只能靠外部上拉和看门狗兜底。
第三处是监测手段太弱。电池电压靠两个电阻分压进 ATmega32 的 ADC,充电状态完全不知道,板子是不是在充电、电池是不是充满了,只能靠经验猜。外设的工作电流也没有任何回读手段,出了故障很难定位是负载短路还是电源没起来。
1.2 PCA9422 和 ATmega32 的角色划分
新方案最大的变化是把“电源”从一堆分立器件变成一颗可配置的 PMIC。PCA9422 内部集成了电池充电管理、多路 DC-DC 输出、LDO、电压电流监测 ADC、输出时序控制,以及过压/欠压/过流保护。这些功能如果全部用分立器件搭,至少要多占 3 倍板面积,而且静态功耗根本做不到 PMIC 的水平。
ATmega32 在这里的定位不是“电源本身”,而是“电源策略大脑”。PCA9422 再智能,它的判断逻辑也是固定的;而 ATmega32 知道系统当前处于哪个任务阶段,该让哪路电源工作、哪路应该关掉、什么时候进深度睡眠,这些都是业务层面的判断,PMIC 做不了。所以分工很清晰:PCA9422 负责功率路径和底层保护,ATmega32 负责参数配置、状态切换和故障恢复。
选 ATmega32 而不是更小封装的 MCU,是因为它的外设刚好够用且不过剩。硬件 TWI(即 I2C)接口可以直接和 PCA9422 通信;外部中断 INT0 可以接收 PMIC 的中断信号;AD 转换在需要额外传感器采集时不必外挂;Power-down 模式下自身静态电流可以做到微安级。这些特性对电源管理这种“平时睡、偶尔醒、醒了干正事”的负载模型非常契合。
1.3 先用一张表把系统电源需求锁死
任何电源设计都怕“边做边改”,所以动手画板子之前,我先列了一张需求表:
| 负载域 | 目标电压 | 最大电流 | 供电来源 | 工作模式 |
|---|---|---|---|---|
| MCU 及数字逻辑 | 3.3V | 250mA | PCA9422 Buck1 | 常开 |
| 传感器/通信模块 | 3.3V | 120mA | Buck1 后级负载开关 | 周期唤醒 |
| 模拟前端 | 1.8V | 20mA | LDO | 仅在采样时开启 |
| 内部逻辑参考 | 1.2V | 5mA | Buck2 | 常开(可关) |
这张表的作用有两个:一是确定 PCA9422 至少需要两路 Buck 加一路 LDO,二是明确哪些电路必须常开、哪些可以随任务切换。后级负载开关虽然还是需要,但只负责通断,不需要承担稳压功能,设计难度大幅降低。
2. 围绕 PCA9422 的管脚级理解,先别急着写代码
2.1 数据手册里最值得先读的几页
拿到一颗没接触过的 PMIC,我一般不会从第一页线性读下去,而是直接翻四部分:管脚图、功能块框图、I2C 寄存器映射、上电时序图。管脚图告诉你能接什么;功能块框图告诉你内部信号怎么走;寄存器映射决定你能不能配置出想要的行为;上电时序图则决定了系统复位之前哪一路电源先起来。PCA9422 这四部分看完,基本就知道这颗芯片的脾气了。
有一个容易忽略的点:PMIC 的默认上电状态和配置后的状态可能完全不同。有些寄存器上电后有默认值,如果你希望一上电就是 3.3V 主输出,必须确认默认电压确实是你需要的;如果默认输出不对,就得设计成“先由低功耗待机轨供电,MCU 起来后立刻通过 I2C 改写配置”。这直接影响 ATmega32 自己的供电设计。
2.2 我最后确认的几个关键功能块
PCA9422 的内部功能块在我这颗样片上包括电池充电管理、两路同步 Buck、一路 LDO、一个用于监测电压电流的 ADC,以及一组 GPIO 和中断控制。充电管理支持 CC/CV 模式,充电电流可以通过寄存器设置;Buck 支持 PWM 和 PFM 两种工作模式,轻载时自动切到 PFM,这一点对降低静态电流至关重要;ADC 可以回读输入电压、电池电压和芯片温度,省掉了外部采样电路。
中断控制也很关键。PCA9422 的 INT 引脚低有效,可以配置成充电完成、故障触发、电源正常(PGOOD)翻转等事件源。ATmega32 的 INT0 外部中断就挂在 INT 上,平时睡眠,事件来了立刻唤醒。这样系统不用轮询 PMIC 状态,也符合低功耗设计的习惯——能睡就睡,别没事睁眼。
2.3 ATmega32 与 PCA9422 的连线规划
实际的管脚连接比想象中简单。SDA 和 SCL 接 ATmega32 的 TWI 引脚,也就是 PC1 和 PC0;PCA9422 的 INT 接 ATmega32 的 PD2 即 INT0;EN 引脚接一个 GPIO,能让 MCU 在极端情况下强制关断 PMIC 输出。ADDR0 引脚决定 I2C 地址,我在板上固定接 GND,从机地址是 0x60。
| PCA9422 引脚 | ATmega32 引脚 | 说明 |
|---|---|---|
| SDA | PC1 (TWI SDA) | 400kHz I2C 数据 |
| SCL | PC0 (TWI SCL) | 400kHz I2C 时钟 |
| INT | PD2 (INT0) | 低电平触发唤醒 |
| EN | PD7 (GPIO 输出) | 强制开关 PMIC |
| VOUT1 | VCC | 3.3V 主供电 |
| GND | GND | 共地 |
这个方案里 MCU 只用了 5 个引脚,剩下的资源仍然可以留给传感器读取和数据通信。有一种常见错误是把 I2C 地址和 ADDR 引脚的上下拉搞反,导致总线地址错位,用逻辑分析仪也查了半天,这个问题后面章节会展开讲。
3. 硬件电路的四个关键设计点
3.1 输入级:电池接入、输入电容和防倒灌
电池输入不是把正负极接到 PMIC 上就完事了。PCA9422 的输入引脚前需要一颗大容量储能电容并联一颗高频去耦电容,我这里用的是 10uF X7R 加 0.1uF C0G,两个都尽量贴近芯片引脚。电池线材较长时,热插拔瞬间会产生电压尖峰,输入端的 TVS 管虽然不一定写进原理图,但射频干扰强的环境里建议留一个位置。
如果系统是 USB 和电池同时接入,还要注意路径管理。PCA9422 的充电输入和系统负载如果共用一条路径,充电电流和系统电流会叠加,需要确认输入源能承受总和。实际上我在调试中遇到过拔掉 USB 的瞬间系统复位,这个后面在第 5 章详细说,这里先记一个结论:输入电容不能只看容值,ESR 和摆放位置同等重要。
3.2 Buck 输出级的电感选型和计算
电感是 Buck 电路最核心的储能元件。用公式计算最小电感量时,我习惯按纹波电流为额定电流的 30% 来算。以 3.3V/600mA 这一路为例,输入电压取 3.7V,开关频率按芯片手册典型值 1.5MHz 计算:
L(min) = (VIN - VOUT) × VOUT / (f × ΔI × VIN)
= 0.4 × 3.3 / (1.5e6 × 0.18 × 3.7)
算出来大约是 1.3uH,考虑负载瞬态和电感容差,我实际选了 2.2uH 的电感。饱和电流要留 1.5 倍以上余量,这里选了额定 1.2A 饱和电流的型号,防止重载瞬间电感饱和导致电流尖峰。
输出电容我也给了两档:10uF 陶瓷电容保证低频稳定,再加 0.1uF 高频电容压纹波。陶瓷电容的直流偏压特性会把有效容值打折扣,所以标称 10uF 的电容在 3.3V 偏压下实际可能只有 6~7uF,布局时在意的话可以选更高耐压等级的规格。
3.3 I2C 和中断线的电平与上拉设计
PCA9422 的 I2C 电平由它的 VDDIO 引脚决定,而 VDDIO 通常接 3.3V,和 ATmega32 一致,所以两者直连不需要电平转换。倒是上拉电阻需要认真选。400kHz 的 I2C 总线在 3.3V 电平下,上拉电阻选 4.7k 比较稳妥;如果总线电容较大,可以降到 2.2k,但要注意上拉越小待机电流越大。
INT 中断线是开漏输出,同样需要上拉。我在 INT 线上放了 10k 上拉电阻,这样平时为高电平,事件发生时被拉低,ATmega32 的 INT0 配置成低电平触发,可以在 Power-down 模式下降沿或者低电平都能唤醒。这里有个细节:如果用边沿触发,PMIC 的中断源如果没有被及时读取清除,下次事件可能会漏掉;低电平触发相对安全,因为只要中断标志没清,INT 就会一直拉着低电平,MCU 醒来后读状态寄存器再释放。
3.4 电池电压分压采样与 ADC 抗干扰
虽然 PCA9422 内置 ADC 可以读电池电压,但 ATmega32 自己也需要一个独立的电压观测量,方便在 PMIC 还没起来时做基本判断。电池电压最高 4.2V,ATmega32 用内部 2.56V 基准,所以分压比要设计在 4.2V 时 ADC 输入不超过 2.56V。我用了 12k 和 18k 两个电阻,4.2V 时分压点约 2.52V,留有很小余量。
分压电阻的阻值不能太大也不能太小。太大则采样时源阻抗过高,ADC 采样电容充电不足;太小则分压网络本身就在耗电。我选 12k/18k 这个量级,总阻抗 30k,配合 1k 串联电阻和 0.1uF 滤波电容组成一阶低通滤波,既能滤掉开关噪声,又能保证 ADC 采样稳定。睡眠时这个分压网络会有约 0.1mA 的电流,如果敏感,可以加一个 MOSFET 断开采样网络,只在测量瞬间连通。我这次没加,但给后来者一个提醒。
4. ATmega32 固件:从 TWI 握手到低功耗状态机
4.1 TWI 初始化和读设备 ID 的握手逻辑
ATmega32 的 TWI 模块就是硬件 I2C 控制器,不需要软件模拟时序。初始化主要设置速率和使能模块,然后读一次设备 ID 做握手,确认总线和 PCA9422 都在线。
#define PMIC_ADDR 0x60 #define F_CPU 16000000UL void twi_init(void) { // SCL = F_CPU / (16 + 2 * TWBR * 4^TWPS) // 16MHz、TWBR=12、TWPS=0 时 SCL = 400kHz TWBR = 12; TWSR = 0x00; TWCR = (1 << TWEN); } static uint8_t twi_start(void) { TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); return (TWSR & 0xF8); } uint8_t pmic_read_reg(uint8_t reg, uint8_t *val) { uint8_t status; status = twi_start(); if (status != 0x08) return 1; TWDR = (PMIC_ADDR << 1) | 0; // 写方向 TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x18) return 1; TWDR = reg; // 寄存器地址 TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x28) return 1; // 重复起始,转为读 TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x10) return 1; TWDR = (PMIC_ADDR << 1) | 1; // 读方向 TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x40) return 1; // 读数据,主机回 NACK 表示最后一个字节 TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x50) return 1; *val = TWDR; // 停止 TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); return 0; }初始化后我会做一次握手:连续读三次设备 ID,三次结果一致才认为通信可靠。这个习惯是从调试中养成的,因为 PMIC 的上电完成需要一点时间,MCU 跑得比 PMIC 快,上来就读很容易碰到 NACK,加个重试机制能避免很多“偶发故障”。
4.2 电源配置寄存器的写入和读回校验
PCA9422 的寄存器配置有些带写保护,直接写无效,必须先解除保护再操作。我建议把“写寄存器”封装成一个带校验的函数,写入后立刻读回比对,不一致就重试几次并记录错误码:
uint8_t pmic_write_reg(uint8_t reg, uint8_t val) { uint8_t status, retry; for (retry = 0; retry < 3; retry++) { status = twi_start(); if (status != 0x08) continue; TWDR = (PMIC_ADDR << 1) | 0; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x18) continue; TWDR = reg; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x28) continue; TWDR = val; TWCR = (1 << TWINT) | (1 << TWEN); while (!(TWCR & (1 << TWINT))); if ((TWSR & 0xF8) != 0x28) continue; TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN); // 读回校验 uint8_t readback; if (pmic_read_reg(reg, &readback) == 0 && readback == val) { return 0; } } return 1; }配置内容按需分层:最基础的是设置各路输出电压和限流,然后是充电电流和截止电压,最后是中断使能。每次配置完一定做一次全寄存器回读,保存一份配置副本到 EEPROM,方便复位后快速恢复。这样如果 PMIC 被异常复位,ATmega32 能在 100ms 内把它重新拉回正确状态。
4.3 低功耗状态机:运行、空闲、深度睡眠
整个固件按照状态机运行。RUN 状态里,ATmega32 以 16MHz 全速跑,传感器上电,完成一次采集;IDLE 状态里关掉传感器电源,MCU 时钟降到 1MHz,AMC 保持 I2C 可通信状态;DEEP 状态里 MCU 进入 Power-down,只留外部中断唤醒。
typedef enum { S_RUN, S_IDLE, S_DEEP } power_state_t; volatile uint8_t pmic_int_flag = 0; ISR(INT0_vect) { pmic_int_flag = 1; } void enter_deep_sleep(void) { // 关掉不必要的负载 sensor_power_off(); comm_power_off(); // 关 ADC 降漏电 ADCSRA &= ~(1 << ADEN); // 配置低电平唤醒 EICRA &= ~(1 << ISC01); EICRA |= (1 << ISC00); // 低电平触发 EIMSK |= (1 << INT0); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sei(); sleep_cpu(); sleep_disable(); // 醒来先清标志 pmic_int_flag = 0; }有一点非常容易被坑:进入睡眠前必须保证 INT0 的电平处于确定的逻辑状态。如果 INT 引脚悬空或者电平不确定,睡眠后立刻被唤醒,系统会反复“假醒”。所以 INT 上拉电阻不是可有可无,而是低功耗状态机正常工作的前提。
4.4 中断处理与通信失败恢复
PCA9422 的 INT 中断拉低后,ATmega32 第一件事不是盲目恢复供电,而是通过 I2C 读取中断状态寄存器,确认是哪种事件。可能是充电完成、电压跌落、过流保护触发,也可能是某个 Buck 输出 PGOOD 翻转。只有读完状态寄存器并清标志,INT 引脚才会释放。
I2C 通信失败时的恢复策略也要预置好。我的做法是:如果连续三次读同一个寄存器失败,先假设是总线问题,把 TWI 模块软复位一遍再重试;如果软复位后仍失败,再假设是 PMIC 进入了保护性关断,这时读取故障寄存器,根据错误码决定是重启 PMIC 还是上报故障。这个分层恢复顺序很重要——盲目写寄存器可能把故障隐藏掉,导致保护机制失效。
5. 调试阶段踩过的坑,以及完整的排查思路
5.1 PMIC 不应答:I2C 波形有 SCL 无 ACK
第一次上电就遇到怪问题:用逻辑分析仪抓 I2C,SCL 时钟在跑,SDA 地址字节发出后没有 ACK 位。排查第一步我用万用表量了 PCA9422 的 VDDIO 和 GND,电压正常;第二步量 EN 引脚,发现为低——问题在这。
原理图里 EN 接的是 ATmega32 的 PD7,PD7 默认是高,但上电瞬间 MCU 还没有运行,I/O 引脚处于高阻态,EN 被下拉电阻拉低,导致 PMIC 一直处于关闭状态。后来我把 EN 改为上拉到 VIN,并串一个 10k 电阻再接 PD7,这样 MCU 跑起来之前 PMIC 默认开启,PD7 可以输出低电平来强制关断。这个改动解决了“上电时序不同步导致 PMIC 不应答”的问题。
5.2 配置 3.3V 输出,实测只有 3.16V
寄存器里明明设置的是 3.3V,量出来只有 3.16V,偏低了约 4%。第一反应是芯片有问题,换了一颗还是一样。后来我读回寄存器,发现写入的值和读回的值不一致,再仔细看数据手册,原来那个寄存器的位定义和我想的完全不同——电压配置位不连续,中间有位宽不同的步进值。
排查链路其实很直接:先读回寄存器值,确认写入生效;再对着手册把二进制逐位拆开,按位宽重新计算目标电压;最后用示波器测空载和满载的电压漂移。问题本质是寄存器位定义理解错误,不是硬件故障。这次之后我养成了一个习惯:任何电压配置寄存器,第一次写入后必须读回并拆解字段验证,不能只看最终输出电压。
5.3 睡眠电流比预期高一个数量级
新板子睡眠实测 230uA,和预期 46uA 差了快五倍。排查时我没有直接拆电阻,而是按模块逐个关断,用排除法缩小范围。先断开传感器负载,电流只降了一点;再断开通信模块,还是变化不大;最后把 PCA9422 的 Buck2 输出禁用,睡眠电流一下掉到 50uA 以内。
原因浮出水面:Buck2 给 1.2V 逻辑参考供电,这路负载本身只有几毫安,但 Buck2 一直工作在 PWM 模式,开关损耗直接摊在睡眠电流里。后来我把 Buck2 配置成 PFM 模式,轻载时自动降频,睡眠电流降到说明书量级。这个坑说明 PMIC 的所有输出都要在低功耗状态下逐一验证,默认的强制 PWM 模式是低功耗杀手。
5.4 电池切换瞬间系统复位
USB 供电同时给电池充电时一切正常,一拔 USB,系统瞬间黑屏重启。用示波器抓 VIN 波形,发现切换瞬间 VIN 跌到约 2.0V,低于 PMIC 的 UVLO 阈值。按常理 PMIC 应该在输入跌落时无缝切换到电池,但实际情况是输入电容不够大,切换瞬间电池回路还没来得及接管,VIN 先被拉穿。
解决办法分两步:一是把输入侧 10uF 电容换成两个 10uF 并联,并尽量靠近 PMIC 输入引脚;二是确认 EN 引脚上的滤波电容,防止切换瞬间 EN 出现毛刺误关断。改完后用示波器复测,切换瞬间 VIN 跌落最小到 3.2V,系统保持运行,没有再复位。
6. 实测数据与续航估算
6.1 各状态电流实测记录
整板调试完成后,我重新测了一组功耗数据,条件为电池电压 3.7V,MCU 使用内部 1MHz 时钟,PMIC 各路输出按前面表格配置:
| 系统状态 | 测试条件 | 板级总电流 |
|---|---|---|
| 深度睡眠 | MCU Power-down,Buck2 切 PFM,传感器电源全关 | 46uA |
| 空闲待机 | MCU 运行 1MHz,传感器电源关,I2C 空闲 | 3.8mA |
| 正常运行 | MCU 16MHz,传感器开启,PMIC PWM 模式 | 18.5mA |
| 满负荷工作 | 传感器+通信发射 | 142mA |
| 电池充电 | USB 5V 输入,0.5C 充电,系统同时运行 | 620mA(输入侧) |
6.2 续航估算方法
用平均电流法估算续航。假设设备每 10 分钟采样一次,每次运行 3 秒,其余时间深度睡眠,则一个周期 600 秒,其中 3 秒消耗 18.5mA,597 秒消耗 46uA:
Iavg = (3 × 18.5 + 597 × 0.046) / 600 ≈ 0.14mA
如果配 3400mAh 的 18650 电池,理想续航约为 3400 / 0.14 ≈ 24285 小时,约合 2.77 年。这个估算没有考虑电池自放电、保护板静态电流和低温容量衰减,实际留 30% 余量的话,工程上可以按 1.8~2 年去规划维护周期。
对比旧方案,睡眠电流从 1.2mA 降到 46uA,这是续航提升的最大来源。原来每天光睡觉就吃掉 28.8mAh,现在只吃 1.1mAh,差距接近一个数量级。这就是把 PMIC 纳入低功耗设计的直接收益。
6.3 和旧方案的最终对比
| 指标 | 旧分立方案 | PCA9422 + ATmega32 |
|---|---|---|
| 睡眠电流 | 1.2mA | 46uA |
| 上电时序 | RC 延时估算 | 寄存器精确配置 |
| 电池电量监测 | 分压 ADC 估算 | PMIC 内置 ADC + 充电状态 |
| 故障保护 | 个别芯片自带,互相独立 | 集中式 OVP/UVP/OCP,中断上报 |
| 板级面积 | 6 个功率器件 | 1 颗 PMIC,面积约少一半 |
| 调试手段 | 全靠示波器猜波形 | I2C 寄存器全可读 |
从设计角度讲,新方案真正解放的不是硬件布局,而是调试效率。电源的每一个状态都可以通过寄存器读回来,出了问题不用拿着示波器到处猜。我个人的体会是,PMIC 加 MCU 的组合值得多用,但前提是先把芯片的功能边界摸清,尤其是寄存器写保护、默认状态和中断行为这几个最容易出问题的点。如果上来就照着参考设计抄板子,遇到坑反而不知道从哪里查起。做低功耗电源管理,慢一点把数据手册吃透,反而比埋头调代码更快。