PD消息结构:协议沟通的通用语言

做PD Negotiation这么多年,打交道最多的就是这个消息结构了。

PD消息的结构其实不复杂,就三部分:消息头、消息体,还有CRC校验。你想想看,就像寄快递,头是快递单,体是包裹,CRC是封条。

我个人习惯把这套结构背得滚瓜烂熟。为什么?因为你要和Source、Sink来回讨价还价,消息结构就是你们共同的语言。语言不通,谈判肯定崩。

消息头(Header)

消息头占16位(2个字节),是整条消息的"身份证"。它告诉对方:我是谁、我想干嘛、我说了多长的内容。

核心字段一览:
字段名 Bit位 含义 我踩过的坑
Message Type 15:12 消息类型号(0-15) 有一次我写错类型号,Source直接不理我了
Port Role 11:10 当前端口角色 DFP/UFP搞反,消息全被丢弃
Specification Revision 9:8 协议版本(2.0还是3.0) 版本不匹配,协商直接失败
Message Type (Sub) 7:6 子类型(保留扩展) 一般不用的字段,但得知道它存在
Data Object Count 5:4 消息体包含的Data Object数量 数错这个字段,CRC永远对不上
Extended 3 是否为扩展消息 嗯,这里要注意,和消息类型是两码事
Direction 2 发送方向(Initiator/Responder) 方向反了,对方会认为你在胡扯
Reserved/STOP 1:0 保留位 或 停止位 标准留的余量,别乱写

说实话,我最头疼的是Message Type这个字段。协议里定义了十几类消息,比如Source Capabilities、Request、Accept、Reject...每个都有固定编号。你写错了,对方解析出来的就是你根本没发过的消息。

我遇到过最奇葩的bug是:某次我把0x01(Source Capabilities)写成了0x02(Request),结果Sink端收到后以为自己发的请求被同意了,直接开始拉电流,把整个系统搞崩了。你说冤不冤?

消息体(Payload / Data Object)

消息体在协议里叫Data Object,每个Data Object固定32位(4个字节)。

消息头里的Data Object Count就是告诉对方本轮有几个Data Object。最常见的是1个(比如Power Delivery Object),也有2个、3个的情况(比如带电池信息的消息)。

我建议你把这个字段当做一个协议状态机来理解。因为PD消息的个数不是随便定的,协议明确规定了哪种消息必须带几个对象。

小经验: 调试时打开逻辑分析仪,直接看SOF后面的前两个字节。如果消息头里的Count和实际来的Data Object数量对不上,八成是你固件里写死了某个数,没有动态计算。

我记得刚接手一个PD项目时,前任写的代码里消息体长度直接写死0。结果发出去的Source Capabilities消息没有附上任何电压电流信息,对面设备根本看不懂你要提供什么,协商直接卡死。后来查了一天,才发现是这里的问题。

CRC校验

CRC的全称是Cyclic Redundancy Check,本质上是消息的"指纹"。

PD协议里使用的是CRC-32算法,覆盖范围是消息头+消息体。你想想看,一条消息从发送到接收,中间经过线缆、连接器、各种静电打火,难免会有比特翻转。这时候CRC就能帮你判断——这条消息到底有没有坏。

为什么CRC很重要?因为在PD Negotiation里,Step和Step之间的时序卡得很死。如果消息错了,对方会直接回一条Soft Reset或Hard Reset,整个协商就得从头来过。你说气不气人?

避坑指南: 我曾经在某款MCU上遇到过,硬件CRC计算电路对短消息(比如仅1个Data Object)的处理有Bug,算出来的CRC和协议规定的标准值不一样。从那以后,我每次用CRC之前都会先跑几个已知向量测一下——不信任任何硬件模块的第一次实现。

消息类型:Control、Data、Extended

注意了,这条消息类型和消息头里的Message Type是两回事。这个消息类型说的是消息的整体分类,而不是具体的功能编号。

我见过很多新手在这儿犯迷糊。他们以为"消息类型"就指Control、Data、Extended这三种。不对,这三种是"大类",而Header里的Message Type是"小类"。

类型 特征 例子
Control Message 不带Data Object,只有Header GoodCRC、Accept、Reject、Soft Reset
Data Message 带至少1个Data Object Source Capabilities、Request、BIST
Extended Message 消息头里Extended位=1,数据包更长 Extended Source Capabilities、Status

实际项目里Control Message用得最频繁。因为每次发完一个数据消息,对方必须回一个GoodCRC来确认收到。你没收到这个GoodCRC,就得重发。这是协议层面的应答机制。

我做过一个调试——用示波器量CC线上的信号,发现某个从设备从来不发GoodCRC。后来拆开一看,固件里把GoodCRC对应的Message Type写成0x00(保留值)。你说这找谁说理去?

调试技巧: 如果发现协商卡在某一轮不动了,优先检查GoodCRC的回复。这是整个PD通信链路上最低层、最基础的确认。GoodCRC回不来,后面全白搭。

至于Extended Message,它是PD 3.0引入的玩意儿。普通的Data Message最多带几个Data Object,但Extended Message可以带更多数据,适合传送厂商自定义信息或复杂状态。不过,说白了,你要是只用标准PD充电,大部分场景碰不到它。

一张图看懂PD消息结构

下面这张SVG图,是我画给自己团队培训用的。它把消息头、消息体、CRC、以及三种消息类型的关系串在一起。你看完应该能有个全景概念。

PD消息结构全景图 消息头 (Header) 消息体 (Payload) CRC-32 校验 Header 内部字段 Message Type (15:12) Port Role + Spec Revision Data Object Count (5:4) Extended (3) + Direction (2) Reserved / STOP (1:0) 三种消息类型 Control Message(无Payload) Data Message(有Payload) Extended Message(扩展更长) 一个实际消息的样子 Header (16bit) Data Object (32bit × N) CRC-32 (32bit) 通过CC线发送,先发Header 调试口诀: 先看头(Message Type + Count),再看体(几个Data Object),最后算CRC 类型分清Control / Data / Extended,Sequence永远靠GoodCRC保底

你从图里可以看出来,Control Message其实就是只有Header、没有Payload、没有CRC的短消息。但不对——仔细看,实际上Control Message也有CRC,它的CRC只覆盖Header自己。这也是协议的规定。

嗯,这里要记住:Control Message虽然短,但它的完整性同样依赖CRC保护。别因为它简单就不重视。

为什么要理解这三者的关系?

说白了,你的调试逻辑是这样的:

  • 收到一条消息,先在固件里解析Header,拿到Message Type和Data Object Count。
  • 根据Count决定要等几个Data Object。
  • 算CRC,比对。
  • 看Extended位,决定是普通数据消息还是扩展消息。

如果任何一步出错——比如你解析成Data Message但对方发的是Control Message,Object Count对不上,CRC校验不过——协议状态机就会被打断,轻则重传,重则硬复位。

我建议你在写固件时,专门加一个消息解析调试函数,把收进来的每一个字节都打印出来,包括Header各字段的解析结果。调试时开着串口或者逻辑分析仪,逐个字段验证。

总的来说,PD消息结构是谈判的基石。你再会写策略,消息格式不对,连第一轮Source Capabilities都发不出去,后面的电压协商、电流协商全都无从谈起。

我个人经验总结: 花一天时间把这套消息结构吃透,后续调试省一礼拜。