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,双向 用于枚举、配置、命令 保证送达,重试机制 带宽:10%~20%预留 典型:设备描述符读取 批量传输 非周期性,大块数据 利用空闲带宽传输 有错误重传,保证数据完整 典型:U盘读写、打印机 带宽:剩余带宽全部可用 中断传输 周期性查询,低延迟 保证最大间隔时间 有错误重传 典型:鼠标、键盘 带宽:低速≤800字节/帧 同步传输 实时数据流,无重传 固定带宽,保证速率 无错误校验/重传 典型:音频、摄像头 带宽:最多90%预留 选择传输类型时,先问自己:数据重要吗?实时性要求高吗?

每种传输类型的特点,我用自己的话总结一下:

  • 控制传输:USB的"官话",所有设备必须支持。枚举、配置、状态查询都走这个通道。端点0是标配。
  • 批量传输:适合U盘这种大块数据,不要求实时性,但数据必须完整。出错就重传,直到成功。
  • 中断传输:名字叫中断,其实是主机轮询。鼠标键盘用这个,保证延迟在可接受范围内。
  • 同步传输:音频视频用这个。数据丢了就丢了,不重传,但带宽必须保证。

选型建议:做USB音频设备时,我建议用同步传输+自适应时钟方案。之前有个项目用批量传输传音频,结果播放时断时续,因为批量传输的带宽不固定。换成同步传输后,问题立刻解决。

1.5 传输事务:一次完整的对话

USB的每次数据传输,都是一个事务(Transaction)。一个事务包含2~3个阶段:

  1. 令牌阶段:主机发送令牌包,指定传输方向、端点地址
  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都分不清,怎么分析逻辑分析仪抓到的数据?