示波器抓USB-C PD协议:BMC与4B5B编码波形解析与实战解码
2026/9/21 0:50:49 网站建设 项目流程

很多搞硬件的朋友和我说,调USB-C充电电路最痛苦的不是画板,而是不知道协议层到底发生了什么。插上充电头,示波器探在VBUS上只能看到5V或者9V跳变,真正的USB-C PD协议握手,其实发生在CC线上——一根你平时根本不会去关注的信号线。这篇文章我就用示波器把BMC编码和4B5B编码的握手过程完整抓下来给你看,从物理层波形一路解到协议包,顺便把踩过的坑也一起交代清楚。

这篇内容适合谁看?做USB-C接口设备的硬件工程师、写PD诱骗器固件的嵌入式开发者、以及那些用Type-C口给产品取电但始终没搞懂“为什么这个充电器认我、那个充电器不认我”的人。读完你会发现,示波器不只是用来量电源纹波和时序的,它还能当协议分析仪用,而且比协议分析仪更接近信号本质。

1. 先搞清楚:谁在CC线上“说话”,为什么VBUS看不出门道

1.1 从5V/9V跳变到真正的数字握手

很多工程师调试USB-C充电时习惯把探头怼在VBUS上,看到电压从5V跳到9V,就觉得“协议走了”。这个判断不能说错,但充其量只是知道结果,不知道过程。VBUS上的电压变化是Source端功率变换的结果,真正用来协商这个电压的多轮对话,全部发生在CC线上。

CC线全称Configuration Channel,在USB-C座子里只有两根,CC1和CC2。它们平时承担连接检测、角色发现、线缆检测这些任务,而在PD通信时,它就是一条半双工的总线。Source端和Sink端在CC线上来回发送BMC编码的数据包,协商出“你要多少电压”“我能给多少电流”“是否支持PPS”这些信息。

用示波器抓CC线,你能看到一串串脉冲按时间顺序排列,这些脉冲就是BMC编码后的数字信号。解析它们,等于把PD协议从前到后重新走了一遍。

1.2 Source与Sink的分工,以及那一对Rp/Rd电阻

在讲抓波形之前,必须先把CC线上的静态直流电平说清楚。

USB-C的Source端(比如充电器)会在CC1和CC2上分别接一个上拉电阻Rp到某个参考电压,Sink端(比如你的手机)则接一个下拉电阻Rd到地,标准阻值通常是5.1kΩ。当设备插入后,CC线被Source的上拉和Sink的下拉分压成一个固定的直流电平,Source检测到这个电平就知道有设备接入,而且是作为Sink接入的。

这个直流电平不是随便定的,它和Rp的阻值直接相关:

Rp阻值通讯前默认能力典型CC线静态电压(以5V上拉估算)
56kΩ默认USB 500mA约0.42V
22kΩ1.5A约0.94V
10kΩ3A约1.69V

PD协商开始后,这个直流电平上会叠加BMC调制信号,也就是说CC线上的波形不是从0V开始跳的,而是从某个固定偏置电平上下摆动。这一点对示波器触发电平的设置影响很大,后面实操部分再细说。

1.3 PD和QC的本质区别

聊到这里顺带提一嘴,很多做快充兼容的人总把PD和QC混在一起说。Qualcomm的QC协议走的是D+/D-两根USB数据线上的电压调制,本质上是被动模拟检测,协议简单但扩展性有限。而PD完全不一样,它走CC线,是真正的双向数字通信协议,支持PPS电压连续调节、多档PDO、线缆模式检测等功能。

PD能成为统一标准,最重要的一点是它不依赖某个厂家的私有芯片,USB-IF把物理层、链路层、策略层都定义得明明白白。你只要按照规范写策略,就能在任何一个PD充电器上协商到合适的电压。这也是为什么我建议做硬件调试的人一定得学会抓CC线——你调QC还能靠一堆示波器量D+电压,调PD如果不会看BMC波形,那就真的只能靠猜。

2. 抓包前的工具准备:示波器、探头和转接夹具

2.1 带宽和采样率该怎么算

很多人一听“抓PD协议”,第一反应是“我那个示波器够不够用”。我直说结论:抓PD握手,带宽真不需要太高。

PD物理层的BMC波特率是300kbps,每位时间是3.3微秒,BMC编码后最小的脉冲宽度大约1.67微秒,换算成频率大概在600kHz上下。考虑到方波的谐波,用20MHz带宽限制是绰绰有余的。普通100MHz的示波器完全够用,哪怕你手头只有一台几十MHz的老机器,只要采样率能达到每bit采15个点以上,就能还原出准确的波形。

真正需要注意的反而是采样率和存储深度。我习惯把采样率设在50MSa/s左右,时基拉到每格200微秒到500微秒,这样一屏大概能覆盖1毫秒到5毫秒的时间窗口,足够抓下一组完整的PD数据包交换。

如果示波器存储深度比较浅,采样率就要适当降低,否则波形会自动压缩,细节全糊了。判断标准很简单:抓到的信号最小脉冲宽度(1.6微秒左右)至少要占到几个采样点,否则后续解码全是错的。

2.2 探头选择与接地注意事项

CC线上的信号是单端信号,参考地就是设备的GND,所以普通无源探头就能测,不需要差分探头,这点和抓CAN总线差分信号不一样。

但有一个细节非常影响波形质量,就是探头的接地。千万不要用那种长长的鳄鱼夹地线去接,探头地线长,寄生电感会把BMC信号的高频分量振出一串毛刺。我试过用长地线抓CC线,波形上每个边沿后面都跟着衰减振荡,虽然能看个大概,但后期做BMC解码的时候就头疼了。

正确的做法是使用探头标配的短接地弹簧,或者找一块铜箔直接贴在探头地线最短的位置。这个习惯养成之后,你抓所有数字信号都会省心很多。

另外,要把探头衰减比调到10x,档位选对之后带宽才对。很多示波器的1x档位带宽只有几MHz,直接把BMC信号的高频分量削掉了,抓出来波形圆乎乎的根本没法解码。

2.3 转接板:没有那根CC线焊盘,就只能碰运气

最理想的方式,是准备一块专门的USB-C测试转接板,把CC1、CC2、VBUS、GND全部引出到测试点。市面上现成的USB-C breadboard breakout模块很多,几块钱到几十块钱不等,选择时注意选那种CC1和CC2分开引出的,而不是把所有引脚都并在一起的简陋版本。

如果没有转接板,直接把探头扎进USB-C座的缝隙里也不是不行,但CC1和CC2引脚在Type-C座子里非常密,而且在不同插头方向下走的是不同的CC引脚,你很难确定自己扎对了。更麻烦的是,很多成品线缆把CC引脚藏在屏蔽层里,外面根本接触不到。

所以我的建议是:要么买转接板,要么自己画一个只有USB-C母座、电阻网络和测试点的简易小板。自己画的话,务必把Rp、Rd都做成可选电阻,因为后面调试不同档位时经常要换阻值。

3. BMC编码:示波器波形里的“长短信号”到底在说什么

3.1 BMC规则:每个比特都有两次跳变机会

BMC全称Bi-Phase Mark Coding,中文常译作双相标记编码。它属于一种自同步的线路编码,信息直接编码在跳变的位置上。

具体规则是这样的:每个比特周期固定为3.33微秒,每个比特周期的起始边界一定有一个跳变,用来给接收端恢复时钟。在这个前提下,如果该比特要表示逻辑1,那么在这个比特周期中间会额外增加一次跳变;如果表示逻辑0,则比特周期内部保持电平不变。

换句话说,在一个比特周期里,逻辑0表现为“一整个3.33微秒宽度内只有两次边沿(起点和终点)”,而逻辑1表现为“在这个比特周期里出现三次边沿(起点、中点、终点)”。

这个特性反映在示波器上就是一串间隔不等的脉冲:宽脉冲代表0,窄脉冲代表1。很多人拿到波形以为这就是普通UART的高低电平,直接按码型去解,结果全乱——因为BMC的本质是“边沿携带信息”,不是“电平携带信息”。

3.2 为什么PD选BMC而不是Manchester或UART

我曾经想,PD为什么不直接用UART或Manchester呢?Manchester编码和BMC其实很像,都是利用跳变位置表示数据,但PD选择BMC而非Manchester,主要原因有两个。

第一,BMC编码后信号的直流分量比Manchester更可控。USB-PD在CC线上传输时,接收端是通过检测信号的平均电平和有效值来判断连接状态的,BMC的功率谱密度在低频段的能量分布更集中,对CC线上的直流偏置影响更小。第二,BMC的接收端实现更简单,一个异或门加一个锁相环就能完成时钟恢复和数据解调,这在成本敏感的Type-C接口芯片里很有优势。

UART就不说了,它需要一个精确的波特率时钟,且没有自同步能力,一旦收发双方时钟偏差超过千分之几就会丢字节,完全不适合这种热插拔场景。

3.3 把波形还原成BIT流的小脚本

既然BMC是边沿编码,那么用示波器导出的CSV文件就能离线解码。我经常这么干:先把示波器的波形数据导出成CSV,然后用Python写个小脚本,先找跳变边沿,再根据边沿间隔判断是一串0还是一串1。

一个懒人版本的解码思路是这样的:

import numpy as np t, v = np.loadtxt('cc_wave.csv', delimiter=',', skiprows=1).T threshold = 0.8 # 根据实际波形调整 edges = np.where(np.diff(v > threshold) != 0)[0] # 得到每个边沿对应的时刻 edge_times = t[edges] dts = np.diff(edge_times) # BMC: bit 周期约3.33us bit_period = 3.33e-6 bits = [] for dt in dts: half = round(dt / (bit_period / 2)) # 相邻边沿间隔若为1个UI -> 连续1最多1个;若为2个UI -> 中间有1个0 # 细节需要结合实际起始边沿做修正 if half == 1: bits.append(1) elif half == 2: bits.append(0)

这段代码只是演示核心思路,真正的完整解码还需要处理起始相位、去掉前导码等问题。实际调试时,其实可以先用示波器自带的解码功能或者开源工具对照一下,再写自己的脚本。我就是这么干的:先用工具确认一段波形是什么内容,再回头硬啃脚本,两相对照很快就通了。

4. 4B5B编码与数据包结构:从BIT流到能读懂的消息

4.1 4B5B映射表

BMC搞定的是物理层,但PD报文并不会直接把字节串变成BMC波形。在BMC之前,还有一层链路编码,就是4B5B。

4B5B的意思是把4bit数据映射成5bit符号。为什么要多做这一步?因为BMC虽然能自同步,但它对长串的连续相同电平比较敏感,如果物理层出现连续多个0,接收端的时钟恢复会越来越吃力。4B5B可以保证线路上不会出现太长的连续同电平,同时引入一定的冗余,让接收端更容易做对齐和校验。

PD协议里使用的4B5B映射表如下:

4bit数据5bit编码4bit数据5bit编码
000011110100010010
000101001100110011
001010100101010110
001110101101110111
010001010110011010
010101011110111011
011001110111011100
011101111111111101

需要注意,除了数据符号之外,4B5B还定义了一些控制符号(K码),用来标识包的开始位置和结束位置。

4.2 一个PD数据包由哪些部分组成

当你把BMC波形解成bit流,再按5bit分组、查找K码对齐之后,你会得到一个类似这样的数据包结构:

  • Preamble(前导码):一段固定模式的0/1交替序列,用来让接收端锁相环锁定时钟。
  • SOP(Start Of Packet):由特定的K码序列组成,标识一个包开始,还能区分SOP1/SOP2/SOP3,用来识别是Source发给Sink、Sink发给Source,还是线缆E-Marker相关通信。
  • Header(16 bit):消息类型、消息长度、报文ID等关键信息。
  • Data Objects(可选):一个或多个32bit的数据对象,比如Source_Capabilities里的PDO列表,每项都写着电压、电流范围。
  • CRC32(32 bit):对整个包的校验。
  • EOP(End Of Packet):结束标志。

在示波器上,如果你只看物理层,能明显看到每个数据包是“一组密集脉冲”,包与包之间有一段安静的空闲期。这个空闲期大概会有好几个微秒甚至几十微秒,取决于数据链路层的策略。

4.3 Header字段里藏着什么

Header是整个PD消息里最容易提取价值的部分。16bit的Header里有几个核心字段:

  • 消息类型(Message Type):比如Source_Capabilities、Request、Accept、Reject、PS_RDY、GoodCRC等。
  • 对象数(Number of Data Objects):后面跟了几个32bit数据对象。
  • 报文ID(MessageID):用来做重传和去重的。
  • 端口角色(Port Role):标识发送方是Source还是Sink。

这些都是标准PD协议定义好的,知道消息类型之后,你再去解读后面的Data Objects就有的放矢了。比如抓到Source_Capabilities之后,后面跟着的几个Object就是它能提供的电压电流组合。

这一层信息没法直接从波形上肉眼读出来,需要把波形解成bit,再按4B5B表反向映射。所以实操时,建议先在PC上用脚本或示波器解码功能做一次自动解析,再对着波形核对关键位置。

5. 实操:示波器抓一次完整的PD握手

5.1 接线和触发参数怎么设置

接下来进入正题。我这里用充电器作为Source,一个带电池负载的PD诱骗器作为Sink,做一次完整的抓包示范。

接线方式如下:USB-C转接板的CC1测试点接示波器CH1探头,探头的接地弹簧接转接板的GND。如果你的转接板把CC1和CC2都引出来了,优先抓CC1,因为Type-C线缆默认使用的就是CC1,除非插反了才会切到CC2。

示波器参数按这套来:

  • 通道耦合:DC
  • 探头衰减比:10x
  • 带宽限制:20MHz(如果示波器支持,把带宽限制打开可以滤掉高频噪声)
  • 采样率:50MSa/s
  • 时基:200us/div
  • 触发电平:先根据CC静态电平估算,如果是0.9V左右,触发电平设在0.6V左右
  • 触发方式:上升沿、Normal模式

然后执行关键一步:把示波器切到Single(单次)触发,再去给系统上电或者插入USB-C连接器。这时候示波器会捕获到一串完整的PD握手波形。

这里提醒一下,很多示波器默认的触发模式是Auto,Auto模式下没有信号时波形会一直刷新,容易错过握手过程。用Single触发能确保只在抓到时停下来,这样后续放大看细节非常方便。

5.2 典型波形解读:Source_Capabilities、Request、PS_RDY

第一次抓到完整握手波形的时候,你看到的画面大概是这样的:先是一段比较稳定的直流电平,然后突然冒出一簇脉冲,间隔几百微秒又来一簇,每簇脉冲都是一两个PD数据包。

按PD规范,连上之后通常会发生这样一个流程:

  1. Source端在CC线上发送Source_Capabilities包,告诉Sink自己支持哪些电压电流档位,例如5V/3A、9V/3A、12V/3A等。
  2. Sink收到后,发送Request包,请求其中一个档位。
  3. Source收到Request后,校验通过则回复Accept,然后完成电源输出切换并发送PS_RDY包。
  4. 之后双方进入稳定供电状态,CC线上安静下来,偶尔会有GoodCRC或者其他消息。

在示波器上,如果你抓的窗口足够长,你会先看到Source发的Source_Capabilities(带多个Data Object,脉冲串比较长),然后隔一段是Sink发的Request(短一些),再往后是关键的两个包:Accept和PS_RDY。

我最早抓的时候只关注了VBUS电压,觉得PS_RDY没什么好看的。实际上PS_RDY信号在CC线上的出现位置很关键,它代表电源转换器完成了档位切换,如果你的产品在那个时刻拉载导致电压跌落,就能明显看出协议状态和电源状态之间的时序关系。

5.3 如何判断抓到了“完整的”握手

判断你抓到的波形是否完整,最简单的标准是看CC线上有没有出现PS_RDY包之后的“安静期”。如果抓完一轮之后一直看到Source反复发送Source_Capabilities,说明Sink没有正确接收或者没有发Request,这种波形虽然也有脉冲,但不代表完成了一次协商。

更准确的做法是同时抓两路信号:CH1接CC1,CH2接VBUS。这样你能在同一个时间轴上看到CC线上的协议包和VBUS电压的切换对齐关系。我就是这么判断的:当VBUS从5V跳到9V的那个边沿,前面必然紧跟着一个PS_RDY包,如果这个对应关系对不上,说明抓到的包里可能有重传或者状态异常。

顺带一提,如果只是想快速测试自己的PD Sink设备能不能正常工作,最省事的办法是在CC线和VBUS之间并联一个合适的Rd电阻,让Source以为有Sink接入,然后手动触发拉载,再观察VBUS响应。这种方法能看到供电切换,但看不到协议细节,属于应急手段。

6. 踩坑记录:CC线抓包常见问题与排查

6.1 抓不到任何波形,先检查CC1/CC2

这是最最常见的问题,十个里有五个是CC1和CC2抓反了。USB-C插头正反插用的是不同的CC引脚,很多转接板做出来之后并没有把CC方向固定,你必须两条都接上探针或者用一根支持PD的线缆直接把其中一根CC引出来。

排查方法很简单:把示波器挂在CC1上,插上设备如果没反应,就换到CC2上。如果两个都没有,再用万用表量一下CC线上的静态电压,看看是不是0.4V到1.7V之间。如果CC电压是0V,大概率是没接对地,或者Sink端根本没有正确下拉,Source根本没识别到设备。

我见过一次最隐蔽的情况是转接板上的Rd焊错了,导致CC线上始终是高电平,Source以为没插线缆,根本不进入PD通信状态。这种问题不看CC电平,光抓波形永远抓不到信号。

6.2 波形乱跳、毛刺多

波形毛刺多,十有八九是探头接地线太长。我前面提过,用长鳄鱼夹地线抓高频数字信号,寄生电感会和探头电容形成谐振,在边沿处产生振铃。

另外还有一个容易被忽略的干扰源:如果你的转接板供电和示波器供电不共地,或者Type-C充电器是反激电源且没有良好接地,共模噪声会直接叠在CC线上。我处理这种问题的方法是在转接板GND和示波器GND之间用一根很粗的短线直接短接,尽量缩短回流路径。

如果BMC波形幅度看着很小,只有几十毫伏,那就要检查是不是探头挡位选错了,很多示波器在1x挡位下带宽降得很低,直接把信号细节削没了。

6.3 只抓到一半就没了

有时候你能看到Source_Capabilities的脉冲,但一直没抓到Sink回的Request,或者抓到了Request却没看到Accept/PS_RDY。这种情况通常是触发电平没设置到位,导致示波器在信号中间某个位置触发,只截到部分包。

解决思路是在Single模式下多试几个触发电平,比如把电平从0.6V调到0.3V或者0.9V,再重新插拔一次。每次握手发生的时间很短,多试几次总能抓到完整窗口。

另一个容易踩的坑是示波器时基太长,把整个握手过程压缩成一团,导致BMC脉冲挤在一起看不出个数。这时候重新把时基调短,只抓单个包。我习惯先长时基抓全貌,再存储波形后逐步放大去看细节,毕竟示波器的缩放功能是零成本的。

6.4 常用参数速查表

参数数值说明
BMC波特率300 kbps每位周期约3.33us
BMC最小脉冲宽度约1.67us数据1时比特周期中间有一次跳变
典型CC静态电平0.4V-1.7V与Rp/Rd分压有关
建议采样率50MSa/s每bit至少采样15个点
建议时基200us/div单次抓包窗口覆盖几个包
探头带宽20MHz以上即可带宽限制开启可滤噪

这套参数适用于绝大多数PD 2.0/PD 3.0协议调试场景,PPS、E-Marker通信这类扩展场景也能覆盖,只需要在分析时多关注SOP'类型的数据包即可。

6.5 给新手的最后提醒

示波器抓PD协议这件事,难点不在设备,而在你愿不愿意从一根波形里拆出所有信息。第一次抓到波形后,别急着换设备,先把波形放大,从第一个边沿开始数,看看BMC脉冲的宽窄分布,再对照4B5B解码表推几个字节出来,那种把一个无名波形彻底理解透的感觉,比直接抄一个协议分析仪结果来得踏实得多。

我个人在实际操作中还有一个小习惯:每次抓完包,都会把CSV导出来存一份,文件名按日期加充电器型号命名。时间长了之后,这就是一台“充电器协议数据库”,哪个牌子的充电器支持哪些PDO、握手时序有什么特点,都一目了然。后面再遇到兼容性问题,翻一翻旧文件往往就能找到答案。

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

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

立即咨询