USB PD 消息类型:控制消息、数据消息、扩展消息

PD 协议的核心就是靠消息对话。消息分三类,每一类的用法和结构都不一样。我刚接触 PD 时,觉得这些分类有点多余,后来才发现——分不清类型,抓包分析都无从下手。

控制消息(Control Messages)

控制消息是最短的一类。它们没有数据体,只有消息头。说白了,就是协议里的“感叹词”——Yes、No、准备好了、重试、CRC 校验通过……

控制消息的长度固定为 0 字节数据,消息头的 Type 字段标识具体含义。常用的控制消息有这些:

  • GoodCRC:接收方确认收到上一条消息,CRC 无误。我调试时最先看的就是它——链路有没有通,GoodCRC 说了算。
  • Accept:响应方同意对方的 Request。
  • Reject:响应方拒方的 Request,常见于电压/电流请求超出能力。
  • PS_RDY:电源已经切换到目标电压,Source 告诉 Sink 可以开始用新电压了。
  • Get_Source_Capabilities:Sink 主动请求 Source 的能力清单。
  • Soft_Reset / Hard_Reset:复位链路,重新协商。

避坑提醒: 我曾经以为 GoodCRC 和 ACK 是一回事,其实它们分别在协议层和事务层。如果只盯着应用层的 ACK,忽略 GoodCRC 是否及时回复,那在噪声环境下很容易丢消息。我建议你在抓包时先看 GoodCRC 的往返延迟,超过 200μs 就该查 PCB 走线了。

数据消息(Data Messages)

数据消息携带了实际的数据对象(SOP 或 SOP' 后的 Data Object / VDM Header / …)。它们才是真正的“谈判筹码”——能力清单、请求、供应商自定义指令都靠它。

数据消息的长度不固定,最长为 26B(含消息头)。消息头里除了 Type,还有一个 Number of Data Objects 字段,告诉接收方后面跟了几个数据对象。

常见的数据消息:

  • Source_Capabilities:Source 广播自己能提供的 PDO(最多 7 个电源数据对象)。我习惯先看它的 PDO 顺序——通常第一个是 5V/3A 默认。
  • Request:Sink 从 Source_Capabilities 里选一个 PDO,提出具体电压电流请求。
  • BIST(Built-In Self Test):用于生产测试,消息类型编号 8。
  • VCONN 相关的 VDM:厂商自定义消息,也归入数据消息大类。

个人经验: 有一次客户设备总是在收到 Source_Capabilities 后回复 Request,但 Source 一直不进入 Ready 状态。我一看抓包,好家伙——Request 里填的 PDO 序号是 0,而 Source 的 PDO 序号从 1 开始。这就是数据消息结构理解不到位踩的坑。

下面是一个简化的数据消息头(SOP Packet)结构示例:

// USB PD 数据消息头(16 bits)
struct pd_msg_header {
    unsigned ext      : 1;   // 0=数据消息, 1=扩展消息
    unsigned num_objs : 3;   // 数据对象个数(0~7)
    unsigned message_type : 4; // 消息类型编码
    unsigned rev      : 2;   // 版本
    unsigned port     : 1;   // 端口号
    unsigned sid      : 5;   // 发送方 ID
};

解析时一定要先读 ext 位,如果为 1,那就不属于数据消息,而是下一节要讲的扩展消息。

扩展消息(Extended Messages)

PD 3.0 引入了扩展消息,本质上是把数据消息的字节数上限从 26B 扩到了 260B。它们依然通过 SOP* 帧传递,但消息头的 ext 位置为 1,并且后面会跟一个扩展头(Extended Header)来描述数据块块号、总长度等信息。

扩展消息的出现主要是为了:

  • 传输更大的安全证书、认证数据
  • 支持 OCPP(电源路径控制协议)等复杂功能
  • 厂商自定义扩展(OEM 扩展消息)

举个实际例子——Extended Source_Capabilities:它可以携带比普通 Source_Capabilities 更多的电压电流组合(PDO 扩展),以及非电气参数(比如电池充电曲线)。

曾经的教训: 我原来以为扩展消息和普通数据消息可以混着发,结果设备解析老是失败。后来查规范才明白——一段事务流里,控制消息和数据消息必须严格交替,而扩展消息必须由源端发起,且接收方回复 GoodCRC 后才能继续。所以设计协议栈时一定要为扩展消息单独维护状态机。

三类消息对比

特性 控制消息 数据消息 扩展消息
消息头 ext 位 0 0 1
有效数据长度(不含头) 0 字节 1~26 字节 1~256 字节
数据对象个数 0 1~7 1~7(扩展头另计)
典型用途 握手、确认、复位 协商能力、请求、VDM 厂商扩展、安全认证、大容量传递
编码示例 GoodCRC (0x01) Source_Capabilities (0x01) Extended Source_Cap (0x21)

核心记忆点: 抓包时先看 Message Header 的 Bit[15](ext 位)。
0 → 再看 Bit[12:10](num_objs),如果 num_objs==0,那就是控制消息;否则是数据消息。
1 → 扩展消息,需要额外解析 Extended Header。

我个人习惯在协议栈代码里把消息入口写成三个分支:

switch (msg_header.ext) {
    case 0:
        if (msg_header.num_objs == 0) handle_control_msg();
        else handle_data_msg();
        break;
    case 1:
        handle_extended_msg();
        break;
}

注意: 扩展消息的 Extended Header 中有 Data Block Number 和 Total Number of Blocks 字段,如果你的设备不支持分块接收,务必在 SOP 层的 Extended Capabilities 里把 Blocked Extended 位设为 0,否则对方可能会发分块消息导致解析失败。

USB PD 消息类型分类 USB PD 消息类型 控制消息 数据消息 扩展消息 GoodCRC Accept / Reject PS_RDY Source_Capabilities Request BIST Extended Source_Cap Security Request OEM 自定义 提示:消息头 Bit15(ext)决定大类,Bit12~10(num_objs)决定是控制消息还是数据消息。 扩展消息还需解析 Extended Header 中的 Data Block Number 和 Total Blocks。