做带无线充电的便携设备电源管理往往比无线协议本身更加费时。我前段时间做了一款手环类产品的电源模块主控选了 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 外设充足且带 CAN2. 硬件链路设计从线圈到电池再到整机供电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 时钟 SCLSCLRC3需上拉电阻I2C 数据 SDASDARC4需上拉电阻中断输出 INTINTRD0配置为输入触发外部中断共地GNDVSS单点接地上拉电阻我用的是 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 主频 4MHzI2C 速率 100kHz // SCL RC3SDA RC4 void i2c1_init(void) { TRISC3 1; // SCL 输入 TRISC4 1; // SDA 输入 SSP1STAT 0x00; // 标准速度模式 SSP1CON1 0x28; // I2C Master 模式 SSPEN SSP1ADD 9; // 4MHz / (4 * (91)) 100kHz }SSP1CON1 0x28这一步我最初是从旧项目里直接复制的后来换到 4MHz 主频时忘了同步修改SSP1ADD结果 I2C 速率变成了 44kHz能工作但效率很低。建议每次新建工程都先算一遍SSPADD Fosc / (4 * FSck) - 1主频 4MHz、目标 100kHz 时代入就是4MHz / (4 * 100kHz) - 1 9。如果系统使用了 PLL 把主频提高到 48MHzSSPADD就要改成 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蓝牙连接无屏幕低功耗休眠4uAMCU 睡PCA9422 待机充满待机12uAPCA9422 输出保持负载关闭休眠状态下唤醒源有两个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 功耗实测数据与优化结果功耗测试是整个系统做完之后最值得看的一张表。我在电池供电模式下分别在未优化固件和最终固件各测了一轮。场景未优化功耗最终功耗主要优化手段运行蓝牙连接无屏幕42mA38mA动态调低 VOUT运行屏幕点亮49mA45mA屏幕供电开关控制休眠待机18uA4uAPCA9422 切待机GPIO 全输出低电平无线充电中60mA55mA充电电流限流档位调整第一版休眠功耗 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 扫描代码其实就是初始化后读一次 IDuint8_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 模块还能用来跑整机通信或故障上报这套电源管理的骨架基本不用动只要在上层加策略就行。