3. USB PD物理层:BMC编码、前导码、SOF与CRC归纳总结
这一章我们聊聊USB PD的物理层。
说实话,很多做协议栈开发的朋友,一开始都容易忽略物理层。觉得那是硬件工程师的事。但我个人经验是,物理层出了问题,上层协议再对也没用。你想想看,底层的比特流都错了,CRC根本过不了,还谈什么协商?
USB PD物理层有几个核心要素:BMC编码、前导码、SOF(帧起始)和CRC(循环冗余校验)。我把它拆开来讲。
3.1 BMC编码:物理层的“摩尔斯电码”
BMC,全称是Bi-Phase Mark Coding,双相标记编码。
说白了,这就是USB PD在CC线上传输数据的方式。它跟我们熟悉的UART那种NRZ编码不太一样。
我记得第一次看BMC波形时,差点看懵了。它每个比特位都会有电平跳变。具体规则是这样的:
- 每个比特位的开始处一定会发生电平翻转。
- 如果当前比特是逻辑“1”,在比特位的中间再翻转一次。
- 如果当前比特是逻辑“0”,比特位中间保持不变。
简单粗暴的记忆法:
- “0”:只在开头跳一下(占一个完整周期)
- “1”:开头跳一下,中间再跳一下(占两个半周期)
为什么要用这种别扭的编码?
好处很明显:自带时钟同步。接收端不需要单独的时钟线,直接从数据流的跳变沿就能提取出时钟信息。这在USB PD这种单线通信的场景下,非常实用。
我在调试一个PD快充项目时,曾经因为线路上的寄生电容太大,导致BMC编码的边沿变缓,接收端锁相环失锁。嗯,这个问题查了我整整两天。后来在CC线上加了个小电阻才解决。
设计建议:CC线上的RC时间常数应尽量小,确保BMC信号上升/下降时间不超过300ns。否则容易导致位采样错误。
3.2 前导码与SOF:数据包的“前戏”
一个完整的USB PD物理层数据包,不是上来就扔数据的。它有一套固定的“开场白”。
3.2.1 前导码(Preamble)
前导码是一串固定的比特序列:64位交替的“0”和“1”,即“01010101...”。
它的作用是什么?
- 让接收端的PLL(锁相环)锁定到发送端的时钟频率。
- 让接收端调整增益,适应信号幅度。
我建议你在示波器上看这个前导码波形。如果前导码部分出现抖动或者缺失,基本可以断定是CC线接触不良或者驱动能力不够。
3.2.2 SOF(Start of Frame,帧起始)
前导码发完之后,紧跟着的就是SOF。
SOF是一个固定的5位序列:"00110"(经过BMC编码后)。
它告诉接收端:“注意了,真正的数据包马上要来了”。接收端检测到SOF后,就能知道接下来的数据是正经的PD消息,而不是干扰信号。
避坑经验:
我曾经遇到过一个兼容性问题。某款充电器发送的SOF序列,因为上升沿过缓,被我们的设备误判为无效SOF。结果整个协商流程都走不下去。解决方法是调整了接收端的边沿检测阈值。所以,SOF的时序必须严格符合BMC比特率(300kbps)的±2.5%误差。
3.3 CRC:数据完整性的“守门员”
USB PD用的是CRC-32,多项式是标准化的0x04C11DB7。
你可能会问:“物理层为什么要有CRC?”
其实,USB PD的物理层数据包包含了SOF + 数据头 + 数据对象 + CRC。CRC就是为了保护数据头和数据对象这两个部分不被噪声干扰。
它的计算范围是:从数据头开始,一直到CRC字段之前的所有比特(不包括SOF和前导码)。
我个人习惯在固件里用查表法算CRC,速度比逐比特运算快很多。标准的多项式是0xEDB88320(这个是反射后的多项式)。
// CRC-32 查表法计算示例(USB PD标准多项式)
uint32_t crc32_table[256];
void crc32_init(void) {
uint32_t crc;
for (uint32_t i = 0; i < 256; i++) {
crc = i;
for (int j = 0; j < 8; j++) {
if (crc & 1)
crc = (crc >> 1) ^ 0xEDB88320;
else
crc >>= 1;
}
crc32_table[i] = crc;
}
}
uint32_t crc32_calc(uint8_t *data, uint32_t len) {
uint32_t crc = 0xFFFFFFFF;
for (uint32_t i = 0; i < len; i++) {
crc = crc32_table[(crc ^ data[i]) & 0xFF] ^ (crc >> 8);
}
return ~crc;
}
注意:发送端发送CRC时,需要把高位在前的CRC值反序(按字节反转)发送。接收端收到后重新计算,如果结果等于0x2144DF1C(CRC魔术校验值),则说明数据完整。
3.4 物理层数据包结构总结
把上面几样东西串起来,一个完整的物理层数据包长这样:
| 字段 | 长度(字节) | 内容 | 作用 |
|---|---|---|---|
| 前导码 | 8 | 0xAA(交替的0/1) | 时钟同步 |
| SOF | 1(固定5位) | 0x06("00110") | 帧起始标识 |
| 数据头 | 2 | 消息类型、长度、方向 | 协议控制信息 |
| 数据对象 | 0~28 | 能力信息、请求数据等 | 具体协商内容 |
| CRC-32 | 4 | 校验值 | 完整性校验 |
| EOP | 1 | 结束序列 | 帧结束标识 |
整个流程你可以理解成:先喊一声“喂”(前导码),再喊一声“我要说话了”(SOF),然后快速把正事说完(数据头+数据对象),最后补一句“我没说错吧?”(CRC)。
3.5 物理层汇总图
下面这张图梳理了从空闲状态到完成一个数据包发送的全过程。你可以对照上面的表格一起看。
再次提醒:
物理层是USB PD协议的基石。很多协议上的疑难杂症,最终都追溯到物理层。
比如CRC不过,别急着怀疑算法,先看看BMC编码的时序对不对。SOF检测失败,先查CC线上有没有直流偏置干扰。
我曾经在一个量产项目中,发现某些线缆在4.75V下发送的前导码振幅不够,导致接收端无法同步。最后是通过调整发送端的驱动能力才解决的。
3.6 本章小结
总结一下今天讲的核心内容:
- BMC编码是物理层的数据编码方式,自带时钟,适合单线通信。
- 前导码用于时钟同步,是数据包发送的前奏。
- SOF标志一个数据包的开始,接收端靠它来识别有效帧。
- CRC-32保证数据完整性,算法固定,固件实现建议用查表法。
嗯,搞懂了这些,你就能真正看懂USB PD总线上的数据流了。下次用逻辑分析仪抓波形时,可以试着把前导码、SOF、CRC一一找出来,挺有成就感的。