1. 项目定位与整体设计思路
一块锂电池塞进设备里,工作时要把 5V USB 切换成系统电源,还要一路降压给 MCU、一路给显示屏,偶尔还要升压输出 5V 给外设,同时盯着电池别过充、别过放。这套活儿如果全让主控自己扛,光枚举一遍中断就不少代码,模拟电路的零漂和开关噪声更是能把人绕晕。我最近在做的某款便携记录仪,主板就是 PCA9422 搭配 PIC18F46K22 这套双芯片方案。这个题目看起来只是两个芯片,其实解决的是一条完整的电源管理闭环:从 USB 插入、电池充电、三路稳压输出,到低功耗唤醒、电压监测、异常保护,全都归结到一颗 MCU 的策略控制里。
PCA9422 承担的是“硬电源”部分,它把充电路径管理、恒流恒压充电曲线、多路 DC-DC 调压、升压输出、保护逻辑都做到芯片内部;PIC18F46K22 承担的是“软策略”部分,它通过 I2C 总线去读 PMIC 的状态、写充电电流、改输出电压,并根据系统负载动态调配供电。这样拆分之后,MCU 不再需要去实时模拟充电环路的反馈,也不需要硬件上堆一堆分立电源电路,整体可靠性和代码可维护性都上了一个台阶。
这个方案很适合做产品的朋友参考:做便携仪表、低功耗数据采集、手持设备、带锂电池但是主控资源不富裕的项目,都可以用这套思路。核心是把“电源状态机”从业务代码里抽出来,交给专用 PMIC,MCU 只做决策。全文我会按硬件选型、外围设计、固件实现、实测排查四个层面把具体细节讲清楚,尽量能让一个有一定单片机基础的人照着把板子跑起来。
1.1 为什么需要一颗专用电源管理芯片
用 MCU 直接管电池听起来很简单:ADC 采电压,PWM 控制充电,GPIO 切负载。但真正把板子做稳定之后,你会撞上几个非常现实的问题。
第一是充电曲线的复杂度。锂电池充电不是“给个恒压就行”,基本都要走预充、恒流、恒压、截止四个阶段。MCU 要自己用定时器算每个阶段的时长、用 ADC 实时判断电压电流边界,一旦采样有毛刺就可能在恒流和恒压之间来回跳,电池寿命受影响不说,设备还容易反复重启。PCA9422 这一类 PMIC 把这条曲线固化在硬件状态机里,MCU 只需要发一个“开始充电”的命令,剩下的阶段切换由芯片自己完成,状态可以通过寄存器查询。
第二是路径管理。设备常常要同时支持 USB 供电和电池供电,而且“插着 USB 时电池边充电边放电”这种双路径会让主控非常头疼。专用 PMIC 内部有电源路径开关:VBUS 在时优先用 VBUS 给系统供电,同时用多余的电流充电池;VBUS 拔掉后无缝切换到电池。这个切换时间在微秒级,MCU 完全感知不到中间掉电。
第三是低功耗。做电池产品的人都懂,最好让整机在休眠时只跑个位数的微安。用分立 DC-DC 加 MCU 的 GPIO 去关断各路电源,往往每个芯片还有漏电,而且关断顺序不对还可能把负载打穿。PMIC 内部有专门的系统状态控制,能一键进入低功耗模式,把非必要通道全部关闭,只保留一个常备电压轨给 MCU 的唤醒逻辑供电,这个功耗指标做起来容易得多。
我当时把这个便携设备从“MCU 硬扛电源”改成 PCA9422 方案,最大的体感就是逻辑回归简单:业务代码里不再有“充电阶段判断”这种东西,所有电源行为都可以用“读寄存器 + 写寄存器”来描述。
1.2 PCA9422 与 PIC18F46K22 的分工方案
很多工程师看到“电源管理”就会以为 PMIC 是主角,MCU 只是配角。实际按我的分工,PIC18F46K22 才是整个系统策略的中枢,PCA9422 更像是一个可以编程执行的电源执行机构。
我把两者的职责切得很清楚:
| 模块 | 负责内容 | 关键体现 |
|---|---|---|
| PCA9422 | 电池充放电、VBUS/USB 路径切换、三路 DC-DC 输出、5V 升压、过压过流保护 | 硬件自动完成,寄存器可读 |
| PIC18F46K22 | 开机时序控制、充电策略决策、电压采集与电量估算、异常关机、低功耗模式调度 | 通过 I2C 访问 PCA9422,通过 ADC 采样系统 |
| 外围协同 | 温敏电阻采样、按键唤醒、LED 指示、外设负载开关 | 由 MCU 统一编排 |
PIC18F46K22 这颗 MCU 选它主要有几个理由。首先是它内部资源足够:32KB 闪存跑电源管理固件绰绰有余,I2C 模块可以直接和 PCA9422 对接,还有两个 MSSP,后续如果外挂传感器也不用抢总线。其次,PIC18F 系列的引脚结构很规整,低功耗模式下有多个唤醒源可配置,非常适合做电池产品的睡眠唤醒。最后就是它 2.3V 到 5.5V 的宽工作电压,可以直接由 PCA9422 的某一路电源轨供电,不需要额外的 LDO,系统整体少一级损耗。
整体架构里,PCA9422 负责把电能变成几路稳定的电压轨,PIC18F 则负责在这几条轨上执行业务策略。比如设备开机时,MCU 要求 PCA9422 把 DC-DC1 拉高到 3.3V 给传感器供电;进入休眠时,MCU 告诉 PCA9422 关闭 DC-DC2,只保留常备轨。这个组合的好处是“电源的实时性”归硬件管、“电源的语义”归软件管,两边的设计边界非常清晰。
2. 硬件电路设计与关键参数
2.1 PCA9422 外围电路与输入路径
PCA9422 的外围电路不算复杂,但每一处都需要注意。首先是 VBUS 输入,我这里是 USB 5V 接入,先通过一个 100mΩ 的电流采样电阻和一颗 P-MOS 防反接,再进入 PCA9422 的 VBUS 引脚。芯片内部会做 OVP、OCP,输入端的 TVS 管我还是保留了一颗,因为 USB 热插拔时确实会产生比较高的电压尖峰,哪怕 PMIC 有保护,TVS 能帮它分担一部分能量冲击。
电池直接接到 BAT 引脚,同时并联一颗低 ESR 的陶瓷电容和一个 100μF 的钽电容,这里不能省。电池线束如果稍微长一点,PCB 走线电感会让充电电流突变时产生压降震荡,钽电容可以有效吸收瞬态。电池端的 NTC 电阻我接在芯片的 TS 引脚上,用 10kΩ 的负温度系数热敏电阻配合固定分压电阻,让 PMIC 在电池过热或过冷时自动停充。
DC-DC 输出的电感选型要注意饱和电流,我用的三路负载分别是 MCU(最大 60mA)、传感器(最大 120mA)、显示屏(最大 90mA),所以电感选了 2.2μH / 220mA 饱和的规格,既够用又不会把板子面积撑大。输出电容选 22μF 加一颗 0.1μF 的高频去耦,这样负载瞬态响应控制在可接受范围内。
还有一点容易被忽略:PCA9422 的 EN/KEY 引脚。我做的是一个便携设备,需要“按一下开机、长按关机”的效果,EN 引脚直接接到系统侧,同时并联一个下拉电阻防止悬空误触发。这里下拉电阻阻值不能太大,我实测用了 10kΩ,开漏驱动时压降正常,也不会在低功耗模式下浪费太多电流。
2.2 PIC18F46K22 接口引脚规划
PIC18F46K22 的引脚规划看起来很基础,但它直接决定了后期调试是否顺畅。我这里把引脚功能分成了三组:I2C 控制总线、ADC 采集通道、中断与状态输入。
I2C 总线用的是 MSSP1 模块,默认引脚是 SCL=RC3、SDA=RC4。PCA9422 是 7 位地址芯片,默认地址我按 0x6C 写,具体到不同批次会有差异,建议在板子上预留两个地址焊盘用于备选。I2C 上拉电阻用的是 4.7kΩ,电源轨 1.8V 时我也算过,上拉电阻把高电平拉到足够裕度,400kHz 模式下实测波形没有明显圆角。
ADC 采集主要管三路:VBAT 电压、系统 3.3V 电压、电池 NTC 的模拟温度。VBAT 电压直接用两个精密电阻分压,分压比例取 2:1,把 4.2V 拉到 2.8V 以内,正好落在 PIC18F 的 ADC 参考电压范围内。这里要特别注意分压电阻的精度,我选了 1% 的电阻,并且并联了一个 0.1μF 电容做滤波,不然采样值会有几十毫伏的波动,会直接影响电量判断。
中断与状态输入是最影响体验的部分。PCA9422 有一个中断输出引脚,我把它的状态输出接到 PIC18F 的 RB0 外部中断引脚上,这样充电完成、异常等事件发生时,MCU 会立刻被唤醒去读寄存器。按键的输入放在 RB1,用带中断的模式,休眠时可以随时唤醒。另外,PCA9422 的健康状态引脚用途比较灵活,我接到 RB2,并通过一个 LED 指示当前的电源状态,调试时一眼就能看出当前是 USB 供电还是电池供电。
2.3 供电路径与上电时序设计
上电时序这种问题,在产品量产的阶段最容易暴露。比如 MCU 还没起来,传感器却被先供了电,就可能出现 I2C 地址冲突甚至是 GPIO 倒灌电流。我在这块板子上设定了明确的时序要求。
第一步,VBUS 接入后,PCA9422 内部自动建立系统电源轨,这个轨固定输出给 MCU 的 VDD,保证 PIC18F46K22 上电就有稳定的供电。第二步,MCU 的固件开始跑起来,初始化自己的时钟、GPIO、I2C,然后通过 I2C 读取 PCA9422 的状态寄存器,确认当前是哪种供电模式。第三步,MCU 根据自己的状态机开启其它电源轨:先给传感器供 3.3V,延时 20ms 等待其稳定,再给通信模块供电。这里延时不是随便拍的,传感器数据手册里规定的上电稳定时间是 30ms 左右,我就给到 50ms 的安全裕量。
有的朋友可能会问,既然 PMIC 自己有上电时序配置,为什么不直接用它硬件的时序功能?我理解是这样:如果系统里全是纯电源负载,靠 PMIC 自己的斜坡时序就够了;但只要负载里有需要“供电后再初始化”的 MCU、传感器、通信芯片,电源时序就要和固件初始化逻辑联动。所以我这里的实际分工是,PMIC 只负责把三路 DC-DC 快速建立起来,先后顺序由 MCU 逐路开启,这样灵活性最高。
供电路径的设计还要考虑反向电流。DC-DC2 给传感器供电时,如果传感器模块内部还有一颗小电池或大电容,拔掉 DC-DC2 之后它会倒灌到电源轨上,导致 MCU 误以为电源还在。解决方法是每路输出后端串联一颗 P-MOS 做输出开关,MCU 把它们当作“负载开关”来管,这样既能切电源,又能防倒灌。
3. 固件架构与核心实现
3.1 I2C 通信驱动的初始化与封装
I2C 驱动是整个电源管理固件的地基。PIC18F46K22 的 MSSP1 模块配置成主模式很直接,但有几个细节不处理就会踩坑。
寄存器配置上,SCL 和 SDA 对应的引脚必须先设为输入模式,因为 I2C 总线是开漏结构,真正的高电平靠上拉电阻拉起来。我的初始化代码如下:
void i2c_master_init(void) { // MSSP1 的 SCL=RC3, SDA=RC4 TRISC3 = 1; TRISC4 = 1; // I2C 主模式, 使能 SMBus 兼容关闭 SSP1CON1 = 0b00101000; SSP1CON2 = 0x00; // 16MHz 主时钟, 100kHz SCL // SSPADD = (FOSC / (4 * FSCK)) - 1 = (16M / (4 * 100K)) - 1 = 39 SSP1ADD = 39; SSP1STAT = 0x80; }我很刻意地把波特率保持在 100kHz,而不是直接跑到 PCA9422 支持的 400kHz。原因很简单,电源管理总线不需要高速率,100kHz 的抗干扰能力明显更好,尤其在电池供电设备里,电压动态变化时 I2C 数据线的毛刺会明显减少。
驱动函数的封装上,我把它分成最底层的几段:起始、停止、发送字节、读取字节。然后在上层组合出“写寄存器”和“读寄存器”两个原子操作。写操作先发芯片地址加写位,再发寄存器地址,最后发数据;读操作则是起始后先写地址连读,再发起重起始和读位,读回一字节后带 NACK 结束。
void i2c_write_reg(uint8_t slave_addr, uint8_t reg_addr, uint8_t val) { i2c_start(); i2c_send_byte((slave_addr << 1) | 0x00); i2c_send_byte(reg_addr); i2c_send_byte(val); i2c_stop(); } uint8_t i2c_read_reg(uint8_t slave_addr, uint8_t reg_addr) { uint8_t val = 0; i2c_start(); i2c_send_byte((slave_addr << 1) | 0x00); i2c_send_byte(reg_addr); i2c_start(); i2c_send_byte((slave_addr << 1) | 0x01); val = i2c_read_byte(1); i2c_stop(); return val; }这个封装的收益在后面所有功能开发里都能体现出来。我写策略逻辑时从来不需要重复关心起始位、应答位这些底层细节,只需要拿着数据手册查寄存器地址,然后调用i2c_read_reg/i2c_write_reg就行了。整个固件核心的控制逻辑,本质上就是一系列这样的读写调用。
3.2 充电管理流程的状态机设计
PCA9422 自己有一套充电状态机,但 MCU 侧还不能省去管理逻辑,因为“要不要充电”这个问题,设备本身最清楚。如果主板正处于高温运行状态,或者用户强制开启了低功耗模式,即便 PMIC 允许充电,MCU 也应该有权限推迟。
我这里设计了一个简单的顶层充电状态机,包含五个状态:空闲、预充、恒流充电、恒压充电、充满。状态转换完全由 MCU 读取 PCA9422 的充电状态寄存器来驱动,MCU 只是很轻量地做判断。核心代码如下:
typedef enum { CHG_IDLE, CHG_PRE, CHG_CC, CHG_CV, CHG_FULL, CHG_FAULT } charge_state_t; charge_state_t charge_state_machine(void) { uint8_t stat = i2c_read_reg(PCA9422_ADDR, REG_CHG_STATUS); if (stat & CHG_STATUS_FAULT) return CHG_FAULT; if (stat & CHG_STATUS_FULL) return CHG_FULL; if (stat & CHG_STATUS_PRE) return CHG_PRE; if (stat & CHG_STATUS_CC) return CHG_CC; if (stat & CHG_STATUS_CV) return CHG_CV; return CHG_IDLE; }在实际应用里,我还会加一个保护条件:当系统处于高温状态,也就是 NTC 读数超过 45°C 时,MCU 会主动把REG_CHARGE_CTRL里的充电使能位清掉,先暂停充电,等温度回落到 40°C 以下再继续。这个保护逻辑 PMIC 本身也有,但 PMIC 通常只在非常极限的温度下才触发,而我们的产品希望在更宽松的区间内就提前降温,所以 MCU 侧的干预是有价值的。
还有一个点容易被忽略:充电电流的设定。PCA9422 支持多档充电电流,我一般通过寄存器把充电电流设定在 500mA 左右,这既不会让电池发热明显,也能保证 2-3 小时充满一块 2600mAh 的电池。充电电流越大,系统里所有电源轨的纹波也越大,得不偿失。
3.3 动态电压调节与掉电监测的实现
动态电压调节是这套方案里最出彩的部分,也是 PCA9422 这类 PMIC 存在的意义之一。PIC18F46K22 可以通过 I2C 在前一次下电之前把系统主跑电压调低,等到需要高性能时再调高。
举例来说,设备待机时,MCU 只需要 2.0V 的电压就能维持静态运行,功耗能低不少;但设备进入通信状态时,CPU 需要全速跑 16MHz,这时就需要 3.3V 保证时序稳定。PCA9422 的 DC-DC1 正好支持动态调压,我只需要切换寄存器里的电压点位即可:
void set_dcdc1_voltage(uint8_t voltage_code) { uint8_t val = i2c_read_reg(PCA9422_ADDR, REG_V_CTL1); val &= 0xF0; val |= (voltage_code & 0x0F); i2c_write_reg(PCA9422_ADDR, REG_V_CTL1, val); }这里的 voltage 码和电压值的对应表我直接查数据手册,写成一张静态表放固件里,切换时顺序从低到高,避免中间出现电压跳变把负载打挂。实测从 2.0V 切到 3.3V 大约用时 30μs,完全无感。
掉电监测部分我用了两层:先是 PIC18F 自身的 ADC 采样 VBAT 电压,低于 3.3V 时进入省电模式;低于 3.0V 时,MCU 主动通知 PCA9422 关闭非必要通道,只保留 MCU 轨。再往下的最后一道防线是 PMIC 的内部欠压保护,一旦电池电压低于其硬件阈值,自动断开系统负载,保护电池不过放。三层防线保证了设备绝不会因为一夜待机而把电池活活耗死。
4. 实测调试图与问题排查
4.1 I2C 总线不稳定问题的排查
第一次把板和上位机对接时,I2C 总线就给出一个下马威:偶尔能读到数据,但更多时候读回的全是 0xFF。我先用逻辑分析仪抓波形,发现 SCL 没问题,但 SDA 在应答位之后一直拉不高,典型的“总线死锁”现象。
这个问题的来源很经典:某个环节没有释放 SDA 总线,或者总线上挂了一个地址冲突的器件。排查下来发现,PCA9422 的地址选择引脚我悬空了,导致上电时地址不确定,有时读到 0x6C,有时读到 0x6D。加上 PCB 上又有一个外设正好用了相邻地址,两者发生冲突,总线一直处于等待应答状态。
解决办法是两处:第一,地址引脚明确接电阻焊盘,默认下拉到 GND,让七位地址固定;第二,给 I2C 驱动加一个超时机制,一旦总线持续低电平超过 10ms,就强行把 SCL 翻转九次,恢复总线状态。这个“九脉冲清总线”的方法是 I2C 社区里的老经验,实测非常管用。
void i2c_bus_recover(void) { // 当 SDA 被拉低超过阈值时,翻转 SCL 最多 9 次 for (uint8_t i = 0; i < 9; i++) { SCL = 0; __delay_us(5); SCL = 1; __delay_us(5); } }另外提一下上拉电阻的取值,有些朋友喜欢用 10kΩ 省电流,但在电池电压比较低的场景下,4.7kΩ 的上拉能力明显更稳。如果板上 I2C 总线上挂了超过 4 个设备,建议降到 2.2kΩ,但要注意这时静态电流会上升,电池待机时间会缩短一点,需要自己权衡。
4.2 充电电流与电量精度校准
一开始我直接用 ADC 测电池电压来估算电量,发现充满了显示 100%,拔掉充电线几分钟后就变成 95%,再过一会儿又回到 98%,非常跳。问题的根源是电池电压在充电结束后会有回弹,而且负载电流变化时,电池内阻导致端电压跟着变。
解决这个问题我做了两个动作。第一个是在充电状态变成充满之前,先让 PMIC 进入一段“浮充”时间,等电池端电压稳定后再判断是否真正充饱。在寄存器层面,我把充电截止电流调低到 50mA,这样充电流程会更晚判定为充满,预留了足够的反应时间。
第二个动作是电量估算不用电压法,改用一个简单的开路电压查表加动态补偿的方法。设备在空闲时记录一次不带负载的开路电压,然后和当前负载电流下的端电压做差分,推算出电池内阻,再根据内阻修正实时电量。这样做完之后,从 100% 到没电的整个过程,显示的百分比和实际掉电速度基本对齐了。
| 状态 | 负载电流 | 实测电池电压 | 修正后估算电量 |
|---|---|---|---|
| 空闲 | 5mA | 3.92V | 96% |
| 通信 | 120mA | 3.78V | 84% |
| 休眠 | 0.3mA | 3.85V | 91% |
校准表我会在出厂前每台设备跑一次,固件里存储修正系数。这里也提醒一句,锂电池的电量估算很难做到绝对精准,但做到“用户体感不跳变、不用到一半突然关机”就是合格的水平,不要追求手机那种复杂的电量计。
4.3 低功耗唤醒冲突的修复
设备休眠之后,我遇到过一个诡异的现象:插上 USB 充电线,屏幕应该立刻亮起,但有时候需要再按一下按键才亮。排查过程很有意思。
逻辑分析仪看 PMIC 的中断引脚,发现 VBUS 插入时,PCA9422 确实拉低了中断输出,MCU 的外部中断也被触发,但 MCU 在读到中断源寄存器后发现是一个“充电开始”事件,而不是“VBUS 插入”事件,于是软件把它当成一个需要忽略的事件,又回到休眠了。根因在于 MCU 的休眠唤醒和 PMIC 的中断事件不是一个语义层级。
修复方法是把“唤醒”和“事件处理”拆成两步:MCU 被任何外部中断唤醒后,先无条件退出休眠,然后再查询 PCA9422 的中断标志,判断事件的类型。如果是充电事件,就直接进入正常充电状态机;如果是无关事件,才重新休眠。这个逻辑听起来简单,但很多做低功耗的人都会把“唤醒原因”和“事件内容”混在一起处理,导致漏掉关键事件。
低功耗模式本身的配置也要注意。PIC18F46K22 进入休眠前,我会先把 I2C 总线的引脚状态设置好,不能让 SDA 和 SCL 处于高阻状态,否则 PMIC 可能会因为总线悬浮而误动作。正确的做法是把这两个引脚拉高,保持 I2C 总线处于空闲电平,这样 PMIC 不会收到任何假的起始位。
5. 经验总结与扩展建议
5.1 设计阶段就该想清楚的几件事
这套方案跑通之后,回看整个设计过程,有几点是如果我重新做一定会在原理图阶段就定下来的。
一是 PMIC 的地址选择引脚和中断引脚一定要留焊盘跳线。调试初期,你可能需要换地址、换中断源来排查问题,如果引脚被焊死,就只能飞线,非常痛苦。我在板子上留了 0Ω 电阻位,后面排查 I2C 冲突时几乎没有改版就解决了问题。
二是电池 NTC 的充放电保护逻辑要提前定义好。PMIC 本身有硬件保护,但 MCU 侧的保护逻辑需要明确温度回差、时间回差,不然设备在夏天高温环境下充电会变成“充一分钟、断十秒”,电池和用户体验都很难受。
三是从一开始就要确定 I2C 总线的波特率。如果你打算在休眠时靠外部中断唤醒,那 400kHz 的高速模式并不合适,唤醒后总线铺平稳定需要时间,反而 100kHz 更稳妥。这个决定要放在硬件设计阶段,而不是等固件写完了再改。
四是电源轨的调试输出点一定要留。PCA9422 的三路 DC-DC 输出,每一路都要在 PCB 上留一个测试点,方便示波器直接观察上电时序和纹波。我第一版板子漏了其中一个测试点,结果调试那一路输出时只能把万用表探头戳在裸焊盘上,既危险又容易短路。
5.2 后续可以扩展的方向
现在这套系统已经可以稳定运行,后续如果要继续升级,我有几个方向可以考虑。
首先是引入更精确的电量计。PCA9422 管充放电没问题,但它不直接做库仑计,如果产品对电量百分比要求很高,可以在系统里加一颗独立的电量计芯片,通过 I2C 和 PIC18F46K22 通信,MCU 就能拿到真实的库仑计数。这个改动不需要碰 PCA9422 的外围电路,固件里加一个驱动就行。
其次是多电池并联扩展。PCA9422 支持单节锂电,但有些设备需要两节并联来延长续航,这时需要额外增加电池均衡电路。MCU 侧只需要把充电策略改成轮流充电,PCA9422 的配置变化不大,整体改动可控。
再一个是把 MCU 的无线功能用起来。PIC18F46K22 本身没有射频,但可以通过 UART 挂一个低功耗蓝牙或 Wi-Fi 模块。这样设备的电源状态、电池电量、温度数据都能远程上报,真正做到“电源管理 + 物联网”二合一。我这个项目后续就会往这个方向走,把 PCA9422 的充电状态和电芯数据统一上报到云平台。
最后分享一个小经验:调这种电源管理方案,手边必须常备逻辑分析仪和可调直流电源。逻辑分析仪负责抓 I2C 时序,可调电源负责模拟电池在不同电压下的表现,这两样东西配合好,基本能把 90% 的调试问题快速定位。这套 PCA9422 加 PIC18F46K22 的组合,我跑下来最大的感受是:电源不再是电老虎,而是一套真正可以被固件“讲道理”管理的系统。