USB协议栈核心:数据包结构、传输类型与帧机制
大家好,我是你们的讲师。今天咱们聊聊USB协议栈最核心的部分——数据包结构、四种传输类型,还有帧与微帧机制。说实话,这部分内容我当年啃协议规范时也花了不少功夫。但只要你搞懂了这些,后面写驱动、移植协议栈就会顺手很多。
一、USB数据包结构:从比特到事务
USB通信的最小单位是数据包。一个数据包由若干字段组成,我习惯把它拆成三层来看:
- 同步字段(SYNC):8位,用于时钟同步。说白了就是告诉接收端“我要发数据了,准备好”。
- 包标识符(PID):8位,其中4位是类型码,4位是校验码。PID决定了这个包是令牌包、数据包还是握手包。
- 数据字段:根据包类型不同,长度可变。比如数据包的数据段是0~1023字节。
- CRC校验:令牌包用5位CRC,数据包用16位CRC。嗯,这里要注意,CRC算错了整个包就会被丢弃。
- 包结束(EOP):单端0信号,表示包传输完毕。
关键点:USB是主机驱动的协议。所有传输都由主机发起,设备只能被动响应。这一点和I2C、SPI完全不同,你写驱动时一定要记住。
数据包组合起来形成事务。一个事务通常包含三个阶段:
- 令牌阶段:主机发送令牌包,指定要访问的设备地址、端点号和传输方向。
- 数据阶段:数据发送方(主机或设备)发送数据包。
- 握手阶段:接收方返回握手包(ACK/NAK/STALL),表示数据是否成功接收。
我在项目中遇到过一个问题:设备总是返回NAK,导致主机反复重试。后来发现是端点FIFO没及时清空。你看,理解事务流程对调试有多重要。
四、USB帧与微帧机制:时间基准
USB是时分复用的总线。全速/低速模式下,主机每1毫秒生成一个帧(Frame);高速模式下,每帧又分成8个微帧(Microframe),每个125微秒。
为什么要分帧?说白了就是为了给等时传输和中断传输提供确定性的时间槽。主机在每个帧/微帧的开始发送SOF(Start of Frame)包,设备用SOF来同步时钟。
| 模式 | 帧周期 | 每帧微帧数 | SOF间隔 |
|---|---|---|---|
| 低速(1.5 Mbps) | 1 ms | 1 | 1 ms |
| 全速(12 Mbps) | 1 ms | 1 | 1 ms |
| 高速(480 Mbps) | 125 µs | 8 | 125 µs |
帧机制直接影响驱动设计。比如你在写音频驱动时,等时传输必须每微帧发送一个数据包,否则就会出现断音。我曾经调试过一个USB摄像头驱动,画面总是卡顿,最后发现是帧同步没处理好,数据包没对齐到微帧边界。
调试技巧:用USB分析仪抓包时,重点关注SOF包的时间戳。如果SOF间隔不稳定,说明主机控制器或USB线缆有问题。我遇到过劣质USB线导致SOF抖动的情况,换了根线就好了。
二、USB传输类型:四种武器
USB定义了四种传输类型,每种都有自己的脾气。我按使用频率给你捋一遍。
1. 控制传输(Control Transfer)
这是USB的“官道”。所有设备都必须支持端点0的控制传输。它用于枚举、配置、获取描述符等管理操作。控制传输分三个阶段:建立阶段、数据阶段(可选)、状态阶段。
我个人习惯把控制传输比作“握手协议”——先发个请求,再传数据,最后确认。写驱动时,控制传输通常由USB核心层帮你封装好了,你只需要调用usb_control_msg()之类的API。
2. 批量传输(Bulk Transfer)
批量传输的特点是“可靠但没保障”。它保证数据无误送达,但不保证带宽和延迟。适合U盘、打印机这类对实时性没要求的设备。
批量传输在总线空闲时才能发送,如果总线繁忙,它就得排队。我在做U盘驱动时发现,如果同时挂载多个U盘,批量传输的吞吐量会明显下降。解决办法是调整端点FIFO大小,或者用多线程提交URB。
3. 中断传输(Interrupt Transfer)
别被名字骗了,USB中断传输不是硬件中断,而是主机轮询。设备告诉主机“我多久需要被查询一次”,主机就按这个间隔来读取数据。
中断传输适合鼠标、键盘这类输入设备。轮询间隔可以设置,全速模式下最小1ms,高速模式下最小125µs。嗯,这里要注意:轮询间隔设得太小会浪费总线带宽,设得太大又会导致输入延迟。
4. 等时传输(Isochronous Transfer)
等时传输是“实时但不可靠”的。它保证带宽和延迟,但不保证数据正确性——没有重传机制。适合音频、视频这类流式数据。
等时传输没有握手阶段,数据丢了就丢了。我在做USB麦克风驱动时,等时传输的缓冲区大小必须精确匹配帧周期,否则就会出现杂音或丢帧。
| 传输类型 | 可靠性 | 带宽保证 | 延迟 | 典型应用 |
|---|---|---|---|---|
| 控制 | 高 | 无 | 不确定 | 枚举、配置 |
| 批量 | 高 | 无 | 不确定 | U盘、打印机 |
| 中断 | 高 | 有 | 确定 | 鼠标、键盘 |
| 等时 | 低 | 有 | 确定 | 音频、视频 |
避坑指南:我曾经在移植USB协议栈时,把批量端点的最大包大小设错了。全速批量端点最大包是64字节,高速是512字节。设小了性能差,设大了设备不认。一定要查datasheet确认。
三、USB协议栈核心知识结构
下面这张图是我自己整理的USB协议栈核心知识结构,从物理层到传输层,帮你建立整体认知。
五、传输类型与帧机制的配合
你想想看,主机怎么在1ms内调度这么多传输?答案是轮询调度。主机控制器维护一个帧列表,每个帧/微帧里安排不同的传输事务。
- 等时传输:优先安排,因为要保证带宽。每个微帧最多发3个等时事务。
- 中断传输:按轮询间隔安排。比如鼠标设置10ms轮询,那就每10帧查一次。
- 控制传输:有固定带宽预留,但优先级低于等时和中断。
- 批量传输:剩下的带宽全给它。所以U盘在系统空闲时跑得快,忙时跑得慢。
实战建议:写驱动时,如果你发现设备响应慢,先别急着优化代码。用USB分析仪看看总线上是不是有太多批量传输在抢带宽。我曾经优化过一个打印机驱动,最后发现是同一个USB控制器上挂了两个U盘在拷贝大文件。
好了,这一章的内容就到这里。数据包结构、四种传输类型、帧与微帧机制,这三块是USB协议栈的基石。你把这些搞透了,后面看USB控制器驱动、设备驱动源码时就会轻松很多。