多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Android BSP视角下的Qi无线充电协议:从qiifa-fwk到调试实战

Android BSP视角下的Qi无线充电协议:从qiifa-fwk到调试实战 做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接口框架。高通把WPCWireless 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到EPTQi协议状态机逐阶段拆解3.1 五大阶段的完整握手流程把手机放到充电板上看起来是放上去就充实际上背后是一套严格的状态机。完整流程分五个阶段Selection选择发射端处于待机周期性发一个模拟探测信号通过检测谐振回路的参数变化判断有没有接收端放上来。如果检测到线圈Q值或频率响应变化就进入Ping阶段。Digital Ping数字ping发射端发出一个功率更高的数字ping等待接收端回Signal Strength包。如果超时没有收到就退回Selection。Identification Configuration识别与配置接收端收到ping后先发Identification包上报厂商代码和设备ID再发Configuration包声明自己的功率等级、最大功率和协议修订版本。Negotiation协商如果双方支持EPP扩展功率协议在这里协商具体的扩展功率参数包括FOD相关阈值。Power Transfer功率传输正式充电阶段接收端周期性地发PCE包发射端据此实时微调输出形成闭环。每个阶段都有超时和重试机制。任何一个环节失败状态机都会回退。这个设计保证了系统在任何异常情况下都能收敛到一个安全态而不是卡在中间状态一直耗着。3.2 关键数据包速查表下面这几个报文在调试里最重要值得背下来报文名称方向作用关键内容Signal StrengthRX→TX上报耦合强弱1字节值越大耦合越好IdentificationRX→TX身份识别厂商代码(2字节)、基本设备ID(4字节)ConfigurationRX→TX声明功率能力功率等级、最大功率、协议版本PCEPower Control ErrorRX→TX实时功率调节1字节有符号数正负表示增减方向EPTEnd Power TransferRX→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相关报错。工程排查上遇到这类构建问题按三步走即可打开android.bp看这个模块导出的是什么——共享库、静态库、头文件库还是defaults配置用m --soong-only或者搜Android.bp里的shared_libs/header_libs引用关系找到是谁依赖了它确认是不是平台分支升级时模块名变了老代码还引用旧名字。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误报金属桌面、硬币和看不见的烧蚀FODForeign 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规范分成很多部分全英文加起来上千页直接对着啃很容易在状态机细节里迷路。我建议按下面的顺序推进先看公开的Qi简介PPT、WPC的官网培训材料把无线充电生态、发射端/接收端角色、功率等级这些概念搭起来再读规范里System Description部分重点看系统架构和数据流然后精读协议接口相关章节把报文格式和状态机流程吃透最后回头看驱动代码把规范里的术语和代码里的寄存器定义一一对应。我自己走过一遍之后发现规范里很多看似枯燥的描述在对照代码之后立刻就活起来了。6.2 用示波器看见一次带内通信如果说有什么方法能让我对Qi的理解从背概念变成真正懂了那就是用示波器抓一次实际波形。方法不难用FET探头跨接在发射端线圈两端触发沿设在输出电压突变的位置。你会看到一段明显的数字ping脉冲然后接收端应答时线圈电压的包络上出现细小的幅度抖动——这就是接收端的负载调制。如果你在Power Transfer阶段持续观察还能看到工作频率在FSK调制下轻微变化。用电流探头或者测采样电阻两端电压看发射端线圈电流能看到接收端调制负载带来的幅度变化。这一眼看到的东西比读十遍规范都记得牢。有条件的朋友强烈建议试一次。6.3 资料之外的几个学习技巧最后分享几个自己用下来很有效的小技巧画状态机不要直接复制规范里的图自己动手把Ping、Config、Negotiation、Power Transfer、EPT以及各种超时回退路径画一遍。画完之后你会发现在画的过程中自然发现了不少之前没注意到的边界条件。看芯片驱动注释接收芯片厂家的驱动代码注释里经常藏着和厂商FAE沟通才能知道的细节——比如某个寄存器的默认值是某个兼容性妥协的结果。这些是规范没有的实战知识。多看充电板协议分析仪日志有些厂家的充电板支持抓包哪怕只是看报文序列都比空想有收获。没有分析仪的话用I2C逻辑分析仪抓接收端和主控之间的通信也能侧面推断协议行为。说实话Qi协议本身并不复杂难的是你不走到具体的工程问题面前根本不会主动去学它。我也算是一个编译报错逼出来的学习者所以这篇笔记里的很多内容都是在报错→查代码→读规范→再回头看代码这个循环里沉淀下来的。希望这份学习路径能帮你少走一些我走过的弯路。
返回列表