ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从IIC数据解码USB PD报文:CH224Q快充实战解析

从IIC数据解码USB PD报文:CH224Q快充实战解析 如果你玩过Type-C快充相关的硬件大概率见过CH224这颗芯片插上充电器它自己就会去跟充电器谈判你要5V给5V、要20V给20V省得自己写USB PD协议栈。但问题往往出在“想看懂它在干什么”这一步。我最早用它做可调电源发现IIC总线上能读回来一堆十六进制数据但完全不知道这些字节和快充协议里的Source Capabilities报文是什么关系。直到我把PD协议的报文格式和CH224A/Q的寄存器回读值一张一张对上才彻底理顺这条链路IIC只是你窥探和遥控它的窗口真正决定“充电器能给你多少电压电流”的是那条CC线上跑的Source Capabilities报文。这篇文章就带你从IIC侧的数据出发把这帧报文从头到尾拆开再用真实充电器验证一遍。适合正在做PD诱骗、快充测试、Type-C供电产品的嵌入式工程师参考。1. 先搞清楚CH224A/Q到底帮你干了什么活1.1 A型号和Q型号的定位差异市面上CH224主要有A和Q两种常见型号很多人买的时候没注意后缀回来发现配置方式完全不一样。CH224A是电阻配置为主外接几个电阻到CFG引脚就能固定请求某个电压档位比如把CFG1拉高、CFG2拉低就请求12V简单粗暴适合只想要固定输出的场景。CH224Q则是把配置通道改成了IIC从机MCU可以在运行过程中随时切换目标电压还能把协商状态回读出来。但要注意这两个型号在协议引擎层面是同一套逻辑它们内部做的事情都是“作为USB PD的Sink端去和充电器完成协商”。所以无论你手里是A还是Q只要理解了报文格式再去看IIC数据思路完全通用。我下面以Q型号为主线来讲因为只有通过IIC回读才能把“Souce Capabilities报文到底包含什么”这个问题落到寄存器数值上。1.2 芯片默默完成的四件事很多开发者把CH224当成一个“电阻转电压”的魔法芯片接上就出电但这掩盖了它的真实工作内容。实际上每次充电器插入芯片都要完成四件事通过CC1/CC2引脚上的Rd下拉电阻让充电器Source感知到有Sink接入。接收Source广播的Source Capabilities消息也就是充电器把自己支持的电压电流列表发过来。根据你配置的目标电压从列表里挑一个匹配的PDOPower Data Object构造Request消息发回去。等Source回复Accept和PS_RDY之后VBUS才真正建立输出。上面这四步里第二步对应的就是标题里的Source Capabilities报文。它是整个协商过程的第一帧关键数据决定了后续所有请求有没有可能成功。比如充电器只支持5V/3A和9V/2A两个PDO你非要请求20V协议层压根就不会理你。我们通过IIC读到的“芯片状态”本质上是这帧报文被解析之后的缓存结果。2. Source Capabilities报文的内部结构一位一位拆开看2.1 一帧PD报文里真正值得看的是哪两段USB PD的物理层是BMC编码跑在CC线上一帧完整的报文包含前导码、SOP、Header、Data Object、CRC和EOP。对于做应用层的工程师来说前导码、CRC这些由芯片和协议引擎处理掉了我们真正需要解析的是两部分16位的Header里面记录着消息类型和后面跟了几个数据对象。32位一个的PDO也就是电压电流能力项。Header的高4位是消息类型Source Capabilities在这个字段里的值是0001。Header中间有3个bit是Number of Data Objects简写NDO表示后面跟了几个PDO。举个例子如果你在逻辑分析仪上抓到一段报文解析出来Header是0x2D09这种高4位是0010的话就不是Source Capabilities别被干扰。CH224Q的IIC缓存里已经帮你把消息类型过滤好了只要进去PDO区段读到的就是纯能力项。2.2 PDO的位域电压和电流藏在哪几个bit每个PDO是32位按bit31和bit30两位的类型字段区分。最常见的是Fixed PDO也就是固定电压输出类型字段是00。Fixed PDO的位域分配如下bit31:30 00表示Fixed supply。bit29 Dual-Role Power表示是否支持双角色供电。bit28 USB Suspend支持位。bit27 Unconstrained Power一般不用太关心。bit26 USB Communications Capable标记是否支持USB通信。bit25 Dual-Role Data。bit24 保留位。bit23:20 峰值电流能力。bit19:10 电压值单位50mV一格共10个bit。bit9:0 最大电流值单位10mA一格共10个bit。换算方式很直接电压 raw_voltage * 50mV电流 raw_current * 10mA。比如电压字段的值是400那就是400 * 0.05 20V电流字段是325就是325 * 0.01 3.25A。除Fixed PDO之外还有APDO可编程电源类型主要用在PPS快充上。APDO类型字段是11电压和电流的换算单位不同电压是100mV一格电流是50mA一格而且它的电压字段表达的是一段可调的区间范围。CH224Q同样支持PPS但本文实战部分先拿最常见的Fixed PDO演示APDO的解析逻辑在后面扩展方向里会单独提一句。2.3 一个具体例子0x040640A5到底是多少伏多少安下面用Python写一个很小的解析函数把32位原始PDO值翻译成电压电流。这是我在调试时常用的工具比手算快得多也稳得多。def parse_fixed_pdo(raw: int): if (raw 30) 0x3 ! 0: print(f0x{raw:08X} not a Fixed PDO) return voltage_raw (raw 10) 0x3FF current_raw raw 0x3FF voltage_mv voltage_raw * 50 current_ma current_raw * 10 print(f0x{raw:08X} - {voltage_mv / 1000:.2f}V / {current_ma / 1000:.3f}A) parse_fixed_pdo(0x040640A5) parse_fixed_pdo(0x0019492C)运行结果0x040640A5 - 20.00V / 3.250A 0x0019492C - 5.00V / 3.000A0x040640A5就是我手头这颗65W氮化镓充电器的第五个PDO换算出来正好是20V/3.25A和充电器铭牌完全一致。0x0019492C是它的第一个PDO5V/3A。到这里IIC数据和快充协议之间的联系开始浮现了芯片把充电器广播的这条报文按4字节一个PDO缓存下来我们只需要把字节拼回uint32再按位域拆开就能还原出充电器的能力列表。3. CH224Q的IIC侧怎么把充电器的“底牌”读出来3.1 IIC硬件连接和地址注意点CH224Q的IIC是从机MCU作为主机去读。硬件连接上没有太多坑SDA和SCL按标准接法加上拉电阻4.7k或者10k都行。我第一次调的时候贪图方便没加上拉结果读回来的全是0xFF还以为是芯片坏了。IIC总线是开漏结构没有上拉就没有高电平这个问题后面在坑位章节会展开说。地址方面我手上的模块7位地址是0x57也就是写地址0xAE、读地址0xAF。但CH224不同封装、不同批次可能存在差异最稳妥的办法是在代码里做一次总线扫描或者读芯片ID寄存器验证。下面这段基于Arduino框架的代码可以直接用在ESP32上先读0x01寄存器确认芯片ID#include Wire.h #define CH224_ADDR 0x57 uint8_t readReg(uint8_t reg) { Wire.beginTransmission(CH224_ADDR); Wire.write(reg); Wire.endTransmission(false); Wire.requestFrom(CH224_ADDR, 1); return Wire.read(); } void setup() { Serial.begin(115200); Wire.begin(21, 22); // ESP32默认IIC引脚按实际接线改 uint8_t id readReg(0x01); Serial.printf(CH224 ID 0x%02X\n, id); } void loop() {}如果能读到ID说明IIC链路通了一半如果读出来全是0xFF或者0x00先查上拉电阻、地址和模块供电。3.2 寄存器映射一份基于实测的参考下面这份寄存器映射是我在自己板子上验证过的参考定义不同批次芯片可能有差异强烈建议对照你手上那颗芯片的数据手册再确认一次。地址名称读写说明0x01CH224_IDR芯片ID/版本号可用于地址验证0x04CH224_CTRLR/Wbit2:0为PDO索引0通常表示自动选择1起对应实际PDO0x05CH224_PDO_NUMRSource Capabilities里的PDO数量0x06CH224_VOLT_LR当前输出电压低字节单位100mV0x07CH224_VOLT_HR当前输出电压高字节0x08CH224_CURRR当前输出电流单位10mA0x10CH224_PDO_BASERPDO缓存区起始地址每4字节一个PDO注意PDO_BASE地址在不同固件版本里可能不同有的版本会把它放在0x20以后。第一次用的时候建议打印整个寄存器区间的值看能不能解出和充电器参数吻合的PDO再来确定真正的基地址。3.3 用ESP32把PDO缓存翻译成电压电流表下面这段代码读取PDO数量和每个PDO的4字节原始值然后按Fixed PDO格式解析。我按小端序拼接字节也就是先读到的字节放在低8位这个顺序不符合时解析出来的数值会乱注意根据实际情况调整。uint32_t readPdoRaw(uint8_t index) { uint32_t raw 0; uint8_t base 0x10 index * 4; for (int i 0; i 4; i) { raw | (uint32_t)readReg(base i) (8 * i); } return raw; } void parseAndPrintPdo(uint8_t index) { uint32_t raw readPdoRaw(index); uint8_t type (raw 30) 0x3; if (type ! 0) { Serial.printf(PDO%d: type%d, not Fixed PDO\n, index 1, type); return; } uint16_t vRaw (raw 10) 0x3FF; uint16_t iRaw raw 0x3FF; float volt vRaw * 0.05f; float curr iRaw * 0.01f; Serial.printf(PDO%d: 0x%08X - %.2fV / %.2fA\n, index 1, raw, volt, curr); } void dumpPdoList() { uint8_t num readReg(0x05); Serial.printf(PDO count %d\n, num); for (uint8_t i 0; i num; i) { parseAndPrintPdo(i); } }把dumpPdoList放到loop里延时执行串口输出类似这样PDO count 5 PDO1: 0x0019492C - 5.00V / 3.00A PDO2: 0x040D492C - 9.00V / 3.00A PDO3: 0x0412492C - 12.00V / 3.00A PDO4: 0x0417492C - 15.00V / 3.00A PDO5: 0x040640A5 - 20.00V / 3.25A到这里你已经把充电器通过Source Capabilities报文广播的能力列表从IIC缓存里完整还原出来了。这个列表就是快充协商的核心依据。4. 实战65W氮化镓充电器从IIC数据到最终供电4.1 测试环境与接线为了验证上面的解析流程我搭了一套最简测试环境CH224Q模块IIC引出。ESP32-S3开发板用Arduino框架。65W氮化镓充电器标称支持5V/3A、9V/3A、12V/3A、15V/3A、20V/3.25A。一个USB电压电流表用来确认VBUS实际输出。可调电子负载用于拉载验证。接线很简单CH224Q模块的SDA接到ESP32的IO21SCL接到IO22模块的VBUS和GND引出来接电压表和电子负载。上电顺序有讲究先接充电器再给MCU上电这样芯片能第一时间完成协商MCU随后用IIC去读缓存。如果你反过来先把MCU初始化好等充电器插入要多做一次重协商才能保证读到的是当前这一轮的数据。4.2 IIC读出的PDO解码结果对比我实测读出来的寄存器缓存值和解码结果如下和充电器铭牌做了对照PDO序号缓存原始值解码电压解码电流铭牌标称PDO10x0019492C5.00V3.00A5.0V/3.0APDO20x040D492C9.00V3.00A9.0V/3.0APDO30x0412492C12.00V3.00A12.0V/3.0APDO40x0417492C15.00V3.00A15.0V/3.0APDO50x040640A520.00V3.25A20.0V/3.25A五组数据全部对得上。这一步如果用普通电阻配置的CH224A只能固定请求一个电压根本看不到充电器还支持哪些档位。换到Q型号相当于免费获得了一个“充电器协议分析视图”对整个系统的可调试性提升非常明显。4.3 通过IIC切换请求到20V的完整结果读懂了PDO剩下就是请求指定档位。CH224Q的CH224_CTRL寄存器里把bit2:0写成PDO索引即可把当前协商目标切换到对应PDO。要注意索引和实际PDO的映射是1开头还是0开头我用的模块是0表示自动1对应PDO1以此类推。请求第五个PDO就写0x05。void requestPdoByIndex(uint8_t idx) { uint8_t val idx 0x07; Wire.beginTransmission(CH224_ADDR); Wire.write(0x04); Wire.write(val); Wire.endTransmission(); delay(200); // 等待协商完成 }写入后观察电压表VBUS从5V逐步爬到19.86V左右。之所以不是完美20.00V是因为轻载下充电器本来就存在电压偏差加上线缆和接触电阻的压降这个数字完全正常。注意不要在写入后立刻去读状态寄存器Source端收到Request之后还要回Accept和PS_RDY整个过程大概几十毫秒建议轮询读取当前电压寄存器直到它稳定再继续下一步业务逻辑。电子负载拉载到2A时输出电压稳定在19.7V左右电流读数也符合预期。这验证了IIC侧配置索引和PD协议侧的Request消息是严格对应的你写给寄存器的索引就是芯片拿去构造Request时使用的PDO编号。5. 调试中我遇到的四个坑及排查思路5.1 IIC读回全是0xFF上拉电阻和地址问题现象是读任何寄存器都返回0xFF偶尔返回0x00。排查思路按顺序走先测芯片供电正不正常CH224Q的VIN有没有电压再量SCL和SDA的高电平挂上示波器或者万用表如果总线电平被拉低到零伏附近说明漏接了上拉电阻最后做一次IIC扫描如果扫描出来没有任何设备基本就是地址不对或者模块没焊好。我那次的问题就是漏接上拉。CH224Q模块板载上拉一般是有的但如果你自己画板或者用飞线连接一定要记得在SDA和SCL上加4.7k到3.3V。另外CH224的IIC电平域要跟MCU匹配如果MCU是5V而芯片是3.3V最好加电平转换别硬拉。5.2 读到的PDO列表是旧数据Source Capabilities只在协商时广播有一次我发现换了另一个充电器之后IIC读到的PDO列表还是上一个充电器的参数一模一样的缓存值。排查下来才发现CH224Q的PDO缓存不是实时刷新的它只在系统重新协商时才会更新。换句话说Source Capabilities这帧报文是在协商阶段广播的不是持续发送你错过那一瞬间缓存里就是旧值。解决办法是在固件里增加重协商流程。常见做法是拔插Type-C连接或者利用芯片的复位引脚触发重新连接等协商完成后再读PDO缓存。如果芯片支持收到Hard Reset后自动重协商也可以利用这个机制。总之读完PDO列表之后要顺手记录一个“协商序列号”或者时间戳避免把旧数据当成当前状态。5.3 请求20V输出却掉回5V超过Source能力导致协商失败这个坑最隐蔽。现象是我写索引请求20V刚写进去VBUS确实往上爬但爬一会儿又跌回5V甚至直接掉到0V。很多人第一反应是芯片坏了其实不是。原因在于Request消息里带的电流值超过了Source在该PDO下允许的最大电流或者Sink端声明的能力不满足Source的约束Source在评估之后拒绝Accept协商失败就回落到5V的安全电压。验证方法是把电子负载串进去看实际拉载同时检查请求的PDO电流是否和缓存里解析出来的最大电流一致。还有一个容易被忽略的因素线缆压降。长线或者劣质线在20V大电流下压降明显Source端会根据线缆压降做补偿如果补偿后电压仍然超过它的容限也可能拒绝。解决思路是换短线测试或者把请求电流调低一档再试。5.4 PDO索引和电压档位对不上寄存器配置值不是电压值我在第一版固件里犯过低级错误以为写0x14就是请求20V结果输出变成9V。后来看了寄存器手册才意识到CH224_CTRL里的bit2:0是PDO索引不是电压值。你要请求20V得先通过解析PDO列表知道20V在第几个PDO然后把那个索引写进去而不是直接把电压数值写进寄存器。这其实暴露了一个设计习惯问题拿到新芯片先读寄存器表别凭感觉写。我后来在固件里维护了一个枚举把PDO索引、目标电压、最大电流绑在一起所有请求都通过这个结构体发起不再手工填数字这个问题就彻底消失了。6. 这套“读报文”能力还能怎么用6.1 做一个多协议快充测试/诱骗工具把前面这套逻辑封装好再加一个串口或CAN接口就能做出一个很实用的产测工具。生产线上需要验证充电器插上后能不能正确协商到指定档位以前是用协议分析仪成本高、操作复杂。用CH224Q加MCUIIC读出PDO列表自动校验关键档位再执行一轮请求和拉载几秒钟就能出结果成本很低。有人问我为什么不用纯软件PD协议栈去做省掉CH224这颗芯片。理论上可以但PD协议栈跑在通用MCU上要处理BMC物理层编码、CRC32、重传机制时间复杂度高而且CC线上的模拟特性很容易踩坑。CH224Q相当于把物理层和协议状态机都包掉了你只需要跟IIC打交道开发量少一个量级。6.2 产品固件里自动适配电源能力如果你的产品需要兼容多种PD电源适配器这个思路特别有价值。比如一个带电池的设备插入充电器时先读一遍PDO列表固件根据当前电池SOC、温度、充电阶段动态选择最合适的电压电流档位。低温时选低电压小电流快充阶段选大功率档位快充满时降流充电。APDO在这里就有用武之地了。如果充电器支持PPS它的APDO会给出一个电压调节区间你可以通过Request逐步微调VBUS精确匹配电池的理想充电电压把转换损耗降到最低。解析APDO的代码和Fixed PDO类似只是电压单位是100mV电流单位是50mA字段含义稍有调整。6.3 别忘了把PDO解析和合规测试联动最后提一个经常被忽略的点量产产品如果要做Type-C相关认证充电兼容性是必测项目。自己用IIC读到PDO列表对比充电器规格书能提前发现一批兼容性问题比如充电器宣称支持20V但实际广播的能力列表里没有20V或者PDO顺序和文档不一致。这些在实验室里提前抓出来比送到认证机构再被打回来划算得多。提示个人DIY和原型验证阶段随便玩但到了量产阶段USB-IF的合规要求要认真对待尤其是PDO解析和协议行为这块不能只看寄存器能读通就完事。最后说个实在话芯片手册里那几页寄存器表比不上自己抓一次真实报文、解码一帧PDO来得直观。我建议你拿到CH224Q板子后先别急着写业务逻辑做一件事把充电器接上把所有能读的IIC寄存器都打印一遍再对照本文的解析流程走一遍。搞懂这一帧Source Capabilities后面所有档位切换、协议兼容性排查都是体力活。等你真的从IIC数据里还原出充电器底牌的那一刻快充协议对你来说就不再是黑盒了。
返回列表