第4章 基本报文结构:前导码、SOP、报文头、数据对象、CRC校验、EOP的格式与长度
把报文结构拆开来看,其实就是一帧数据从开始到结束,每个部分都扮演特定角色。我刚开始接触PD握手协议时,总觉得不就发几个字节嘛,结果被各种不同长度的前导码坑了好几次……后来才明白,报文结构就像人的骨架——骨头长短、位置错了,整个数据就瘫了。
这一章我们把这六个字段逐个揉碎了讲。每个字段的长度、格式、作用,我都会结合项目里踩过的坑来说。嗯,你要是面试被问到“报文结构”,能把这些讲清楚,基本就过关了。
4.1 前导码 (Preamble)
前导码是报文的最前面一段固定序列,长度通常是16字节(128比特)。它的作用很简单——让接收端找到报文的起始位置,并完成同步。
格式:交替的“01b”和“00b”序列,以“00b”结尾。举个例子,16字节的前导码就是:01 00 01 00 01 00 ... 重复8次,最后多一个00。
长度建议:大多数PD控制器默认16字节。如果链路噪声大,可以适当加长到24字节,但会增加开销。我们项目里一般固定16字节,不改。
4.2 SOP (Start of Packet)
SOP是“数据包起始定界符”,告诉接收端:前导码结束了,真正有用的报文头从这里开始。
格式:由K码与D码交替组成,总长度为5字节。以USB PD为例,SOP序列是:K-code, K-code, D-code, D-code, K-code。K码用于特殊控制,D码是普通数据。
注意:SOP之后不能再有前导码的电平翻转。为什么?因为SOP是用非归零(NRZ)编码的,跟前导码的曼彻斯特编码不一样。接收端通过检测K码的特定模式来判断是否进入SOP阶段。
4.3 报文头 (Header)
报文头紧跟在SOP之后,长度固定为16比特(2字节)。它包含本次传输的关键控制信息。
结构:报文头由以下几个字段组成(参考USB PD 3.0规范):
| 字段 | 比特位置 | 长度(bit) | 说明 |
|---|---|---|---|
| Message Type | 15:12 | 4 | 消息类型,如GoodCRC、Request等 |
| Port Data Role | 11 | 1 | 端口数据角色:Source |
| Specification Revision | 10:9 | 2 | PD协议版本号(2.0/3.0) |
| Port Power Role | 8 | 1 | 电源角色:Provider/Consumer |
| Message ID | 7:4 | 4 | 消息编号,用于去重和重传 |
| Number of Data Objects | 3:0 | 4 | 后面跟随的数据对象个数(0~7) |
报文头是整个报文的“大脑”——它告诉接收方这是什么类型的数据(电源请求?握手确认?还是错误报告?),版本是多少,角色是什么。我每次调协议栈,都会先打印报文头,确认字段没配错。
4.4 数据对象 (Data Object)
数据对象是报文的“信息载体”,长度可变,每个数据对象固定为4字节(32比特)。报文头中的Number of Data Objects决定后面跟几个数据对象(0~7个),所以报文总长 = 报文头(2B) + 数据对象个数 × 4B。
格式:每个数据对象内部又分不同子结构,取决于消息类型。比如电源能力(Source Capabilities)的数据对象包含电压、电流、峰值电流等信息。具体子字段我们后面章节再拆,现在你记住:数据对象就是一段4字节对齐的二进制信息。
常见场景:Request消息有1个数据对象(请求的电压/电流),电源能力消息有多个数据对象(最多7组电压电流组合)。
4.5 CRC校验
CRC(循环冗余校验)用来保证从SOP到最后数据对象的完整性。长度固定为4字节,使用CRC-32算法:0x04C11DB7(多项式)。
计算范围:从SOP的第一个字节开始,包含报文头和所有数据对象,不包括前导码。接收端收到后重新算一遍CRC,如果与发送过来的CRC不同,就说明传输有错误,直接丢弃整个包。
实现细节:实际计算时,SOP的K码需要先转成对应的D码值再参与计算。嗯,这里有坑——我处理过一版固件,它没做码字转换,CRC永远算不对。
4.6 EOP (End of Packet)
EOP是报文的结束标记,长度固为2字节,格式为固定的K码对:K1, K2。它的作用是告诉接收端:数据已经发完,后面都是空闲状态了。
EOP之后,发送端总线转为空闲电平(通常为高阻或0V),接收端就知道这个帧到此为止。如果接收端检测到EOP,就会开始处理这个报文;如果没检测到,就会一直等,等到超时,然后丢弃当前缓存。
我遇到过一个问题:EOP后总线空闲电平没保持住,被噪声干扰了一下,接收端误认为是下一个SOP,结果报文错位。解决办法是在EOP后增加一段总线保持时间(例如10µs),让电平稳定再释放。
报文结构总览
下面这张图把整个报文结构串起来,你可以对着看每个字段的顺序和大致长度。
各字段长度与格式汇总
| 字段 | 长度(字节) | 编码方式 | 是否参与CRC |
|---|---|---|---|
| 前导码 (Preamble) | 16 (可调) | 曼彻斯特 | 否 |
| SOP | 5 | K/D码 (NRZ) | 是 |
| 报文头 (Header) | 2 | 二进制小端 | 是 |
| 数据对象 (Data Object) | N×4 (N=0~7) | 二进制小端 | 是 |
| CRC | 4 | CRC-32 | 自身不参与 |
| EOP | 2 | K码对 | 否 |
你看这张表,CRC参与范围是SOP、报文头、数据对象——这三个字段加起来,就是接收端需要“验证完整性”的部分。前导码和EOP不参与CRC,因为它们只是同步和结束的标志,不携带有效信息。
嗯,最后我提醒一句:实际解析报文时,**一定**要从SOP开始算CRC范围,而且前导码可能会因为噪声干扰而出现几比特的毛刺,导致接收设备误判起始位置。我调试时习惯在捕获到的完整数据帧之前,先丢掉前几个疑似垃圾的比特,再重新对齐前导码。这个习惯帮我省了不少事儿。