这块板子的电源部分,前前后后改了三版,最后还是落到“PMIC + 8位MCU”的组合上。设备本身是一台便携式采集仪表,使用 PCA9422 和 PIC18F45K22 搭建了完整的电源管理链路:USB 5V或一节18650电池输入,PCA9422负责输入切换、充电和电源路径,PIC18F45K22作为主控通过I2C配置充电电流、各路输出电压、中断和状态读取,同时用自己的ADC对电池电压做交叉校验。整机待机电流最后压在280µA左右,关机态整板低于10µA。
这篇文章把这套系统的选型、电路、状态机、寄存器初始化、低功耗调优,还有几个比较“阴”的坑完整写一遍。给后面做电池供电产品、准备把分散的充电IC和LDO收敛成一颗电源管理芯片的同行一个参考。无论你是第一次碰PMIC,还是手里已经有类似板子想优化待机电流,应该都能找到对应的段落。
1. 选型逻辑:为什么要拆成“一颗PMIC加一颗8位MCU”
1.1 真实需求:一台便携仪表对电源的要求
先交代这次项目的硬指标。设备是一台电池供电的采集仪表,平时做数据采集和显示,大部分时间处于“等人按键”的状态,所以电源管理不只是“能把电变出来”,而是要解决下面这几件事:
- 输入要兼容USB 5V和单节锂电池(3.0V~4.2V),插上USB就充电,拔掉USB自动切电池;
- 需要三路电源:3.3V给主控和外设、1.8V给传感器、一路3.3V给射频模块,其中射频模块必须能独立关断;
- 充电要恒流恒压,目标500mA充电电流,4.2V截止;
- 整机休眠电流目标低于1mA,最好能冲到300µA级别;
- 要知道电池电压和大概电量,低电量要告警,关机保护不能少。
这套需求如果拆开看,每一个都不难,难的是它们混在一起:充电、电源路径切换、多路输出、低功耗、保护,全都要协同。以前的做法是一颗充电IC、一颗LDO、一个保护电路、几个MOS管开关,状态全靠模拟电路拼,改一版需求就要重新画一版电源,而且休眠电流很难压下去。
1.2 PCA9422 和 PIC18F45K22 各自的分工
PCA9422承担了“电力供应”的脏活累活:输入电源选择(VBUS优先、电池备份)、锂电充电的恒流恒压控制、多路可编程输出、I2C寄存器读写、内部ADC读取电池电压和温度、以及中断输出。我只需要给它配置好参数,它就会自动维持输出和充电行为。
PIC18F45K22在这里不是“应用处理器”,而是一个“电源管家”。它负责上电时把PCA9422初始化成我们需要的模式,运行中根据业务逻辑切换电源状态,收到PCA9422的中断后去读状态寄存器判断发生了什么(充电完成?VBUS插入?电池低压?),再决定要不要切状态。顺便用自己片上的10位ADC对电池电压做第二路采样,两个数据互相印证。
这种分工的好处是:电源管理逻辑完全在MCU这一侧可见、可改,不依赖模拟电路的“玄学”。PCA9422只负责执行,什么时候切、怎么切,由PIC18F45K22的固件说了算。
1.3 和“独立充电IC + DC-DC + 模拟控制”方案对比
我也列过一版分散方案,把充电、降压、开关全都用独立器件拼,结论是除了选型文档多,几乎没什么优势。
| 方案 | 优点 | 真正的麻烦 |
|---|---|---|
| 独立充电IC + LDO/DC-DC + 模拟逻辑 | 每个器件资料多、替换灵活 | 器件数量多、上电时序靠外面搭、休眠电流难收敛、EMI点多 |
| PMIC + 高端应用级MCU | 功能丰富、能跑系统 | 成本高、MCU自身功耗吃掉低功耗收益、杀鸡用牛刀 |
| PCA9422 + PIC18F45K22 | 电源路径集中、状态机可控、成本低、待机实测优秀 | 驱动和状态机需要自己写,出问题要先排硬件 |
分散方案的休眠电流基本压在几百µA到几mA量级就很难再往下走,因为每个独立器件都有一份自己的静态电流,加起来就是一笔不小的开销。PMIC方案里,待机时把多余输出轨全关掉,只留一颗PMIC和一个处于Sleep状态的MCU,电流能少一个数量级。
1.4 为什么不直接上带电源管理的高端MCU
这是个很自然的疑问:都2025年了,随便一颗带PMU的低功耗MCU不就能自己管电吗?我的看法是,高端MCU确实自带部分电源管理,但它自身的工作电流和待机电流都更高,而且启动过程中要先把一堆外设和时钟拉起来,功耗峰值不好控制。更重要的是,这种设备不需要跑系统,逻辑就是“按键按下、采个样、显示一下、回去睡”,用PIC18F45K22这种8位MCU裸机跑状态机,反而最简单可靠。
PIC18F45K22本身的规格也够用:32KB Flash跑固件、1.5KB RAM存变量、256B EEPROM存校准参数,片上带了MSSP(I2C/SPI)、10位ADC、多个定时器,供电范围1.8V~5.5V,电池电压降到3.3V附近它还能正常工作。这种“刚好够用”的选型,反而让整个系统的功耗边界非常清晰。
2. 硬件电路:电源树、I2C电平域与上电时序
2.1 电源树:每一路输出的分配
硬件上最核心的就是电源树。下面是我这版板子的结构,用文本画出来:
USB 5V ──┬──> PCA9422 VBUS │ 电池 ─────┴──> PCA9422 VBAT │ PCA9422输出: ├─ OUT1 3.3V ──────> PIC18F45K22 VDD + 系统外设 ├─ OUT2 1.8V ──────> 传感器(由状态机开/关) └─ GPIO负载开关 ────> 3.3V_RF(射频模块独立断电) │ INT ────────────────> PIC RB4 SCL/SDA ─(电平转换)──> PIC MSSP13.3V这一路是系统的主电源,直接给MCU供电,也承担所有不能随便断的电路。1.8V和3.3V_RF都是“可关断轨”,平时由状态机决定是否输出。射频模块我没有直接挂在PMIC某一路输出上,而是多放了一个负载开关,理由是想通过负载开关实现更干净的“硬断电”,避免模块内部的旁路漏电路径在PMIC输出关闭后仍然从电池抽电。
输入端的电容按照PMIC手册的典型电路来放:USB输入处放10µF陶瓷电容加一个0.1µF高频电容,电池端放22µF陶瓷电容。需要注意电池端的电容不能用太大,否则上电瞬间PMIC要往里灌的能量太大,容易触发输入过流保护,我第一次打样时在这里吃过亏。
2.2 I2C引脚的电压域问题:千万别想当然直连
这是硬件部分最容易翻车的地方。PCA9422的I2C逻辑电平由它自己的IO电源域决定,我这颗样片把IO电压域单独接到了1.8V,而PIC18F45K22跑在3.3V,两条总线直接连肯定会出问题:3.3V的高电平对1.8V域来说就是过压,1.8V的高电平对3.3V域来说又不够高。
我的做法是用经典的BSS138双向电平转换电路,低压侧接1.8V,高压侧接3.3V,SCL和SDA各接一路,两边各配上拉电阻。低压侧上拉拉到1.8V,高压侧上拉拉到3.3V,总线速率跑400kHz没问题。如果你手头刚好有PCA9306这类专用电平转换芯片,会更省事,BSS138电路对某些从机的驱动能力要求比较高,IMC总线设备多的时候会出现上升沿不够快的现象。
提示:如果你设计的系统里PCA9422的IO电压域可以直接接3.3V,那电平转换电路可以省掉。但一定要先确认手册里VDDIO的允许范围,不要在没确认的情况下一根导线直连。
2.3 使能、复位与上电时序
PCA9422有一个使能引脚EN。最初我的设计是把EN通过10k电阻直接上拉到电池电压,一接电池就开机。后来发现这个方案没法做“长按关机”,因为关机状态下MCU自己都没电了,谁来把EN拉低?后来改成经典软锁存结构:EN通过10k上拉到VBAT,按键按下时把EN拉高,PMIC输出3.3V后MCU起来,MCU再通过一个GPIO经二极管继续维持EN高电平;需要关机时,MCU先写寄存器让PMIC关闭输出,然后再把维持EN的GPIO拉低,整个系统就彻底断电。这个电路很小,但它是整套状态机里“关机状态”能成立的硬件前提。
复位时序方面,PCA9422的复位输出直接接到PIC18F45K22的MCLR引脚,保证上电后MCU在电源稳定之后才被释放复位。三路输出虽然只需要按需开启,但每一路在“被开启”和“被关断”之间至少要留1ms以上的间隔,避免同一时刻多个输出轨同时切换造成电源轨抖动。1.8V轨和3.3V_RF轨我都是通过MCU的GPIO控制PMIC寄存器或负载开关来切换,切换完成后等PMIC的电源状态寄存器反馈,确认该轨真的起来了,再继续下一步。
2.4 布局布线的几条硬要求
PCB布局上,PMIC的地焊盘必须充分接地,QFN封装中间的热焊盘我打了9个过孔到地平面,焊接不良会导致PMIC疯狂过温保护,后面踩坑部分会细说。输入输出电容都贴着对应引脚放,电流路径要短、要粗,电池走线尽量远离I2C和中断线,防止开关电流的di/dt串进信号里。
另外无论板子多小,I2C和INT都一定要引出测试点。低功耗调试时你没有示波器探头直接夹的位置,就只能靠猜,效率会低很多。
3. 电源状态机:四个状态与切换时的责任划分
3.1 四个电源状态的定义
整套系统的功耗逻辑就是下面四个运行状态,状态机是整个固件的骨架:
| 状态 | 该状态做什么 | 开启的电源轨 | MCU状态 | 目标电流 |
|---|---|---|---|---|
| Active | 采集、显示、通信 | 3.3V + 1.8V + 3.3V_RF | 正常运行 | 几十~几百mA |
| Idle | 等待下一次任务 | 3.3V + 1.8V | 低功耗运行,定时器扫描 | 约5mA |
| Sleep | 深度待机 | 仅3.3V | Sleep + WDT / 外部中断 | <300µA |
| Off | 整机关机 | 无,PMIC待机 | 断电 | <10µA |
Active状态不用多解释。Idle状态把射频模块断电,但保留1.8V传感器供电,因为传感器需要预热,如果不保留,每次测量前要等它稳定,体验很差。Sleep状态则把1.8V也关掉,MCU进入Sleep模式,靠GPIO中断或看门狗唤醒。Off状态是真正意义上的关机,除了PMIC自身待机漏电,板上其他电路全部断电。
3.2 状态切换的关键路径
状态切换不是简单的“改个变量”,每一步都有明确的触发条件和动作:
- Active → Idle:20秒无操作,MCU通过I2C把3.3V_RF负载开关关闭,然后进入Idle循环;
- Idle → Sleep:60秒内无任务,MCU写PMIC寄存器关闭1.8V轨,确认该轨状态反馈为关闭后,MCU进入Sleep;
- Sleep → Active:外部按键中断或看门狗唤醒,MCU先读PMIC状态寄存器,再开启1.8V和射频轨;
- 任意状态 → Off:长按电源键3秒,MCU先关射频和传感器轨,再写PMIC关机命令,最后拉低EN维持GPIO,整板断电。
这里有一条铁律:MCU永远是最后一个关自己电源的。也就是说,关其他轨之前必须先保证自己的3.3V还在,所有需要保存的数据先写进EEPROM,关机命令发出后还要等PMIC状态寄存器确认输出已经关闭,最后才断开EN。顺序反了,轻则数据丢失,重则PMIC被瞬间拉低EN导致内部状态错乱。
3.3 MCU 与 PMIC 在切换中的责任划分
这个状态机跑了一段时间后,我总结出一个原则:PMIC只负责“按配置执行”,MCU负责“决定何时执行”。PMIC内部没有业务逻辑,充电是否完成、哪个轨该关,这些判断全部由MCU根据中断和状态寄存器来做。
这样做有个明显好处:调试时所有决策点都在MCU代码里,你可以打日志、可以单步、可以反复触发,不用拿示波器去猜PMIC内部到底怎么想的。整机功耗里80%的优化空间其实都在状态机的“决策质量”上,PMIC只负责提供低功耗的硬件基础。
3.4 状态机与低功耗目标的对应关系
把四个状态的目标电流写出来之后,再回头看你就能发现,真正难的是Sleep和Off。Active和Idle有业务在跑,电流大一点无所谓;但Sleep状态只要多留一个1.8V的传感器供电,目标就直接从300µA跳到几mA。所以状态机的原则是:能关的轨全关,能断的外设全断,MCU自己只保留最小的唤醒源。Off状态则完全依赖硬件软锁存电路,MCU断电后只能靠PMIC自己的待机漏电来控制。
4. 寄存器初始化与I2C通信实战
4.1 先读ID:一条必须做的“握手”
PIC18F45K22和PCA9422之间就一条I2C链路,但这条链路承载了所有配置和状态交互。上电第一件事永远不是写配置,而是读器件ID。这个动作能同时验证三件事:I2C硬件通了、从机地址对了、PMIC已经正常上电。
#define PCA9422_I2C_ADDR 0x11 // 7位地址,由ADDR引脚配置,以手册为准 #define PCA9422_I2C_ADDR_W (PCA9422_I2C_ADDR << 1) #define PCA9422_I2C_ADDR_R (PCA9422_I2C_ADDR << 1 | 1) uint8_t PCA9422_ReadID(void) { // 发送寄存器地址 i2c_start(); i2c_write_byte(PCA9422_I2C_ADDR_W); i2c_write_byte(0x00); // ID寄存器 i2c_stop(); // 重复起始,读一字节 i2c_start(); i2c_write_byte(PCA9422_I2C_ADDR_R); uint8_t id = i2c_read_byte(0); // NACK结束 i2c_stop(); return id; }如果读出来的ID是全0xFF,或者总线在START之后就一直卡死,先别怀疑固件,去量SCL/SDA电平、检查焊接。我在调试阶段遇到过太多次“以为固件写错,实际是电平转换电路没焊好”的情况。
4.2 初始化序列:按手册顺序逐个写寄存器
PCA9422的初始化不是写一个寄存器就完事,要按依赖关系来。我这里的顺序是:先配充电参数,再配输出使能,最后配中断。擅自调整顺序可能导致PMIC进入一个半初始化状态,后面状态机跑起来行为很怪。
void PCA9422_Init(void) { uint8_t id = PCA9422_ReadID(); if ((id & 0xFF) == 0xFF || id == 0x00) { // 上电I2C失败,进入错误处理,等看门狗复位 return; } // 1. 充电配置:4.2V目标电压,500mA充电电流,50mA终止电流 PCA9422_WriteReg(0x10, 0x30); // 目标电压档位 PCA9422_WriteReg(0x11, 0x1F); // 充电电流500mA PCA9422_WriteReg(0x12, 0x0A); // 终止电流50mA // 2. 输出配置:3.3V主轨立即开启,其余两路由状态机控制 PCA9422_WriteReg(0x20, 0x01); // OUT1使能 PCA9422_WriteReg(0x21, 0x00); // OUT2先关闭 PCA9422_WriteReg(0x22, 0x00); // OUT3先关闭 // 3. 中断配置:只开放需要的几类中断 PCA9422_WriteReg(0x30, 0x04); // 充电完成/接入VBUS/电池低压 // 4. 清残留中断 PCA9422_ReadInterruptStatus(); }注意:上面代码里的寄存器地址和位定义是我这片板子固件里实际用的,不同批次的PMIC手册可能会有差异,移植到别的项目时一定要对照官方手册核对一遍。代码结构比寄存器数值更值得参考——先充电、后输出、再中断,这个顺序不要乱。
4.3 中断处理与动态参数调整
PCA9422的INT引脚接到PIC18F45K22的RB4。安装中断里只做一件事:读状态,改标志,然后快速退出。不要在ISR里做长任务。
void __interrupt() isr(void) { if (RB4IF) { RB4IF = 0; uint16_t status = PCA9422_ReadInterruptStatus(); if (status & MASK_VBUS_IN) sysEvent |= EV_VBUS_CONNECT; if (status & MASK_VBUS_OUT) sysEvent |= EV_VBUS_DISCONNECT; if (status & MASK_CHARGE_DONE) sysEvent |= EV_CHARGE_DONE; if (status & MASK_BAT_LOW) sysEvent |= EV_BAT_LOW; } }这里有个最容易踩的坑:PMIC的中断源是电平型的,读状态寄存器动作本身就是在清中断。如果你只读了中断标志却不把对应的状态寄存器完整读一遍,INT引脚可能会一直拉低,ISR就会不停进、不停读,看起来像“系统卡死”。所以我的ReadInterruptStatus函数里会把状态寄存器整组读一遍,宁可多读,不可漏清。
动态参数调整的典型场景是温度补偿:当NTC检测到电池温度超过45°C,MCU就通过I2C把充电电流寄存器降一半;温度回到40°C以下再恢复。这种调整在Active状态做即可,不要在ISR里做,因为写PMIC寄存器需要几个I2C周期,长时间占着中断会拖累主循环。
4.4 波形观察:I2C别只看逻辑分析仪
我用逻辑分析仪和示波器都抓过这条总线。逻辑分析仪看协议没问题,但看不到电平转换后的实际摆幅。示波器探头点在PCA9422侧的SCL上,能看到1.8V逻辑域的高电平约1.75V,上升沿大约300ns,没有振铃。3.3V那一侧因为多了一级BSS138,上升沿会稍微软一点,但400kHz下完全够用。
确认波形这一步花的时间不多,收益很大。很多I2C“偶发卡死”的问题,最后定位下来都是信号质量问题,而不是代码逻辑问题。有条件的话,每次改板之后都把这条总线抓一遍保存下来,后面出问题可以直接对比基线波形。
5. 电池估算与保护阈值:从“有电”到“可知电”
5.1 双通道电压采样:PMIC内部ADC加外部分压
电池电压是整台设备最重要的“健康指标”。PCA9422内部有ADC,可以通过I2C直接读VBAT电压,这个值在Active和Idle状态下都拿得到。问题是进入Sleep状态后MCU虽然醒着,但长时间读I2C本身会增加功耗,而且如果PMIC进入特殊模式,内部ADC不一定随时可用。
所以我在PIC18F45K22的ANx引脚上加了一路外部电阻分压采样。分压电阻选得比较大(1M和470k),等效阻抗接近330k,采样时PIC的ADC需要更长的采样时间,我把采样电容充电时间配置到20µs以上,保证转换精度。分压支路平时用一个GPIO控制的NMOS断开,只有在需要采样时才把分压回路接通,避免休眠时这支分压电阻一直在耗电。这个“采样前先开电源,采完立刻断”的思路,在低功耗系统里非常常用。
5.2 负载补偿后的OCV查表法
电量估算我没有用专门的库仑计芯片,而是用了“开路电压查表 + 负载补偿”的方案。这个方法对单节锂电够用,因为设备大部分时间处于空闲状态,有很多机会测到接近开路电压的电池电压。
| 电池电压(静置30分钟) | 估算电量 |
|---|---|
| 4.20V | 100% |
| 3.90V | 80% |
| 3.70V | 50% |
| 3.50V | 20% |
| 3.30V | 0%(关机保护) |
但实际运行时不可能每次都静置30分钟,所以要在测量值上做负载补偿:读到的电压会减去“当前电流 × 电池内阻估计值”,得到一个等效开路电压,再去查表。电池内阻我通过一次完整的放电实验标定,写进EEPROM。这个方法精度大概在10%以内,显示电量百分比足够用了。
5.3 充电温度曲线与保护门槛
保护逻辑的阈值我用了一张表来定义,每个成员都是一个“阈值+动作+恢复条件”的组合:
| 事件 | 触发阈值 | 动作 | 恢复条件 |
|---|---|---|---|
| 低电量警告 | 电池电压 < 3.5V | 点亮LED、发中断事件 | 电压回升到3.6V以上 |
| 关机保护 | 电池电压 < 3.3V | 状态机进入Off | 插上充电后重新开机 |
| 充电高温 | NTC > 45°C | 充电电流降为一半 | 温度回落到40°C以下 |
| 充电过压 | 电池电压 > 4.25V | 停止充电 | 电压回落到4.2V以下 |
NTC用的是10k负温度系数热敏电阻,贴在电池表面,通过10k电阻分压后进PIC的ADC。这里同样要做“分压电路按需供电”,否则NTC分压支路在休眠时也会一直耗电。
6. 低功耗实测:从5mA压到不到300µA的过程
6.1 测试环境怎么搭
低功耗测量不是拿个万用表夹上就行。PMIC的输出是动态切换的,电流波形里有脉冲和尖峰,普通万用表的平均模式会给出一个“看着还行”但实际不可复现的数值。我用了一个可编程直流电源充当电池,电源输出的电流档位开精密档,再串联一块毫安/微安级电源表,用数据模式连续记录。测量时把设备本身的所有通信、显示都停掉,保证测到的就是电源管理系统的本底电流。
每次改完固件或电路,我会按顺序测四个状态的值,记录成表,和上一次的基线对比。没有基线,低功耗优化就是闭眼开车。
6.2 五个耗电源的逐个排查
第一版板子跑出来的Sleep状态电流是4.8mA,和目标的300µA差了整整一个数量级。把各电源轨的电流逐个断开后,问题就一个个浮出来了:
| 排查项 | 优化前贡献 | 做法 | 优化后贡献 |
|---|---|---|---|
| 1.8V传感器轨未真正关闭 | 2.8mA | 修正PMIC寄存器配置,让该轨进入彻底关断而非PFM模式 | 0 |
| 射频模块负载开关未断 | 1.1mA | 确认状态机在Sleep前关闭负载开关 | 0 |
| 状态指示灯常供电 | 0.8mA | LED改由GPIO直接控制,Sleep时GPIO输出低 | 0 |
| 电池分压支路常开 | 2.5µA | 加NMOS开关按需供电 | 0 |
| MCU I/O悬空脚泄漏 | 约20µA | 所有未用引脚配置为输出低或模拟模式 | 0 |
这里面最值得说的就是“1.8V轨未真正关闭”。当时我在代码里写了关闭该轨的寄存器,猜它应该已经关了,结果实测电流纹丝不动。后来读了PMIC的状态寄存器才发现,那颗样片的该输出通道在“关闭”状态下仍然保留了一个低功耗维持模式,不是真正的断电。这是典型的“以为做了、实际没做”的问题,唯一的排查方法就是逐轨断开测量。所以低功耗调试一定要靠数据,不能靠代码自我感觉。
6.3 最终数据与温度影响
调完之后的数据如下:
| 状态 | 优化前 | 优化后 |
|---|---|---|
| Sleep | 4.8mA | 286µA |
| Idle | 9.2mA | 5.1mA |
| Off | 320µA | 6µA |
286µA这个数据在25°C下测的,温度升高后漏电会涨。60°C环境下Sleep电流大约620µA,0°C环境下则比常温略低,约240µA。温度对锂电的放电平台也有影响,低温下电池电压在3.5V时实际容量可能只剩常温的一半,所以我的关机保护阈值留了0.2V的余量,避免低温误关机。
Off状态从320µA降下来的关键是软锁存电路。第一版Off状态下整板还有电流,是因为PMIC的EN引脚一直被维持高电平,PMIC根本没进关机。把软锁存改成“长按关机后MCU拉低EN”,这个值才真正到了6µA。
7. 三个踩坑问题与完整排查链路
7.1 问题一:上电后PMIC输出全部没有
三块样板打出来,有两块上电后3.3V完全没输出,PMIC表面温度正常,不热也不冷。排查链路如下:
- 先量VBAT引脚电压,电池电压3.8V,在;再量VBUS,5V在。输入没有问题;
- 量EN引脚,电压只有0.8V。PMIC的EN阈值一般要1.2V以上,显然没到开启电平;
- 用镊子轻压PMIC封装表面,再量EN,变成3.7V,输出出来了。这说明EN引脚和芯片焊盘之间是虚接的;
- 拆下PMIC看焊盘,发现热焊盘上锡不足,EN引脚边缘的焊料也没有完全润湿。虚焊的引脚在静止时接触电阻极大,一压就导通,一松就断开。
修复办法是重新植锡、用钢网保证热焊盘锡量,然后回流。这个问题其实很典型:QFN封装的PMIC,热焊盘如果不能和地平面充分连接,整个芯片的“地参考”都不稳定,外围引脚电平全都会乱。所以焊接QFN器件,回流曲线和锡量必须严格控制,这不是“能通就行”的事。
7.2 问题二:I2C偶发卡死导致整个系统假死
这个问题断断续续折磨了我将近两个星期。现象是设备长时间运行(大概两三天)后,屏幕卡死,看门狗复位后又能跑,但跑不了多长时间又卡。用示波器抓总线,发现SDA被拉低后一直不放,像是从机死锁了。
排查过程:
- 先在代码里加I2C超时,超时后主动发9个SCL时钟做总线恢复,发现能恢复,但过一段时间又会死;
- 用示波器长时间挂在SCL上,终于抓到一次SCL上出现了明显的过冲和下冲,幅值超过了逻辑电平的容忍范围,MSSP模块把错误的边沿当成了有效位,导致状态机错乱;
- 检查布局,发现SCL走线离射频模块的电源开关节点太近,开关动作的dv/dt耦合到了SCL上;
- 修复:I2C两根线上各串100Ω电阻,SCL对地并一个100pF电容,同时把布线绕开开关节点;另外在固件里保留总线恢复程序作为兜底。
这之后跑了两轮50小时以上老化测试,再没有复现。这个坑的教训是:不要一遇到I2C卡死就怀疑固件,先在示波器上看长时间波形。“偶发”这个词的背后,十有八九是信号完整性或噪声问题。
7.3 问题三:深度休眠唤醒后进入异常充电模式
有段时间客户反馈设备电量掉得特别快,拿回来一测,醒来之后PMIC竟然在500mA充电,而当时根本没有插USB。充电配置寄存器在休眠过程中被改写成了默认值。
顺着代码一路查,发现是“读-改-写”寄存器时的中断竞争事故。状态机在进入Sleep前要关闭1.8V轨,流程是先读PMIC输出配置寄存器,把对应位改掉,再写回去。但就在“读”和“写”之间,PMIC的INT引脚来了一个充电完成中断,ISR里也读了这个寄存器,把某个中间变量改了,等主循环拿着旧值写回去时,居然把充电电流配置一起覆盖了。
修复方法很简单:所有对PMIC寄存器的读-改-写操作,在操作期间关中断保护;或者干脆把输出配置和充电配置放在不同的结构体里,每次只读改写自己负责的那一段。另外,唤醒后的第一步一定要重新读一遍PMIC全部关键寄存器,做一次“配置校验”,发现和期望值不一致就重新初始化。这相当于给电源管理加了一个廉价看门狗。
这个问题的教训比前两个更深:电源管理代码里,寄存器访问的中断安全性和普通业务代码一样重要,甚至更重要。因为PMIC寄存器一旦被写坏,影响的不是一段逻辑,而是整个系统的“命脉”。
最后再说一句实操体会:这套PCA9422加PIC18F45K22的组合,用下来最省心的部分是“电源路径集中”,调试时所有逻辑都在MCU这一侧,不用满板子追信号。要是再给我一次重做的机会,我会把I2C测试点、EN测试点和几个关键电源轨的测试点从一开始就全部引出来,排错效率会高非常多。另外建议每一个用PMIC的项目都备一个最简的软件复位PMIC流程,无论寄存器被写坏还是总线死锁,都能让设备自己“缓过来”,这个兜底动作在长期运行的产品里几乎一定会用到。