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_intelbtusb_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接口的蓝牙模块,插拔时经常导致内核崩溃。到时候我会分享几个我踩过的坑,保证让你少走弯路。