4、PD协议层详解:SOP类型、GoodCRC机制、消息重传与超时处理、协议层状态机

各位工程师朋友,咱们今天聊聊PD协议层。说实话,很多做电源的同行,硬件功底都不错,但一碰到协议层就头疼。我当年也是这样,总觉得协议是软件的事。直到有一次,我调试一个65W快充,死活拉不起电压,最后发现是SOP类型搞错了。嗯,从那以后,我再也不敢小看协议层了。

4.1 SOP(Start of Packet)类型

SOP,全称是Start of Packet。说白了,就是数据包的“头”。它告诉接收方:嘿,注意了,我要开始发消息了!

PD协议里,SOP分好几种。我习惯把它们分成三类:

  • SOP:标准的起始包,用于Source和Sink之间的通信。这是最常用的。
  • SOP':带一个撇的,用于Source和Cable之间的通信。比如你要读线缆的E-Marker信息,就得用这个。
  • SOP'':带两个撇的,用于Source和Cable内部更深层的通信。说实话,这个我平时用得不多。

关键点:不同的SOP类型,对应不同的通信对象。用错了,对方根本不搭理你。

你可能会问:“为什么要分这么细?” 原因很简单——避免冲突。你想啊,如果Source发一条消息,Sink和Cable都收到了,那到底谁该回应?所以PD协议干脆把通信通道分开了。

我在项目中遇到过一件事:有个同事用SOP去读E-Marker信息,结果读回来的全是0。他折腾了两天,最后发现是SOP类型选错了。换成SOP',一次就成功了。

4.2 GoodCRC机制

GoodCRC,全称是Good Cyclic Redundancy Check。听起来很唬人,其实就是一个“确认收到”的信号。

PD协议里,每发一条消息,接收方都必须回一个GoodCRC。如果发送方没收到GoodCRC,它就认为消息丢了,会重发。

这个机制的好处很明显——可靠。坏处也很明显——增加了通信开销。你想想看,每发一条消息,都要等一个回执,速度自然就慢了。

我的经验:调试时,如果发现通信卡住了,先检查GoodCRC有没有回。很多时候,问题就出在这里。

GoodCRC的格式是这样的:

// GoodCRC消息结构
Bit 0-1: 消息类型 (00表示GoodCRC)
Bit 2-3: 保留位
Bit 4-7: 消息ID (与原始消息的ID一致)
Bit 8-15: CRC校验值

注意看,GoodCRC里带了一个消息ID。这个ID必须和原始消息的ID一致。为什么?因为发送方可能同时发了多条消息,它得知道这个GoodCRC是对应哪一条的。

我曾经犯过一个低级错误:在写固件时,忘了更新GoodCRC里的消息ID。结果发送方收到GoodCRC后,发现ID对不上,以为消息丢了,就一直重发。整个系统就这么卡死了。嗯,这个坑我替你们踩过了。

4.3 消息重传与超时处理

PD协议里,消息重传和超时处理是配套的。简单说就是:发一条消息,等一段时间,没收到回应就重发。

这个“一段时间”是多少?协议里定义了几个关键的超时时间:

超时名称 时间值 说明
SenderResponseTimer 24-30ms 发送方等待GoodCRC的最大时间
HardResetTimer 4.5-5.5ms 硬复位信号的持续时间
NoResponseTimer 4.5-5.5ms 检测对方是否无响应

我个人习惯把SenderResponseTimer记成“30毫秒规则”。为什么是30毫秒?因为PD通信的速率是300kbps,一条消息大概需要几十微秒。30毫秒足够对方处理并回复了。如果超过30毫秒还没收到GoodCRC,那基本可以断定消息丢了。

重传次数也有讲究。协议规定,最多重传3次。3次都失败怎么办?那就进入Hard Reset流程。说白了,就是重启通信。

注意:重传次数不是越多越好。我见过有人把重传次数设成10次,结果通信卡死的时间更长,用户体验极差。3次是协议规定的,也是经验值,别乱改。

超时处理还有一个细节:超时计时器必须在发送完消息后立即启动。有些工程师习惯在发送前启动计时器,这会导致计时不准。为什么?因为发送本身也需要时间,如果提前启动,计时器可能提前到期,导致误判。

4.4 协议层状态机

PD协议层状态机,说白了就是一套“规则”。它规定了:在什么状态下,你能做什么事;收到什么消息后,你要切换到什么状态。

状态机主要分两大类:

  • Source端状态机:负责管理电源输出
  • Sink端状态机:负责请求电源输入

我重点讲一下Source端状态机,因为它更复杂一些。Source端状态机包括以下几个关键状态:

  1. PE_SRC_Startup:上电初始化状态。这时候Source在检测CC线,判断有没有设备接入。
  2. PE_SRC_Send_Capabilities:发送能力包。Source告诉Sink:“我能输出5V/3A、9V/3A、15V/3A、20V/5A,你选一个。”
  3. PE_SRC_Negotiate_Capability:协商状态。Source收到Sink的请求后,确认是否同意。
  4. PE_SRC_Transition_Supply:切换电压状态。Source把输出电压从当前值切换到目标值。
  5. PE_SRC_Ready:就绪状态。电压稳定了,可以正常供电了。

你可能会问:“状态机切换有没有什么坑?” 有,而且不少。我举一个例子:

在PE_SRC_Transition_Supply状态,Source需要先关闭输出,再开启新电压。这个过程中,如果Sink突然发来一条消息,Source该怎么处理?协议规定:在电压切换期间,Source不响应任何消息。Sink如果发了消息,会超时重传,直到Source切换完成。

避坑指南:我曾经在切换电压时,忘了屏蔽消息接收。结果Sink发来一条请求,我这边电压还没稳定,就回了GoodCRC。Sink以为电压已经稳定了,就开始大电流拉载,结果电压瞬间掉下去了。从那以后,我每次做电压切换,都会先关中断,等电压稳定了再开。

状态机还有一个容易忽略的点:状态超时。每个状态都有一个最大停留时间。比如PE_SRC_Send_Capabilities状态,如果发送完能力包后,30毫秒内没收到Sink的回应,Source就要进入Hard Reset。为什么?因为Sink可能已经掉线了,Source不能一直等下去。

好了,PD协议层的内容就讲到这里。下一章,咱们聊聊物理层的那些事。记住,协议层是骨架,物理层是血肉,两者缺一不可。