2. USB协议核心:包结构、传输类型与帧/微帧

USB协议说复杂也复杂,说简单也简单。你想想看,一根线上要传数据、地址、校验信息,还得让设备知道自己该干什么——这全靠包结构来组织。我个人习惯把USB协议拆成三个层次来理解:包(Packet)、事务(Transaction)、传输(Transfer)。这一章我们先啃最底层的包结构,再把四种传输类型和帧概念讲透。

2.1 包结构:USB通信的最小单元

USB总线上的所有通信,本质上就是主机和设备之间来回扔包。每个包由五个字段组成,但不是每个包都包含全部字段。我画了一张结构图,你先有个整体印象:

USB包结构(Packet Format) SOP 同步域 PID 包标识符 ADDR 设备地址 ENDP 端点号 CRC 校验码 EOP 结束符 字段说明: • SOP(Start of Packet):同步序列,告诉接收方“包要来了” • PID(Packet Identifier):标识包的类型(令牌、数据、握手、特殊) • ADDR(Device Address):目标设备地址,7位,最多127个设备 • ENDP(Endpoint):端点号,4位,每个设备最多16个端点 • CRC(Cyclic Redundancy Check):循环冗余校验,检测传输错误 • EOP(End of Packet):包结束信号,由SE0状态标识

2.2 逐字段拆解

2.2.1 SOP与EOP:包的边界

SOP其实就是一串特定的同步序列(Sync Pattern),让接收方的时钟与发送方对齐。USB 2.0高速模式下,SOP是32位的同步码。EOP则用SE0(Single-Ended Zero)状态来表示包结束。嗯,这里要注意:EOP之后总线会进入空闲状态(Idle),等待下一个包。

2.2.2 PID:包的身份标签

PID是包类型标识符,一共8位——高4位是类型码,低4位是取反校验。为什么这么设计?说白了就是为了抗干扰。接收方收到PID后,会检查高4位和低4位是否互为取反,不一致就直接丢弃。

PID分为四大类,我列个表方便你对照:

PID类型 PID值(二进制) 名称 用途
令牌(Token) 0001 OUT 主机→设备,通知设备准备接收数据
令牌(Token) 1001 IN 主机→设备,请求设备发送数据
令牌(Token) 0101 SOF 主机→设备,帧起始标记
令牌(Token) 1101 SETUP 主机→设备,控制传输的起始
数据(Data) 0011 DATA0 数据包,偶数据切换位
数据(Data) 1011 DATA1 数据包,奇数据切换位
数据(Data) 0111 DATA2 高速同步/中断专用
数据(Data) 1111 MDATA 高速同步/中断专用
握手(Handshake) 0010 ACK 接收方确认,数据正确接收
握手(Handshake) 1010 NAK 设备忙,暂时无法接收/发送
握手(Handshake) 0110 STALL 端点挂起,出现错误
握手(Handshake) 1110 NYET 高速模式下,设备未准备好
特殊(Special) 1100 PRE 低速设备前同步
特殊(Special) 1100 ERR 高速模式错误报告
特殊(Special) 1000 SPLIT 高速HUB拆分事务
特殊(Special) 0100 PING 高速模式探测端点状态
我的经验:调试USB通信时,逻辑分析仪抓到的第一个字段就是PID。我习惯先看PID是不是预期的类型——比如主机发了IN令牌,设备却回了NAK,那八成是设备还没准备好数据。有一次我调一个U盘固件,发现设备老是回NAK,查了半天才发现是端点FIFO没清空。嗯,这种坑踩过一次就记住了。

2.2.3 ADDR与ENDP:谁跟谁说话

ADDR是设备地址,7位宽,范围0~127。地址0是默认地址,设备刚上电时用这个地址跟主机通信,枚举完成后主机才会分配一个新地址。ENDP是端点号,4位宽,每个设备最多16个端点(0~15)。端点0是控制端点,所有USB设备都必须有。

你想想看,主机怎么找到某个设备上的某个端点?其实就是靠ADDR+ENDP的组合。比如ADDR=3, ENDP=1,就表示“3号设备的1号端点”。

2.2.4 CRC:数据完整性保障

CRC分两种:令牌包用5位CRC,数据包用16位CRC。5位CRC的生成多项式是x⁵+x²+1,16位CRC的生成多项式是x¹⁶+x¹⁵+x²+1。接收方计算CRC,如果跟发送方的不一致,就说明传输过程中出了错。

避坑指南:我曾经在一个项目中,设备偶尔会丢包,查了很久才发现是CRC校验逻辑写错了——我把数据包的CRC当成了令牌包的5位CRC来算。结果就是数据明明是对的,但CRC校验总失败,设备一直回NAK。所以写固件时,一定要分清当前包的类型,用对CRC算法。

2.3 四种传输类型

USB协议定义了四种传输类型,每种类型对应不同的应用场景。我习惯用一个表格来对比它们:

传输类型 特点 带宽保证 错误重试 典型应用
控制传输 双向、可靠、有握手 10%保留带宽 支持 设备枚举、配置、命令
批量传输 可靠、无带宽保证 无 支持 U盘、打印机、扫描仪
中断传输 可靠、有轮询周期 有(按轮询间隔) 支持 鼠标、键盘、游戏手柄
同步传输 实时、无重试 有(按分配带宽) 不支持 音频、视频、摄像头

2.3.1 控制传输

控制传输是USB的“官方语言”,所有设备都必须支持。它分三个阶段:建立阶段(SETUP事务)、数据阶段(可选,DATA0/DATA1交替)、状态阶段(握手)。控制传输的默认端点号是0,最大包长在低速模式下是8字节,全速是8/16/32/64字节,高速是64字节。

我个人习惯把控制传输看作“命令通道”——主机通过它来配置设备、获取描述符、设置地址。枚举过程就是靠控制传输完成的。

2.3.2 批量传输

批量传输的特点是“尽力而为”——有带宽就用,没带宽就等着。但它保证数据可靠,因为出错会重试。批量传输适合大数据量、对实时性要求不高的场景,比如U盘读写。

批量传输的最大包长:全速是8/16/32/64字节,高速是512字节,超速(USB 3.0)是1024字节。嗯,这里要注意:批量传输没有固定的带宽,如果总线上有其他实时传输(比如同步传输),批量传输就得让路。

2.3.3 中断传输

中断传输的名字容易让人误解——它并不是真的“中断”,而是主机定期轮询设备。轮询间隔由设备在描述符中指定,范围是1~255毫秒(全速/低速)或1~65535微帧(高速)。

中断传输保证在轮询间隔内至少有一次传输机会,出错会重试。鼠标、键盘这类人机交互设备就用中断传输——你想想看,鼠标移动的数据如果丢了,光标就会卡顿,用户肯定受不了。

关键点:中断传输的“中断”是指主机主动查询,不是设备主动上报。设备不能主动发数据给主机,只能等主机来问。这个设计是为了简化总线仲裁——所有通信都由主机发起。

2.3.4 同步传输

同步传输是唯一不保证可靠性的传输类型——出错不重试。为什么?因为音频、视频这类数据对实时性要求极高,重试会导致延迟和卡顿。宁可丢一帧,也不能等重试。

同步传输的最大包长:全速是1023字节,高速是1024字节。它需要预先分配带宽,主机在每帧/微帧中为同步传输预留固定时间片。

2.4 帧与微帧

USB总线的时间被划分成一个个“帧”(Frame)。全速/低速模式下,每帧1毫秒。高速模式下,每帧又分成8个微帧(Microframe),每个微帧125微秒。

主机每帧开始时发送一个SOF(Start of Frame)包,里面包含帧号(11位,0~2047循环)。设备可以用SOF来同步自己的时钟,或者计算时间间隔。

帧与微帧结构(Frame & Microframe) 1 帧 = 1 ms(全速/低速) SOF 事务1 事务2 事务3 事务4 事务5 空闲 1 帧 = 8 微帧(高速模式),每个微帧 = 125 µs µ帧0 µ帧1 µ帧2 µ帧3 µ帧4 µ帧5 µ帧6 µ帧7 每个微帧内可以包含多个事务(令牌+数据+握手),主机根据调度算法分配带宽。 同步传输和中断传输在微帧中预留固定时间片,批量传输使用剩余带宽。

帧/微帧的概念对固件开发有什么实际意义?我举个例子:你在写一个USB音频设备的固件,音频数据必须每微帧发送一次,否则就会出现爆音。这时候你就得算好每个微帧能传多少数据——高速模式下每微帧125微秒,如果同步传输分配了80%的带宽,那每微帧最多能传1024字节(同步包最大长度)。

实用技巧:调试USB时序问题时,我习惯用逻辑分析仪抓SOF包。看SOF的间隔就能判断总线时钟是否稳定。有一次我调一个高速HUB,发现SOF间隔忽长忽短,最后查出是晶振的负载电容焊错了。嗯,这种问题光看代码是看不出来的,必须上仪器。

2.5 本章小结

这一章我们把USB协议的核心概念过了一遍。包结构是USB通信的基石——SOP、PID、ADDR、ENDP、CRC、EOP,每个字段都有它的作用。四种传输类型各有各的适用场景:控制传输管配置,批量传输管数据,中断传输管交互,同步传输管实时流。帧和微帧则是USB总线的时间骨架,所有传输都在这个骨架里调度。