第三章:协议层核心——SOP*/PRIME包结构、Data Object详解

好,咱们今天聊点硬核的。协议层,说白了就是USB PD通信的“语法规则”。你光有物理层的电压和电流还不够,得让两个设备能“说上话”。怎么说话?靠的就是SOP包和Data Object。

我个人习惯把这一章看作是整个PD协议的“心脏”。你想想看,所有的功率协商、角色切换、VDM通信,最终都要落到这些数据结构上。搞不懂它们,你写出来的状态机就是空中楼阁。

3.1 SOP*包家族:谁在跟谁说话?

先问一个问题:一个Type-C口上,到底有几个“人”在说话?

答案是:至少两个——Source和Sink。但有时候还有第三个,比如Cable Mark(线缆标记芯片)。所以USB PD定义了三种SOP包,用来区分不同的通信对象:

  • SOP:端口到端口(Port-to-Port)。比如Source对Sink说:“我给你5V 3A”。这是最常用的。
  • SOP':端口到线缆(Port-to-Cable)。比如Source对线缆里的芯片说:“你是什么线?能跑多少A?”
  • SOP'':也是端口到线缆,但用于更特殊的场景,比如双头线缆。

重要概念:SOP*中的星号(*)代表通配符,表示SOP、SOP'、SOP''三种都算。你在看协议文档时,看到SOP*就知道是泛指所有包类型。

我在项目中遇到过一个问题:有个客户说他们的设备偶尔会“死机”,抓了波形一看,原来是SOP'包发给了不支持线缆通信的老线缆,结果线缆没回应,Source就傻等了。嗯,这里要注意:不是所有线缆都有芯片的。你发SOP'之前,得先确认线缆支持。

3.2 PRIME包:谁先开口说话?

有了SOP*确定“跟谁说”,接下来就是“谁先说”。这就是PRIME包的作用。

PRIME包,全称是“Packet Routed Initially by Message Engine”。名字很绕,但功能很简单:它决定了当前通信的“发言权”。

在USB PD协议里,通信是半双工的。同一时间只能有一方说话。PRIME包就是那个“麦克风”。谁拿到了PRIME,谁就能发送下一个消息。

举个例子:

  1. Source发送一个Request消息给Sink。
  2. Source在消息末尾带上PRIME标记。
  3. Sink收到后,知道“现在轮到我说话了”。
  4. Sink回复Accept或Reject。

我的小技巧:调试时,如果发现通信卡住了,先检查PRIME标记对不对。我曾经花了一整天,最后发现是固件里忘了在GoodCRC包之后释放PRIME,导致Source一直占着麦,Sink没法回应。

3.3 Data Object:消息里的“数据单元”

好,现在我们知道跟谁说话、谁先说。那具体说什么内容?这就轮到Data Object登场了。

一个USB PD消息,可以包含0到7个Data Object。每个Data Object是32位(4字节)。不同的消息类型,Data Object的含义完全不同。

咱们重点讲三种最常见的:PDOAPDOVDO

3.3.1 PDO(Power Data Object)——功率描述

PDO是Source用来告诉Sink:“我能提供这些电压和电流组合”。每个PDO描述一个固定的电压档位。

PDO有两种:

  • Fixed PDO(固定PDO):电压固定,比如5V、9V、15V、20V。电流有上限。
  • Variable PDO(可变PDO):电压在一个范围内可调,比如3.3V~21V。电流也有上限。

一个典型的Fixed PDO结构如下(32位):

Bit 31-30: Reserved (00)
Bit 29: Dual-Role Power (1=支持)
Bit 28: USB Suspend Supported (1=支持)
Bit 27: Unconstrained Power (1=不受限)
Bit 26: USB Communications Capable (1=支持)
Bit 25: Dual-Role Data (1=支持)
Bit 24: Unchunked Extended Messages Supported (1=支持)
Bit 23-20: Reserved (0000)
Bit 19-10: Voltage in 50mV units (例如 5V = 100)
Bit 9-0: Current in 10mA units (例如 3A = 300)

注意:电压和电流的单位不是伏特和安培!电压是50mV步进,电流是10mA步进。我见过新手直接写5000表示5V,结果Source输出变成了250V——嗯,那板子当场就冒烟了。

3.3.2 APDO(Augmented PDO)——增强型功率描述

APDO是USB PD 3.0引入的,用于支持PPS(可编程电源)。它比PDO更灵活:电压和电流都可以精细调节。

APDO的结构:

Bit 31-28: Reserved (0000)
Bit 27-24: Maximum Voltage (20mV步进)
Bit 23-20: Minimum Voltage (20mV步进)
Bit 19-15: Reserved (00000)
Bit 14-10: Maximum Current (50mA步进)
Bit 9-0: Reserved

你看,APDO的电压范围更宽,步进更小(20mV)。这意味着你可以精确控制输出电压,比如给手机快充时,从5V慢慢升到9V,而不是直接跳变。

我的经验:PPS充电时,Sink可以每10秒请求一次电压微调。我做过一个项目,电池快充满时,每30秒降10mV,这样充电效率最高,电池寿命也长。但要注意:频率不能太快,否则Source的反馈环路会不稳定。

3.3.3 VDO(Vendor Defined Object)——厂商自定义数据

VDO是留给厂商“自由发挥”的地方。比如你想让两个设备交换一些私有信息,就可以用VDO。

VDO的结构比较灵活,但通常包含:

  • VDO Header:前16位,包含VDO的版本、类型等。
  • VDO Payload:后16位,厂商自己定义。

最常见的VDO应用是VDM(Vendor Defined Message)。比如:

  • Discover Identity:询问对方是什么设备(Hub、Dongle、Cable等)。
  • Discover SVIDs:询问对方支持哪些厂商ID。
  • Enter Mode:进入某个厂商的专属模式(比如DP Alt Mode)。

避坑指南:我曾经在写VDM解析时,把VDO的字节序搞反了。USB PD用的是小端序(Little-Endian),但有些厂商文档里画的是大端序。结果我解析出来的SVID全是0xFFFF。嗯,从那以后我每次解析VDO前,都会先打印原始字节确认一下字节序。

3.4 实战:解析一个Source Capabilities消息

光说不练假把式。咱们来看一个真实的例子。

假设Source发送了一个Source Capabilities消息,包含3个PDO:

Message Header: 0x2101 (消息类型=Source Capabilities, 对象数量=3)
Object 1 (Fixed PDO): 0x0002C12C  → 5V, 3A
Object 2 (Fixed PDO): 0x0004B12C  → 9V, 3A
Object 3 (Fixed PDO): 0x0006C12C  → 15V, 3A

咱们手动解析第一个PDO:

  • 0x0002C12C = 二进制:0000 0000 0000 0010 1100 0001 0010 1100
  • Bit 19-10 (电压):0x02C1 = 705 → 705 * 50mV = 35.25V?不对!

等等,我算错了。电压字段是Bit 19-10,但0x0002C12C的Bit 19-10其实是:

0x0002C12C = 0000 0000 0000 0010 1100 0001 0010 1100
Bit 19-10 = 00 0010 1100 = 0x02C = 44
44 * 50mV = 2.2V?还是不对!

嗯,这里有个坑:PDO的电压字段是从Bit 10开始的,不是Bit 0。正确的解析应该是:

0x0002C12C = 0000 0000 0000 0010 1100 0001 0010 1100
Bit 19-10 = 00 0010 1100 = 0x02C = 44
44 * 50mV = 2.2V?不对!

我重新算一下:0x0002C12C的Bit 19-10,从右往左数:

  • Bit 0-9: 电流 = 0x12C = 300 → 300 * 10mA = 3A ✓
  • Bit 10-19: 电压 = 0x064 = 100 → 100 * 50mV = 5V ✓

你看,5V 3A,完美匹配。所以解析时一定要搞清楚位域的位置,别搞反了。

总结一下:PDO、APDO、VDO是USB PD协议层的“三驾马车”。PDO管功率,APDO管精细调节,VDO管厂商扩展。搞懂它们,你就能读懂任何USB PD消息了。

下一章咱们会讲状态机——怎么把这些数据对象串起来,形成一个完整的协商流程。到时候你会发现,有了今天的基础,状态机其实就是“收到什么数据,做什么反应”而已。