
简介面向DSView和DSLogic逻辑分析仪用户的快充协议解码插件旨在解决华为FCP、三星SCP、OPPO/AFC等私有快充协议的数据解析难题。插件基于Python开发通过标准协议插件接口集成可实时捕捉USB PD协商之外的厂商定制快充通信流支持波形触发、字段高亮、时序标注和CSV导出适用于快充芯片验证、充电器兼容性测试及Type-C协议逆向分析。压缩包共6个文件以Python脚本为主包含核心解码脚本与模块初始化文件另附说明文件和版本控制配置体积仅6KB轻量易部署。目前已有77人学习下载对于需要快速搭建逻辑分析仪解码环境的开发者这份工具能直接减少协议解析的重复工作便于聚焦通信异常定位与充电策略分析。 上周帮人排查一个22.5W充电头的兼容性问题手机插上去只走普通5V充电快充死活不触发。手头没有专用协议分析仪只有一台DSView逻辑分析仪于是直接在USB的D/D-上飞了两根线抓波形。抓下来的数据确实有东西在跳——先是两根线同时被拉到某个电压接着又是一串脉冲——但DSView自带的解码器列表里压根没有FCP/SCP/AFC这些选项我只能肉眼盯着波形猜状态。折腾了一下午之后我决定自己写一套快充协议解码工具把FCP、SCP、AFC全部做成DSView里的解码器插件。这篇文章就把整个开发过程和踩坑记录完整写出来包括三类协议的物理层差异、解码器架构选型、具体解码逻辑实现以及我在实测中遇到的采样率、通道接反、小信号触发等问题。适合手里有逻辑分析仪、经常调试充电器或手机电源电路的工程师也适合想入门sigrok/DSView协议解码器开发的人。1. FCP、SCP、AFC三类协议为什么让逻辑分析仪“抓瞎”1.1 三者本质上是两种完全不同的协商方式很多人以为快充协议都是“发数据包”协商电压实际上FCP/SCP/AFC这三兄弟的思路差别很大。我做了一个对比表方便理解协议常见来源物理层特征协商方式典型电压档位FCP华为系快充D/D-上的直流电平电压档位通过电平组合表达5V / 9V / 12VAFC三星系快充D/D-上的直流电平与FCP类似电平组合请求档位5V / 9VSCP华为系超级快充D/D-上的差分小信号通信类I2C帧结构双向传输数据和命令5V/4.5A、10V/2.25A、10V/4A等从这张表能看出来FCP和AFC本质上不是“通信”而是“模拟电位协商”适配器把D/D-拉到某个电压设备检测到之后再通过D或D-的电平状态告诉适配器“我要升压到几伏”。整个过程里没有严格意义上的数据帧只有电平组合和时间上的状态切换。SCP则完全不同它走的是类似I2C的双向数据通信有起始条件、从机地址、读写标志、数据字节和校验位是一个真正意义上的私有协议。1.2 没有现成解码器时的调试痛点DSView确实内置了不少协议解码器比如UART、SPI、I2C、CAN这些通用总线都有。但快充协议的解码器一个都没有而且这三个协议在DSView里连“半成品”都算不上。这就导致实际调板时非常被动我用逻辑分析仪抓到了D/D-上的波形但不知道这个波形对应哪一档电压请求也不知道SCP那个看似I2C的脉冲串里到底包含了什么数据。只能一边翻充电头厂商的资料一边手工量电压、算脉宽效率极低。更麻烦的是逻辑分析仪只能采集数字电平而FCP/AFC的协商电压是0.6V、1.2V这样的模拟电平。普通逻辑分析仪的输入阈值一般在0.7V左右0.6V会被判成低电平、1.2V会被判成高电平勉强能区分但如果你想精确判断“当前是哪个档位请求”光靠逻辑分析仪的数字通道是不够的。SCP更是低摆幅差分信号普通逻辑分析仪如果直接把探头搭在D/D-上很可能什么都采不到因为信号幅度达不到逻辑分析仪的触发阈值。1.3 写在前面协议版本差异很大这里必须提醒一句FCP/SCP/AFC都是厂商私有协议不同版本的充电器、不同年代的手机物理层参数和命令字可能会有差异。我在这篇文章里给出的电平对应关系和帧结构是基于我自己抓到的波形和公开逆向资料整理的不敢保证和每一台设备完全一致。因此最好的习惯是“拿到一台新设备先抓波形再对照解码器输出验证”不要盲目相信网上任何一个版本的协议文档。2. 解码器架构选型基于sigrok的DSView插件开发路线2.1 为什么不自写Python脚本解析CSV最开始我的想法很简单把逻辑分析仪导出的CSV数据拿过来自己写Python脚本按时间戳和电平值分析。试了之后发现这条路不好走。CSV文件动辄几十万行每行包含时间戳和多个通道的电平值处理起来非常笨重。而且分析结果只是打印在终端里没法直观地叠加在波形上排查问题时还得来回切换。后来我意识到DSView本身就是基于sigrok开源框架改的它的解码器接口和sigrok一致。sigrok的协议解码器是Python类写好之后放到DSView的decoders目录下就能在软件里直接调用解码结果会以注释标签的形式显示在波形上。这样既能自动解析又能直观定位比脚本分析高出一个维度。2.2 最小可运行解码器的骨架以sigrok的标准Python接口为例一个解码器至少要包含元信息、通道定义、start()和decode()。下面是FCP解码器的最小骨架import sigrokdecode as srd class Decoder(srd.Decoder): id fcp_decoder name FCP decoder longname Fast Charge Protocol decoder desc Decode FCP voltage negotiation state license gplv2 inputs [logic] outputs [fcp] channels ( {id: dp, name: D, desc: USB D line}, {id: dm, name: D-, desc: USB D- line}, ) annotations ( (state, State), (request, Voltage request), ) def __init__(self): self.samplerate None def start(self): self.out_ann self.register(srd.OUTPUT_ANN) def decode(self): if not self.samplerate: raise Exception(Missing samplerate) # 在这里实现状态解析逻辑DSView在加载解码器时会主动把samplerate注入进来所以decode()里直接用self.samplerate计算时间宽度即可。通道定义里dp和dm分别对应USB的D和D-这是所有快充协议抓取的基础。2.3 放置路径与加载方式DSView不同版本的decoders目录位置略有差异。我这边是把.py文件直接放到DSView安装目录下的decoders文件夹里重启DSView后在协议解码器列表里就能看到“FCP decoder”。如果你用的是免安装版这个目录一般就在解压目录的decoders子目录下如果找不到可以直接在DSView安装目录里搜索“decoders”关键字基本都能定位到。加载之后选中解码器把dp和dm两个通道分别赋给逻辑分析仪的通道1和通道2再开始采集就能在波形上看到解码结果。3. FCP/AFC这类“电压协商”协议的解码实现3.1 物理层观察先搞清楚D/D-上的状态变化FCP和AFC的抓取相对简单逻辑分析仪采样率不用太高一般1M到5M就够因为电平状态变化是毫秒级的。把D接到通道1、D-接到通道2、参考地接USB的GND抓一次手机插入充电器的完整过程就能看到D/D-上出现若干次电平跳变。这些跳变的组合就是协商过程。需要说明的是不同设备的电平定义存在差异。网上流传的一组说法是适配器先把D/D-短路并拉高到1.2V左右设备检测到后确认“这是快充适配器”随后设备通过把D或D-拉到0.6V、1.2V、2.0V等不同电平来请求不同电压档位。我自己抓到的波形和这个描述大体一致但具体电平值有出入。3.2 解码逻辑把电压状态翻译成“请求档位”对于数字逻辑分析仪0.6V、1.2V、2.0V这样的模拟电平会被阈值判定成各种高低电平组合。以0.7V左右的输入阈值为例0.6V判低1.2V和2.0V判高。这时“电压档位”就变成了“通道电平组合”。我在解码器里的做法是def decode(self): # 等待D和D-任意一根线有电平变化 # 读取一个短暂窗口内的平均状态消除抖动 # 根据(dp, dm)组合输出对应注释 pass核心思路上不要盯着单个采样点判断而是取一个时间窗口内的稳定状态。比如连续500us内D/D-保持同样的电平组合才认为这是一个有效的协商状态。否则毛刺和噪声很容易造成误判。对于AFC处理逻辑几乎一样只是电平与档位的对应关系不同。我实际测试中AFC会在空闲时先拉高D/D-识别快充然后通过类似FCP的方式组合电平请求5V或9V。由于AFC的复杂度比SCP低很多解码器代码量其实很小大部分工作都花在统计电平组合和整理状态迁移上。3.3 用“窗口平均”规避噪声误判这里有一个我在实测中踩过的坑逻辑分析仪输入阻抗虽然高但接线一长D/D-上就会引入噪声。尤其是手机充电过程中有大电流流过开关电源的纹波会耦合到信号线上导致电平状态反复跳变。直接按采样点判断状态机会乱成一团。我最后的做法是维护一个环形缓冲区取最近N个采样点的平均值作为当前电平。N的大小取决于采样率比如采样率1MHzN取100相当于100us的窗口。实测下来这个窗口长度既能滤掉毛刺又不会把真实的毫秒级状态变化拖模糊。另外前面也提到逻辑分析仪数字通道对0.6V和1.2V这种小电压分辨有限。如果想精确区分多个档位更稳妥的方案是用两个比较器把D/D-的电平转换成多路数字信号再送进逻辑分析仪。我为了快速验证先用LM393搭了个简单的电平判断电路把D/D-分别与0.9V基准比较输出两路数字信号再接入逻辑分析仪。这个方案虽然简陋但对于分辨“有没有进入快充协商”和“大致请求哪个档位”完全够用。4. SCP这种“数据通信”协议的解码难点与实现4.1 SCP的物理层与帧结构SCP和FCP/AFC完全不是一个量级。SCP在D/D-上传输的是低摆幅差分小信号通信速率比我想象的高不少。我抓到的波形显示单bit宽度在微秒级所以采样率至少要5M建议直接用10M以上否则边沿采样点不够状态机很容易崩。从公开逆向资料和我自己抓包的结果看SCP的帧结构非常接近I2C有START条件、7位从机地址加读写位、8位数据字节、校验字节和STOP条件。但具体到命令字和寄存器地址每家电源芯片可能定义不同。我在解码器里只解析帧结构把地址、读写方向、数据字节和校验结果展示出来不强行解释每个数据字节的含义。4.2 差分合成D/D-如何还原成单端数据流SCP是差分信号直接看D或D-任意一根线都会很困惑因为单端波形里有大量共模成分。正确做法是先做差分合成在解码器里把两个通道采样值相减得到差分信号再根据差分信号的正负判断逻辑0和1。diff sample[dp] - sample[dm] if diff 0: bit 1 elif diff 0: bit 0 else: bit None # 空闲或无效这个差分计算必须在解码器里做不是简单地在DSView里把D/D-两个信号相减就完事。因为逻辑分析仪每个采样点都是独立采的只有按照时间戳逐点做减法才能保持对应的时序关系。用这个差分数据流再喂给状态机解析帧结构就很清晰了。4.3 状态机解析从START到STOP的完整流程SCP解码器的核心是一个状态机。我按照I2C的经典五状态来设计IDLE、ADDRESS、DATA、CHECK、STOP。在IDLE状态检测到START条件后进入ADDRESS状态解析出7位地址和1位读写标志后进入DATA状态每个字节读完后判断是校验字节还是普通数据最后检测到STOP条件就结束一帧。以下是一段简化的状态机示例STATE_IDLE 0 STATE_ADDR 1 STATE_DATA 2 class ScpDecoder: def __init__(self): self.state STATE_IDLE self.bit_count 0 self.current_byte 0 self.address 0 self.rw 0 def process_bit(self, bit): if self.state STATE_IDLE: if bit 0: # 检测到起始位 self.state STATE_ADDR elif self.state STATE_ADDR: # 先收7位地址再收1位读写位 ... elif self.state STATE_DATA: self.current_byte (self.current_byte 1) | bit self.bit_count 1 if self.bit_count 8: # 输出一个数据字节 self.bit_count 0实际的代码会比这个长很多因为要处理位对齐、异常退出和超时复位。我给的建议是在状态机里加一个“超时检测”如果一段时间没有bit进来就强制回到IDLE。否则一旦解错一位后面整帧全乱排查起来非常痛苦。4.4 输出注释帧的布局SCP解码器的输出注释我设计了四种类型START、ADDRESS、DATA、STOP外加一个CRC错误提示。在DSView里这些注释会以标签形式显示在波形上方可以直接看到一帧数据从哪个采样点开始、在哪个采样点结束。调试时的体验比看终端打印好太多。5. 调试中的三个大坑与排查过程5.1 采样率不足导致SCP数据全部乱套第一次抓SCP时我图省事用了5M采样率结果解出来的数据全是乱的校验基本过不去。当时我以为是解码器状态机写错了反复检查了半天的代码。后来把抓到的波形放大一看才发现SCP的bit宽度在5M采样率下只有几个采样点每个bit的边沿都因为采样点太少而出现了明显的抖动状态机经常在错误的位置判断bit。换到20M采样率之后波形干净了很多解码结果立刻稳定下来。这里我得到一个经验对于SCP这种私有协议采样率宁高勿低存储深度不够就设置触发条件而不是降低采样率。我后来习惯用D或D-的下降沿作为触发条件只抓取握手开始后的几十毫秒这样既保证了采样率又不会占用太多存储空间。5.2 D/D-接反导致解码结果完全错乱SCP是差分信号D/D-一旦接反差分合成出来的数据流就会变成“反码”所有bit都是反的状态机自然完全错乱。第一次遇到这个问题时我还以为是充电器和手机的协议不兼容折腾了快一个小时。后来发现的解决方式简单到令人无语在DSView里把解码器的通道配置里dp和dm互换一下不需要重新抓波形。如果你的解码器输出结果看起来像反码或者地址字段明显不对劲先试试在DSView里交换通道而不是急着改代码。这是一个能省下大量时间的排查方法。5.3 解码器注释和波形对不齐另一个让我头疼的问题是解码器输出的注释标签和波形之间的对齐关系。DSView中解码器默认把注释显示在独立的注释通道里有时候看起来和实际波形有偏移。这通常不是解码器逻辑的问题而是显示层把注释锚定到了采样点索引上当波形缩放比例不合适时注释标签会显得位置偏了。调整方法是把波形视图放大到合适的级别或者拖动注释通道的时间对齐滑块。每台设备上DSView版本不同这个功能的位置可能不太一样但只要你确认解码器输出的时间戳和采样点索引是正确的显示层的偏移不影响实际定位。我在抓SCP时经常边看注释边对照波形确认每一个START和STOP注释都对得上信号边沿之后才敢说这次抓解是有效的。6. 如何扩展成你自己的“充电协议调试工具包”6.1 把解码器固化成DSView插件后的日常用法三个解码器都写完并加载到DSView之后我现在的调试习惯是无论遇到什么充电兼容性问题先抓D/D-同时挂上FCP、AFC、SCP三个解码器。整个过程只需要飞两根线、配好通道、点开始采集五分钟左右就能判断出问题出在哪个环节。比如之前遇到一个充电头不触发快充的问题我用FCP解码器看到协商电平一直停留在“未识别快充”的状态说明适配器根本没有把D/D-拉到识别电平问题在适配器端另一次是SCP解码器能正常解析出地址和命令但数据帧发到一半就停了说明手机端的协议芯片可能在等待某个响应时超时了。能快速定位到“是设备端不请求还是适配器端不响应”是这套工具最大的价值。6.2 下一步可以考虑扩展的方向如果你也想做一套类似的工具我建议在完成FCP/SCP/AFC之后再往这几个方向扩展增加PD和QC2.0/QC3.0解码器PD走的是CC线通信QC2.0和QC3.0同样依赖D/D-电压组合思路和FCP/AFC很像。把解码结果导出成CSV方便写自动化测试脚本批量验证不同充电头的握手行为。把逻辑分析仪的“电流/电压采样通道”和解码器输出放一起对比可以直观看到“升压请求发出之后VBUS实际升到多少伏”。这个功能对验证适配器响应特别有效。6.3 一句实在的经验抓这类低压差分小信号最大的坑其实不是协议解析本身而是物理层信号调理。SCP这种低摆幅差分信号如果直接拿普通逻辑分析仪探头去戳经常什么都采不到。我最后的做法是用一个简单的运算放大器电路把D/D-差分信号放大并转成标准逻辑电平再送进逻辑分析仪。很多看起来“解码器写错了”“协议不兼容”的问题根子上都是物理层没调理好。工具终究是死的波形才是活的。你手里那台逻辑分析仪能解出什么协议取决于你肯不肯在信号调理上多花一点功夫。本文还有配套的精品资源点击获取