4、蓝牙驱动基础:USB蓝牙适配器驱动(btusb)、UART蓝牙驱动(hci_uart)、SDIO蓝牙驱动
好,咱们进入第四讲。这一章我打算聊聊蓝牙驱动的三种主流物理接口:USB、UART 和 SDIO。说白了,就是蓝牙芯片怎么跟主处理器“说话”的三种方式。
你可能会问,搞这么多种接口干嘛?直接统一用一种不就行了?嗯,这里有个现实问题——不同的应用场景,对成本、功耗、速率的要求完全不一样。我当年刚入行时也觉得接口越多越麻烦,后来踩了几个坑才明白,选对接口比写对代码更重要。
4.1 USB蓝牙适配器驱动(btusb)
先讲最常见的——USB接口。你买的那种几十块钱的USB蓝牙适配器,插上电脑就能用,背后就是btusb驱动在干活。
btusb驱动是Linux内核里最成熟的蓝牙驱动之一。它负责把USB总线上的蓝牙控制器,映射成HCI(Host Controller Interface)设备。我个人习惯把btusb看作一个“翻译官”——把USB的URB(USB Request Block)翻译成HCI命令、事件和ACL数据。
核心要点:btusb驱动主要处理三类传输通道:
- 控制通道(Control Endpoint):用于发送HCI命令和接收命令完成事件
- 中断通道(Interrupt Endpoint):用于接收HCI事件(如连接断开、配对请求)
- 批量通道(Bulk Endpoint):用于传输ACL数据(音频、文件传输等)
我记得有一次调试一个USB蓝牙适配器,插上后lsusb能识别,但hcitool dev就是看不到设备。查了半天,发现是USB的固件下载流程没走完。很多USB蓝牙芯片(比如CSR、Broadcom的)上电后需要先下载固件,btusb驱动里有个专门的btusb_setup_intel或btusb_setup_bcm函数干这事。
来看一段btusb驱动的核心初始化流程(简化版):
static int btusb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct btusb_data *data;
// 1. 分配驱动私有数据结构
data = devm_kzalloc(&intf->dev, sizeof(*data), GFP_KERNEL);
// 2. 查找并保存USB端点
data->isoc_ep = usb_find_int_in_endpoint(intf->cur_altsetting);
data->bulk_ep = usb_find_bulk_in_endpoint(intf->cur_altsetting);
// 3. 初始化HCI设备
hdev = hci_alloc_dev();
hdev->bus = HCI_USB;
hdev->open = btusb_open;
hdev->close = btusb_close;
hdev->send = btusb_send_frame;
// 4. 注册HCI设备
hci_register_dev(hdev);
}
这段代码看着简单,但坑不少。比如第2步找端点,有些山寨适配器的端点描述符不标准,usb_find_int_in_endpoint可能返回NULL。我曾经遇到过一款适配器,它的中断端点居然被标记为批量传输类型,导致事件通道一直收不到数据。
避坑指南:我曾经调试过一个项目,USB蓝牙适配器在Windows上工作正常,但在Linux上频繁断连。最后发现是USB的自动挂起(autosuspend)导致的。btusb驱动默认开启了USB省电模式,如果蓝牙设备长时间没有活动,USB控制器会把它挂起。解决办法是在驱动初始化时调用usb_disable_autosuspend()。
4.2 UART蓝牙驱动(hci_uart)
接下来聊UART接口。这个在嵌入式设备里太常见了——很多蓝牙模块(比如HC-05、RN4020)都是通过串口跟主控芯片通信的。
hci_uart驱动比btusb复杂一些,因为UART没有像USB那样标准化的枚举过程。你得先配置好串口的波特率、数据位、停止位、流控,然后才能跟蓝牙芯片握手。
hci_uart驱动支持多种协议:
| 协议类型 | 说明 | 常见芯片 |
|---|---|---|
| H4 | 最基本的HCI UART传输协议,无流控 | CSR BC4、BC5系列 |
| BCSP | BlueCore Serial Protocol,带可靠传输 | CSR BC3系列 |
| H5 | 基于3-wire的HCI UART协议 | TI CC256x、STM32WB |
| Intel | Intel专有的UART协议 | Intel Wireless-AC 7260 |
我个人最常用的是H4协议,简单粗暴。但要注意,H4协议没有校验和重传机制,如果串口线质量不好或者干扰大,数据包可能会丢。这时候就得考虑用BCSP或H5了。
来看hci_uart驱动的初始化流程:
static int hci_uart_setup(struct hci_uart *hu)
{
struct hci_dev *hdev = hu->hdev;
int err;
// 1. 设置串口参数
err = uart_set_options(hu->tty, hu->tty->driver,
B115200, CS8, CSTOPB, 0);
// 2. 发送厂商特定的初始化序列
// 比如有些芯片需要先发一条特殊命令才能进入HCI模式
err = hci_uart_send_cmd(hdev, 0xFC00, NULL, 0);
// 3. 等待芯片就绪
msleep(100);
// 4. 注册HCI设备
hdev->bus = HCI_UART;
hdev->open = hci_uart_open;
hdev->close = hci_uart_close;
hdev->send = hci_uart_send_frame;
return hci_register_dev(hdev);
}
经验之谈:UART蓝牙驱动调试时,我建议先用逻辑分析仪抓一下串口波形。很多问题一眼就能看出来——比如波特率不匹配、流控信号没接对。我曾经花了两天时间调一个蓝牙模块,最后发现是RTS/CTS两根线接反了。
4.3 SDIO蓝牙驱动
最后说说SDIO接口。这个在手机和平板上用得比较多,因为SDIO的吞吐量比UART大得多,适合WiFi+蓝牙二合一芯片(比如Broadcom BCM4334、Marvell 88W8787)。
SDIO蓝牙驱动的实现思路跟btusb类似,但有个关键区别:SDIO设备是通过功能号(Function Number)来区分的。蓝牙功能通常对应Function 1,WiFi对应Function 0。
驱动初始化时,需要先通过SDIO的CMD52和CMD53命令来读写芯片的寄存器,完成上电和复位流程。我记得有一次调试BCM4334,芯片死活不响应SDIO命令,后来发现是上电时序不对——需要先拉高一个GPIO给芯片供电,等10ms后再拉低复位引脚。
static int btmrvl_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
struct btmrvl_sdio_card *card;
// 1. 使能SDIO功能
sdio_claim_host(func);
sdio_enable_func(func);
// 2. 读取芯片信息
card->reg = sdio_readb(func, 0x00, &err);
card->fw_ver = sdio_readb(func, 0x01, &err);
// 3. 下载固件
err = btmrvl_sdio_download_fw(card);
// 4. 注册HCI设备
hdev = hci_alloc_dev();
hdev->bus = HCI_SDIO;
hdev->open = btmrvl_sdio_open;
hdev->close = btmrvl_sdio_close;
hdev->send = btmrvl_sdio_send_frame;
sdio_release_host(func);
return hci_register_dev(hdev);
}
三种接口对比:
- USB:即插即用,速率高(USB 2.0可达480Mbps),适合PC和开发板
- UART:引脚少(4线),成本低,但速率受限(通常115200~921600bps),适合低功耗嵌入式设备
- SDIO:速率介于USB和UART之间(SDIO 2.0可达50MB/s),适合手机、平板等移动设备
嗯,这一章的内容就这些。三种接口各有千秋,选型时得看你的具体需求。我个人建议,如果只是做原型验证,用USB蓝牙适配器最省事;如果是做产品,UART接口的蓝牙模块成本最低;如果对速率有要求(比如同时跑蓝牙音频和BLE),那就得上SDIO。
下一章我会讲蓝牙驱动的热插拔处理——这可是个容易出bug的地方,尤其是UART接口的蓝牙模块,插拔时经常导致内核崩溃。到时候我会分享几个我踩过的坑,保证让你少走弯路。