第2章:PD协商流程与关键状态归纳总结

PD协商,说白了就是Source和Sink之间的“讨价还价”。

Source说:“我能提供5V、9V、15V、20V,每种电流多少。”

Sink回:“给我9V 3A,谢谢。”

Source确认:“OK,Ready。”然后电压电流才真正切换。

这中间任何一个环节出错,设备要么不充电,要么乱跳电压。我调过不少次这种bug,所以这一章我把整个流程拆开,把关键状态拎出来,咱们一条一条过。

2.1 完整协商流程图

先上一张图,把整件事串起来。你盯着它看三分钟,再往下读心里就有底了。

PD协商核心流程 设备插入 / VBUS 稳定 Source 发送 Capabilities Sink 发送 Request Source 回复 Accept Source 发送 PS_RDY 注意 SourceCapabilities 最多7个PDO。 Request 需包含 Object Position。 PS_RDY 后才正式切电压。 常见失败点 Accpet 超时 → 重试或 HardReset。 Capabilities不符合 PD 2.0/3.0。 Sink 请求了不支持的 PDO。

2.2 每一步在做什么

图中的5个节点,就是一次成功协商必须走过的台阶。少一个都不行。

  • 设备连接:VBUS拉高,Source检测到Sink的存在。这是物理层的事,但软件里也要确认CC线状态。
  • Source Capabilities:Source通过CC线广播自己支持的所有电压/电流组合(PDO)。消息类型是Source_Capabilities。
  • Sink Request:Sink从收到的PDO里选一个,发Request消息。里面关键字段是Object Position,指向选中的第几个PDO。
  • Source Accept:Source检查请求合法后,回复Accept。如果Source不认可(比如电流超出),它会回Reject,协商直接崩。
  • PS_RDY:Source完成所有内部电压/电流切换后,发PS_RDY。只有收到这个消息,Sink才敢大功率拉电流。

2.3 关键状态机介绍

PD协议里,两边的状态机名字又长又绕。我习惯只看几个核心状态,剩下的遇到再查。

常用状态对照表(Source侧):

状态缩写全称做什么
PE_SRC_CapabilitiesSource Capabilities 发送等待发完Capabilities后等待Sink回复
PE_SRC_Negotiate协商中收到Request后处理,决定Accept/Reject
PE_SRC_Transition电压切换中收到Accept后调压,完成后发PS_RDY
PE_SRC_Ready就绪稳定供电,持续发送PS_RDY或保持

我的经验:Sink侧的状态更简单。但要注意PE_SNK_EvaluateCapabilities这个状态——很多固件在这里选了不存在的PDO。我翻过一个项目,Sink的Request写死了Object Position = 3,可Source只发了2个PDO,结果一直收不到Accept,整机不充电。

2.4 避坑指南:常见错误

讲了这么多理论,说几个我实际踩过的坑,你写固件或者排查时直接对应。

  1. Capabilities 消息格式不对:PD 3.0要求SourceCapabilities的MessageHeader里Number of Data Objects必须和实际PDO数量一致。我见过有人填错了,Sink解析后直接崩溃。
  2. Request 里的 Checksum 算错:Request消息的CRC32计算方式与普通数据包一致,但很多新手把Object Position等字段的字节顺序搞反了。
  3. SOP 序列用错:PD协商全部走SOP(即Source→Sink方向),不要用SOP'或SOP''。这些是给VCONN或Cable用的。有一次我在调一个多口Hub,把SOP'发给了Source,Source完全不理。
  4. PS_RDY 超时:Source切换电压需要时间(通常在10ms内)。如果Sink在发送Request后等了超过500ms还没收到PS_RDY,协议规定要发Hard Reset。我建议你在Sink固件里加上超时计数器,省得死等。

2.5 用逻辑分析仪抓一次完整协商

光看书不实战,等于白看。我整理了一份抓包后的数据,你跟着走一遍。

假设Source发了一条Capabilities,包含两个PDO:

# 原始二进制(4字节Header + 8字节 PDO1 + 8字节 PDO2)
Header: 0x2C 0xF1 0x00 0x02  (MessageID=2, #DataObj=2)
PDO1:   0x19 0x2C 0x05 0x00 0x00 0x00 0x00 0x00  (5V @ 3A Fixed)
PDO2:   0x19 0x2C 0x09 0x00 0x00 0x00 0x00 0x00  (9V @ 3A Fixed)
CRC32:  0xA3 0x4B 0x7C 0xFE

你看,PDO1的第一个字节0x19表示Fixed Supply,第2、3字节0x2C 0x05表示电压是500mV * 0x2C(44) = 22V?不对!注意电压编码:实际是(Voltage & 0x03FF) * 50mV。0x2C05的低10位是0x05(5V),高位是控制位。这里容易算错,建议参考PD3.0标准第6.4.1节。

Sink回Request时,Object Position填0x01(选第一个PDO)。Source收到后如果发现电流不符合,会返回Reject。最好的做法是Sink遍历所有PDO,挑最能满足自己功率需求的,而不是写死。

2.6 小结

PD协商,说白了就是5个步骤、4个关键状态。大部分问题出在消息格式上(CRC、Checksum、PDO数量),其次是时序(超时)。

我自己习惯先把逻辑分析仪抓到的数据用PD Analyzer解码,再看是哪一步卡住了。遇到僵局,就先复位CC线,重新来一轮。