☰
Android BSP视角下的Qi无线充电协议:从qiifa-fwk到调试实战
2026/10/3 3:40:26 网站建设 项目流程

做Android BSP这几年,我很少真正去关心无线充电的协议细节。平时干活也就是改改dts节点、看看充电曲线,最多次用adb拉一遍power_supply的sysfs信息。直到有一次整机构建,编译日志里蹦出一行vendor/qcom/proprietary/commonsys-intf/qiifa-fwk/android.bp:8:18: module "qiifa",依赖解析直接报错,我不得不去翻这个目录。一查才发现,这个QiIFA-FWK是高通平台在Android侧对接Qi无线充电标准的框架层。真正让我难受的是:Qi这个标准,我当时除了知道"手机放上去就能充"之外,几乎说不出任何细节。

于是花了大概两周时间,一边读WPC的规范文档,一边对照平台代码,把Qi标准从物理层到协议层再到软件栈捋了一遍。这篇笔记就是这段学习过程的沉淀。适合跟我一样在Android BSP、充电驱动、或者做整机电源方案的工程师参考,也适合刚接触无线充电、想知道"Qi到底在传输什么"的硬件/嵌入式朋友。我不打算照搬规范术语,尽量用工程视角把协议讲清楚,后面还会补上调试中遇到的实际问题和排查思路。

1. 被"qiifa"点名之后:那个藏在编译错误里的无线充电框架

1.1 解析这一行报错背后的目录结构

先拆一下那段报错路径,它比表面上看起来信息量大得多:

  • vendor/qcom/proprietary/——高通闭源BSP包的标准位置,OEM拿到后一般会合入自己的device目录一起编。
  • commonsys-intf——Common System Interface,按高通的一贯思路,这个目录放的是"跨产品线共用的系统接口",里面通常不只有qiifa-fwk,还会看到wlan-fwk、bt-fwk、sensor-fwk之类。它解决的核心问题是:同一套接口逻辑,不能每款SoC、每个产品都写一份,必须抽出来统一维护。
  • qiifa-fwk——Qi Interface Framework,直接翻译就是"Qi接口框架"。高通把WPC(Wireless Power Consortium)的Qi标准能力,从芯片固件层往上做了这么一层软件封装。
  • android.bp——Android从8.0开始全面引入的Soong构建配置格式,替代了旧的Android.mk。
  • :8:18——报错指向android.bp第8行第18列,也就是模块名字符串"qiifa"所在的位置。
  • module "qiifa"——Soong构建系统注册的模块名,通常对应一个vendor分区共享库或头文件库。

这里我要强调一下:不同平台版本,高通对这个框架的模块划分一直在变。有的版本就叫qiifa-fwk,有的版本把它拆成了base/extension等多个子模块。所以遇到具体问题时,先打开自己平台上的android.bp看实际内容,别拿着别的项目的结论直接套。

1.2 为什么软件侧还需要一层"框架"

很多人会问:无线充电不是硬件加固件的事吗?接收芯片厂(NXP、TI、Cypress/英飞凌、伏达、易冲这些)不是已经把WPC协议栈烧在芯片里了吗?为什么Android上还要再架一层框架?

关键在于"产品化"。接收芯片固件确实处理了绝大多数WPC协议交互——握手、身份识别、功率协商、PCE闭环,这些都是芯片内部的MCU在做。但系统层还需要知道很多协议之外的东西:

  • 当前从线圈接收到的功率是多少,无线充电是否真正处于工作状态;
  • 接收端温度、FOD(异物检测)状态,要不要做降功率或报警;
  • 对不同品牌充电板的兼容参数配置;
  • 固件升级、产测校准数据的维护通道;
  • 把充电状态同步给电池服务,最终反映在状态栏和设置页。

这些能力在芯片厂那一侧的表现形式,通常是寄存器、I2C命令、厂商私有接口。平台方案商为了避免每一颗芯片都写一套系统代码,就有必要做一个统一抽象层。qiifa-fwk干的就是这个活:它不完全参与协议电波层面的交互,而是作为"标准协议芯片"和"Android系统"之间的翻译官和管家。

提示:如果只是做整机调试,不需要读懂qiifa-fwk所有源码,但一定要知道这个模块在编什么、被谁依赖、日志从哪出。这些信息在排查充电异常时能少绕很多弯路。

2. 物理层与隐藏数据通道:电磁感应之外的Qi通信机制

2.1 为什么Qi选择100kHz到205kHz这个频段

Qi的物理基础是电磁感应。发射端线圈通上交流电,在近场范围产生交变磁场,接收端线圈感应出电压,再经整流稳压给电池充电。这里的关键参数是频段:Qi定义的工作频率范围是100kHz到205kHz,而不是像NFC那样用13.56MHz,更不是微波那套。

低频频段是成本和效率博弈后的最优解,原因有几条:

  • 功率器件在这个频段的开关损耗低,MOSFET驱动电路简单、成熟;
  • 100多kHz的EMC设计难度不高,磁屏蔽材料、利兹线(Litz wire)工艺成熟且便宜;
  • 低频下趋肤效应不明显,线圈的交流电阻容易控制,传输效率能稳定做到70%甚至85%以上;
  • 对通信带宽的需求本来就不高,Qi的带内通信速率只有kbps级别,低频天然够了。

实际工作时,发射端会根据负载情况在100kHz到205kHz区间内微调频率,这也是后面要说的功率控制的一种手段——频率升高,耦合到接收端的能量通常会下降,反过来则提高。这就是PCE闭环的本质。

2.2 带内通信:用同一对线圈既输电又传数据

接收端和发射端之间必须通信。接收端需要告诉发射端"功率再大一点""功率收一点""我要结束了",发射端也要把自己的配置信息发给接收端。这些数据不经过蓝牙、不经过Wi-Fi,而是直接跑在电源线圈上,这就是带内通信(in-band communication)。

接收端到发射端的方向,用的是负载调制(load modulation)。接收端通过断续接入一个负载电阻,改变自己线圈回路的等效阻抗,发射端线圈上的电流和电压随之产生微小幅度变化。发射端检测这个包络变化,解调出比特流。这本质上是ASK调制。

发射端到接收端的方向,用的是FSK(频移键控)。发射端在基准工作频率附近做微小的频率偏移,用不同的频偏来表示0和1。接收端通过锁相或者过零检测方式解调。

整个通信速率非常低,BPP模式下大约2kbps这个量级。但充电控制报文就那么几个字节,这个速率绰绰有余。我之前总觉得"无线充电就是个电源",直到看到这里才意识到,它其实是一个"带无线链路的电源系统",跟充电桩、甚至跟简单的单线总线通信在思路上是共通的。

2.3 一个Qi报文的格式:简单到没地方出错

每个Qi数据包分为几个部分:前导码、起始位、头字节、信息字节、校验字节。头字节定义了报文类型和后面信息字节的长度;校验字节是头字节加全部信息字节的8位累加校验。整个结构非常简单,特别适合在低速、干扰环境复杂的带内链路上做鲁棒传输。

打个比方,这就像两个人用一根绳子拉货,绳子既承担牵引力,也通过有节奏的拉扯传递指令。"拉货的人拽三下"是信号,"拽一下停一下"是另一个信号。Qi的带内通信跟这个在逻辑上是一模一样的。

提示:调试时如果看到校验错误率偏高,不要先怀疑协议栈,多半是线圈耦合不好或者负载调制深度不够——硬件问题往往以软件现象的形式显现出来。

3. 从Digital Ping到EPT:Qi协议状态机逐阶段拆解

3.1 五大阶段的完整握手流程

把手机放到充电板上,看起来是"放上去就充",实际上背后是一套严格的状态机。完整流程分五个阶段:

  1. Selection(选择):发射端处于待机,周期性发一个模拟探测信号,通过检测谐振回路的参数变化判断有没有接收端放上来。如果检测到线圈Q值或频率响应变化,就进入Ping阶段。
  2. Digital Ping(数字ping):发射端发出一个功率更高的数字ping,等待接收端回Signal Strength包。如果超时没有收到,就退回Selection。
  3. Identification & Configuration(识别与配置):接收端收到ping后,先发Identification包上报厂商代码和设备ID,再发Configuration包声明自己的功率等级、最大功率和协议修订版本。
  4. Negotiation(协商):如果双方支持EPP(扩展功率协议),在这里协商具体的扩展功率参数,包括FOD相关阈值。
  5. Power Transfer(功率传输):正式充电阶段,接收端周期性地发PCE包,发射端据此实时微调输出,形成闭环。

每个阶段都有超时和重试机制。任何一个环节失败,状态机都会回退。这个设计保证了系统在任何异常情况下都能收敛到一个安全态,而不是卡在中间状态一直耗着。

3.2 关键数据包速查表

下面这几个报文在调试里最重要,值得背下来:

报文名称方向作用关键内容
Signal StrengthRX→TX上报耦合强弱1字节,值越大耦合越好
IdentificationRX→TX身份识别厂商代码(2字节)、基本设备ID(4字节)
ConfigurationRX→TX声明功率能力功率等级、最大功率、协议版本
PCE(Power Control Error)RX→TX实时功率调节1字节有符号数,正负表示增减方向
EPT(End Power Transfer)RX→TX结束功率传输1字节原因码

很多人对PCE有误解,觉得充电功率是"设置"出来的——充电板标称10W,它就按10W输出。实际上完全不是,它是一个实时闭环调节。接收端根据输出电压、电流、温度,每隔约几十毫秒到几百毫秒发一个PCE包,数值为负表示"输出太高,降一点",数值为正表示"输出不够,加一点",发射端收到后调整工作频率。整个过程一直在动态平衡中,跟你看不到的地方一直在微调,而不是给定一个固定占空比就不动了。

3.3 EPT原因码:调试时最有价值的1个字节

EPT报文只有1个字节的原因码,但它在排查问题时的价值极高。我整理了一个对应关系表:

原因码含义典型现场
0x01Charge Complete电池充满,正常结束
0x02Internal Fault接收端内部异常,比如PMIC报错
0x03Over Temperature过热保护,最常见之一
0x04Over Voltage整流电压过高
0x05Over Current输出过流
0x06Battery Failure电池异常
0x07Reconfig需要重新配置
0x08No Response发射端无应答

我在实际调试里遇到最多的是0x03和0x04。0x04过电压,很多充电板起振瞬间会有过冲,接收端整流电压如果没钳位好,一下就触发保护。这种问题看起来是电压问题,实际上是环路补偿或软启参数的问题。而0x03过热,很多时候不是真的温度到了极限,而是温感位置离线圈太近,线圈的温升被误判成了整机温度。看到EPT原因码之后,再去看波形和温升曲线,就能从"玄学"快速收敛到具体环节。

3.4 看门狗机制:兜底安全设计

协议里还有一个容易被忽略的看门狗机制。如果发射端连续一段时间没有收到接收端的任何报文,就会主动终止功率传输。触发条件是连续的通信超时,不是单包超时。

这个设计极其重要。试想一个场景:接收端固件跑飞了,或者手机突然被拿走了但接收端仍然在磁场范围内,如果发射端还傻乎乎地持续输出功率,金属异物就可能在手机壳下面发热甚至燃烧。看门狗机制保证了"接收端失联"最终会被发射端兜底切断。做嵌入式的人看到这个设计应该会有共鸣——系统级的安全兜底,跟协议本身同样重要。

4. 从协议回到代码:qiifa-fwk在完整充电链路上的位置

4.1 全链路分工:固件、内核、框架、应用各干各的

一台手机的无线充电链路大致可以画成这样的层级关系:

WPC接收线圈/整流 → 无线充电接收芯片(内部固件跑Qi协议栈) → I2C/SPI/UART 总线 → Linux内核驱动(power_supply类、I2C设备驱动) → Native框架层(qiifa-fwk、HAL接口) → Java框架层(电池服务、系统服务) → 应用层(设置页、状态栏、锁屏动画)

接收芯片固件承担的是模拟域和协议域的重活:调解调制、状态机流转、PCE生成、FOD计算,全都跑在芯片自带的MCU里。到了操作系统这一侧,事情就变简单了——通过I2C读取状态寄存器、下发控制命令、接收中断上报。qiifa-fwk这个vendor侧公共接口层干的事情,通常可以归为这几类:

  • 抽象不同接收芯片厂商的私有寄存器访问方式,向上层提供统一接口;
  • 输出无线充电状态(在线、功率、温度、FOD告警)给Android电池服务;
  • 处理产测模式、固件升级等高通框架需要但AOSP没覆盖的逻辑。

要特别说明的是,我上面是基于高通平台通用代码架构做的合理推断,不同芯片、不同厂商分支差异非常大。你手上项目的实现细节,最终要以自己BSP里的源码为准。

4.2 android.bp里到底在构建什么

Soong构建文件的核心是module声明。以commonsys-intf目录下常见的写法,qiifa这个模块很可能长这样(不同平台差异很大,仅示意):

cc_library { name: "qiifa", vendor_available: true, srcs: [ "src/qiifa_main.cpp", "src/qiifa_wireless_charger.cpp", ], shared_libs: [ "libhidlbase", "libbase", "liblog", "libutils", ], header_libs: [ "libqiifa_headers", ], proprietary: true, }

这类模块通常编成vendor分区共享库,被充电HAL或系统服务通过动态链接引用。一旦它的依赖项出问题——比如某个HIDL接口版本没对上、某个header库找不到——就会出现开头那一行module "qiifa"相关报错。

工程排查上,遇到这类构建问题按三步走即可:

  1. 打开android.bp,看这个模块导出的是什么——共享库、静态库、头文件库还是defaults配置;
  2. 用m --soong-only或者搜Android.bp里的shared_libs/header_libs引用关系,找到是谁依赖了它;
  3. 确认是不是平台分支升级时模块名变了,老代码还引用旧名字。

4.3 调试时比框架更直接的内核节点

qiifa-fwk内部逻辑再复杂,调试时真正高频使用的还是内核power_supply子系统暴露的节点:

/sys/class/power_supply/wireless/type /sys/class/power_supply/wireless/status /sys/class/power_supply/wireless/online /sys/class/power_supply/wireless/power_now /sys/class/power_supply/wireless/temp /sys/class/power_supply/wireless/input_current_limit

这些节点由内核充电驱动创建,上层的HAL、qiifa-fwk、Java电池服务的数据最终都来自这里。遇到"无线充充不进电",第一步就执行:

adb shell cat /sys/class/power_supply/wireless/*

先看online是不是1、temp有没有爆表、status是不是Discharging。这一步能筛掉一半问题。只有这些节点显示正常但充电仍有异常时,才有必要往下挖芯片寄存器和协议层面的事情。

5. 调试无线充电最容易翻车的三个环节

5.1 FOD误报:金属桌面、硬币和"看不见的烧蚀"

FOD(Foreign Object Detection,异物检测)是Qi里非常实用的安全机制,也是调试时最容易让人一头雾水的坑。它有两套检测途径:

  • Q值检测:发射端在空闲状态测量谐振回路的品质因数。金属异物放在线圈上方会显著改变Q值,超过阈值就报异物。
  • 功率损耗计算:传输过程中对比发射端发出的功率和接收端实际收到的功率。如果差值超过约定阈值,就认为有异物在吸收能量发热,立即终止。

我在项目里踩过最典型的坑,是在金属实验桌上直接放充电板测试。结果FOD全程误报,怎么调阈值都没用。原因很简单——金属桌面本身就是个巨大的金属异物,它参与电磁耦合并且吸收涡流,功率损耗计算永远不可能是正常值。正确做法是先在绝缘台面上做验证,排除环境因素后再谈参数调优。这个坑看起来基础,但我几乎每个月都在群里看到有人再踩一遍。

5.2 线圈错位与Signal Strength值:充电慢的隐形原因

Qi对线圈对准是有明确要求的。线圈偏移太多,耦合系数降低,Signal Strength包的值就很小。发射端可能直接判定为"不是合法接收端",也可能进入传输后效率极低、线圈发热、充电功率上不去。

实际表现是两种极端:要么放上去闪一下就没反应了,要么充电慢得离谱。排查思路并不复杂,先找到接收芯片寄存器里的Signal Strength值(有的平台也透传到frameworks日志里),这个值越大越好。如果数值偏低,就要检查线圈位置公差、磁铁定位环装配、手机壳厚度。

还有一种容易被忽略的情况是手机壳夹层里的金属贴片或者银行卡。有一阵子我调某款机型,一直出现间歇性充电断连,最后发现是用户手机壳里嵌了一张带金属涂层的磁吸卡套。金属涂层直接在磁场里感应出涡流,不仅干扰通信,还发热。所以做整机验证时,测试用的手机壳也要标准化。

5.3 PCE振荡:功率闭环的稳定性问题

PCE闭环在调节功率时,如果发射端的频率响应和接收端的PCE上报节奏匹配不好,就会形成振荡——充电电流出现周期性抖动,严重时接收端认为控制不住功率,直接发EPT终止充电。从现象看是"充电一会快一会慢"甚至"反复断充",但本质上是控制环路的稳定性问题,不是协议错误。

排查方法我建议用示波器同时看两路信号:一路是发射端线圈电流的包络波形,一路是接收端I2C总线上PCE寄存器值的变化。如果包络出现了低频振荡,同时PCE寄存器值在正负之间来回大幅摆动,那基本就是环路稳定裕度不足。解决手段通常在两个方向:调整发射端的频率调节步进(协议允许的最小频偏范围内),或者在接收端固件里给PCE做平滑滤波。这种问题需要硬件和固件联动调,单独改哪一边都不彻底。

6. 啃Qi标准的一个可行学习路径

6.1 别从规范全文开始,会劝退

WPC官方发布的全套Qi规范分成很多部分,全英文,加起来上千页,直接对着啃很容易在状态机细节里迷路。我建议按下面的顺序推进:

  1. 先看公开的Qi简介PPT、WPC的官网培训材料,把"无线充电生态、发射端/接收端角色、功率等级"这些概念搭起来;
  2. 再读规范里System Description部分,重点看系统架构和数据流;
  3. 然后精读协议接口相关章节,把报文格式和状态机流程吃透;
  4. 最后回头看驱动代码,把规范里的术语和代码里的寄存器定义一一对应。

我自己走过一遍之后发现,规范里很多看似枯燥的描述,在对照代码之后立刻就活起来了。

6.2 用示波器"看见"一次带内通信

如果说有什么方法能让我对Qi的理解从"背概念"变成"真正懂了",那就是用示波器抓一次实际波形。

方法不难:用FET探头跨接在发射端线圈两端,触发沿设在输出电压突变的位置。你会看到一段明显的数字ping脉冲,然后接收端应答时,线圈电压的包络上出现细小的幅度抖动——这就是接收端的负载调制。如果你在Power Transfer阶段持续观察,还能看到工作频率在FSK调制下轻微变化。

用电流探头(或者测采样电阻两端电压)看发射端线圈电流,能看到接收端调制负载带来的幅度变化。这一眼看到的东西,比读十遍规范都记得牢。有条件的朋友强烈建议试一次。

6.3 资料之外的几个学习技巧

最后分享几个自己用下来很有效的小技巧:

  • 画状态机:不要直接复制规范里的图,自己动手把Ping、Config、Negotiation、Power Transfer、EPT,以及各种超时回退路径画一遍。画完之后你会发现在画的过程中自然发现了不少之前没注意到的边界条件。
  • 看芯片驱动注释:接收芯片厂家的驱动代码注释里,经常藏着和厂商FAE沟通才能知道的细节——比如某个寄存器的默认值是某个兼容性妥协的结果。这些是规范没有的实战知识。
  • 多看充电板协议分析仪日志:有些厂家的充电板支持抓包,哪怕只是看报文序列,都比空想有收获。没有分析仪的话,用I2C逻辑分析仪抓接收端和主控之间的通信也能侧面推断协议行为。

说实话,Qi协议本身并不复杂,难的是你不走到具体的工程问题面前,根本不会主动去学它。我也算是一个编译报错逼出来的学习者,所以这篇笔记里的很多内容,都是在"报错→查代码→读规范→再回头看代码"这个循环里沉淀下来的。希望这份学习路径能帮你少走一些我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询