1. USB协议基础:总线架构、物理层信号与数据包格式
各位同学,咱们今天正式开讲USB。说实话,USB这玩意儿看着简单——插上去就能用,对吧?但真到了嵌入式开发里,它能把人折腾得够呛。我当年第一次调USB设备枚举,愣是花了三天才把描述符配对。嗯,咱们从最底层开始,一步步把USB的底裤扒干净。
1.1 USB总线架构:主机、设备与集线器
USB总线是典型的主从架构。整个系统里只有一个主机(Host),其他全是设备(Device)。主机说了算,设备只能乖乖听话。你想想看,这跟I2C、SPI那种多主架构完全不一样。
总线拓扑是星型结构,但实际布线是树形。核心角色就三个:
- USB主机(Host):负责调度总线、发起传输、管理电源。PC里的OHCI/EHCI/xHCI控制器就是干这个的。
- USB设备(Device):响应主机请求,不能主动发数据。比如U盘、鼠标、摄像头。
- USB集线器(Hub):扩展端口,转发信号。每个Hub最多接7个下游端口。
关键点:USB总线不允许设备之间直接通信。所有数据必须经过主机中转。我在做OTG设备时踩过这个坑——以为两个设备能直接传文件,结果发现还得靠主机调度。
物理连接上,一条USB链路最多支持7层Hub级联(包括根Hub)。超过这个深度,信号时序就扛不住了。实际项目中我建议最多用3-4层,否则枚举失败率会明显上升。
1.2 USB物理层:信号与电气特性
物理层是USB最底层的硬功夫。说白了,就是两根差分信号线(D+和D-)怎么传0和1。咱们先看电气参数:
| 参数 | USB 1.x/2.0 | USB 3.x |
|---|---|---|
| 信号线 | D+, D-(差分对) | SSRX+, SSRX-, SSTX+, SSTX- |
| 电压摆幅 | 0~3.3V(低速/全速) 0~400mV(高速) |
0~1.0V(差分) |
| 终端阻抗 | D+上拉1.5kΩ(全速/高速) D-上拉1.5kΩ(低速) |
差分阻抗100Ω |
| 传输速率 | 1.5Mbps(低速) 12Mbps(全速) 480Mbps(高速) |
5Gbps(Gen1) 10Gbps(Gen2) |
这里有个重要的概念——设备速度识别。主机是怎么知道插上来的是低速还是全速设备?靠的是D+和D-上的上拉电阻位置:
- 低速设备:D-线上拉1.5kΩ到3.3V
- 全速/高速设备:D+线上拉1.5kΩ到3.3V
主机检测到哪根线被拉高,就知道设备速度等级了。我曾经在调试一个定制键盘时,发现主机死活不认设备。查了半天,原来是PCB上D+和D-的上拉电阻焊反了——低速设备的上拉电阻跑到了D+上,主机以为它是全速设备,结果枚举到一半就崩了。
注意:高速设备在初始化阶段会先以全速模式启动,然后通过"Chirp J/K"握手序列切换到高速模式。这个切换过程对时序要求极高,很多国产芯片在这里翻车。
1.3 USB数据包格式:从位到字节再到包
USB的数据传输,说白了就是一层层往上封装。咱们从最底层的位编码开始看。
1.3.1 NRZI编码与位填充
USB物理层使用NRZI(Non-Return-to-Zero Inverted)编码。规则很简单:
- 数据位为0:信号电平翻转
- 数据位为1:信号电平保持不变
但有个问题——如果连续传输多个1,信号就一直不变,接收端会失去同步。所以USB引入了位填充(Bit Stuffing):连续6个1之后,发送端自动插入一个0。接收端检测到6个1后,自动丢弃后面的0。
实战经验:我在分析USB逻辑分析仪抓到的波形时,经常先看位填充是否正确。如果波形上出现连续7个以上的相同电平,基本可以断定发送端位填充逻辑有bug。
1.3.2 数据包结构
USB数据包由以下字段组成:
| 字段 | 长度 | 说明 |
|---|---|---|
| SOP(包起始) | 8位 | 同步序列,用于时钟同步 |
| PID(包标识符) | 8位 | 高4位为类型,低4位为校验 |
| 数据字段 | 0~1024字节 | 具体数据内容 |
| CRC(循环冗余校验) | 5位或16位 | 校验数据完整性 |
| EOP(包结束) | 2位 | SE0状态,表示包结束 |
PID是包类型的核心标识。常用的PID类型有:
- 令牌包:OUT (0xE1)、IN (0x69)、SETUP (0x2D)、SOF (0xA5)
- 数据包:DATA0 (0xC3)、DATA1 (0x4B)、DATA2 (0x87)、MDATA (0x0F)
- 握手包:ACK (0xD2)、NAK (0x5A)、STALL (0x1E)、NYET (0x96)
你想想看,PID只有4位有效值,但实际传输8位——低4位是高4位的取反。这样接收端可以快速校验PID是否被干扰。我在调试时经常用这个特性:如果收到的PID高低4位不是取反关系,那这包数据肯定坏了,直接扔掉。
1.4 USB传输类型:四种模式
USB定义了四种传输类型,每种对应不同的应用场景。我画了张图帮大家理解:
每种传输类型的特点,我用自己的话总结一下:
- 控制传输:USB的"官话",所有设备必须支持。枚举、配置、状态查询都走这个通道。端点0是标配。
- 批量传输:适合U盘这种大块数据,不要求实时性,但数据必须完整。出错就重传,直到成功。
- 中断传输:名字叫中断,其实是主机轮询。鼠标键盘用这个,保证延迟在可接受范围内。
- 同步传输:音频视频用这个。数据丢了就丢了,不重传,但带宽必须保证。
选型建议:做USB音频设备时,我建议用同步传输+自适应时钟方案。之前有个项目用批量传输传音频,结果播放时断时续,因为批量传输的带宽不固定。换成同步传输后,问题立刻解决。
1.5 传输事务:一次完整的对话
USB的每次数据传输,都是一个事务(Transaction)。一个事务包含2~3个阶段:
- 令牌阶段:主机发送令牌包,指定传输方向、端点地址
- 数据阶段:发送或接收数据包(可选,握手包没有数据阶段)
- 握手阶段:接收方返回ACK/NAK/STALL,确认传输结果
举个例子,主机从设备读取数据:
主机发送 IN令牌包 → 设备返回 DATA0/DATA1包 → 主机发送 ACK握手包
如果设备还没准备好,会返回NAK包。主机看到NAK后,会在下一个帧/微帧重试。我调试时经常用逻辑分析仪抓这个——如果看到大量NAK,说明设备处理速度跟不上主机节奏,需要优化固件。
坑点提醒:我曾经在一个项目中,设备返回STALL而不是NAK。STALL表示端点挂了,需要主机重新配置。结果我的固件里把STALL当NAK处理了,导致设备一直卡在挂起状态。记住:STALL是致命错误,NAK是暂时忙。
1.6 本章小结
这一章咱们把USB的底子打好了。总线架构、物理层信号、数据包格式、四种传输类型——这些都是后面调试USB协议栈的必备知识。说实话,USB协议栈调试最怕的就是底层概念不清。你想想看,如果连PID和CRC都分不清,怎么分析逻辑分析仪抓到的数据?