基于PCA9422与K60的便携设备电源管理与低功耗设计
便携设备做电源这件事,前期省下来的每一分功耗,最后都会以返工工时的形式还回来。我最近刚收尾一个手持式数据采集终端的电源系统,主控用的是 MK60DN512(下文简称 K60),电源管理核心用的是 PCA9422 这颗带充电功能的 PMIC,整块板子从画原理图到调通低功耗模式,前后折腾了将近两个月,中间踩的坑足够写一本小册子。PCA9422 + MK60DN512 这个组合并不算主流搭配,网上能查到的现成资料不多,大部分时间是我对着数据手册一行一行抠寄存器,再用示波器和电子负载一点点试出来的。
这篇记录想解决的问题很具体:如果你手上正好要用一颗集成充电器、多路 DCDC/LDO 的 PMIC 去喂一颗 Cortex-M4 级别的 MCU,并且对体积、待机电流、充电安全有要求,那这篇文章里的参数计算、寄存器配置、布局要点和排查思路应该能让你少走一大半弯路。全文会从方案选型的理由讲起,把充电电流、Buck 电感、反馈电阻这些需要算的东西算给你看,再往下是 I2C 驱动、上电时序、低功耗配置这些必须落地的代码,最后是调试阶段撞上的真实问题和我整理出来的速查表。新手可以照着抄参数,有经验的同行可以重点看第 2、5 章的取舍逻辑。
1. 需求拆解与整体架构设计
1.1 便携设备对电源系统的四个硬约束
做便携设备的电源设计,跟做市电供电的板子完全是两种思维方式。市电供电的板子你只需要关心输出电压精度、纹波和散热,功耗大一点顶多多交点电费;便携设备不一样,电池就那么点容量,每一个微安的漏电流都会直接换算成用户手里的续航焦虑。所以我在动手之前先把约束条件列清楚,一共四条,后面所有的选型和参数都围着这四条转。
第一条是体积约束。整机外壳内部留给电源部分的面积大概只有 22mm × 18mm,高度限制在 5mm 以内。这个尺寸下你不可能摆一堆分立器件去做充电管理加多路降压,光是充电 IC、两路 Buck、两路 LDO 加上各自的电感电容,面积就已经爆了。这是最终选 PMIC 而不是分立方案的最直接原因,高集成度把外围元件数量压到了十几颗。
第二条是充电安全约束。设备用的是单节锂聚合物电池,标称 3.7V,满电 4.2V,容量 500mAh。锂电这东西,过充、过流、过温任何一个条件失控都可能出问题,所以充电管理必须支持可编程的充电电流、可编程的终止电流、带 NTC 温度监测,最好还有充电超时保护。这一条直接排除掉了一大批只能固定电流充电的简单方案。
第三条是待机功耗约束。设备的使用场景是"用一次放几天",也就是大部分时间处于关机或者深度休眠状态,用户按一下按键才唤醒工作几十秒。这种场景下待机电流的重要性远超运行效率,我给自己定的目标是整机待机小于 60µA,其中电源部分自己的静态电流要控制在 25µA 以内。这个指标看起来不苛刻,实际做下来会发现在电池反灌、上下拉电阻、LDO 静态电流这些地方到处漏电。
第四条是供电域约束。K60 这颗 MCU 本身需要 3.3V 的主供电,内核由内部稳压器产生 1.8V 左右的电压,另外还有 VDDA 模拟供电、VBAT 备份域供电。外设这边有 3.3V 的传感器、1.8V 的屏逻辑供电、以及 3.0V 的基准。四个不同的电压域,如果每个都用一颗独立 LDO,光是静态电流加起来就超标了,必须用一颗能同时输出多路电压的器件统一管理。
1.2 选型对比:为什么是 PCA9422 + K60
把这四条约束往桌面上摆,选型的路径其实就收窄了。我最初考虑过三条路线:分立方案(充电 IC + 独立 Buck + 独立 LDO)、通用 PMIC(不带充电器)、以及带充电器的集成 PMIC。前两条路线我都实际画过一版原理图,最后都放弃了,原因值得说清楚,因为很多人在选型阶段容易低估集成度带来的收益。
分立方案的好处是每一颗芯片都能选到最优解,充电可以选一颗专门的大电流线性充电器,Buck 可以选 2MHz 高频小封装,LDO 挑静态电流 1µA 的超低功耗款。听起来很美,但把料单拉出来一算,光是充电 IC、两路 Buck、两路 LDO 就是五颗芯片,加上各自的电感和电容,BOM 行数超过 30 行,布板面积轻松超过 300mm²。更麻烦的是上电时序——五颗芯片各自有各自的使能引脚,你得额外加一片时序控制逻辑或者用 MCU 的 GPIO 去拉,而 MCU 自己还没上电的时候谁去拉?这是个鸡生蛋的问题。
通用 PMIC 解决了多路输出和时序问题,但通常不带电池充电器,你还是得单独加一颗充电 IC,等于把问题拆成了两半。
PCA9422 这类集成 PMIC 的价值就在这儿:它把线性充电器、两路 Buck、两路 LDO、I2C 控制接口、以及上电时序控制全部塞进一颗 QFN 封装里。从系统层面看,你只需要给它一路 VBUS(USB 5V)和电池,它就能自己管理充电路径、自动切换系统供电来源(VBUS 在的时候用 VBUS,拔掉用电池)、按预设顺序输出各路电压。它内部还有一次性可编程的默认配置,可以在没有 MCU 干预的情况下就输出一组安全电压,等 MCU 起来之后再通过 I2C 改成实际需要的值。这个特性对调试阶段太友好了,我第一版板子 MCU 还没跑起来的时候,PMIC 已经能稳定输出 3.3V 让 MCU 有电可用了。
K60 这边我选它的原因是外设够用、封装是 LQFP 便于手工焊接和调试、以及它的低功耗模式(LLS、VLLS)在 M4 阵营里做得比较扎实,配合 PMIC 的电源域切换能做出比较极致的待机表现。代价是 K60 的供电结构比一般 MCU 复杂,VDD、VDDA、VBAT、VOUT33 这几路要分别处理,后文会专门讲。
1.3 供电域划分与系统架构
具体到这块板子,我最终的供电域划分是这样的:VBUS 从 USB Type-C 进来,经过一颗限流开关和 TVS 保护后进入 PCA9422 的 VBUS 引脚。PCA9422 内部把 VBUS 分两路,一路给充电器,一路给系统供电,同时监测电池电压,当 VBUS 掉电时无缝切到电池。这一路叫 SYS 节点,是整个系统的能量池,所有下游转换都从这里取电。
从 SYS 出来,BUCK1 配成 3.3V/600mA,给 K60 的 VDD、VDDA(经过 LC 滤波)、以及 3.3V 的传感器供电。BUCK2 配成 1.8V/400mA,给屏的逻辑部分和一颗射频模块。LDO1 配成 3.0V/150mA,专门给 ADC 基准和模拟前端,因为 LDO 的噪声比 Buck 低一个量级,基准这一路不能省。LDO2 配成 1.8V/100mA,给 RTC 域和备份电路,这一路在系统休眠时保持开启。还有一路 VSYS 直接引出给蜂鸣器这种对电压不敏感的负载。
K60 内部自己还有一个 USB 收发器用的稳压器,输出在 VOUT33 引脚上,这个引脚必须外接电容,否则 USB 功能会不稳定。另外 VBAT 引脚接的是纽扣电池或者直接接 3.0V 的 LDO2 输出,用于 RTC 走时,这一路的电流在几十微安级别,不需要太大驱动能力。
整个架构里有个容易被忽略的点:地回流路径。充电的时候电流从 VBUS 经 PMIC 到电池,放电的时候从电池经 PMIC 到负载,两个方向的电流都很大(峰值能到 500mA),如果地平面切得不好,这些大电流会在地平面上产生毫伏级的压差,串到 ADC 的模拟地上,直接反映成采样值的跳动。我在第一版板子上就吃了这个亏,后来把功率地和模拟地做了单点连接才解决,第 2 章会详细讲布局。
2. PCA9422 外围电路与参数计算
2.1 充电电流、终止电流与计时器参数怎么算
充电参数这块很多人是拍脑袋定的,反正能充进去就行。但锂电池的充电电流选得不对,轻则充得慢,重则循环寿命腰斩。我把当时的计算过程完整写出来,你可以照着换成自己的电池参数。
先说充电电流。锂电池的标准充电倍率一般是 0.5C 到 1C,C 是电池容量。我用的电池是 500mAh,0.5C 就是 250mA,1C 是 500mA。便携设备追求的是充电时不要明显发热,所以我选了 0.5C,也就是 250mA。这里有个细节:如果你的设备支持边充边用,充电电流还要加上系统负载电流,否则总电流会超过 USB 端口的供电能力。我这个设备边充边用时系统大概吃 120mA,250 + 120 = 370mA,USB 2.0 端口标准供电是 500mA,留了 130mA 余量,可以接受。如果系统负载更大,就得考虑把输入限流调高或者降低充电电流。
然后是终止电流。充电进入恒压阶段后,电流会自然衰减,当衰减到某个值以下就应该判定充满并停止充电,这个值一般取 C/10,也就是 50mA。我这里取了 25mA(C/20),稍微激进一点,好处是电池能充得更满,坏处是恒压阶段拖得更久。实测下来从 90% 到 100% 这一段多花了大概 20 分钟,能接受。
充电时间的估算可以给你一个参考:从 3.4V(大概 10% 电量)充到 4.2V,恒流阶段按 250mA 走,需要充进去的电量约 400mAh,时间是 400/250 = 1.6 小时。恒压阶段大概再要 0.4 到 0.5 小时,总计约 2 小时充满。实测结果在 2 小时 10 分左右,跟估算比较接近。
安全计时器这块,我设的是 3 小时。这个值要比理论充电时间留出足够余量,因为低温环境下充电会变慢,如果计时器卡得太紧,冬天在室外充电会被提前掐断。但也不能设太长,否则电池异常时保护不及时。3 小时是个比较平衡的值。
温度保护用的是外置 NTC,10kΩ B=3435 的 0603 电阻,贴在电池附近。PMIC 内部有对应的偏置电流源和比较器,通过 I2C 可以读到当前的温度区间,也可以设置充电的允许温度窗口。我把窗口设成 0℃ 到 45℃,超出范围就暂停充电但保持系统供电,这样用户在极冷极热环境下至少还能用设备,只是充不进电。
| 参数 | 取值 | 计算依据 |
|---|---|---|
| 电池容量 | 500mAh | 硬件规格 |
| 充电电流 | 250mA | 0.5C,兼顾发热与时间 |
| 终止电流 | 25mA | C/20,偏向充满 |
| 安全计时 | 3h | 理论 2h × 1.5 倍余量 |
| 充电温度窗口 | 0~45℃ | 常规锂电安全区间 |
| 输入限流 | 450mA | USB 500mA 留余量 |
| 满充电压 | 4.20V | 电池规格 |
注意:满充电压这一项千万别图省事直接按 4.2V 写死。有些高压电芯标称 4.35V,有些磷酸铁锂是 3.65V,写错了轻则充不满,重则过充。拿到电池第一件事是看规格书上的充电截止电压,不是看标称电压。
2.2 Buck 电感与输出电容的取值逻辑
PCA9422 内部两路 Buck 的开关频率是固定的,我手上这版是 2MHz。这个频率选得比较聪明,一方面电感可以做得小,另一方面开关噪声落在容易滤除的频段。但 2MHz 也意味着开关损耗会比 500kHz 的方案高一些,轻载效率要做取舍。
电感取值我的计算过程是这样的:BUCK1 输出 3.3V,最大负载 600mA,但我按实际工作点 400mA 来算,纹波电流取负载电流的 30%,也就是 120mA。用降压电感的经典公式 L = (Vin - Vout) × Vout / (Vin × fsw × ΔIL),代入 Vin 取电池典型电压 3.8V,Vout = 3.3V,fsw = 2MHz,ΔIL = 0.12A:
L = (3.8 - 3.3) × 3.3 / (3.8 × 2×10^6 × 0.12) = 1.65 / 912000 ≈ 1.8µH
算出来是 1.8µH,我实际选的是 2.2µH 的标准值。为什么不选更接近的?因为电感的直流电阻和饱和电流跟感值是一组互相牵制的参数,2.2µH 这个规格在 3mm×3mm 尺寸下有更多低 DCR 的选项,我选的这颗 DCR 是 45mΩ,饱和电流 1.5A,够用。感值偏大一点,纹波电流会小一些,输出纹波也更好,代价是瞬态响应稍慢,但这个系统没有大动态负载,无所谓。
BUCK2 输出 1.8V,负载 400mA,纹波取 0.1A,Vin 同样是 3.8V:
L = (3.8 - 1.8) × 1.8 / (3.8 × 2×10^6 × 0.1) = 3.6 / 760000 ≈ 4.7µH
正好是标准值,直接选 4.7µH。
输出电容的计算用的是纹波电压公式 ΔVout = ΔIL / (8 × fsw × Cout),反过来解 Cout = ΔIL / (8 × fsw × ΔVout)。以 BUCK1 为例,要求输出纹波小于 10mV:
Cout = 0.12 / (8 × 2×10^6 × 0.01) = 0.12 / 160000 = 0.75µF
理论值只要 0.75µF,但实际不能这么选。这个公式只考虑了容性纹波,没算 ESR 带来的纹波和负载瞬变时的电压跌落。我实际用了两颗 22µF 的 X5R 陶瓷电容并联,ESR 控制在 3mΩ 以内,实测纹波 6mV 左右,瞬变跌落也在 30mV 以内。电容的直流偏压特性要注意,22µF 的 0805 X5R 在 3.3V 偏压下实际容值会掉到 14µF 左右,所以标称值要留足余量。
反馈电阻这块,BUCK1 如果用的是外部可调版本,反馈基准电压是 0.6V,那么分压比就是 (3.3/0.6) - 1 = 4.5。取下电阻 10kΩ,上电阻就是 45kΩ,标准值选 45.3kΩ,实际输出 0.6 × (1 + 45.3/10) = 3.318V,误差 0.5%,完全够用。电阻精度选 1% 的薄膜电阻,温漂 50ppm/℃ 以内。这里顺带提一句,分压电阻的阻值不是随便选的,太小了会白白消耗静态电流,太大了容易被噪声干扰,10kΩ 这个量级是个比较舒服的折中。
| 电源轨 | 输出 | 电感 | 输出电容 | 反馈分压 |
|---|---|---|---|---|
| BUCK1 | 3.3V/600mA | 2.2µH | 2×22µF | 45.3k/10k |
| BUCK2 | 1.8V/400mA | 4.7µH | 2×22µF | 20k/10k |
| LDO1 | 3.0V/150mA | 无 | 10µF | 内部固定 |
| LDO2 | 1.8V/100mA | 无 | 4.7µF | 内部固定 |
2.3 PCB 布局:三个必须守住的底线
电源板画得好不好,七分看布局,三分看原理图。我第一版板子原理图完全正确,但布局出了问题,导致纹波大、待机电流超标、充电时 ADC 读数乱跳。第二版重画之后问题全消。总结下来有三条底线不能碰。
第一条,功率回路面积必须小。Buck 的输入电容、芯片的 SW 引脚、电感,这三个点构成的回路是 di/dt 最大的地方,面积越大辐射越强。我的做法是把输入电容紧贴芯片的 VIN 和 PGND 引脚放,电容的接地端直接打过孔到地平面,SW 走线短而粗,电感紧接着 SW 引脚放。这个回路我控制在 20mm² 以内,实测 SW 节点的振铃幅度比第一版小了一半。
第二条,地平面不能乱切。大电流回流和小信号地必须分开走但最后单点汇合。具体来说,充电器的功率地、Buck 的功率地、电池的负极,这些走一条粗的地线回到 PMIC 的 PGND,而模拟部分(ADC 基准、NTC 分压)的地单独走,最后在 PMIC 的 AGND 引脚附近用一个 0Ω 电阻或者一颗过孔连到主地。我在第一版里图省事整板铺了一个地,结果充电电流 250mA 在地平面上产生的压降直接串到 ADC 参考地上,采样值跳动超过 8 个 LSB。
第三条,电感和 NTC 要远离敏感走线。2MHz 的开关节点是个很强的干扰源,如果电感离 ADC 输入走线太近,会耦合进明显的开关频率纹波。我把电感放在板子边缘,NTC 走线用包地处理,两侧打满地过孔,中间走信号线。NTC 本身是高阻信号(10kΩ 量级),特别容易被干扰,包地这一步不能省。
实操心得:画完 PCB 之后别急着发板,把电源部分的走线单独高亮出来看一眼。如果 SW 节点的走线比输出走线还长,那基本可以判定布局有问题了,重新摆一遍比调试一周划算。
3. I2C 驱动与寄存器配置实操
3.1 从零写一个能用的 I2C 读写封装
K60 的 I2C 外设用起来不算难,但寄存器配置有几个容易漏的位。我不打算在这儿贴完整的初始化代码,那个抄手册就行,重点讲我在实际调试中改过的几处关键配置,这些是手册上不会强调但直接影响能不能通的地方。
第一个是引脚的上拉。K60 的 I2C 引脚内部有可配置的上拉,但阻值偏大(几十 kΩ 量级),在 400kHz 速率下上升沿会明显变缓。我一开始用内部上拉,示波器上看 SCL 的上升时间超过了 300ns,偶尔出现通信失败。后来换成外部 2.2kΩ 上拉,上升时间降到 80ns 以内,问题消失。上拉电阻的取值跟总线电容有关,走线短的话 4.7kΩ 也行,我板子上 I2C 走线不长但挂了两个从设备,所以选了 2.2kΩ。
第二个是时钟频率。K60 的 I2C 时钟源是总线时钟分频来的,分频系数的计算容易算错。我用的总线时钟是 60MHz,要得到 400kHz,分频比是 150。K60 的 I2C 分频寄存器是 8 位,最大 256,150 在范围内。但要注意,实际速率还受 SCL 高低电平占空比寄存器的影响,如果这两个值配得不匹配,实际速率会偏离目标值。我的做法是先按理论值配好,再用示波器量实际频率,微调分频值直到落在 380~420kHz 之间。
第三个是超时和重试。I2C 总线有个经典问题:从设备在传输过程中被复位,会把 SDA 拉低不放,导致总线死锁。标准解法是发 9 个 SCL 脉冲把从设备的数据移位出来,再发一个 STOP 条件。我在驱动里加了这个恢复流程,并且给每次传输设了 10ms 超时,超时之后自动走恢复流程再重试一次。这套机制上线之后,现场再没出现过需要断电重启才能恢复的情况。
下面是我封装的读写函数,做了地址对齐和错误重试:
#define PCA9422_ADDR 0x4B /* 7 位地址,以手册为准 */ #define PCA9422_I2C I2C0 #define I2C_TIMEOUT_MS 10 #define I2C_RETRY_CNT 1 static int pca9422_write_reg(uint8_t reg, uint8_t val) { uint8_t buf[2] = { reg, val }; int ret = i2c_master_send(PCA9422_I2C, PCA9422_ADDR, buf, 2, I2C_TIMEOUT_MS); if (ret != 0) { i2c_bus_recover(PCA9422_I2C); /* 9 时钟 + STOP 恢复 */ ret = i2c_master_send(PCA9422_I2C, PCA9422_ADDR, buf, 2, I2C_TIMEOUT_MS); } return ret; } static int pca9422_read_reg(uint8_t reg, uint8_t *val) { int ret = i2c_master_send(PCA9422_I2C, PCA9422_ADDR, ®, 1, I2C_TIMEOUT_MS); if (ret == 0) { ret = i2c_master_recv(PCA9422_I2C, PCA9422_ADDR, val, 1, I2C_TIMEOUT_MS); } if (ret != 0) { i2c_bus_recover(PCA9422_I2C); } return ret; }这段代码里有个细节值得说:读寄存器的操作是"先写寄存器地址,再读数据",中间不能插入 STOP 条件,否则有些 PMIC 会认为事务结束,读回来的是当前地址指针指向的值而不是目标寄存器的值。我第一版驱动里用了两个独立的事务,读出来的数据全是 0xFF,查了半天才发现是这个原因。正确的做法是用重复起始条件把两个阶段连起来,很多 MCU 的 I2C 外设库把这个做成了i2c_master_send_recv这样的组合接口。
3.2 上电时序:先给谁上电、后给谁上电
上电时序这件事,做得好没人夸你,做不好各种玄学问题。K60 对上电顺序没有特别严格的要求,但有几个点必须注意。
首先是 VBAT 和 VDD 的关系。K60 的 VBAT 是给 RTC 和备份寄存器供电的,数据手册里明确写了 VBAT 不能比 VDD 高太多,否则会有电流从 VBAT 倒灌到 VDD。我的处理是让 LDO2(1.8V)给 VBAT 供电,而 LDO2 的开启时序排在 BUCK1 之后,这样 VDD 先建立,VBAT 后建立,不会出现倒灌。
然后是 VDDA 和 VDD 的关系。VDDA 是模拟供电,要求跟 VDD 的压差不超过 0.1V,最好同时建立。我在 BUCK1 输出之后加了一颗磁珠和 10µF 电容做 LC 滤波,再送给 VDDA。磁珠选的是 600Ω@100MHz 的型号,直流电阻 0.3Ω,压降可以忽略。上电的时候 VDDA 比 VDD 稍微滞后一点点,但由于 LC 的时间常数只有几十微秒,实际测下来两者基本同步。
PCA9422 这边提供了可配置的启动时序,各路输出的开启顺序和间隔都能通过一次性可编程或者 I2C 设置。我配的顺序是 BUCK1 → BUCK2 → LDO2 → LDO1,每路间隔 2ms。为什么是这个顺序?BUCK1 给 MCU 供电必须最先建立,MCU 起来之后才能通过 I2C 配置其他路;LDO1 给 ADC 基准,放在最后是为了让基准电压在系统其他部分稳定之后再建立,避免基准受到上电冲击。
注意:如果你用的是 MCU 主动配置 PMIC 的架构,那么 MCU 起来的时候 PMIC 还处于默认配置状态,这个默认配置必须是"安全"的——电压不能超过下游器件的耐压,输出能力不能被瞬间大电流拉垮。我在调试阶段遇到过 PMIC 默认输出 3.4V 而下游屏的逻辑耐压是 3.6V,看着没问题,但上电瞬间的过冲冲到 3.9V,屏直接不亮。后来把默认配置改成 3.0V,等 MCU 起来再拉到 3.3V,问题解决。
3.3 关键寄存器配置代码与逐行解释
寄存器配置这块我不打算把整张表搬过来,手册上有。我挑几个真正影响行为的配置项,讲清楚每个值是怎么算出来的。下面这段是充电相关的初始化,寄存器地址和位定义请以你手上那版数据手册为准,我这里用的宏名是我自己整理的,方便阅读。
/* 充电电流设置:目标 250mA */ /* 假设电流档位为 1mA/LSB,寄存器值 = 250 */ #define CHG_CURRENT_250MA 250 /* 终止电流设置:目标 25mA,档位 1mA/LSB */ #define CHG_TERM_CURRENT_25MA 25 /* 满充电压 4.20V,档位 25mV/LSB, * 寄存器值 = (4200 - 3000) / 25 = 48 */ #define CHG_VFLOAT_4200MV 48 /* 安全计时器:3 小时,档位 30min/LSB,值 = 6 */ #define CHG_SAFETY_TIMER_3H 6 static int pca9422_charger_init(void) { uint8_t v; /* 1. 关闭充电,改参数前必须先关 */ if (pca9422_write_reg(REG_CHG_CTRL0, 0x00) != 0) return -1; /* 2. 充电电流 */ pca9422_write_reg(REG_CHG_ISET, CHG_CURRENT_250MA); /* 3. 终止电流与满充电压 */ v = (CHG_VFLOAT_4200MV << 2) | (CHG_TERM_CURRENT_25MA >> 3); pca9422_write_reg(REG_CHG_VFLOAT, v); /* 4. 安全计时器使能 + 时长 */ v = 0x80 | CHG_SAFETY_TIMER_3H; pca9422_write_reg(REG_CHG_TIMER, v); /* 5. 温度保护窗口:0~45℃ */ pca9422_write_reg(REG_CHG_TEMP, TEMP_WIN_0_45C); /* 6. 参数写完后使能充电,并打开看门狗喂狗 */ v = CHG_EN | CHG_WDT_EN; if (pca9422_write_reg(REG_CHG_CTRL0, v) != 0) return -1; return 0; }这段代码里有三个"为什么"值得展开。
第一个是改参数前先关充电。PMIC 的很多参数寄存器在充电进行中写入是无效的,或者要等到下一个充电周期才生效,手册里通常会写"建议在 CHG_EN = 0 时修改"。我一开始没注意这条,改了终止电流之后发现充电行为没变化,还以为是芯片坏了,后来才发现是写入被忽略。
第二个是满充电压的编码方式。这类 PMIC 通常是"基准值 + 档位 × 步进"的编码,比如 3.0V 基准、25mV 步进,那么 4.2V 对应的就是 (4200 - 3000) / 25 = 48。如果你的电池是 4.35V,就是 (4350 - 3000) / 25 = 54。这个计算一定要自己算一遍再写进代码,别直接抄别人的常量,因为不同批次、不同型号的基准值和步进都可能不一样。
第三个是看门狗。这颗 PMIC 带一个充电看门狗,如果在设定时间内 MCU 没有通过 I2C 喂狗,它会自动把充电电流降到安全值。这个功能本意是防止 MCU 死机之后充电失控,但在调试阶段特别烦人——我单步调试的时候经常超过超时时间,充电就被限流了,还以为是电池的问题。调试阶段可以先关掉看门狗,量产版本一定要打开,并且把喂狗周期放在一个低优先级的定时任务里,周期取超时时间的 1/3。
4. 固件侧电源管理与低功耗落地
4.1 K60 侧的电源域与去耦处理
K60 的供电引脚比一般 MCU 多,第一次画原理图的人很容易漏。我把当时核对过的引脚清单列一下:VDD 有 4 组(每组都要单独去耦),VSS 对应 4 组,VDDA 一组,VREFH 一组,VBAT 一组,VOUT33 一组。每一组 VDD 旁边都要放一颗 100nF 的 0402 电容,紧贴引脚放,另一侧打过孔到地平面。这四颗电容不能共用,也不能省,我见过有人为了省面积只放两颗,结果跑高速的时候偶尔 HardFault,查了很久才发现是电源噪声。
VDDA 的处理要更讲究一些。除了 100nF 之外,还要加一颗 1µF 的电容,并且从 BUCK1 输出到 VDDA 之间串一颗磁珠。VREFH 是 ADC 的参考高电平,如果外部有精密基准就接基准,没有的话接 VDDA,但同样要加 LC 滤波。我把 VREFH 单独用一颗 10µF 加 100nF 的组合去耦,实测 ADC 的有效位数能提高 0.5 位左右。
VOUT33 这个引脚容易被人忽略。它是 K60 内部 USB 稳压器的输出,必须外接一颗 2.2µF 的电容,否则 USB 收发器工作不正常,表现为插上 USB 之后枚举失败或者时不时断开。我第一版板子漏了这颗电容,USB 能识别但传大文件的时候会掉线,加了电容之后稳定。
实操心得:K60 的 VDD 和 VDDA 之间如果有压差,会导致内部 ESD 二极管导通,产生额外的漏电流,这个漏电流在低功耗模式下特别明显。我测到过 VDDA 比 VDD 高 0.3V 时,休眠电流从 20µA 涨到 80µA。所以 LC 滤波的磁珠直流电阻要选小一点,别用那种 DCR 一两欧的型号。
4.2 低功耗模式选择与唤醒源配置
K60 的低功耗模式有几种:WAIT、STOP、VLPS、LLS、VLLS0~3。选择哪种取决于你要保留什么。我的场景是按键唤醒,唤醒之后要快速恢复工作,不需要保留 RAM 内容,所以用 VLLS1 是最省电的。VLLS1 下整颗 MCU 的电流大概在 5µA 量级,加上 PMIC 的静态电流 20µA 左右,整机待机可以做到 30µA 以内。
进入 VLLS1 之前要做几件事,顺序不能错。先把所有不用的 GPIO 配成模拟输入或者输出低电平,浮空的输入引脚会因为电平不定导致输入缓冲器来回翻转,白白耗电。然后关闭所有外设时钟,特别是 ADC 和定时器。再把 PMIC 上不需要保持的电源轨关掉,比如 LDO1(ADC 基准)在休眠时没有意义,直接关。最后配置唤醒源,我用的是 PORT 模块的引脚中断,按键接在 PTA4 上。
这里有个坑我踩过:唤醒源的引脚在进入低功耗之前必须配置好上下拉。如果按键是低电平有效,引脚要上拉;如果是高电平有效,要下拉。我一开始配了内部上拉,但外部还有一个 100kΩ 的下拉电阻,两者打架,休眠时这个引脚上有几十微安的电流流过,待机电流直接翻倍。后来把外部下拉去掉,只用内部上拉(大概 30kΩ),问题解决。
另一个坑是PMIC 的 I2C 接口在 MCU 休眠期间的状态。如果 I2C 引脚在休眠时被配成复用功能但没有时钟,引脚会保持最后一次的电平状态,如果恰好是低电平,PMIC 会认为总线忙,某些功能会受影响。我的做法是在进入低功耗前把 I2C 引脚切成普通 GPIO 并输出高电平,唤醒之后再切回复用功能。
void enter_vlls1(void) { /* 1. 关闭不需要的电源轨 */ pca9422_write_reg(REG_LDO1_CTRL, 0x00); pca9422_write_reg(REG_BUCK2_CTRL, 0x00); /* 2. I2C 引脚切 GPIO 并拉高 */ PORT_PinMuxConfig(PORTE, 24, kPinAlt1); /* SCL -> GPIO */ PORT_PinMuxConfig(PORTE, 25, kPinAlt1); /* SDA -> GPIO */ GPIO_PinWrite(GPIOE, 24, 1); GPIO_PinWrite(GPIOE, 25, 1); /* 3. 未使用引脚配成模拟输入,降低漏电 */ disable_unused_pins(); /* 4. 配置唤醒源 */ PORT_Init(PORTA); PORT_SetPinInterruptConfig(PORTA, 4, kPORT_InterruptFallingEdge); NVIC_EnableIRQ(PORTA_IRQn); /* 5. 清标志,进 VLLS1 */ LLWU_Init(); LLWU_SetExternalWakeupPinMode(LLWU, 12, kLLWU_ExternalPinFallingEdge); SMC_SetPowerModeProtection(SMC, kSMC_AllowPowerModeVlls); SMC_SetPowerModeVlls(SMC); while (1) { __asm("nop"); } }代码里的disable_unused_pins()这个函数值得单独说。它的作用是把所有没接外设的引脚都配成模拟输入(ALT0 加禁用数字输入缓冲)。这一步对低功耗的影响比很多人想象的大,我有一次测待机电流 120µA,找了两天,最后发现是一个调试用的 UART 引脚浮空导致的。把全部未用引脚处理一遍之后,电流降到 25µA。
4.3 充电状态机与电量估算
充电状态机这块,PMIC 本身提供了状态寄存器,可以读到"充电中 / 充电完成 / 充电暂停 / 故障"这几种状态,还有故障类型(过温、超时、电池异常)。我的固件里用一个 500ms 的定时任务去轮询这个状态寄存器,做三件事:更新 UI 上的充电图标、处理异常状态、以及做电量估算。
电量估算是个老生常谈的问题。单节锂电的电压和电量关系不是线性的,3.7V 到 4.2V 这一段大概对应 80% 到 100%,而 3.5V 到 3.7V 这一段对应 20% 到 80%。如果直接用电压除以 4.2 算百分比,会出现"前 80% 掉得飞快、后 20% 用很久"的现象。我的做法是打一张 11 点的查找表,用分段线性插值。
| 电压(V) | 4.20 | 4.10 | 4.00 | 3.90 | 3.80 | 3.70 | 3.60 | 3.50 | 3.40 | 3.30 | 3.00 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 电量(%) | 100 | 92 | 80 | 66 | 50 | 34 | 20 | 12 | 7 | 4 | 0 |
这张表是我用电子负载做恒流放电实测出来的,不同批次的电芯会有几个点的偏差,但趋势一致。用这张表加上充电状态做修正,电量显示的跳变问题基本解决了。
还有一个细节是负载压降补偿。电池有内阻,大电流放电的时候端电压会比开路电压低,这时候如果直接读电压算电量,会显示"用着用着突然掉 10%"。我的处理是在大电流负载(比如射频发射)的时候,给测到的电压加上一个补偿量,补偿量等于负载电流乘以电池内阻(我用 150mΩ 估算)。这个补偿不用很精确,能把跳变压到 2% 以内就够了。
5. 调试实录:那些把人按在工位上的问题
5.1 上电反复复位
第一版板子焊完之后,上电就是这个现象:电源灯亮一下灭一下,示波器上看 3.3V 轨每隔 20ms 左右跌一下,MCU 根本没机会跑起来。第一反应是电源带不动负载,但把 MCU 摘掉之后 3.3V 很稳,说明问题在 MCU 侧。
排查的思路是从复位源入手的。K60 有个复位状态寄存器,记录上一次复位的原因。但问题是 MCU 跑不起来就读不到寄存器,所以只能从硬件上找。我先量了 RESET 引脚,发现它是被拉低的,说明是外部复位信号。顺着 RESET 往回找,发现板上有一颗外部复位芯片,它的阈值设的是 3.08V,而我的 3.3V 轨在上电过程中会在 3.0V 附近停留十几毫秒,这期间复位芯片一直保持复位状态,等电压爬到 3.08V 才释放。但问题是释放之后 MCU 启动,电流突然增大,3.3V 又跌回阈值以下,再次复位,形成循环。
根因是 BUCK1 的软启动时间设得太短,上电瞬间的浪涌电流把输出拉垮了。PMIC 的软启动时间是可以配的,我把 BUCK1 的软启动从 0.5ms 改到 4ms,上电斜率变缓,浪涌电流从 800mA 降到 250mA,问题解决。另外我把复位芯片的阈值改到 2.93V,留出更多余量。
这个问题的教训是:上电时序不只是"谁先谁后",还包括"多快"。软启动时间这个参数在选型的时候很容易被忽略,但它直接决定了上电瞬间的峰值电流。
5.2 I2C 扫不到设备
板子能跑起来之后,第二关是 I2C 通信。我写了个简单的扫描程序,遍历 0x00 到 0x7F,结果一个设备都没扫到。这个问题的排查路径比较典型,我按顺序说一下。
第一步,确认物理连接。用万用表量 PMIC 的 SCL、SDA 引脚对地电阻,都是几十 kΩ(上拉电阻的值),说明没有短路。量 PMIC 的供电引脚,3.3V 正常。
第二步,确认引脚复用。K60 的 I2C0 默认引脚是 PTE24/PTE25,但我第一版原理图上把 I2C 画到了 PTE18/PTE19,这两个引脚对应的是 I2C1。代码里配的是 I2C0,引脚配的是 PTE24 的复用功能,但实际板子的走线在 PTE18 上,所以信号根本没出去。这个问题是我看原理图的时候想当然了,把 I2C0 和 I2C1 的引脚搞混了。改代码用 I2C1 之后,扫描立刻扫到了 0x4B。
第三步,确认上拉。虽然扫到了设备,但偶尔会有 NAK。把内部上拉换成外部 2.2kΩ 之后彻底稳定。
注意:I2C 扫不到设备,九成以上的原因在引脚复用和上拉这两件事上,芯片坏了的概率极低。建议调试顺序是:先量电平(高电平是不是接近 VDD)→ 再量波形(有没有时钟脉冲)→ 最后才是查地址。如果示波器上看不到时钟脉冲,那一定是引脚复用或者外设没使能,跟从设备没关系。
5.3 充电发热与截止异常
充电调试阶段遇到两个问题,都跟温度有关。第一个是充电时 PMIC 明显发烫,手摸上去烫得不敢停留。用热像仪看,热点在芯片的充电功率管附近,温度 78℃。算一下功耗:充电电流 250mA,VBUS 5V,电池 3.7V,线性充电器的功耗是 (5 - 3.7) × 0.25 = 0.325W,QFN 封装的 θJA 大概 60℃/W,温升 19.5℃,环境温度 30℃ 的话结温应该是 50℃ 左右,跟实测的 78℃ 对不上。
后来发现是 PCB 的散热没做好。QFN 封装底部的散热焊盘我只打了 4 个过孔,而且过孔没有做盖油处理,散热效果很差。重新画板的时候把散热焊盘下的过孔加到 9 个,孔径 0.3mm,并且在背面铺了一块 15mm × 15mm 的铜皮,温度降到 62℃。另外一个优化是把充电电流从 250mA 降到 200mA,功耗降到 0.26W,温度进一步降到 55℃。最后我权衡了一下,充电时间从 2 小时变成 2.5 小时,但温度控制住了,选了 200mA。
第二个问题是充电截止异常。有几次电池明明冲到 4.2V 了,充电状态还是显示"充电中",一直不停。查了状态寄存器,发现是安全计时器没有触发。原因是看门狗喂狗任务被我放在了低优先级,充电后期系统负载轻,任务调度不及时,看门狗超时之后充电被限流,电流一直维持在很小的值,始终达不到终止电流的条件,就卡在那儿了。把喂狗任务提到中等优先级,并且把终止电流的判定时间窗口从 1 秒放宽到 10 秒,问题解决。
5.4 待机电流超标
这个问题最折磨人。设计目标 60µA,实测 180µA,超标三倍。用高精度电流表串在电池回路里测,然后逐个断开负载排除。
先断 MCU 的供电,电流降到 42µA,说明 MCU 这边漏了 138µA。再断外部传感器,降到 35µA,说明传感器漏了 7µA。剩下 35µA 是 PMIC 自身的,比手册标称的 20µA 高了 15µA。
MCU 这 138µA 是怎么来的?我用了最笨的办法:在代码里把外设一个一个关掉,每关一个测一次电流。最后定位到两个源头,一个是 ADC 模块没有完全关闭,只关了时钟但没断电,漏了 60µA;另一个是某个调试用的 UART 引脚浮空,输入缓冲器在中间电平来回翻转,漏了 70µA。ADC 的问题通过写它的电源控制寄存器彻底断电解决,UART 引脚配成模拟输入解决。
剩下 PMIC 的 15µA,查了半天发现是 LDO2 在给 VBAT 供电。K60 的 RTC 在休眠的时候还要走时,所以 VBAT 不能断,但 VBAT 域的电流大概是 3µA,加上 LDO2 自身的静态电流 12µA,正好 15µA。后来我把 LDO2 的输出从 1.8V 改成 3.0V(K60 的 VBAT 允许范围是 1.71V 到 3.6V),LDO 在压差更小的情况下静态电流降到 6µA。最终整机待机 43µA,达标。
5.5 问题速查表
把上面这些问题整理成一张表,方便你在遇到类似现象的时候快速定位。
| 现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| 上电反复复位 | 软启动太快、浪涌拉垮电源 | 示波器看电源轨上电斜率 | 加长软启动时间 |
| 复位芯片误触发 | 复位阈值太接近工作电压 | 量复位引脚波形 | 降低复位阈值 |
| I2C 扫不到设备 | 引脚复用配错、无上拉 | 示波器看 SCL 有无脉冲 | 核对引脚复用表、换外部上拉 |
| I2C 偶发 NAK | 上拉太弱、总线电容大 | 量上升沿时间 | 上拉改 2.2kΩ |
| 充电发烫 | 散热焊盘过孔不足 | 热像仪定位热点 | 增加散热过孔和背面铜皮 |
| 充电不截止 | 看门狗超时导致限流 | 读状态寄存器 | 提高喂狗任务优先级 |
| 待机电流超标 | 未用引脚浮空、外设未断电 | 逐个断开负载测量 | 未用引脚配模拟输入 |
| ADC 读数跳动 | 功率地与模拟地混在一起 | 对比充电时的采样值 | 单点接地、包地处理 |
| USB 枚举失败 | VOUT33 缺电容 | 量 VOUT33 电压 | 补 2.2µF 电容 |
6. 实测数据与经验沉淀
6.1 效率、温升、待机电流实测
所有调试完成之后,我做了一轮完整的实测,数据如下。测试条件是室温 25℃,用电子负载做恒流拉载,电池用直流电源模拟(设 3.8V,串联 150mΩ 内阻)。
BUCK1 在 3.8V 输入、3.3V 输出下的效率:轻载 10mA 时 82%,中载 200mA 时 91%,满载 600mA 时 88%。轻载效率偏低是因为开关损耗占比大,这个在 2MHz 开关频率下是必然的,如果你的应用大部分时间在轻载,可以考虑让 PMIC 进入 PFM 模式,轻载效率能提到 88% 左右,代价是输出纹波会变大一点。
BUCK2 在 3.8V 输入、1.8V 输出下的效率:100mA 时 87%,400mA 时 85%。压差大的时候效率会低一些,这是 Buck 的固有特性。
温升方面,满载运行 30 分钟后,BUCK1 的电感表面温度 48℃,PMIC 表面 52℃,充电时 PMIC 表面 55℃。整机放在密闭外壳里温升会再高 8℃ 左右,所以设计的时候要留出余量。
待机电流最终实测 43µA,分解下来是 PMIC 20µA、MCU VLLS1 状态 12µA、RTC 域 6µA、其他漏电 5µA。按 500mAh 电池算,纯待机可以撑 500/0.043 ≈ 11600 小时,也就是一年多,对于这种"用一次放几天"的设备来说完全够用。
| 测试项 | 条件 | 实测值 | 备注 |
|---|---|---|---|
| BUCK1 效率 | 3.8V→3.3V, 200mA | 91% | PWM 模式 |
| BUCK1 纹波 | 200mA 负载 | 6mV | 20MHz 带宽限制 |
| BUCK2 效率 | 3.8V→1.8V, 200mA | 86% | PWM 模式 |
| 充电温升 | 200mA 充电 | +30℃ | 环境 25℃ |
| 待机电流 | VLLS1 + PMIC 待机 | 43µA | 电池端测量 |
| 满充时间 | 3.4V→4.2V | 2h28min | 200mA 充电 |
| 上电时间 | 冷启动到 MCU 运行 | 38ms | 含软启动 4ms |
6.2 几条不走弯路的建议
如果让我重新做一遍这个项目,有几件事我会毫不犹豫地照做,也有几件事我会换一种做法。
第一件照做的事是先做电源评估板再做整板。我第一版直接把 PMIC 画进了整板,结果电源出问题的时候分不清是电源本身的问题还是负载的问题。后来我单独做了一块 30mm × 30mm 的电源评估板,把 PMIC 的所有输出都引出排针,用跳线帽选择负载,调起来效率高太多了。这块板子花了我半天时间画、两天时间打样,但省下来的调试时间至少一周。
第二件照做的事是把 I2C 通信做成可观测的。我在固件里加了一个环形缓冲区,记录最近 32 次 I2C 事务的寄存器地址、写入值、返回值,出问题的时候通过串口 dump 出来。这个机制帮我定位了好几次偶发通信失败,如果没有它,那种一天出现一两次的问题根本没法查。
会换做法的是电池连接器的选型。我用的是一颗 2.0mm 间距的卧贴座子,插拔力偏小,样机摔过一次之后电池线松了,设备直接断电。后来换成带锁扣的型号,成本贵了两毛钱,但可靠性提升明显。便携设备上,电池连接器这种"不起眼但一松就完蛋"的器件,千万别省。
还有一个经验是关于调试接口的。我在板子上留了 4 个测试点:VBUS、SYS、BUCK1 输出、电池正极。这四个点用示波器一夹就能看出能量流向,判断是充电问题还是放电问题。测试点的焊盘用 1.5mm 直径的圆形裸铜,别盖油,这样探针能直接戳上去,比找元件引脚方便得多。
最后分享一个我在低功耗调试上用得很顺手的技巧:准备一个能测微安级电流的治具,串在电池和板子之间,治具上带一个拨动开关,可以瞬间短接。这样你既能测到真实的微安级待机电流,又能在需要的时候不断电切换工作状态,避免每次测量都要重新上电初始化。这个治具成本不到二十块钱,但对低功耗调试的效率提升是数量级的。