做低功耗设备的人应该都有体会:电源管理做得好的板子,功耗数字不是靠哪一颗芯片“省”出来的,而是靠整套供电架构一点点“抠”出来的。我前阵子给一款边缘采集终端做电源系统改造,主控用的是 PIC32MZ1024EFE144,跑 200MHz,片内外设很全,但供电需求也杂:数字逻辑要 3.3V,以太网 PHY 和电平转换要 1.8V,传感器组要一路能随时关断的电源轨,再加上复位、唤醒、深睡眠这些要求,原来的方案是在板上挂一排 LDO,结果待机电流压不下去,时序也全靠硬件凑。后来把 LDO 阵列换成 PCA9422 这颗 PMIC,用 I2C 把每路输出电压、开关、状态监控全部收进固件,才算是把“供电”真正变成了“电源管理”。
这篇文章我按自己做项目的顺序来写:为什么换方案、原理图和 PCB 阶段埋了哪些伏笔、I2C 寄存器怎么配、电源状态机怎么搭,最后是实测功耗数据和两个让我折腾到半夜的调试过程。内容偏工程实践,正在选 PMIC、想把 MCU 低功耗做扎实的硬件和嵌入式工程师可以直接参考。
1. 从 LDO 阵列到 PMIC:这套电源架构为什么值得改
1.1 原来那套方案的三个痛点
先说旧方案。当时为了快速出板,电源部分直接用了 LDO 阵列:5V 进来,一路 LDO 出 3.3V 给 MCU 和数字逻辑,一路 LDO 出 1.8V 给以太网 PHY,还有一路小电流 LDO 给传感器。功能上没问题,但仔细一算全是窟窿。
第一个窟窿是效率。线性稳压器的效率等于输出电压除以输入电压,5V 降到 3.3V 是 66%,降到 1.8V 只有 36%。整机平均负载 200mA 的话,LDO 上白白烧掉的功率就有差不多 0.5W,板子局部发热很明显,在密闭壳体内还会拉高环境温度。第二个窟窿是待机功耗。每颗 LDO 自身静态电流几十微安,五颗加起来就是几百微安,而这正好是设备“假关机”状态下待机电流超标的主要来源。第三个窟窿是时序。PIC32MZ 上电需要电源轨先稳、复位再释放,旧方案只能靠 RC 延时和使能脚串联来凑顺序,每改一次上电时序就要飞线、换电阻,量产时非常被动。
旧方案还有个隐性限制是没法动态调压。低功耗模式下想把主轨电压往下拉一点,LDO 阵列完全做不到,只能靠 MCU 内部手段硬扛。所以换 PMIC 不是赶时髦,而是这三条痛点已经影响到产品指标了。
1.2 PCA9422 能覆盖哪些供电需求
PCA9422 这颗 PMIC 的特点是面向低功耗便携设备,把多路降压 DC-DC 和 LDO 做进一颗芯片里,全部输出都通过 I2C 寄存器控制。我手头这个子型号的构成是两路 BUCK 加一路 LDO,输入电压范围大概在 2.8V 到 5.5V 之间,兼容 5V USB 和单节锂电池,输出电流从几百毫安到 1A 级不等,具体以所选子型号数据手册为准。选型的时候要特别注意:PCA9422 系列不同子型号的通道数、电流档位是有差异的,千万别拿着一个型号的功能框图去套另一颗芯片。
它的核心价值在于轻载时自动进入 PFM 模式,把空载损耗压得很低,这是 LDO 阵列给不了的;同时输出电压、电流限制、软启动斜率、Power Good 行为都能通过寄存器配置,这意味着电源策略可以完全由固件决定。内部还带欠压、过流、过温保护,故障状态能读回来,对远程维护很有用。
当然,这里要提醒一个“先有鸡还是先有蛋”的问题:MCU 要靠 PMIC 供电才能启动,而 PMIC 又需要 MCU 通过 I2C 来精细化配置。实际上不用慌,PMIC 的寄存器复位值会提供一组安全的上电默认输出,保证 MCU 能正常跑起来。等 MCU 启动完成后,再通过 I2C 把各路电压、开关、PGOOD 掩码配成最终形态。所以选型时第一件事就是核对默认输出电压是否满足主控的最小系统要求,这一步错了后面全是坑。
1.3 给 PIC32MZ1024EFE144 配电源轨要先算清楚的几笔账
PIC32MZ1024EFE144 的电源域看起来简单,实际有几处容易想当然。主电源是 3.3V,给 VDD 和所有 I/O 供电,全速跑 200MHz 时电流可能是百毫安级,这个电流如果走 LDO,前面算过效率只有 66%,发热量在小型终端上不可接受,所以主轨必须用 BUCK。
内核电压这块要理清一个概念:PIC32MZ 内部自带核心稳压器,从 3.3V 生成内核电压,外部不需要再单独做一路内核电源。有些设计为了极致功耗会把内部稳压器旁路,用外部 DC-DC 直接供核,但这个方案要仔细研究 ENVREG 相关引脚的配置,我们这次为了保证可靠性,保持了数据手册的默认做法。
以太网 PHY 的 1.8V 轨需要单独处理。这路负载不大,但对纹波敏感,一般要求纹波在 50mV 以内,而且要和 PHY 的复位释放时序配合好。传感器组则适合做成可关断 rail,平时不采集就整个断电,能省下不少功耗。把这些负载按电流、纹波、可关断性三个维度分类,电源树自然就出来了:大电流数字轨用 BUCK,敏感模拟用 LDO 或带强制 PWM 的 BUCK,可关断轨单独挂一路输出。
另外千万别忽略 PIC32MZ 的 VBAT 引脚,它给内部 RTCC 供电。如果产品要支持深睡眠唤醒计时,VBAT 必须保持有电,我把它接在 3.3V 主轨上,中间串一颗二极管做隔离,防止掉电时倒灌。
2. 原理图与 PCB 阶段就该埋好的伏笔:轨分配、时序和布局
2.1 我手上的电源树分配:2 路 BUCK 加 1 路 LDO 怎么分
PCB 还没画之前,先把电源树钉死。我的方案是 5V 输入进 PCA9422,BUCK1 出 3.3V 做主数字轨,BUCK2 出 1.8V 给以太网和电平转换,LDO1 出 3.3V 给传感器组,VBAT 从 BUCK1 经二极管单独拉出来。各路典型负载如下表:
| 电源轨 | 输出电压 | 主要负载 | 平均电流 | 峰值电流 | 控制方式 |
|---|---|---|---|---|---|
| BUCK1 | 3.3V | PIC32MZ、LED、数字逻辑 | 约 120mA | 300mA+ | I2C 可调、默认常开 |
| BUCK2 | 1.8V | 以太网 PHY、电平转换 | 约 60mA | 200mA | I2C 可调、可关断 |
| LDO1 | 3.3V | 传感器组、外部接口电源 | 约 20mA | 50mA | I2C 可关断 |
| VBAT | 3.3V/二极管隔离 | MCU 内部 RTCC | 微安级 | 微安级 | 跟随 BUCK1 |
为什么主轨用 BUCK 而不是 LDO,前面已经算过效率账。为什么传感器轨用 LDO 而不是第三路 BUCK?因为电流只有 20mA,用 BUCK 省下的功耗有限,反而多一颗电感和更大占板面积,不划算。这种“大电流用 BUCK、小电流用 LDO”的分配原则,在中小型嵌入式系统里基本通用。
这里要特别强调默认输出核对。PCA9422 上电后所有 rail 会按寄存器复位值输出,BUCK1 的默认电压必须正好是 3.3V,否则 MCU 起不来。我们的板子定制了上电默认配置,出厂前让 PMIC 厂商或者用烧录器先把默认值写成 3.3V/1.8V,这样即使 MCU 固件还没跑起来,板子也能稳定进入 bootloader。
2.2 软启动和 Power Good 信号的接法
电源管理不只是“把电压供上去”,还要考虑电压上升的斜率。PCA9422 的软启动时间可以通过寄存器配置,斜率太缓会拉长启动时间,系统迟迟进不了工作状态;太陡又可能在输出电容充电瞬间触发过流保护。我的经验是 3.3V 轨从 0 升到 90% 取 1 到 2ms 比较稳,1.8V 轨可以稍微快一点,因为它的负载电容更小。
Power Good 是另一件必须提前想清楚的事。PGOOD 引脚一般是开漏输出,必须加上拉到 MCU 的 3.3V 域。不要直接把 PGOOD 接 MCU 的 MCLR,因为 PGOOD 是正逻辑,而 MCLR 是低电平复位,中间需要加反相器或者三极管,否则逻辑正好反了。更稳妥的做法是把 PGOOD 接到一个 GPIO,固件里通过“等待 PGOOD 变高”来判断供电就绪,再往下走初始化流程。
同时要把 PGOOD 的 mask 配置好。PMIC 的全局 PGOOD 默认是“所有开启的 rail 都正常”才算好,如果你只开了 BUCK1 去跑低功耗模式,而 BUCK2 和 LDO1 本来就是关的,那 PGOOD 会一直拉低,看起来像是故障。在寄存器里把 mask 设置成“只监控当前已使能的轨”,能省掉大量误报警。
2.3 布局去耦:小封装 PMIC 最容易忽略的细节
PCA9422 这类小封装 PMIC 对 PCB 布局很敏感,尤其是 BUCK 部分。输入电容要尽量靠近 VIN 引脚,我放了 10µF 加 0.1µF 组合,负责吸收开关电流的高频分量。输出电容按数据手册推荐值来,我在 3.3V 轨放了两颗 22µF 加一颗 0.1µF,1.8V 轨放了一颗 22µF 加一颗 0.1µF。
电感选型有三个参数要盯:DCR、饱和电流和屏蔽。DCR 高的电感在中大电流下发热明显,饱和电流至少按最大输出电流的 1.5 倍选,最好选屏蔽电感,减少对边上模拟电路的磁场干扰。电感位置要紧贴 SW 引脚,SW 节点铺铜不要贪大,因为这里是开关节点,高频噪声就是从这个铜皮辐射出去的。
反馈采样、走线布局也有讲究。如果 PMIC 支持输出远端采样,反馈线应该走到负载端的电容处,避免导线压降把电压拉低;如果不支持,就按数据手册在输出电容处就近采样。I2C 的 SCL/SDA 这两根线要远离 SW 节点和电感,否则开关噪声会耦合进总线,轻则导致通信误码,重则配置写错电压。QFN 封装底部的散热焊盘必须打过孔阵列到地,只打一个孔或者不打,芯片会明显发烫。
3. I2C 控制链路:上电之后让 PMIC 真正“听懂话”
3.1 地址与总线设计,以及一个隐藏的握手问题
PCA9422 通过 I2C 跟主控通信,地址由 ADDR 引脚或内部配置决定,我板上的地址按数据手册默认是 0x57。不同子型号、不同引脚配置地址可能不一样,所以地址不要焊死在代码里,写成宏定义,调试时方便改。上电后的第一件事永远是读器件 ID,确认总线通、地址对、芯片活着,再往下做任何配置。
总线电气上,I2C 引脚要用开漏加外部上拉,上拉电阻选 2.2k 到 4.7k 之间。总线速率我建议先用 400kHz 跑,如果板子上 I2C 走线比较长或者挂的器件多,边沿变缓就降到 100kHz,稳定性远重要于那点速率提升。
这里有个隐藏的握手问题:MCU 上电后马上读 PCA9422,但 PMIC 内部可能还没完全 ready。低温下尤其明显,我后面会展开讲。工程上的通用做法是读 ID 前加一个 5 到 10ms 的延时,或者等 PGOOD 稳定后再发起 I2C 通信,两者结合最稳妥。
3.2 寄存器配置的关键位段按用途分类
PCA9422 的寄存器说多不多说少不少,按用途分成五类去理解,配置时就不会乱:
| 类别 | 典型寄存器/位段 | 作用 |
|---|---|---|
| 器件识别 | DEVICE_ID、REVISION | 通信自检、版本适配 |
| 电压选择 | BUCKx_VSEL、LDOx_VSEL | 设定输出电压档位 |
| 工作模式 | BUCKx_MODE | PWM-auto、强制 PWM、PFM 切换 |
| 输出控制 | RAIL_EN | 各路 rail 的使能/关断 |
| 保护状态 | UVP/OCP/OTP 状态、PGOOD mask | 读取故障、屏蔽误报 |
电压选择寄存器是核心,多数 PMIC 的 VSEL 字段与输出电压之间有线性关系,一般是 VOUT = 基准电压 + VSEL 乘以步进。比如步进 10mV、基准 0.6V,想要 3.3V,先算差值 2.7V,再除以 0.01V,得到 270,换算成寄存器位段填进去。这一步一定要拿数据手册里的公式仔细算,我见过的低级错误多半是把十进制和十六进制搞混,结果输出 4V 直接烧外设。
有些 PMIC 对关键寄存器有写保护机制,直接写不进去,需要先写 unlock 密钥。如果发现某位段配置后读回来不对,先怀疑这个,再去查地址和时序。
3.3 PIC32MZ 的 I2C 驱动与初始化代码
配置 PMIC 的底层是 I2C 读写,我用 PIC32MZ 的 I2C1 外设写了一个精简驱动。核心就两个函数,一个是写寄存器,一个是读寄存器。
#define PMIC_I2C_ADDR 0x57 /* 初始化 I2C1,总线速率 400kHz,PB_CLK 为外设时钟频率 */ static void i2c1_init(uint32_t pb_clk) { /* 配置 SDA1/SCL1 为开漏输出 */ /* 具体寄存器位根据引脚复用表设置 */ I2C1BRG = (pb_clk / (2 * 400000)) - 1; I2C1CONbits.ON = 1; } /* 写 PMIC 寄存器:先发地址,再发送寄存器号和数据 */ static int pmic_write_reg(uint8_t reg, uint8_t val) { /* 发送 START */ I2C1CONbits.SEN = 1; while (I2C1CONbits.SEN); /* 发送从机地址 + 写位 */ I2C1TRN = (PMIC_I2C_ADDR << 1) | 0; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 发送寄存器号 */ I2C1TRN = reg; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 发送数据 */ I2C1TRN = val; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 发送 STOP */ I2C1CONbits.PEN = 1; while (I2C1CONbits.PEN); return 0; } /* 读 PMIC 寄存器:先写寄存器号,再发重复 START + 读位,收一字节 NACK 结束 */ static int pmic_read_reg(uint8_t reg, uint8_t *val) { /* 发送 START */ I2C1CONbits.SEN = 1; while (I2C1CONbits.SEN); /* 发送从机地址 + 写位 */ I2C1TRN = (PMIC_I2C_ADDR << 1) | 0; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 发送寄存器号 */ I2C1TRN = reg; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 发送重复 START + 从机地址 + 读位 */ I2C1CONbits.RSEN = 1; while (I2C1CONbits.RSEN); I2C1TRN = (PMIC_I2C_ADDR << 1) | 1; while (I2C1STATbits.TRSTAT); if (I2C1STATbits.ACKSTAT) return -1; /* 接收一字节,回复 NACK */ I2C1CONbits.ACKDT = 1; I2C1CONbits.ACKEN = 1; while (I2C1CONbits.ACKEN); *val = I2C1RCV; /* 发送 STOP */ I2C1CONbits.PEN = 1; while (I2C1CONbits.PEN); return 0; }这段代码看起来啰嗦,但每一步都对应 I2C 协议的时序:START、地址、ACK、数据、重复 START、NACK、STOP。实际项目里我还会加超时保护,防止 PMIC 不回应时 I2C 状态机卡死。
3.4 配置顺序:默认值→完整配置→使能输出
初始化顺序比具体哪个寄存器更重要。我固件里的顺序是这样的:
- 上电,PMIC 按默认值输出,MCU 启动。
- 等待 PGOOD 变高,确认 3.3V 轨稳定。
- 读取 DEVICE_ID,校验通信正常。
- 依次写 BUCK1、BUCK2、LDO1 的电压选择寄存器,先不使能输出。
- 配置工作模式、软启动时间、PGOOD mask。
- 最后写 RAIL_EN,把需要的轨一次性打开。
这个顺序的核心逻辑是“先配电压、再开电源”。如果先使能输出再配电压,负载会在错误的电压下工作一小段时间,对敏感外设不友好,还可能触发 PMIC 自身的保护。配置完成之后,我还会周期性地读故障状态寄存器,一旦出现 OCP 或 OTP,就把状态记录下来并尝试重启对应 rail,而不是让整个系统静静死掉。
4. 电源状态机:把 MCU 低功耗模式和 PMIC 输出绑定
4.1 四档电源状态定义
电源管理做到最后,本质是一个状态机。我把整机功耗分成四档,每一档都明确“MCU 在干什么、PMIC 各路 rail 是什么状态”:
| 状态 | 含义 | MCU 模式 | BUCK1 3.3V | BUCK2 1.8V | LDO1 传感器 | 典型 5V 输入电流 |
|---|---|---|---|---|---|---|
| S0 RUN | 正常工作 | Run 200MHz | 开 | 开 | 开 | 约 260mA |
| S1 LIGHT | 浅休眠定时采集 | Idle | 开 | 开 | 关 | 约 42mA |
| S2 SLEEP | 深睡眠待唤醒 | Deep Sleep | 开(PFM) | 关 | 关 | 约 8mA |
| S3 OFF | 软关机 | VDD 掉电,完全关闭 | 关 | 关 | 关 | 小于 100µA |
S2 是最常用的低功耗档位。PIC32MZ 进 Deep Sleep 后,自身电流降到很低,外部 3.3V 轨由 BUCK1 在 PFM 模式下维持,VBAT 保持 RTCC 计时,1.8V 和传感器轨全部关掉。这个状态下设备还能定时醒来采集一次数据,再睡回去。
S3 是彻底关机。MCU 在掉电前通过 I2C 把 PMIC 所有 rail 关掉,自己也失去供电,整机只剩下 PMIC 自身的静态功耗。要注意的是 S3 状态下 MCU 不可能靠内部 RTCC 唤醒,因为 VDD 都没了,必须由外部 RTC 或者按键去拉 PMIC 的 EN 引脚,触发一次完整的冷启动流程。如果你的产品不想增加外部 RTC,那 S3 实际上会退化成 S2,只是把 PMIC 那点静态功耗继续往下抠而已。
4.2 状态切换时的关键时序约束
状态切换比状态本身更容易出问题。从 S0 往 S1 切的时候,要先关 LDO1(传感器轨)再让 CPU 进 Idle,避免传感器还带着负载的时候突然掉电,造成电源轨上的负压毛刺。从 S1 往 S0 恢复时顺序反过来:先唤醒 CPU,再开 LDO1,等 PGOOD 确认电压起来后,才允许访问传感器总线。
从 S2 深睡眠唤醒后,不要急着把 PMIC 所有 rail 一次性打开。我吃过亏,唤醒后立刻开 BUCK2 和 LDO1,瞬间冲击电流把 3.3V 轨拉低,MCU 又崩了。正确做法是分两步:第一步恢复 BUCK2 并等待 PGOOD,第二步再开 LDO1。每一步之间留几毫秒的间隔,让输出电容充电完成。
动态调压的时序约束也要讲。PIC32MZ 在深睡眠时如果能接受更低的 3.3V 电压,可以把 BUCK1 的输出往下调,进一步省电。但降压必须在 CPU 进入 Deep Sleep 之前完成,升压必须在唤醒之后、开启外设之前完成。核心原则是“负载对外设的要求先满足,再去动电压”,顺序反了轻则数据错误,重则损坏外设。
4.3 唤醒路径设计与软关机方案
我的板子设计了两种唤醒路径,分别对应 S2 和 S3。S2 的唤醒源是 PIC32MZ 内部 RTCC 闹钟或者外部按键中断,MCU 从 Deep Sleep 退出后,读取一个保存在 RAM 里的电源状态标志,知道自己是从 S2 来的,然后按顺序恢复各路 rail。
S3 的唤醒路径是完全冷启动。MCU 上一次掉电前,把“为什么关机”的原因写入外部串行 Flash,然后 PMIC 断电。唤醒时,外部 RTC 输出一个脉冲拉到 PMIC 的 EN 引脚,PMIC 重新上电,MCU 冷启动后读取掉电原因,如果是定时唤醒,就恢复工作状态;如果是低电量关机,就直接进充电流程。这两种路径用一个枚举pmic_boot_reason区分,对后面的故障日志和远程维护帮助很大。
这里要提醒硬件上的一个细节:PMIC 的 EN 引脚如果直接接机械按键,按键抖动会让 PMIC 在几十毫秒内反复通断,非常伤电路。我在 RTC 输出和 EN 之间加了一级 RC 滤波,按键场景则靠固件在第一个下降沿后屏蔽后续抖动,实测稳定。
4.4 状态机代码的组织方式
电源状态机我不建议用一堆 if else 去写,而是用表驱动。把每种状态对应的 PMIC 寄存器值放在一张表里,切换函数只负责把表里的值刷进去:
typedef enum { PMIC_STATE_RUN = 0, PMIC_STATE_LIGHT, PMIC_STATE_SLEEP, PMIC_STATE_OFF } pmic_state_t; typedef struct { uint8_t buck1_vsel; /* 3.3V,实际需要拆高低字节 */ uint8_t buck2_vsel; /* 1.8V */ uint8_t ldo1_vsel; /* 3.3V */ uint8_t rail_en; /* 各路使能位 */ } pmic_cfg_t; /* 注:以下数值是占位,真实值按 VSEL 公式回推后填入 */ static const pmic_cfg_t pmic_state_cfg[4] = { { 0x5C, 0x3A, 0x5C, 0x07 }, /* RUN:三路全开 */ { 0x5C, 0x3A, 0x00, 0x06 }, /* LIGHT:关 LDO1 */ { 0x5C, 0x00, 0x00, 0x01 }, /* SLEEP:只留 BUCK1 */ { 0x00, 0x00, 0x00, 0x00 }, /* OFF:全部关闭 */ }; void pmic_set_state(pmic_state_t state) { const pmic_cfg_t *cfg = &pmic_state_cfg[state]; /* 先关需要关的轨,避免负载带电变化 */ pmic_write_reg(PMIC_REG_RAIL_EN, 0); /* 再更新电压配置 */ pmic_write_reg(PMIC_REG_BUCK1_VSEL, cfg->buck1_vsel); pmic_write_reg(PMIC_REG_BUCK2_VSEL, cfg->buck2_vsel); pmic_write_reg(PMIC_REG_LDO1_VSEL, cfg->ldo1_vsel); /* 最后使能目标轨 */ pmic_write_reg(PMIC_REG_RAIL_EN, cfg->rail_en); }表驱动的最大好处是新增一个电源档位时,只需要在枚举和配置表里各加一项,切换逻辑完全不用动。量产时如果不同 SKU 的电压需求不一样,这张表还能放到外部 Flash 里按型号下发,固件主体保持不变。
5. 实测数据与踩坑记录:电流、纹波、启动竞争
5.1 各状态下的电流实测对比
改完硬件和固件后,我在 5V 输入端串联了一个 10mΩ 采样电阻,用示波器加电子负载校准,实测四档状态的数据如下:
| 状态 | 5V 输入电流 | 3.3V 轨电流 | 1.8V 轨电流 | 备注 |
|---|---|---|---|---|
| S0 RUN | 262mA | 128mA | 58mA | 网络 PHY 全速收发 |
| S1 LIGHT | 43mA | 18mA | 9mA | 定时采集,传感器轨关闭 |
| S2 SLEEP | 7.8mA | 2.1mA | 0mA | BUCK1 在 PFM,RTCC 计时 |
| S3 OFF | 0.08mA | 0mA | 0mA | 仅 PMIC 静态功耗 |
对比最明显的是 S3 的 0.08mA,原来的 LDO 阵列方案在假关机状态下怎么也压不到 1mA 以内。S2 的 7.8mA 里,PMIC 自身静态和 BUCK1 在 PFM 维持 3.3V 的损耗占了大头,MCU 深睡眠本身只占了很小一部分。这说明到了这个阶段,想继续往下降,就得研究 ENVREG 旁路或者换更低静态功耗的 PMIC,收益已经很小了。
5.2 PFM/PWM 切换对纹波和系统的影响
BUCK 在轻载时自动进入 PFM 模式,效率很高,但代价是输出电压纹波变大,而且开关频率会随负载漂移,可能落入音频范围或者和 ADC 采样频率产生差拍。我用示波器对比过:3.3V 轨在强制 PWM 模式下纹波约 10mV,切到 PFM 后纹波升到 30 到 40mV,峰峰值还会随负载小幅跳动。
这个纹波对数字逻辑没有影响,但 1.8V 轨连着以太网 PHY 的模拟电路,纹波和频率漂移会在信号质量和接收灵敏度上体现出来。所以我的配置是:BUCK2 的 1.8V 轨在 S0 和 S1 永远强制 PWM,只有 S2 才允许它关断;BUCK1 在 S0 和 S1 也强制 PWM,进入 S2 后才自动切 PFM,这时候负载只有 MCU 深睡眠加 RTCC,纹波大一点完全无所谓,反而换来了更低的静态损耗。
另外输出电容的高频特性也会影响纹波,我在这两颗 BUCK 的输出端都加了 0.1µF 高频陶瓷电容来抑制开关噪声尖峰,效果比单纯堆大容量电容更明显。
5.3 两个让我抓狂的调试过程
第一个坑:I2C 读 ID 偶尔超时
现象是板子大概每十次启动里有两三次,读 PMIC 的 DEVICE_ID 返回超时,导致系统认为 PMIC 故障,反复重启。用示波器挂在 SCL 和 SDA 上抓启动瞬间,发现 SCL 上升沿特别慢,从低到高接近 1µs,在 400kHz 速率下这个边沿已经快要踩到从机的输入阈值区间了。根因是我一开始用了 10k 的上拉电阻,总线上还有几十皮法寄生电容,RC 常数偏大。
解决也很直接:上拉电阻从 10k 换成 2.2k,并把 I2C 速率降到 100kHz 作为默认值,之后连续测了几百次启动都稳定通过。这个坑其实在原理图阶段就能避开,I2C 总线上拉要按照“总线电容尽量小、边沿尽量陡”的原则去选,不要照搬网上那些随手写的 4.7k。
第二个坑:低温环境下启动失败
现象更诡异,常温一切正常,把板子放进低温箱里降到零下二十度,启动失败的几率突然升到 20% 左右。逻辑分析仪抓到的时序显示,MCU 复位释放后立刻去读 PMIC ID,但此时 PMIC 内部 LDO 还没完全稳定,读回来的全是 0xFF。MCU 认为 PMIC 不在线,进入重试循环,而重试期间又去操作其他外设,把 3.3V 轨瞬间拉了一下,彻底死锁。温度越低,PMIC 内部基准建立越慢,问题越明显。
修复分两步:第一步是固件里读 ID 之前加 10ms 延时,并且等 PGOOD 信号稳定后再发 I2C 命令;第二步是把 PMIC 的软启动时间从原来的 2.5ms 调整到 1ms,保证 3.3V 轨在 MCU 复位释放前已经稳定了至少 2ms。改完以后在零下三十度做了 100 次冷启动,全部通过。这个坑给的教训是:上电时序问题高低温差异巨大,实验室常温验证过关不代表产品能过环境测试,电源相关的时序一定要在温箱里重点测。
最后补几句个人心得
这次把电源管理从 LDO 阵列换成 PCA9422 加 PIC32MZ1024EFE144 的组合,我最大的收获不是待机功耗降了多少,二十几倍的差距确实好看,但更关键的是电源策略的控制权终于回到了固件手里。以前调时序要飞线、换电阻,现在改一版寄存器表就行,批量生产甚至可以按 SKU 从外部 Flash 下发不同配置,不用动硬件。如果你也要做类似的供电改造,我的建议是选型阶段就把 PMIC 的默认上电电压、I2C 地址选项、各路最大电流全部列成表格,拿去和主控的每个电源域逐条核对,再开始画原理图,这一步省下来的时间足够你把状态机多调试好几轮。后续我打算在这块板子上加入电池充电管理和电量计,把电量监测也并入同一套 I2C 管理链路,到时候再写一篇实测记录。