做过带电池的嵌入式产品就会知道,选型阶段让人兴奋的是 MCU 的主频、外设数量和跑分,真正装机后让人头疼的却是另外一回事:待机电流压不下去、DCDC 纹波把 ADC 采样带偏、外设上电时序不对导致偶发死机。最近一版项目里,我把主控定为 TM4C1299KCZAD,电源部分全部交给 PCA9422 这颗 PMIC 来管,把原本板子上散落的多路 DCDC、LDO、充电管理和电压监测整合到了一颗芯片里。这篇文章就把这个组合的完整实现过程拆开讲一遍:从为什么这么选、硬件怎么接、软件状态机怎么写,到实测数据和踩过的坑。
先交代一下场景。这套方案做的是一个带电池供电的工业数据采集网关,主控需要以太网、USB、多路串口和比较强的浮点运算能力,TM4C1299KCZAD(Cortex-M4F 内核)非常合适。难的是供电链路:系统里同时存在 3.3V 逻辑、1.8V 存储、0.9V 内核电压(部分外设),还要兼容锂电池充电、外部 5V 适配器供电、深度休眠时整机电流控制在微安级。用分立器件搭这套电源树林不是不行,但动态调压、模式切换、充电管理都要自己做逻辑,稍有不慎就变成一个巨大的调试黑洞。PCA9422 就是把这些问题一次性打包处理掉。
1. 方案选型与整体设计思路
1.1 为什么省不掉 PMIC 这颗芯片
很多工程师惯用的做法是:一颗降压芯片给 MCU 供电,几颗 LDO 给传感器和外设供电,再用 GPIO 控制负载开关实现外设断电。这种方案的优点是人人都能画,缺点却在后半夜集中爆发。
首先是启动时序问题。MCU 上电不能比外设慢,否则 IO 口可能出现倒灌电流;多路 DCDC 的使能顺序要靠 RC 延时甚至 CPLD 去凑,每改一版硬件都要重新验证。其次是功耗管理很粗暴,系统进入低功耗状态后,LDO 自身的静态电流常年挂在那里,3.3V 待机想做到 100uA 以下都费劲。第三是动态电压调节基本没法做,想根据负载高低调整电压来省电,分立的 DCDC 必须有反馈电阻网络,改电压就要改电阻,系统运行中根本做不到。
PCA9422 这类 PMIC 把这些问题集中到一个芯片里解决:多路 DCDC 和 LDO 集成在内部,每一路的开关和输出电压都能通过 I2C 寄存器随时调整;有多档预配置模式,可以做到“硬件上电默认一个状态、MCU 初始化后切换到另一个状态”;内置电池充电管理、电压电流温度监测和中断输出,正好填补了 MCU 在电源域上的管理盲区。
1.2 PCA9422 与 TM4C1299KCZAD 的职责分配
这套方案的总体逻辑非常简单,就是一句话:PCA9422 负责“管电”,TM4C1299 负责“控制”。
PCA9422 处于系统电源链路的中心位置,输入来自锂电池或外部 5V 适配器,输出给 MCU、存储、通信模块和传感器。它内部有完整的电源路径管理,在适配器和电池同时存在时自动选择供电来源,并对电池进行充电。TM4C1299KCZAD 通过 I2C 总线读写 PCA9422 的寄存器,执行以下操作:
- 配置各路 DCDC/LDO 的电压和开关状态
- 切换 PCA9422 的预配置模式(例如从 BOOT 模式切到 RUN 模式再切到 SLEEP 模式)
- 读取当前输入电压、电池电压、内部温度和各路输出电流
- 接收 PCA9422 的中断通知(低电压告警、过温保护、充电完成)
TM4C1299 本身也有很强的低功耗能力,支持 Sleep、Deep Sleep 和 Hibernate 模式,因此在系统级休眠时,流程是:MCU 先通过 I2C 让 PCA9422 关掉外设供电通道,再命令 PMIC 进入低功耗模式,最后 MCU 自己进入 Hibernate。这样整机功耗由“MCU 休眠功耗 + PMIC 静态功耗 + 必然泄漏电流”三部分组成,可以做到非常低。
1.3 目标系统的电源树
在具体画板子之前,先列出整机需要哪些电源轨,这样才知道 PCA9422 的通道怎么分配。我们这个项目的电源树是这样的:
| 电源轨 | 电压 | 负载 | 来源 |
|---|---|---|---|
| VCC_3V3 | 3.3V | MCU、以太网 PHY、USB 逻辑 | PCA9422 DCDC1 |
| VCC_1V8 | 1.8V | 存储芯片、部分传感器 | PCA9422 DCDC2 |
| VCC_0V9 | 0.9V | 大功耗 FPGA 内核(实际项目中) | PCA9422 DCDC3 |
| VCC_3V3_SENSOR | 3.3V(可关断) | 气体传感器、运放 | PCA9422 LDO1 |
| VCC_REF | 2.5V | ADC 基准源 | PCA9422 LDO2 |
| VBAT | 3.0~4.2V | 电池直供 RTC、备份域 | 电池输入旁路 |
MCU 本体是 3.3V 单电源供电,内部 LDO 会自己降出 1.2V 核心电压,所以 TM4C1299 不需要单独的 0.9V。但板子上如果有移动通信模组或者大规模逻辑器件,PCA9422 的多路输出能力就体现出价值了——它一个芯片最多可以提供好几路独立调压输出,还支持 DVS(动态电压调整),这是分立方案很难做到的。
2. 硬件核心:PCA9422 的电源资源与接线细节
2.1 电源通道和可配置范围
PCA9422 是一颗高度集成的电源管理 IC,内部主要包含以下资源:
- 三路高效率降压转换器(DCDC1/2/3),单路最大输出电流通常在 600mA 左右,输出电压可通过寄存器调节,范围大约从 0.6V 到 3.3V 以上
- 两路低压差线性稳压器(LDO1/2),输出电流相对小一些,适合对纹波敏感的模拟电路
- 锂电池充电管理单元,支持单节锂离子/锂聚合物电池恒流恒压充电,充电电流可配置
- 电源路径管理,在适配器和电池之间自动切换,系统没电池时也能直接上电工作
- 内部 ADC,可测量输入电压、电池电压、多路输出电压和芯片温度
- I2C 接口、中断输出、多个 MODE 引脚
实际项目中哪些用 DCDC、哪些用 LDO,需要按负载类型来区分。数字核心、通信模组这种大电流且对纹波不敏感的负载放 DCDC 上,模拟前端、基准源这种对噪声敏感的负载放 LDO 上。PCA9422 的 DCDC 频率内部固定,但外围电感取值要和它匹配,通常选 2.2uH 到 4.7uH,饱和电流按最大负载电流的 1.5 倍以上留裕量。电容方面,每路输出建议至少一颗 22uF 陶瓷电容并联一颗 0.1uF 高频旁路,布局时先经过电感、再进电容、最后才到负载器件,顺序不能反。
2.2 关键引脚与主控的物理连接
TM4C1299KCZAD 和 PCA9422 之间最核心的连接只有四条线加若干控制线,我做成了这样的引脚分配:
| 功能 | PCA9422 引脚 | TM4C1299KCZAD 引脚 | 说明 |
|---|---|---|---|
| I2C 时钟 | SCL | I2C0SCL(PB2) | 1MHz Fast Mode |
| I2C 数据 | SDA | I2C0SDA(PB3) | 同上 |
| 中断输出 | INT | GPIO(PH0) | 下降沿触发,PMIC 事件通知 |
| 模式引脚 | MODE0/MODE1 | GPIO 或硬件拨码 | 上电默认配置选择 |
这里有一个非常容易被忽略的点:MODE 引脚决定了 PCA9422 在上电瞬间使用哪一套默认配置,而 MCU 此时往往还没初始化 I2C。如果板子设计时把 MODE 引脚接到 MCU 的 GPIO 上,那么这个 GPIO 必须有默认上拉或下拉,确保 MCU 复位期间 MODE 引脚的电平是确定的。我实际遇到过 GPIO 悬空、默认状态不定导致 PMIC 上电就跑进错误配置的情况,整板电流直接多出几十毫安,排查了很久。
PCA9422 的 I2C 地址在手册里有明确说明,通常是 7 位地址,具体值以实际丝印和手册为准。I2C 总线上记得加 2.2kΩ 到 4.7kΩ 的上拉电阻,如果 PCB 走线比较长,2.2kΩ 优先。TM4C1299 的 I2C 模块支持 100kHz 到 3.4MHz,为了在噪声环境下稳定,我当时锁定了 400kHz,漏极开路加上拉,效果很好。
2.3 上电时序与复位策略
PCA9422 上电后处于其 OTP(出厂预配置)状态,这个状态是原厂烧录或者通过寄存器配置后保存的。上电瞬间它就会按照默认配置输出各路电压。因此硬件上电时很长一段时间里,MCU 和所有负载都是在 PCA9422 默认配置下工作的。
设计时必须把这一事实嵌入到整个系统的上电时序逻辑中:
- 系统接入适配器或电池,PCA9422 上电,按 OTP 默认配置输出各路电压
- 如果默认配置中 3.3V 输出是开启的,MCU 开始复位、启动
- MCU 运行 bootloader 期间,I2C 初始化,然后读取 PCA9422 寄存器确认当前电源状态
- MCU 写入实际所需配置,覆盖默认配置(如调整 DCDC2 电压、关闭用不到的 LDO)
- 确认配置完成后,系统进入正常 RUN 模式
这个流程意味着 PCA9422 的 OTP 默认配置必须保证“裸机也能跑 MCU”,所以 OTP 里的 3.3V 通道通常要预设开启。如果默认配置把关键通道关了,MCU 根本起不来,调试就无法进行。
PCA9422 通常会有一个硬件复位引脚(有些 PMIC 叫 RSTB、有些叫 nRESET)。这个引脚的用途是:当 MCU 通过 I2C 对 PMIC 的状态控制失效时,用硬件方式把 PMIC 拉回默认状态。不要把该引脚直接接地,最好通过一个 GPIO 控制,这样软件里可以执行“软复位 PMIC”的操作。我在调试阶段频繁切换模式,偶尔会把 PMIC 弄到异常状态,这个硬件复位引脚成了救命稻草。
3. 主控侧资源分析与低功耗配合
3.1 TM4C1299KCZAD 上具体用到哪些模块
TM4C1299KCZAD 外设很丰富,但电源管理方案里真正用到的模块并不多,反而越精简越稳定。我用到了以下资源:
- I2C0:它运行在 400kHz,承担所有 PMIC 寄存器读写
- GPIO 中断:PCA9422 的 INT 引脚接到 PH0,配置为下降沿触发的中断输入
- ADC0:用来采样输入电压、电池分压电压和外部电流检测放大器输出
- Hibernate 模块:系统深度休眠时保留 RTC,也可以设置唤醒源(RTC 定时器或 GPIO 唤醒)
TM4C1299 的 ADC 是 12 位的,如果直接测量锂电池电压,4.2V 超出了 ADC 量程,需要先用电阻分压再进 ADC。分压电阻要选高阻值(比如 100kΩ + 200kΩ),减少电池侧漏电;同时在 ADC 引脚并联一个 100nF 电容做滤波。我用的分压比是 1/3,这样 4.2V 变成 1.4V,落在 3.3V 量程内,还有余量。
另外,测量充电电流时要用低侧电流检测方案:在电池负端与地之间串联一个 50mΩ 采样电阻,电流信号通过运放放大后接 ADC。TM4C1299 内部有运放资源(本项目中用到了内部集成运放),用内部运放可以减少一颗外部芯片。这里的放大倍数要算好,充电电流最大 1A,50mΩ 上压降 50mV,放大 20 倍正好 1V,ADC 采样值就在一个比较舒服的范围内。
3.2 系统状态与 PMIC 供电档位的配合
电源管理最核心的就是把“系统运行状态”和“电源芯片输出状态”对应起来。我们定了四个系统状态:
| 系统状态 | MCU 自身状态 | PCA9422 输出状态 | 适用场景 |
|---|---|---|---|
| RUN | Run 模式 120MHz | 所有通道全开,DCDC1 3.3V、DCDC2 1.8V、DCDC3 0.9V、LDO1/2 开启 | 正常采集、通信 |
| IDLE | Sleep 模式 | DCDC2/3 和 LDO 关闭,DCDC1 保持 3.3V | 短时等待、数据暂存 |
| HIBERNATE | Hibernate 模式 | 只保留一路 3.3V 给 MCU RTC 和备份域,其余全部关闭 | 长时间待机 |
| SHUTDOWN | 完全断电(PMIC 关闭输出) | 全部通道关闭,PMIC 自身进入低功耗待机 | 产品关机但电池不拔 |
这里有一个经验:不要试图把 MCU 状态切到 Hibernate 之后再通过 I2C 去关 PMIC,因为 MCU 进入 Hibernate 后 I2C 已经停了。正确的顺序是:
- MCU 处于 RUN 模式,先通过 I2C 关闭 PCA9422 上所有非关键输出通道
- 等待 PMIC 输出电压稳定(读取状态寄存器确认)
- 再执行 TM4C1299 的 Hibernate 指令,让 MCU 自身进入低功耗
- 唤醒时 MCU 先恢复执行,再通过 I2C 打开 PCA9422 上被关闭的通道
这个顺序问题值得特别注意,我在初期验证时直接先让 MCU 进了 Hibernate,结果 PMIC 把所有外设电都断了,但 MCU 自己的 3.3V 还在,一觉醒来之后 I2C 通信完全正常,不过外设重新上电需要额外的延时,启动逻辑就多了一堆状态判断。后来把 PMIC 关电放在 MCU 待机前,整个流程干净很多。
4. 软件实现:从 I2C 驱动到电源状态机
4.1 PMIC 寄存器读写驱动
PCA9422 的寄存器操作是标准的 I2C 从设备通信。我基于 TivaWare 库写了下面这套读写函数,直接可以作为项目底子:
#define PCA9422_I2C_ADDR 0x10 // 具体地址以实际手册为准 uint8_t pmic_read_reg(uint8_t reg, uint8_t *value) { uint8_t tx_data = reg; uint8_t rx_data = 0; uint32_t err_code = 0; // 先发送寄存器地址 err_code = I2C0_Transfer(&g_pmic_i2c, PCA9422_I2C_ADDR, I2C_MASTER_CMD_SINGLE_SEND, &tx_data, 1, 0, 0, 0); if (err_code) return err_code; // 再读取数据 err_code = I2C0_Transfer(&g_pmic_i2c, PCA9422_I2C_ADDR, I2C_MASTER_CMD_SINGLE_RECEIVE, &rx_data, 1, 0, 0, 0); if (err_code) return err_code; *value = rx_data; return 0; } uint8_t pmic_write_reg(uint8_t reg, uint8_t value) { uint8_t tx_buf[2] = { reg, value }; uint32_t err_code = 0; err_code = I2C0_Transfer(&g_pmic_i2c, PCA9422_I2C_ADDR, I2C_MASTER_CMD_SINGLE_SEND, tx_buf, 2, 0, 0, 0); return err_code; }I2C 驱动的核心就这两个函数,所有上层功能都建立在它们之上。有一点要提醒:I2C 读写失败时绝不能直接忽略错误继续执行,否则后续几十个寄存器参数可能是写到空气里的。我在工程代码里加了错误重试机制:连续读写失败 3 次就触发 PMIC 硬件复位,复位后再重新初始化。这个机制救过我好几次,特别是现场环境差、电源波动大的时候。
4.2 上电初始化:从默认配置覆盖到实际运行配置
MCU 启动后,执行完外设时钟和 GPIO 初始化,紧接着就是 PMIC 初始化。初始化顺序非常讲究,大体上分为三步:
先读 PCA9422 的 DEVICE ID 寄存器,确认 I2C 通信正常。如果读不到任何有效数据,大概率是总线被拉死或 PMIC 不在所期望的地址上,这时候要先查硬件,别急着写驱动。
然后配置所有通道的输出电压寄存器,但此时先不打开通道使能。先调电压再使能是原则,否则在调整过程中输出电压瞬态掉到负载器件的最低工作电压以下,系统会直接复位。
最后打开通道使能。使能顺序也要按照负载依赖关系来:先开 MCU 所在通路(3.3V),再开需要 3.3V 作为参考的 1.8V,最后开大电流负载所在通路。PCA9422 本身有上电时序控制能力,但 MCU 接管后最好自己再控制一遍流程。
void pmic_init(void) { uint8_t id = 0; // 第一步:确认通信 if (pmic_read_reg(REG_DEVICE_ID, &id) != 0) { // 触发硬件复位后重试 pmic_hw_reset(); delay_ms(10); pmic_read_reg(REG_DEVICE_ID, &id); } // 第二步:配置所有输出电压,先不使能 pmic_write_reg(REG_DCDC1_VSEL, 0x1E); // 3.3V pmic_write_reg(REG_DCDC2_VSEL, 0x1D); // 1.8V pmic_write_reg(REG_DCDC3_VSEL, 0x09); // 0.9V pmic_write_reg(REG_LDO1_VSEL, 0x1E); // 3.3V pmic_write_reg(REG_LDO2_VSEL, 0x1A); // 2.5V // 第三步:按顺序使能输出通道 pmic_write_reg(REG_DCDC1_EN, 0x01); delay_ms(1); pmic_write_reg(REG_DCDC2_EN, 0x01); pmic_write_reg(REG_DCDC3_EN, 0x01); delay_ms(1); pmic_write_reg(REG_LDO1_EN, 0x01); pmic_write_reg(REG_LDO2_EN, 0x01); // 第四步:使能 PMIC 中断上报 pmic_write_reg(REG_INT_MASK, 0x00); // 不屏蔽任何中断 }实际寄存器地址和数值映射以手册为准,思想就是“先电压、后使能、按顺序、有确认”。初始化完成后再看一遍关于充电的寄存器,如果电池电压低于阈值且适配器在位,就使能充电。
4.3 电源状态机的实现细节
系统运行时,电源状态之间的切换集中在下面这个函数里。我把它设计成同步阻塞式的,每一次状态切换都会等待 PMIC 寄存器回读确认,确保切完了才往下走。
void pm_set_state(pm_state_t target_state) { // 通知应用层:准备离开当前状态 pm_pre_change_callback(current_state, target_state); switch (target_state) { case PM_STATE_ACTIVE: // 打开所有输出通道 pmic_write_reg(REG_DCDC1_VSEL, 0x1E); pmic_write_reg(REG_DCDC2_EN, 0x01); pmic_write_reg(REG_DCDC3_EN, 0x01); pmic_write_reg(REG_LDO1_EN, 0x01); pmic_write_reg(REG_LDO2_EN, 0x01); // 等待输出稳定 delay_ms(2); break; case PM_STATE_IDLE: // 关闭外设供电,保留 MCU 通道 pmic_write_reg(REG_LDO1_EN, 0x00); pmic_write_reg(REG_LDO2_EN, 0x00); pmic_write_reg(REG_DCDC3_EN, 0x00); pmic_write_reg(REG_DCDC2_EN, 0x00); break; case PM_STATE_HIBERNATE: // 只保留一路给 MCU RTC pmic_write_reg(REG_DCDC3_EN, 0x00); pmic_write_reg(REG_DCDC2_EN, 0x00); pmic_write_reg(REG_LDO1_EN, 0x00); pmic_write_reg(REG_LDO2_EN, 0x00); break; case PM_STATE_SHUTDOWN: // 关闭全部输出通道,进入低功耗待机 pmic_write_reg(REG_DCDC1_EN, 0x00); pmic_write_reg(REG_DCDC2_EN, 0x00); pmic_write_reg(REG_DCDC3_EN, 0x00); pmic_write_reg(REG_LDO1_EN, 0x00); pmic_write_reg(REG_LDO2_EN, 0x00); pmic_write_reg(REG_SYS_CTRL, 0x00); // PMIC 进入待机模式 break; } current_state = target_state; // 通知应用层:已进入新状态 pm_post_change_callback(current_state, target_state); }这里必须说明一点:如果 PM_STATE_SHUTDOWN 要关闭的通道里包含 MCU 自己的 3.3V 供电,那么 MCU 在执行这一句之后会立刻掉电,后续代码根本不会运行。所以实际项目中 SHUTDOWN 状态有两种含义:一种是整机关机,这种操作通常由 MCU 发出一条指令写入 PMIC 寄存器,然后 PMIC 会把自身的电源输出全部关闭,MCU 是被断电的一方,根本走不到回调函数;另一种是 MCU 还活着但系统外设全关,那其实等价于 IDLE 状态。
为了保险,我通常在执行关机命令前先把关键数据写入非易失存储,再延时几十毫秒,最后才发关机寄存器命令。延时是为了让最后的数据写入完成。
4.4 中断处理:PMIC 主动通知 MCU
PCA9422 的 INT 引脚是低电平有效的中断输出。当发生过压、欠压、过温、充电完成、按钮事件等状态变化时,PMIC 会把 INT 引脚拉低,TM4C1299 的 PH0 检测到下降沿后进入中断服务函数。
中断服务函数里不要做太多事,正确做法是:
- 进入 ISR 后清除中断标志
- 通过 I2C 读取 PMIC 的中断状态寄存器,判断具体是哪类事件
- 如果是低电压告警,设置一个全局事件标志,回到主循环再处理
- 如果是过温告警,立即降低负载(关闭非关键通道),然后再通知应用层
void GPIOH_IRQHandler(void) { uint8_t int_status = 0; // 清除 GPIO 中断标志 GPIOIntClear(GPIO_PORTF_BASE, GPIO_PIN_0); pmic_read_reg(REG_INT_STATUS, &int_status); if (int_status & INT_LOWBAT) { g_pm_events |= PM_EVENT_LOW_BATTERY; } if (int_status & INT_OVERTEMP) { // 直接降低负载,不进主循环 pmic_write_reg(REG_LDO1_EN, 0x00); pmic_write_reg(REG_LDO2_EN, 0x00); g_pm_events |= PM_EVENT_OVERTEMP; } }在 I2C 中断服务函数里直接调用阻塞式 I2C 通信理论上可行,但速度太慢。我实际测试下来,TivaWare 的 I2C 传输是阻塞式轮询,单次传输大约几十微秒,在中断里执行也问题不大,前提是中断频率不能太高。如果 PMIC 在异常状态下频繁上报中断,ISR 可能会被 I2C 阻塞占满,正常任务全部饿死。应对办法是在 ISR 里只读一次中断状态寄存器,然后立刻返回,后续处理全部丢给主循环事件表。
5. 实测数据与故障排查实录
5.1 四种状态模式下的功耗参考
调试完成之后,用高精度万用表对整个系统供电链路做了功耗测量。以下数据是针对我们这款带以太网接口、若干传感器和通信模组的网关整机,不代表所有 TM4C1299 + PCA9422 组合都能达到同样水平,但可以作为参考:
| 系统状态 | 整机电流(3.7V 电池端) | 主要耗电来源 |
|---|---|---|
| RUN 正常采集 + 以太网通信 | 86mA | MCU 约 40mA、以太网 PHY 约 30mA、传感器约 10mA、PMIC 静态约 1mA |
| IDLE 等待(外设关断,MCU Sleep) | 8.2mA | MCU 内部 LDO 和 RAM 保持 |
| HIBERNATE(外设全关,MCU Hibernate) | 38uA | MCU Hibernate 约 2uA、PMIC 待机约 20uA、电源链路泄漏 |
| SHUTDOWN(PMIC 全关,电池在线) | 15uA | PMIC 自身待机电流 + 电池保护板静态电流 |
这里 HIBERNATE 状态整机 38uA 是我们花了大力气才做到的。最初测量时是 1.6mA,排查后发现问题不在 MCU,也不在 PMIC,而在以太网 PHY 的供电链路上:PHY 芯片的 3.3V 电源虽然被 LDO1 关断了,但 PHY 的复位引脚还通过一个上拉电阻挂在 3.3V 电源轨上,这个电阻在系统休眠时把 PHY 的电源引脚反向灌了一部分电流。把上拉改到 MCU 的 GPIO 上、由 MCU 控制复位脚电平后,待机电流立刻降下来了。
这个案例给所有做低功耗的人提个醒:断电时电流不从断电的那一路走,而是从所有相连的 GPIO、信号线、上拉电阻上悄悄漏掉。
5.2 踩过的三个典型坑
说几个调试过程中遇到的实际问题,每一个都在现场消耗了半天以上时间。
第一个是 I2C 总线挂死。现象是 MCU 读取 PMIC 寄存器时偶尔返回超时,而且一旦发生就持续无法恢复,必须按复位键。排查过程很曲折,最后发现是 PMIC 的中断引脚和 I2C 数据线在 PCB 上相距太近,中断脚频繁翻转时对 SDA 产生耦合干扰。解决办法是在 PCB 改版。临时方案则是在 I2C 驱动里增加软件复位:连续超时两次就通过 GPIO 把 PMIC 的硬件复位脚拉低再释放,强制 PMIC 重新上电初始化。如果大家是已经画完板的情况,建议先把 I2C 速率降到 100kHz 试试,很多时候能缓解但不能根治。
第二个是低负载电流下 DCDC 输出纹波变大。系统在 IDLE 状态下外设全部关闭后,DCDC1 负载只有 MCU 的几毫安,这时 DCDC 会进入轻载模式,表现为间歇性开关,输出纹波峰值接近 30mV,能够干扰到模拟传感器。解决方法是关闭 PMIC 的轻载省电模式(如果有相关寄存器),让 DCDC 保持连续导通模式(CCM),纹波可以降到 10mV 以下。代价是轻载时静态电流稍微增大,在 IDLE 这种只有几毫安的场景下完全可以接受。
第三个是充电管理使能后电池发热。这是硬件参数问题。PCA9422 有可配置的充电电流和截止电压,我一开始把充电电流直接设到了 1A,结果 800mAh 的小电池被 1A 电流猛灌,开会 30 分钟就明显烫手。正确做法是按电池容量 0.5C 计算充电电流,800mAh 电池限制在 400mA,同时配置截止电压 4.2V ± 1%,在温度超过 45°C 时通过 PMIC 的充电温度保护自动降流。
5.3 调试过程的一手经验
最后分享几条写进团队内部文档的经验,都是实际敲过钉子之后总结出来的。
第一,PMIC 寄存器初始化完成之后,最好回读一遍全部写入的寄存器,确认写入值与预期一致。这个回读动作如果放在 MCU 进入正式运行流程之前,可以筛掉绝大部分 I2C 偶发错误导致的“假运行”问题。
第二,切换 DCDC 输出电压时一定要在空载或轻载状态下进行。比如从 3.3V 切到 1.8V 时,如果负载已经占了满额 80% 以上,电压下降过程中会有一段区间输出电压低于负载最低工作电压,直接触发系统看门狗复位。具体操作上我都是先让外设进入休眠、断开大电流负载,再切电压,切完确认输出稳定后,再恢复负载。
第三,系统级的电源状态切换必须加延时。PMIC 寄存器写入后,输出电压不会立刻稳定,内部补偿环路需要一个过程。我实测通常在写入使能寄存器后等待 1ms 到 2ms,输出电压的瞬态过冲和跌落才会收敛到目标值以内。如果等待时间不足,后级负载的供电电压会出现一个“深坑”或者“尖峰”,对整个系统都有威胁。
第四,PCA9422 的 MODE 引脚在量产板上建议用电阻固定到确定的电平,而不是裸奔接 GND 或 VCC。固定电平的脚也要加上下拉电阻,方便测试时飞线修改。调试阶段我通过 MODE 引脚的组合快速切换过好几套预配置,省下了反复烧录 OTP 的时间,量产阶段锁死为上电默认 ACTIVE 状态,稳定性有保证。
6. 这套方案还能怎么扩展
目前这套 PCA9422 + TM4C1299KCZAD 的电源管理框架不仅是针对特定产品做的,绝大多数结构都是可复用资产。换一颗 MCU、换一个 PMIC 型号,只要把驱动层寄存器配置替换掉,上层状态机几乎可以原样搬走。
一些值得往下做的扩展方向:
电池电量估算是下一个可以集成的模块。PCA9422 有电池电压和电流 ADC,TM4C1299 跑一个简单的开路电压法加库仑积分法复合估算,精度大约能做到 5% 以内。这比外挂电量计芯片省一颗料,代价是算法标定工作。
动态电压调节也可以做深一步。当前是固定档位切换,如果外设对供电电压要求比较宽,可以根据负载状态实时调节 PCA9422 DCDC 的输出电压,低负载时降低电压省功耗,高负载时提电压保性能。PCA9422 的 DCDC 输出寄存器本来就能实时改写,软件只需要保证切换时机准确。
多 PMIC 级联也不是不行。如果系统里有一块主电源板和多块子板,每块子板各放一颗 PCA9422,主控 TM4C1299 挂一条 I2C 总线管理多颗 PMIC,每颗 PMIC 用不同地址区分。这样整个系统的每个电源域都能统一拉电、统一监测,比每块板子各自为政要可靠得多。
回到电源管理本身,我的感受是:PMIC 这颗芯片把硬件上的杂乱电源方案收拢了,但真正决定整机功耗和稳定性的仍然是软件统筹。MCU 和 PMIC 之间只有四条线,跑在上面的却是一整套状态机、中断事件和异常处理逻辑。花时间把状态机设计清楚,比调一天 DCDC 的反馈环路更有价值。这套方案从画板到实测经历了几轮迭代,整体达到预期的低功耗目标,也希望这篇记录能给做电源管理的人省下一些排查时间。