一、什么是无线充电
无线充电,也称为感应充电或非接触式充电,设备无需通过传统电缆连接电源,只需与无线充电发射线圈靠近,即可实现充电。应用在桌面的场景可以提高桌面的整洁度,在公共场所可以提供便携式服务。
其工作原理:
发射端(TX):电源提供直流电 → 桥式逆变器(全桥/半桥)把直流斩成高频方波 → 经谐振网络(线圈 L + 串联谐振电容 C)形成高频交变电流 → 在空间建立交变磁场。
接收端(RX):线圈处于交变磁场中感应出高频交流电压 → 同步整流 → 直流稳压 → 给负载充电。
以 WPC Qi 标准为例,工作频率覆盖约 110 kHz ~ 205 kHz,按功率等级分为 BPP(5 W)、EPP(10~15 W)。两个线圈之间除了传能量,还要传数据用于协商协议:
接收端到发射端调制方式:ASK(负载调制)RX通过改变自身负载->发射阻抗变化->TX线圈检测电流/电压变化获取数据,可以通过ASK向TX发送信号强度包SS、识别包 ID、配置包 CFG、控制误差包 CE、结束包 RP 等。
发射端到接收端调制方式:FSK(调频),通过调频向RX发送频率/功率调整指示。
发射端在通讯过程要做的事情:
1、通过报文识别RX端的设备信息,
2、通过获取到的信息控制给出去多大功率,
3、通过PWM控制桥臂调整输出的功率。
二、发射端的工作逻辑
本文用CH248的15W通用版本的工程举例:
协议流程(模拟 PING → 数字 PING → 识别配置 → 协商 → 功率传输 → 结束/异常)由库libWirelessPower.a实现,用户层可以通过这些回调参与控制:
调用顺序由协议库掌控,用户代码只填回调 app_cfg_init() → 上电参数配置(死区、9V/12V、低功耗、是否弹窗) ↓ (模拟PING / 数字PING / 识别配置) get_cur_q_ref() → 异物判定阈值 get_isen_state() → PING 期间的电流判定 qi_ce_handle() → 功率传输阶段,处理 RX 的 CE 误差包 qi_rp_handle() → 动态 FOD(功率损耗法异物检测) ntc_check() / vhv_check() / over_isen_check() / osc_check() → 各类保护 led_rdy2work() / led_full() / led_rdy2full() / led_tog() → 状态灯 blind_callback() / remove_device()1.异物检测(Q值检测 / 检测PING过程电流)
待机时发射端周期性发出很短的探测脉冲,通过测量线圈的谐振电压(Q值)判断上面有没有物体、是不是金属异物。阈值随输入电压档位变化。
void get_cur_q_ref(uint16_t meas) { if(pwm_ctrl.bit_watch_mode == 0) { if(volt_state.bit_fastcharge == 0) expt.q_value_ref = Q_VALUE_LTHRD; else expt.q_value_ref = Q_VALUE_HTHRD; if(volt_state.bit_highvol) expt.q_value_ref = Q_VALUE_HHTHRD; } ... }Q值判断阈值可以在user_proc.h文件中修改。
//实测6.95uH+68n+300n: 127K/320K,重点是兼顾苹果耳机,观察苹果耳机过温后感应到的值不灵敏 // 5V空载84/45,耳机3mm Q值66,阈值设70;苹果手表3mm Q值27,设置35 // 9V空载76/48,耳机3mm Q值60,阈值设64; 苹果手表3mm Q值34,设置38 // 12V空载105/67,耳机3mm Q值83,阈值设90; 苹果手表3mm Q值45,设置53 #define Q_VALUE_LTHRD 70 /* 5V下判断的阈值 */ /* 如果待机报错,可以查看当前ADC值来适配 */ #define Q_VALUE_HTHRD 64 /* 9V下判断阈值 */ #define Q_VALUE_HHTHRD 90 /* 12V下判断阈值 */ #define Q_VALUE_WTH_LTHRD 35 #define Q_VALUE_WTH_HTHRD 38 #define Q_VALUE_WTH_HHTHRD 53当线圈上有放置金属时,Q值检测就会减小;Q 值被金属拉低到阈值以下之后会触发FOD。
另一个独立的判据是 PING 电流法:数字 PING 时如果电流远大于正常设备(金属会大量吸能),直接判异物
if( adc_temp > max_rmv_isen ) /* 电流超过 1.6A/0.8A 阈值 */ { expt.fod_cnt |= 0x80; if(expt.fod_cnt&0x40) expt.fod_cnt = 0x80; expt.fod_cnt++; /* 连续多次命中 → 累积判定 */ ... }线圈谐振电压、输入电流、输入电压、NTC 温度全部靠 ADC 轮询采样,通道切换逻辑
void adc_set_osc( void ) { ADC1->RSQR3 = ADC_Channel_OSC; } /* 线圈谐振电压 */ void adc_set_isen( void ) { ADC1->RSQR3 = ADC_Channel_ISP; } /* 输入电流(ISP脚) */ void adc_set_vhv( void ) { ADC1->RSQR3 = ADC_Channel_VHV; } /* 输入电压(20%分压) */ void adc_set_ntc( void ) { GPIOA->CFGLR &= ~0x0F000000; /* PA6 模拟输入 */ ADC1->RSQR3 = ADC_Channel_NTC; }2. 数字 PING → 识别 → 配置 → 协商电压
数字 PING 给线圈足量能量后,RX 被唤醒并依次回包:信号强度包 SS(告知耦合强度)→ 识别包 ID/ 扩展识别包→ 配置包 CFG(自身最大功率能力)。发射端从 CFG 得知对方是 5 W / 10 W / 15 W 档,从而确定限功率上限
#define MAX_WATT_LIMIT 341000 /* 22W 22000 * 15.5 */ #define MAX_AP_WATT_LIMIT 186000 /* 12W 12000 * 15.5 苹果 127K 定频模式 */ #define MAX_WATCH_WATT_LIMIT 46500 /* 3W 3000 * 15.5 手表 */随后进入协商阶段:RX 请求更高电压以提高效率,发射端按配置决定给 9 V 还是 12 V
void app_cfg_init( void ) { usercfg.bit_vhv12_prefer = 0; /* 默认请求9V电压;置1则优先请求12V */ usercfg.pwm_dead_time = 5; /* 桥臂死区时间 = 5/48 us ≈ 104ns */ usercfg.pwm_dt_forword = 3; /* 另一侧半桥的独立死区,用于压缩死区 */ ... }3. 功率传输:CE 包 → PID → 调频/调占空比
这是闭环的核心。RX 每个控制周期返回一个 CE(Control Error)包,对比输出和需要的功率。符号决定加功率还是减功率
slow_adj = 0; pwm_ctrl.bit_power_dir = 0; if(dat & 0x80) /* 最高位为符号:负值=请求降功率 */ { dat = 0-dat; pwm_ctrl.bit_power_dir = 1; }在调节之前先做安全钳位,顺序很关键:先欠压、再过流,最后才允许按 CE 调功率
adc_set_vhv(); vs_temp = adc_sample_x4(); if(vs_temp < uvlo_thrdvalue) { pwm_ctrl.bit_power_dir = 1; power_proc(1); } /* 欠压 → 降功率 */ else { adc_set_isen(); is_temp = adc_start(); if(pwm_ctrl.bit_127k_mode) max_isen = MAX_AP_WATT_LIMIT/vs_temp; /* 苹果 127K 定频模式 */ else max_isen = is_watch_device() ? (MAX_WATCH_WATT_LIMIT/vs_temp) : (MAX_WATT_LIMIT/vs_temp); /* 限功率 = 限流*mA */ if(max_isen > MAX_ISEN_LIMIT) max_isen = MAX_ISEN_LIMIT; /* 硬件物理上限 2.3A */ if(is_temp > max_isen) { pwm_ctrl.bit_power_dir = 1; power_proc(1); } /* 过流 → 降功率 */ else { ... 按 CE 调功率:pid_process(dat); } }注意:MAX_WATT_LIMIT/vs_temp是把"功率上限"换算成"电流上限",而ADC_V*ADC_I的单位就是毫瓦——所以限功率最终落在限流上,这也是"输入电压越高、允许的输入电流越小"的原因。
调功率的具体动作由库函数power_proc()/pid_process()执行,最终表现为改变桥臂的开关频率,必要时叠加占空比调节。在库中还提供了打印,能直接观察到调功结果
is_temp = 48000/(TIM1->ATRLR+1); printf("freq:%d ",is_temp); if(pwm_ctrl.bridge_mode & BIT_BR_MODE_HALF) { is_temp = bridge_duty_get(); is_temp = is_temp*100/(TIM1->ATRLR+1); printf("duty:%d half",is_temp); /* 半桥:调占空比 */ } else { is_temp = bridge_duty_get(); is_temp = is_temp*100/(TIM1->ATRLR+1); printf("duty:%d full",is_temp); /* 全桥:调频率为主 */ }4. 保护:过温降功率、过压、动态 FOD、异常移除
过温是两级处理:超过 80 ℃ 直接停机,超过 70 ℃ 只把输出功率压到 5.5 W 以内,回到 60 ℃ 以下再解锁
adc_set_ntc(); adc_temp = adc_start(); adc_ntc_release(); /* PA6 切回推挽复用 LED */ if(adc_temp > NTC_TRIG_VALUE) { expt.bit_ntc_trig = 1; remove_device(); } else if(adc_temp > NTC_ADJ_VALUE) expt.bit_ntc_lock = 1; /* 限功率点 */ else expt.bit_ntc_lock = 0; /* 解锁 */动态 FOD(功率损耗法):同时测量输入功率UI和 RX 上报的接收功率RP,两者之差就是"凭空消失的功率",超过阈值即认为是金属异物发热
ui_mw = get_input_power_in_mw(); /* 采样输入电压、电流算得 mW */ if(ui_mw > MIN_FOD_PWR) /* 小功率时不准,不计算 */ { loss_th = (ui_mw*4)/15 + 1666; /* 允许损耗门限:随功率线性放宽 */ rp_mw = dat * 39; /* 5W档,10W/15W 依次 78 / 117 */ dt_mw = ui_mw - rp_mw; if(dt_mw > loss_th) { expt.fod_cnt++; if(expt.fod_cnt >= FOD_CHECK_MAXCNT) /* 连续 3 次异常才判 FOD */ { expt.bit_is_thing = 0; return 1; /* 触发 FOD */ } } else expt.fod_cnt = 0; }此外还有:输入欠压/过压(vhv_check(),13.3 V 以上或 3.8 V 以下停机)、实时过流(over_isen_check(),超 2.4 A 直接降功率)、线圈谐振电压限幅(osc_check(),1.8 V 锁定限幅 / 2 V 直接停机)。
5. 结束与异常
充电完成(收到充满/结束包 RP)、温度/电压触发保护、异物触发 FOD、设备被移走,都会回到待机重新 PING。静态 FOD 还有"超时 2 分钟重试"或"死等移除"两种策略,由usercfg.bit_fod_notry选择。