做带无线充电的便携设备,电源管理往往比无线协议本身更加费时。我前段时间做了一款手环类产品的电源模块,主控选了 PIC18F4685,无线充电接收与输出稳压部分选了 PCA9422,整个项目从原理图到量产测试跑完后,发现这套组合很值得单独拿出来分享。它特别适合中小团队:PCA9422 把整流、稳压、协议通信这些模拟电源的脏活累活全部收掉,PIC18F4685 只通过 I2C 去读状态、写配置、做策略,不用自己去搭充电环路,也不用处理无线充电的调制解调细节。
这篇文章我会按照“选型思路、硬件链路、I2C 固件、电源状态机、实测踩坑”这条线来讲,包含了完整代码片段和可复现的排查过程。如果你正在做类似的便携设备,或者只是想把一个带 I2C 的电源芯片接入 PIC 系列 MCU,这里的内容可以直接参考。
1. 为什么把模拟电源和高层策略拆给两颗芯片
1.1 两个芯片的分工边界
一开始我并没有直接认定 PCA9422 加 PIC18F4685 这个组合。先看需求:手环类设备体积小、电池容量低,既要有无线充电接收能力,又要在运行、休眠、关机充电几种模式下切换,整机 MCU 还要顺便管屏幕、传感器和通信。如果所有电源功能都堆在 MCU 周边自己搭,电路会非常复杂。
PCA9422 在系统里的定位是“电源前端”。它接收线圈过来的交流波形,在内部完成整流和稳压,然后输出一路可调的直流电压。这路电压不是固定的,MCU 可以通过 I2C 随时调整。它还自己处理 Qi 协议相关的通信握手、异物检测、温度监测,这些本来是需要单独控制的环节,现在我完全不需要关心。我只需要在初始化时写几个寄存器,运行中定时读状态。
PIC18F4685 在系统里的定位是“电源策略大脑”。它的 ADC 采集电池电压和系统电流,它的 GPIO 控制负载通断,它的外部中断接收 PCA9422 的输出状态变化,I2C 则负责跟 PCA9422 通信。PIC18F4685 不需要知道桥式整流器的压降是多少,也不需要理解线圈谐振频率,它只关心一件事:当前系统处在什么状态,VOUT 应该设定为多少伏。
这个分工的本质是把“模拟控制”和“数字策略”彻底分开。模拟电源工程师扣的那些环路补偿、纹波抑制、整流效率问题,被 PCA9422 封装在了芯片内部;而数字侧的逻辑复杂度,则靠 PIC18F4685 的 I2C、中断、ADC 资源来消化。两者之间只有一个接口,就是 I2C 寄存器,这给固件开发和后期调试都省了大量时间。
1.2 与另外两种常见方案对比
我在选型阶段其实画过三个方案对比,这里直接摊开说。
第一方案是最常见的“两颗专用芯片”:一颗独立的无线充电接收芯片加一颗带 I2C 的电池充电管理芯片。这个方案每个芯片的单一功能都很成熟,但问题在于两套 I2C 寄存器要分别配置,充电状态和无线接收状态是两个独立状态机,MCU 需要频繁同步它们的关系,稍不注意就会出现“无线接收已就绪但充电芯片还在等待”这种尴尬状态。
第二方案是用一颗更高端的 MCU,内部直接集成 DCDC 和充电管理。这个方向的集成度最高,但成本也高,而且很多高端 MCU 的电源部分并不针对无线充电接收场景设计,线圈输入的整流路径我仍然要外接,等于只集成了一半。
最终选择了 PCA9422 加 PIC18F4685,理由很务实。PCA9422 一颗芯片就覆盖了无线接收、稳压输出和可调电压三个核心能力,非常适合电池供电的小型产品。PIC18F4685 虽然只是 8 位 MCU,但它的 I2C 模块、12 位 ADC、外部中断、ECAN 接口这些外设在这个项目里全都派上了用场。最关键的是,这两颗芯片在软件层面只需要维护一套 I2C 通信协议,状态同步逻辑简单很多。
| 对比维度 | 双专用芯片方案 | 高集成 MCU 方案 | PCA9422 + PIC18F4685 |
|---|---|---|---|
| 模拟电路复杂度 | 中高,两套外围 | 中,仍需外置接收 | 低,一颗芯片搞定接收与输出 |
| 软件工作量 | 高,两套寄存器与状态机 | 中,依赖厂商库 | 低,一套 I2C 寄存器 |
| BOM 成本 | 中 | 高 | 中低 |
| 扩展能力 | 一般 | 取决于 MCU | 强,PIC18F4685 外设充足且带 CAN |
2. 硬件链路设计:从线圈到电池再到整机供电
2.1 PCA9422 输出的三种应用形态
PCA9422 的 VOUT 引脚是整条电源链路的中间节点。按产品是否需要电池,可以把输出侧的电路设计成三种形态。
第一种是纯外接电源形态,不需要电池,PCA9422 的 VOUT 直接给系统负载供电。这种形态最简单,适合桌面小设备。它的问题在于一旦无线充电中断,系统立刻掉电,所以必须在软件里处理“输入掉电检测”,防止写到一半的数据丢失。
第二种是电池充电路径形态,PCA9422 的 VOUT 接到电池充电前端,由系统里的电池充电线路负责限流和恒压,PCA9422 只作为一个可调电源。手环类产品大多采用这种方案。要注意的是,PCA9422 的输出电压必须留出充电芯片工作需要的压差,比如充电芯片要求输入至少 4.3V,那么 VOUT 就不能设成 4.2V。
第三种是双电源自动切换形态,PCA9422 的 VOUT 和电池通过一个切换开关共同给系统供电。这种电路需要在 VOUT 和电池之间加隔离二极管或专用负载开关,防止电池反向灌入 PCA9422。因为这个切换逻辑涉及 MOSFET 方向、体二极管泄漏这些问题,我最终没有在量产板上使用,而是采用了第二种形态,稳定性优先。
我做选择时考虑很简单:手环开机电流峰值接近 80mA,充电时整机同时工作,如果切换逻辑有一瞬间的衔接漏洞,系统就会掉电复位。第二种形态下,负载始终挂在 PCA9422 输出和充电回路之间,路径简单,不会出现切换中断。
2.2 PIC18F4685 与 PCA9422 的接口连接
接口连接说起来非常直接,PCA9422 是一颗 I2C 从机,PIC18F4685 是主机,两边就是 SCL、SDA、中断、地线四个连接。我把引脚关系列在下面。
| PIN 功能 | PCA9422 引脚 | PIC18F4685 引脚 | 连接说明 |
|---|---|---|---|
| I2C 时钟 SCL | SCL | RC3 | 需上拉电阻 |
| I2C 数据 SDA | SDA | RC4 | 需上拉电阻 |
| 中断输出 INT | INT | RD0 | 配置为输入,触发外部中断 |
| 共地 | GND | VSS | 单点接地 |
上拉电阻我用的是 4.7kΩ,这个值在 100kHz I2C 速率和短距离走线下表现稳定,具体踩坑过程后面会专门说。INT 引脚是开漏输出还是推挽输出,不同芯片批次可能有差异,我在原理图上加了 100kΩ 下拉电阻,防止 MCU 复位期间该引脚浮空引起误触发。
PCA9422 的 VOUT 输出端需要靠近放一组去耦电容,我放了 1uF 加 0.1uF,位置尽量贴近芯片引脚。这个电容不光是稳压需求,还关系到无线充电接收的瞬态响应。如果 VOUT 走线到负载之间有一段较长的 PCB 走线,我会在这段走线末端再补一颗 1uF 电容,实测纹波有明显改善。
2.3 电路板布局的实操经验
第一版 PCB 我踩过一个布局坑:无线充电线圈的输入走线绕了大半块板子,结果电磁干扰串到 SCL/SDA 线上,I2C 偶发通信失败。后来把线圈输入走线改成短而宽的路径,并且让 I2C 两根线尽量与线圈输入线保持垂直关系,问题基本消失。
另一个经验是 VOUT 去耦电容不要离线圈太近。第一版验证板把大电容放在线圈接收区域旁边,金属外壳涡流导致无线充电效率掉了 3% 左右。后来把电容挪到 VOUT 引脚附近、远离线圈的位置,效率才恢复正常。
走线层面还有一条容易被忽略的规则:SCL 和 SDA 属于低速数字信号,表层布线即可,但不要在它们下面铺大块完整地平面,否则走线对地电容增大,I2C 信号上升沿会被拖慢。我一般在信号层留出空隙,让两根线走在比较干净的区域。
3. 固件层核心:MSSP1 初始化与 PCA9422 寄存器配置
3.1 MSSP1 主机模式初始化细节
PIC18F4685 的 MSSP1 模块可以配置成 I2C 主机模式,对应引脚是 RC3 和 RC4。我先说初始化代码,再说几个容易写错的细节。
// PIC18F4685 主频 4MHz,I2C 速率 100kHz // SCL = RC3,SDA = RC4 void i2c1_init(void) { TRISC3 = 1; // SCL 输入 TRISC4 = 1; // SDA 输入 SSP1STAT = 0x00; // 标准速度模式 SSP1CON1 = 0x28; // I2C Master 模式 + SSPEN SSP1ADD = 9; // 4MHz / (4 * (9+1)) = 100kHz }SSP1CON1 = 0x28这一步我最初是从旧项目里直接复制的,后来换到 4MHz 主频时忘了同步修改SSP1ADD,结果 I2C 速率变成了 44kHz,能工作但效率很低。建议每次新建工程都先算一遍:SSPADD = Fosc / (4 * FSck) - 1,主频 4MHz、目标 100kHz 时代入就是4MHz / (4 * 100kHz) - 1 = 9。
如果系统使用了 PLL 把主频提高到 48MHz,SSPADD就要改成 119。不要以为 I2C 速率越高越好,PCA9422 的手册里很多寄存器是普通寄存器,400kHz 虽然在规格内,但实际板子上如果布线质量一般,高速率很容易在长走线上翻车。我的原则是 100kHz 起步,把功能调通之后再决定要不要提速。
初始化里还有一个容易忽略的点:TRISC3和TRISC4必须配置为输入。很多人习惯把 SCL、SDA 设成输出,这在 SPI 模式下是对的,但 I2C 是开漏协议,引脚方向本来就由硬件自动切换,初始化时设成输入才能让模块正确接管引脚。
3.2 I2C 读写函数与 PCA9422 从机地址
PCA9422 的 I2C 从机地址默认值,不同丝印批次存在差异,我这里以实际项目中使用的 7 位地址 0x56 为例。7 位地址左移一位后,写地址是 0xAC,读地址是 0xAD。
#define PCA9422_ADDR_W 0xAC #define PCA9422_ADDR_R 0xAD void i2c1_start(void) { SSP1CON2bits.SEN = 1; while (SSP1CON2bits.SEN); // 等待 START 完成 } void i2c1_stop(void) { SSP1CON2bits.PEN = 1; while (SSP1CON2bits.PEN); } void i2c1_write(uint8_t data) { SSP1BUF = data; while (!PIR1bits.SSP1IF); // 等待发送完成 PIR1bits.SSP1IF = 0; } uint8_t i2c1_read(uint8_t ack) { SSP1CON2bits.RCEN = 1; while (!PIR1bits.SSP1IF); PIR1bits.SSP1IF = 0; uint8_t data = SSP1BUF; SSP1CON2bits.ACKDT = ack; SSP1CON2bits.ACKEN = 1; while (SSP1CON2bits.ACKEN); return data; }这里有个细节值得提醒:发送完地址或数据后,一定要检查从机是否回复了 ACK。如果 PCA9422 还没有完成上电复位,或者 I2C 总线上有其他设备冲突,它不会回复 ACK。我的做法是在i2c1_write之后立刻检查 ACKSTAT,不要等到整包数据发完才发现失败。
uint8_t i2c1_write_check(uint8_t data) { SSP1BUF = data; while (!PIR1bits.SSP1IF); PIR1bits.SSP1IF = 0; if (SSP1CON2bits.ACKSTAT) { i2c1_stop(); return 1; // 从机无 ACK } return 0; }3.3 PCA9422 关键寄存器配置顺序
PCA9422 的寄存器具体映射要按你手里的数据手册来,但配置顺序是有共性的。我的经验是先复位、再配中断、再设输出电压,最后使能输出,不要在芯片还没就绪时就写电压寄存器。
// 以实际项目的寄存器映射为例,地址请按手册核对 #define PCA9422_REG_RESET 0x00 #define PCA9422_REG_MODE 0x01 #define PCA9422_REG_VOUT 0x02 #define PCA9422_REG_INTENA 0x03 #define PCA9422_REG_STATUS 0x04 void PCA9422_init(void) { // 1. 软复位 PCA9422_write_reg(PCA9422_REG_RESET, 0x80); __delay_ms(10); // 2. 使能需要的中断 PCA9422_write_reg(PCA9422_REG_INTENA, 0x03); // 3. 设目标输出电压为 4.2V PCA9422_write_reg(PCA9422_REG_VOUT, 0x28); // 4. 切到正常接收模式并使能输出 PCA9422_write_reg(PCA9422_REG_MODE, 0x01); }所有写寄存器操作,我都在最后加了一个读回校验。步骤很简单:写完一个寄存器,立刻读回来比对。如果读回值和写入值不一致,说明 I2C 线上存在干扰或者寄存器写入时序有问题。这个习惯帮我省了很多排查时间。
读回校验有一个注意点:有些寄存器是只读的状态寄存器,写入后读回本来就会不一样,所以校验只针对可写寄存器。我把所有可写寄存器的默认值和预期值做成一个表,在初始化阶段批量检查,产测时也复用这组代码。
3.4 电压动态调节的实际逻辑
PCA9422 的 VOUT 可以通过 I2C 在线调整,这是做动态功耗管理的基础。手环的处理器在运行、休眠、唤醒三种功耗档位下,对输入电压的需求是不同的。
void set_system_power_voltage(uint8_t vout_code) { PCA9422_write_reg(PCA9422_REG_VOUT, vout_code); // 等待 VOUT 稳定,读状态寄存器里的 VOUT_RDY 标志 uint8_t status = 0; for (uint8_t i = 0; i < 10; i++) { status = PCA9422_read_reg(PCA9422_REG_STATUS); if (status & 0x01) break; __delay_ms(2); } if ((status & 0x01) == 0) { // 电压切换超时,进入故障处理 enter_fault_state(); } }电压切换不是瞬间完成的,PCA9422 内部需要一点时间建立新的输出。如果写完寄存器立刻去跑高负载程序,可能触发欠压复位。我的做法是每次切换后都等 VOUT_RDY 标志位,确认就绪再继续,这个细节在快速切换电压的场景下非常重要。
4. 电源管理状态机的设计与实现
4.1 状态划分与迁移条件
电源管理本质是一个状态机。我把整个系统划分成六个状态:上电初始化、无线充电中、充满保持、电池供电运行、低功耗休眠、故障保护。每个状态之间的迁移条件必须明确,不能有双条件同时成立的分支。
typedef enum { ST_BOOT = 0, ST_CHARGING, ST_CHARGED, ST_RUNNING, ST_LOWPOWER, ST_FAULT } SystemState_t;上电初始化之后,系统要判断两件事:有没有无线充电输入,电池电压是否够用。如果 PCA9422 的状态寄存器显示线圈已经收到稳定能量,系统进入充电状态。如果没有充电输入但电池电压正常,直接进入电池供电运行状态。
充电状态里每 500ms 读一次 PCA9422 的状态寄存器,如果充电电流降到阈值以下并且持续 3 次采样,判定为充满,进入充满保持状态。充满保持状态下,PCA9422 继续维持输出,但系统负载切到低功耗模式。
一个容易漏掉的迁移条件是“充电输入断开”。手环放在充电盘上时不会突然断开,但用户可能中途拿走。如果只盯着充满标志,忽略线圈能量消失,系统可能会在充电状态下消耗电池,直到电池保护板切断。我的实现里,充电状态下如果连续两次读到线圈无能量,立即迁移到 RUNNING 状态。
4.2 低功耗模式下的处理策略
低功耗休眠状态是整个状态机里最需要打磨的部分。PIC18F4685 进入休眠前,我做了三件事:关闭 ADC、关闭 EUSART、把未用的 GPIO 全部设成输出低电平,避免引脚悬空造成的漏电。
PCA9422 在低功耗状态下也要处理。我把它切到待机模式,VOUT 输出关闭,只保留基本状态监测。这个模式下整板功耗大约能降到 30uA 左右,比运行模式低了好几个数量级。数据如下表。
| 工作模式 | 整板实测电流 | 说明 |
|---|---|---|
| 运行模式,无线充电接收中 | 45mA | 手环点亮屏幕 |
| 运行模式,电池供电 | 38mA | 蓝牙连接,无屏幕 |
| 低功耗休眠 | 4uA | MCU 睡,PCA9422 待机 |
| 充满待机 | 12uA | PCA9422 输出保持,负载关闭 |
休眠状态下,唤醒源有两个:RD0 上 PCA9422 的 INT 中断,以及 PIC18F4685 的定时唤醒。当充电盘重新放上来或者用户按键唤醒时,系统先读 PCA9422 的状态寄存器,再决定回到充电状态还是运行状态。
4.3 异常保护与错误恢复流程
异常保护不能只靠硬件,软件侧也要有一套降级策略。我先列一下处理过的异常场景。
过压保护:电池电压或 VOUT 超过安全阈值时,立即把 PCA9422 输出电压切到最低档,同时关闭负载输出。我用的是 ADC 轮询,每 100ms 采样一次,阈值设为 4.5V,迟滞 0.1V,防止在阈值附近抖动导致反复切换。
过温保护:PCA9422 内部有温度监测寄存器,MCU 每 1 秒读一次。温度超过 55 度时降低充电电流,超过 70 度时直接关闭充电,保持电池供电运行。这个策略能有效延长充电盘上连续充电时的安全性。
I2C 通信超时是最容易误判的异常。刚开始我把任何一次通信失败都当成故障,结果上电瞬间因为 PCA9422 还没准备好,系统频繁误入故障状态。后来改成连续 3 次失败且间隔 100ms 才认定故障,并加了一个恢复尝试:故障状态下每 5 秒尝试重新初始化 PCA9422,连续 3 次恢复成功就自动回充电状态。
void fault_recovery_handler(void) { for (uint8_t retry = 0; retry < 3; retry++) { PCA9422_init(); if (read_status() == STATUS_NORMAL) { system_state = ST_CHARGING; return; } __delay_ms(1000); } // 三次恢复失败,保持故障状态并通知用户 }5. 实测踩坑:上拉电阻、超时重试与功耗调优
5.1 I2C 上拉电阻的取值问题
调试阶段最让我头疼的就是 I2C 上拉电阻。第一版验证板用了 10kΩ 上拉,总线电容在长走线下偏大,400kHz 速率下通信经常失败,表现为读取 PCA9422 状态时会时不时返回 0xFF。
排查过程比较经典。先用示波器抓 SCL 和 SDA,发现 SCL 和 SDA 的上升沿明显变缓,接近 1us 级别。这基本上就是上拉电阻太大、总线电容太大导致的。我从 10kΩ 直接换成 2.2kΩ,问题立刻消失。
但 2.2kΩ 也有副作用:当 PCA9422 输出驱动的低电平电流较大时,总线空闲电流变高,整板待机功耗增加了大概 10uA。这个数值对一般系统可能无所谓,但手环这种对功耗敏感的产品就不可接受。最终我把 I2C 速率降到 100kHz,上拉电阻改用 4.7kΩ,可靠性、待机功耗都满足要求。
注意:I2C 上拉电阻的取值不是越大越好,也不是越小越好。总线空闲时电流由它决定,信号上升沿时间也由它决定。我的经验是 100kHz 速率、总线长度小于 5cm 时,4.7kΩ 是一个不容易出错的起点。
5.2 通信超时误判与重试逻辑
上电瞬间通信超时的问题,我在初版固件里处理得不好。系统初始化时,PIC18F4685 复位比 PCA9422 快,MCU 立刻去读 PCA9422 寄存器,此时 PCA9422 还处于内部复位状态,从机不响应,MCU 报 I2C 无 ACK 错误。
一开始我以为 PCA9422 硬件有问题,换了几颗芯片都一样。后来在初始化序列前面加了 50ms 延时,让 PCA9422 先完成内部启动,之后第一次读取就稳定通过了。
这个延时不算浪费。PCA9422 上电后需要加载配置寄存器、初始化内部状态,过早写寄存器可能被它后续的复位覆盖。我实测下来,上电后 50ms 写入的电压值能保持,10ms 内写入的则可能丢失。
在初始化函数里,我增加了一个统一的wait_pcatimeout()函数,凡是系统冷启动、软复位、故障恢复三种场景都会调用,避免不同入口漏掉延时。
5.3 功耗实测数据与优化结果
功耗测试是整个系统做完之后最值得看的一张表。我在电池供电模式下,分别在未优化固件和最终固件各测了一轮。
| 场景 | 未优化功耗 | 最终功耗 | 主要优化手段 |
|---|---|---|---|
| 运行,蓝牙连接,无屏幕 | 42mA | 38mA | 动态调低 VOUT |
| 运行,屏幕点亮 | 49mA | 45mA | 屏幕供电开关控制 |
| 休眠待机 | 18uA | 4uA | PCA9422 切待机,GPIO 全输出低电平 |
| 无线充电中 | 60mA | 55mA | 充电电流限流档位调整 |
第一版休眠功耗 18uA,明显偏高。逐项排查后发现,PCA9422 的 VOUT 在休眠状态下依然维持输出,这路电流直接流到负载侧被白白消耗了。把 PCA9422 切到待机模式后,休眠功耗降到 7uA。随后又把三颗未使用的 GPIO 从悬空改成输出低电平,再降了 3uA。
运行功耗方面,动态调压效果最明显。屏幕点亮时我把 VOUT 从 3.3V 提到 3.6V,因为屏幕背光在高亮度下需要更高输入才能稳定工作;屏幕关闭时降回 3.3V。这个动态调整配合 PCA9422 的 VOUT_RDY 等待机制,没有引入任何稳定性的问题。
5.4 量产阶段才暴露的 PCB 问题
开发板阶段一切正常,到了小批量试产时,出现了几块板子 I2C 完全不通、PCA9422 无法识别的现象。拿万用表量 SCL、SDA 对地电压,发现 SDA 始终是 0V,也就是说总线一直被拉低。
用放大镜看焊盘,发现试产板 PCA9422 的 SDA 焊盘和相邻的电源焊盘之间出现了锡桥,导致 SDA 被直接拉到地。这个问题在开发板手工焊接时不会出现,因为手工焊接每颗都会检查,但贴片产线的焊锡量控制不好就容易短接。
这件事给我的教训是:量产前必须在固件里加一个 I2C 设备扫描步骤,上电后读一次 PCA9422 的 ID 寄存器,如果读取失败就报错并停在自检状态。这样产线能快速挑出焊接不良的板子,而不是把问题留到整机测试阶段。
产测阶段的 I2C 扫描代码其实就是初始化后读一次 ID:
uint8_t check_pca9422_presence(void) { uint8_t id = PCA9422_read_reg(PCA9422_REG_ID); return (id == PCA9422_EXPECT_ID); }如果生产线上有烧录器,可以直接在烧录流程后运行这段自检,节省人工目检时间。
最后分享一个我自己的经验:这套方案最值钱的地方,是把模拟电源的不确定性隔离在了 PCA9422 芯片内部,MCU 侧只需要管理 I2C 寄存器和状态策略。如果你也要做类似的产品,我建议在原理图阶段就留出 I2C 测试点,并且把“上电延时 + 重试机制 + 读回校验”这三个基础函数先写好,再开始碰具体的充电逻辑。后续如果要扩展能力,PIC18F4685 的 ECAN 模块还能用来跑整机通信或故障上报,这套电源管理的骨架基本不用动,只要在上层加策略就行。