USB PD协议基础:物理层、协议层、策略层概述

做PD开发这些年,我经常被问到同一个问题:PD协议到底分几层?说白了,就是这句话。

我们搞硬件的人都知道,一套完整的协议栈不可能平铺直叙。USB PD也是一样。它分成三个层次:

  • 物理层:负责「怎么传」——电压、电流、时序、线缆状态
  • 协议层:负责「传什么」——消息格式、握手、重传、超时
  • 策略层:负责「什么时候传」——协商流程、角色切换、电源行为

这三层叠在一起,才构成了一次完整的PD协商。你想想看,没有物理层,消息在空中乱飞;没有协议层,双方听不懂对方说什么;没有策略层,就算能听懂也不知道该做什么。

嗯,我们一层层来看。

USB PD 三层参考模型 策略层(Policy Layer) 电源协商流程 (Source/Capability/Request Accept) 角色切换 (PR Swap / DR Swap / VCONN Swap) 协议层(Protocol Layer) 消息格式 (Header + Data Object) 握手机制 (GoodCRC / Retry) 超时与差错控制 (Protocol Timer / Error Recovery) 物理层(Physical Layer) CC线的电平与时序 BMC编码 / 4b5b 线缆检测(Rp / Rd)

物理层:信号的钢筋水泥

物理层是最底下那一层。它不是跑USB 2.0那对D+/D-,而是单独走一根CC线(Configuration Channel)。

CC线负责两件事:

  1. 检测连接:判断有没有插线、线缆的方向、是不是电子标记线缆
  2. 传输数据:用BMC编码(Bi-Phase Mark Coding)把消息从Source传到Sink,或者反过来

我刚开始调试PD时,踩了一个坑——CC线上的电阻配置。

简单讲:Source端接Rp上拉电阻,Sink端接Rd下拉电阻。通过检测CC线上的电压,Source就能知道对面是Sink、还是另一个Source、或者根本没有连接。

做个表,把常见的配置列出来:

CC线上的电压 代表的意义 常见场景
0 ~ 0.2V没有连接(未插入)空闲状态
0.2 ~ 0.66V连接了Sink(Rd)标准充电 / 数据
0.66 ~ 1.23V连接了另一个SourceOTG / 双角色端口
1.23 ~ 1.6V连接了电子标记线缆高速传输 / 大功率

我的个人习惯:每次画原理图时,我先确认Rp的值——5W充电用10kΩ,大功率用4.7kΩ或更小。搞错了这个,CC线上的电压就不在协议要求的范围内,协商根本不会开始。

物理层还规定了波特率——300kbps、400kbps、还有1Mbps的快速角色交换模式。这些数字看着简单,但实际调试时示波器一抓,时序稍有偏差,通信就断了。

避坑:我曾经在RC滤波上偷懒,CC线上串了一个大电容,结果每次协商到一半就超时。研究了两天才发现是电容充放电拖慢了BMC信号的边沿。后来我改用RC时间常数小于1μs的配置,问题就解决了。

协议层:消息是怎样炼成的

协议层,说白了就是约定一个标准格式,让双方能互相理解。

PD 3.0的消息格式长这样:

  • 前导码(Preamble):4个字节,用于同步时钟
  • SOP(Start of Packet):1个字节,标识消息从哪里开始
  • 消息头(Message Header):2个字节,包含消息类型、数据大小、方向
  • 数据对象(Data Object):0~7个字节,真正的内容
  • CRC:4个字节,校验有没有传错
  • EOP(End of Packet):1个字节,结束符

你想想看,物理层只负责把这一串0和1发出去。但协议层必须保证:发出去的内容,接收方能正确解读。

PD协议里有一个重要的概念叫GoodCRC。每次接收到一个完整的消息,接收方都必须回一个GoodCRC确认。如果在协议计时器超时前没收到GoodCRC,发送方就会重传。最多重传3次。

我在项目中遇到过一个问题:一次PD协商,消息经常丢。找来找去,发现是Source端的IR Drop太大,导致电源抖动,影响了CRC的计算。加了几颗去耦电容就稳定了。

协议层的关键消息类型大致分为这几类:

  1. 控制消息:比如GoodCRC、Accept、Reject、Wait,这些没有数据负载
  2. 数据消息:比如Source_Capabilities、Request、BIST,带有PDO(Power Data Object)
  3. 扩展消息:PD 3.0新增的,用于电池信息、制造商信息等

嗯,这里要注意:控制消息和数据消息的编码不一样。控制消息的Header里有一个专门的域来标识类型,而数据消息后面还会跟着若干个Data Object。

举个例子,Source_Capabilities这个数据消息,它的PDO里会包含电压、电流、电源角色等信息。我们做嵌入式开发的,经常要解析这些PDO。我习惯用工具先把PDO打印出来看,再决定怎么配置协议层。

// 一个典型的Source Capability PDO(固定电压5V,3A)
struct pdo_fixed {
    uint32_t voltage_10mv  : 10;  // 5000mV
    uint32_t current_10ma  : 10;  // 3000mA
    uint32_t reserved      : 5;
    uint32_t peak_current  : 2;   // 0
    uint32_t role_swap     : 1;   // 1 支持角色交换
    uint32_t usb_comms     : 1;   // 1 支持USB通信
    uint32_t dual_role     : 1;   // 1 DRP
    uint32_t fixed_supply  : 2;   // 0b00
};

一句话总结协议层:物理层负责把比特流送出去,协议层负责保证送出去的消息能被正确理解和确认。没有GoodCRC,一次握手都完不成。

策略层:决策的大脑

策略层是三层里让很多人头疼的一层。其实不用怕——它就是一个状态机。

物理层和协议层只是工具,真正决定「什么时候发送什么消息」的是策略层。

我举一个典型的PD协商流程:

  1. 物理层检测到连接:CC线电压变化,策略层知道对面插进来了
  2. 协议层发送Source_Capabilities:策略层告诉协议层「现在发你的能力包」
  3. Sink回复Request:Sink的策略层根据能力包,决定要多少电压、多少电流
  4. Source回答Accept:如果Source接受,就切换电源输出
  5. 双方进入协商完成状态:电源稳定输出

听着挺顺吧?但实际里面有陷阱。比如Sink发Request时,策略层要检查Request里的电压是否在Source_Capabilities里。如果不在,策略层应该拒绝,而不是强行协商。

我曾经遇到一个案例:某款手机在OTG模式下,Source_Capabilities发送了5V/9V两档,但Sink策略层写死了硬编码,非要请求12V。结果协商失败,两边都进入ErrorRecovery状态,过了好几秒才重新尝试。嗯,这就是策略层写错了。

策略层里还需要管理电源角色。USB PD定义了三种电源角色:

角色 行为 OTG中的典型表现
Source提供VBUS电源充电宝、充电器
Sink消耗VBUS电源手机、平板
DRP动态切换手机OTG,即能做Source也能做Sink

对于DRP来说,策略层要按时间分片切换Source/Sink状态。我建议把这个时间窗口设置为50ms~100ms,这样既能快速响应连接,又不会太费电。

避坑:我曾经在一个OTG项目中,DRP切换太快——30ms一次,结果CC线上的电容还没放完电,状态机就跳到下一帧,导致物理层检测永远不稳定。我后来改成80ms切换一次,问题就消失了。所以,设计策略层的时候,一定要留出给物理层沉降的时间。

策略层还有一个跟OTG关系很大的能力:角色交换(PR_Swap)。两个DRP连接后,如果手机想变成Source给外设供电,或者想变成Sink让充电宝供电,就可以发起PR_Swap。这个过程需要策略层管理一个完整的握手——Source发PR_Swap消息,Sink回Accept或者Reject。

我个人一直认为,策略层是PD协议的精髓。物理层和协议层都是死的(标准明确、容易实现),但策略层可以做很多定制——比如你的产品要支持5V/3A、9V/2A、15V/1.8A,策略层要按优先级选择最优组合,而不是简单点一个。

一句话总结策略层:策略层就是一个决策调度器。它告诉物理层什么时候检测、告诉协议层什么时候发什么消息。理解了策略层,整个PD协商的骨架就清楚了一半。

嗯,这三层一层叠一层,你中有我,我中有你。物理层出问题,协议层就会看到CRC错误;协议层丢了消息,策略层就会超时重试。调试的时候要一层层查,不能跳过。