3. USB传输类型:控制传输、批量传输、中断传输、同步传输

好,咱们今天聊聊USB的四种传输类型。说实话,这四种类型是USB协议的核心,也是你写驱动时绕不开的坎儿。我刚开始接触USB时,总觉得这四种类型就是四种不同的“快递方式”,后来发现这个比喻还挺贴切的。

USB协议规定了四种传输类型:控制传输、批量传输、中断传输和同步传输。每种类型都有它特定的应用场景和时序要求。你想想看,鼠标和摄像头的数据传输需求能一样吗?肯定不一样。所以USB协议才设计了这四种类型来满足不同的外设需求。

3.1 控制传输

控制传输是USB的“官方语言”。每个USB设备都必须支持它。说白了,它就是用来做设备枚举、配置、状态查询这些管理工作的。

控制传输有三个阶段:Setup Stage、Data Stage、Status Stage。我习惯把这三个阶段叫做“握手-传数据-确认”。

3.1.1 Setup Stage

这个阶段由主机发起。主机发送一个8字节的Setup包,告诉设备“我要干什么”。这8字节包含了请求类型、请求码、值、索引和长度等信息。

举个例子,主机想读取设备的描述符,就会发一个GET_DESCRIPTOR请求。Setup包里的内容就是:

bmRequestType = 0x80  // 设备到主机,标准请求,发给设备
bRequest = 0x06       // GET_DESCRIPTOR
wValue = 0x0100       // 描述符类型为设备描述符,索引为0
wIndex = 0x0000       // 语言ID(这里用不到)
wLength = 0x0012      // 期望接收18字节

嗯,这里要注意:Setup包是固定8字节的,不能多也不能少。我在项目中遇到过有人把wLength填错了,结果设备返回的数据长度不对,导致枚举失败。这种错误很隐蔽,排查起来特别费时间。

3.1.2 Data Stage

Data Stage是可选的。有些控制传输不需要数据阶段,比如设置地址请求。但大多数情况下,我们需要传输数据。

数据阶段的方向由Setup包中的bmRequestType决定。如果是主机到设备,主机发送数据;如果是设备到主机,设备发送数据。数据包的大小不能超过端点描述符中定义的最大包长,通常是64字节。

我个人习惯在写驱动时,把控制传输的数据阶段单独封装成一个函数。这样代码结构清晰,也方便调试。

3.1.3 Status Stage

Status Stage是最后一个阶段。它用来确认整个控制传输是否成功完成。方向与Data Stage相反。

如果Data Stage是主机到设备,那么Status Stage就是设备到主机,设备发送一个0长度的状态包表示“我收到了”。如果Data Stage是设备到主机,那么Status Stage就是主机到设备,主机发送一个0长度的状态包表示“我收到了”。

我曾经在调试一个U盘驱动时,发现控制传输总是超时。查了半天,原来是Status Stage的握手包没发出去。后来发现是设备的端点配置有问题,导致状态包被阻塞了。这种问题,你光看协议文档是看不出来的,得实际抓波形才能发现。

重要提示:控制传输是USB通信的基础。所有设备枚举、配置、状态查询都依赖它。如果你的设备连控制传输都过不了,那其他传输类型就别想了。

3.2 批量传输

批量传输,顾名思义,就是用来传输大量数据的。它不保证实时性,但保证数据完整性。USB协议会通过错误检测和重传机制来确保数据正确到达。

批量传输适用于打印机、扫描仪、U盘这类设备。它们对数据准确性要求高,但对实时性要求不高。你想想看,打印一张图片,慢个几毫秒无所谓,但绝对不能打印出乱码。

批量传输使用批量端点,端点号通常是1到15。每个批量端点都有一个输入端点和一个输出端点。输入端点用于设备向主机发送数据,输出端点用于主机向设备发送数据。

批量传输的数据包大小取决于USB速度:

USB速度 最大包长
低速 不支持批量传输
全速 8, 16, 32, 64字节
高速 512字节
超高速 1024字节

我在项目中遇到过一个问题:批量传输的数据包大小设置不当,导致传输效率极低。当时我用的是全速USB,把包长设成了8字节。结果传输1MB数据需要发送128000个包,效率可想而知。后来改成64字节,效率提升了8倍。

经验之谈:批量传输的包长尽量设成最大值。这样能减少握手包的开销,提高传输效率。但要注意,包长不能超过端点描述符中定义的值。

3.3 中断传输

中断传输这个名字容易让人误解。它并不是真的“中断”CPU,而是主机定期轮询设备,看看有没有数据要传输。轮询间隔由设备在描述符中指定。

中断传输适用于鼠标、键盘、游戏手柄这类人机交互设备。它们的数据量小,但要求低延迟。你想想看,按一下鼠标,如果等100毫秒才有反应,那体验得多糟糕。

中断传输的轮询间隔范围:

USB速度 轮询间隔范围
低速 10-255毫秒
全速 1-255毫秒
高速 1-16个微帧(125微秒到2毫秒)
超高速 1-16个服务间隔(125微秒到2毫秒)

嗯,这里要注意:轮询间隔设得太短,会占用大量总线带宽;设得太长,又会影响响应速度。我建议游戏手柄的轮询间隔设在1-4毫秒之间,这样既能保证响应速度,又不会占用太多带宽。

我曾经调试过一个游戏手柄,发现按键响应有延迟。抓包一看,轮询间隔设成了10毫秒。后来改成2毫秒,延迟问题就解决了。但代价是总线带宽占用增加了5倍。所以,这是一个权衡问题。

避坑指南:我曾经在高速USB上把中断传输的轮询间隔设成了1个微帧(125微秒)。结果主机忙不过来,导致其他设备无法正常工作。后来查资料才知道,高速USB的中断传输轮询间隔不能小于1毫秒,除非你用的是等时传输。

3.4 同步传输

同步传输,也叫等时传输。它保证实时性,但不保证数据完整性。USB协议不会对同步传输进行错误检测和重传。说白了,就是“丢了就丢了,不补发”。

同步传输适用于音频、视频这类流媒体设备。它们对实时性要求极高,但对偶尔的数据丢失可以容忍。你想想看,播放视频时,偶尔丢一帧画面,你可能根本注意不到。但如果因为重传导致画面卡顿,那体验就很差了。

同步传输使用同步端点。每个同步端点也有输入和输出之分。同步传输的数据包大小也是固定的,由端点描述符定义。

同步传输的带宽预留机制很有意思。主机在枚举时会计算所有同步端点的总带宽需求。如果总带宽超过了USB总线的90%,主机就会拒绝新的同步端点。这是为了保证已有的同步传输不受影响。

我在项目中遇到过一个问题:同时连接了USB摄像头和USB麦克风,结果麦克风的声音断断续续。后来发现是同步传输的带宽不够了。摄像头的分辨率太高,占用了大部分带宽。最后只能降低摄像头的分辨率,才解决了问题。

重要提示:同步传输的带宽是预留的,但数据不保证可靠。如果你的应用对数据完整性要求高,比如医疗设备,那就别用同步传输。用批量传输更靠谱。

3.5 四种传输类型的对比

最后,我用一个表格来总结这四种传输类型的特点:

传输类型 应用场景 实时性 可靠性 带宽
控制传输 设备枚举、配置、状态查询
批量传输 U盘、打印机、扫描仪
中断传输 鼠标、键盘、游戏手柄
同步传输 音频、视频、摄像头

好了,这四种传输类型就讲完了。记住一句话:控制传输是基础,批量传输是主力,中断传输是响应,同步传输是实时。写驱动时,根据外设的需求选择合适的传输类型,能省不少事。

下一章,咱们聊聊USB描述符。这东西是USB设备的“身份证”,搞懂了它,你就能看懂任何USB设备了。