第四章:HCI传输层详解(USB)
好,咱们今天来啃一块硬骨头——USB传输层。说实话,很多做蓝牙的工程师一听到USB就头大,觉得它太复杂。但我个人觉得,USB其实是个很优雅的设计,只要你抓住了它的核心脉络。
4.1 USB硬件接口:不只是插拔那么简单
先说说物理层。USB硬件接口,说白了就是四根线:VBUS、D+、D-、GND。嗯,就这么简单。但这里有个坑——D+和D-是差分信号线,不是普通的IO口。
我记得刚入行那会儿,有个项目用USB连接蓝牙芯片,结果死活枚举不上。折腾了两天,最后发现是D+和D-的走线长度差了3mm。你想想看,高速信号对时序有多敏感。
USB接口有几种常见的物理形态:
- Type-A:最常见的方形口,主机端用得多
- Type-B:方形的,设备端常见,比如打印机
- Micro-B:小型的,很多蓝牙dongle在用
- Type-C:现在的主流,正反都能插
对于蓝牙HCI来说,最常见的还是Micro-B和Type-C。我个人习惯用Type-C,因为它的CC引脚可以做更多控制,比如检测设备插入、协商供电等。
4.2 HCI USB传输协议:数据怎么跑起来的
USB传输协议,说白了就是一套数据打包和解包的规则。蓝牙HCI通过USB传输时,用的是USB的批量传输和中断传输两种方式。
为什么会这样?因为蓝牙HCI的数据包有两种:
- 命令和事件:用中断传输,保证实时性
- ACL数据:用批量传输,追求吞吐量
这里有个关键点——USB的端点(Endpoint)。每个端点就像一个邮箱,主机往里面放数据,设备从里面取数据。蓝牙HCI通常需要三个端点:
| 端点号 | 方向 | 传输类型 | 用途 |
|---|---|---|---|
| 0x00 | 双向 | 控制传输 | USB枚举和配置 |
| 0x01 | 输入(设备到主机) | 中断传输 | HCI事件 |
| 0x02 | 输出(主机到设备) | 批量传输 | HCI命令和ACL数据 |
| 0x03 | 输入 | 批量传输 | ACL数据 |
你看,这个结构其实很清晰。主机通过端点0x02发命令,设备通过端点0x01回事件。ACL数据则走批量端点,因为它的数据量大,但实时性要求没那么高。
4.3 USB中断传输与批量传输:选对路子
咱们深入聊聊这两种传输方式。
4.3.1 中断传输
中断传输,名字听着吓人,其实它并不是真的中断CPU。它是USB主机定期轮询设备,看看有没有数据要传。轮询间隔可以设置,比如1ms、2ms、8ms等。
对于蓝牙HCI事件来说,我建议轮询间隔设为1ms。为什么?因为蓝牙的ACL数据包最大也就几百字节,1ms的轮询足够应付。而且事件包通常很小,几十个字节就搞定了。
我曾经遇到过一个坑:把中断传输的轮询间隔设成了8ms,结果蓝牙连接断开后,主机要等8ms才能收到断开事件。用户体验极差。后来改成1ms,问题解决。
4.3.2 批量传输
批量传输,顾名思义,就是一次传一大包数据。它没有固定的轮询间隔,主机有空就来取数据。所以它的延迟不确定,但数据完整性有保证——USB协议会做CRC校验和重传。
对于蓝牙ACL数据,批量传输是首选。因为ACL数据包可能很大(比如传文件时),而且允许一定的延迟。你想想看,传一个MP3文件,延迟几毫秒根本感觉不到。
4.4 USB描述符配置:让主机认识你的设备
USB描述符,说白了就是设备的「身份证」。主机通过读取描述符,才知道你是什么设备、需要什么驱动、用哪些端点。
对于蓝牙HCI设备,我们需要配置以下几类描述符:
- 设备描述符:描述设备的基本信息,比如VID、PID、USB版本等
- 配置描述符:描述设备的供电方式、最大电流等
- 接口描述符:描述设备的功能接口,蓝牙HCI通常用两个接口
- 端点描述符:描述每个端点的属性,比如端点号、传输类型、最大包大小等
这里我贴一个典型的蓝牙HCI USB描述符配置示例:
// 设备描述符
0x12, // bLength: 18字节
0x01, // bDescriptorType: 设备描述符
0x00, 0x02, // bcdUSB: USB 2.0
0xE0, // bDeviceClass: 无线控制器
0x01, // bDeviceSubClass: 蓝牙
0x01, // bDeviceProtocol: HCI
0x40, // bMaxPacketSize0: 64字节
0x0A, 0x12, // idVendor: 0x120A
0x01, 0x00, // idProduct: 0x0001
0x00, 0x01, // bcdDevice: 1.0
0x01, // iManufacturer: 厂商字符串索引
0x02, // iProduct: 产品字符串索引
0x00, // iSerialNumber: 无序列号
0x01 // bNumConfigurations: 1个配置
// 配置描述符
0x09, // bLength: 9字节
0x02, // bDescriptorType: 配置描述符
0x29, 0x00, // wTotalLength: 41字节
0x02, // bNumInterfaces: 2个接口
0x01, // bConfigurationValue: 配置值1
0x00, // iConfiguration: 无字符串
0x80, // bmAttributes: 总线供电
0x32 // bMaxPower: 100mA
嗯,这里要注意一点:蓝牙HCI设备通常需要两个接口描述符。第一个接口用于HCI命令和事件,第二个接口用于ACL数据。但有些实现会把所有功能放在一个接口里,用不同的端点来区分。
最后说说字符串描述符。虽然它不是必须的,但我建议加上厂商和产品字符串。这样在Windows的设备管理器里,你能看到「CSR Bluetooth Dongle」而不是「未知设备」。对调试和用户友好度都有帮助。
好了,USB传输层的内容就讲到这里。下一章咱们会深入HCI命令和事件的封装细节,到时候你会看到这些USB端点是怎么实际跑起来的。