资讯动态

RK3568 UART蓝牙主机外设驱动移植实战:从硬件连接到BlueZ调试

发布时间:2026/9/17 6:05:48 来源:尧图企业网站定制
在嵌入式Linux项目里碰上RK3568上要出蓝牙功能这种需求十有八九不是随便接个USB蓝牙适配器就能交差的。我做这块板子的时候情况很典型USB口被4G模组和调试占了板子预留的是一个UART口给低功耗蓝牙/WiFi二合一模块系统还要能作为蓝牙主机去连接耳机、手柄和手机说白了就是得把蓝牙协议栈完整跑起来而不是用AT命令控制一个串口蓝牙透传模块。这篇文章就把我在RK3568平台上从硬件连接、设备树配置到HCI驱动加载、BlueZ调试的完整过程梳理一遍给踩同样坑的朋友一个可以直接参考的“作业”。1. 方案选型与硬件设计思路1.1 为什么是UART而不是USB或SDIORK3568的接口资源其实很丰富USB、SDIO、UART、PCIe都有但蓝牙模块的接入方式基本就三种USB、SDIO和UART。USB方案最省事内核自带支持随便插个模块就能用可是工业板卡上USB口往往已经被4G、加密狗、调试口占用而且USB蓝牙模块在天线设计、功耗和成本上都未必有优势。SDIO方案多见于WiFiBT的二合一模组SDIO走WiFi数据蓝牙走UART或USB这时候蓝牙通道其实还是UART居多。我这次选的是标准UART接口原因很实际模块是板厂推荐的正点原子RK3568核心板配套的WiFiBT模组引脚上已经预留了UART和PCM/I2S而且UART通道在Linux内核里有非常成熟的HCI UART协议栈支持不需要额外的SDIO驱动开发工作量。更重要的是UART方案在调试阶段非常友好逻辑分析仪直接挂着就能看到HCI指令和数据流定位问题比SDIO直观多了。1.2 硬件连接与关键引脚定义拿到模块原理图第一件事不是写驱动而是把引脚定义理清楚。典型蓝牙模块和RK3568之间的连接有这么几组信号UART数据线TX、RX、CTS、RTS四线全接硬件流控必须用蓝牙使能脚BT_REG_ON / BT_EN控制模块内部LDO和射频部分电源一般由GPIO控制唤醒信号BT_WAKE_HOST蓝牙唤醒主机、HOST_WAKE_BT主机唤醒蓝牙这两个脚决定系统能否进入低功耗状态时钟源部分模块需要外部提供32.768kHz慢时钟或者直接用主控的26MHz参考时钟PCM/I2S语音数据通路做蓝牙通话才需要纯数据场景可以先不接我用的模块是AP62系列那种封装依数据手册把BT_REG_ON接到了GPIO4_B0HOST_WAKE_BT接到GPIO4_B1BT_WAKE_HOST接到GPIO4_B2。类似AP6256、AP6236引脚功能基本一致如果不确定只管照数据手册核对确认别只靠丝印猜。注意UART的TXD和RXD名字是相对模块的接主控时要交叉连接模块TXD接主控RXD模块RXD接主控TXD。很多人第一次画板子就在这栽跟头硬件一回来蓝牙完全没反应。2. Linux蓝牙协议栈与HCI UART驱动机制2.1 蓝牙协议栈的整体分层Linux的蓝牙子系统叫BlueZ它对上层应用提供标准的socket接口对底层蓝牙控制器则通过HCI协议通信。整个链路分四层蓝牙控制器就是模块里的射频部分负责基带、射频、链路管理对外暴露HCI接口传输层HCI数据在物理通道上的封装UART接口对应的就是H4、H5或BCSP协议内核蓝牙核心管理HCI设备、连接状态、L2CAP、SMP、A2DP/AVRCP等profile用户空间BlueZ工具bluetoothd守护进程、bluetoothctl、hciconfig等内核里管蓝牙的那几块最关键的就是net/bluetooth/hci_uart.c这个文件它把串口驱动和蓝牙核心串起来。文件里注册了一个线路规程line discipline当用户空间用btattach之类的工具把串口设置为蓝牙线路规程时串口收到的字节流就会被解析为HCI command、event、ACL数据包然后送给蓝牙核心。2.2 HCI UART的H4与H5协议差别说到HCI over UART最常见的是H4协议就是四线UART直接传HCI包。它的格式很简单每个HCI包前面加一个长度1字节的类型指示0x01表示command0x02表示ACL data0x03表示SCO data0x04表示event。接收端靠这个字节区分包类型然后按照HCI规范里每种包各自的长度字段解析。H4协议看似简单但有一个致命弱点如果UART线上突发干扰丢了一个字节后面所有帧同步都会错乱而且它没有重传机制。于是有了H5协议它在H4基础上加了滑动窗口确认重传机制每个数据帧前有头字节校验失败会请求重传可靠性和健壮性大大提高。AP62、BK、RTL等模块出厂固件一般默认走H4不过很多模块可以在初始化阶段通过HCI vendor command切换到H5。在内核配置时对应关系是这样的CONFIG_BT_HCIUART_H4支持H4协议CONFIG_BT_HCIUART_BCM支持Broadcom/Cypress模块的H4初始化和固件下载CONFIG_BT_HCIUART_INTELIntel模块CONFIG_BT_HCIUART_RTLRealtek模块支持通过UART给RTL8821CS这类模块加载固件如果你用的模块是AP6256或AP6212这类Broadcom方案走的是CONFIG_BT_HCIUART_BCM模块上电后内核需要发送一系列厂商命令先把波特率从默认的115200切到更高再加载固件。2.3 线路规程Line Discipline到底干了什么很多人一提到串口蓝牙驱动就懵觉得是不是要写一个独立的字符设备驱动。其实不是UART蓝牙驱动的主体是复用现有串口驱动关键点就是把串口默认的N_TTY线路规程换成N_HCI。可以把线路规程理解成“串口数据流的解析器”。普通串口终端用N_TTY收到什么就在终端上显示什么换到N_HCI之后串口收到的数据不再交给终端而是直接进蓝牙HCI层的数据队列。这个切换动作就是btattach程序做的事情它打开串口设备调用TIOCSETDioctl把线路规程从N_TTY改成N_HCI然后把蓝牙控制器的设备注册到蓝牙核心。开发阶段不需要每次都手动btattach也可以在内核配置里开启CONFIG_SERIAL_DEV_BUS和CONFIG_SERIAL_DEV_CTRL_TTYPORT配合设备树里蓝牙节点来自动完成。这个我们后面讲设备树的时候再展开。3. 设备树配置与内核相关配置项3.1 RK3568串口复用与pinctrl配置RK3568上UART资源很多UART0到UART9都有而且很多带m0/m1两组引脚复用比如UART1的m0组是GPIO2_B4到GPIO2_B7m1组是GPIO3_B0到GPIO3_B3。设备树里订引脚复用要确认实际板上模块接的是哪一组接错了串口就废了。我的板子上蓝牙模块接的是UART1用的是m0组并且四线全用所以设备树里pinctrl要同时包含数据线和流控线uart1 { status okay; pinctrl-names default; pinctrl-0 uart1m0_xfer uart1m0_ctsn uart1m0_rtsn; assigned-clocks cru SCLK_UART1; assigned-clock-rates 19200000; };这里assigned-clock-rates是给UART外设时钟设定频率一般设成24MHz或19.2MHz具体看串口驱动支持的分频范围。实际操作中如果波特率上3M或4M之后乱码首先检查这个时钟频率够不够。3.2 在UART节点下声明蓝牙子节点如果你的内核开启了CONFIG_SERIAL_DEV_BUS串口驱动会解析UART节点下的蓝牙子节点自动完成HCI UART的注册和初始化。这时候需要在uart1节点里加上uart1 { status okay; pinctrl-names default; pinctrl-0 uart1m0_xfer uart1m0_ctsn uart1m0_rtsn; bluetooth: bluetooth { compatible brcm,bcm43438-bt; reg 1; max-speed 3000000; shdn-gpios gpio4 RK_PB0 GPIO_ACTIVE_HIGH; host-wake-gpios gpio4 RK_PB1 GPIO_ACTIVE_HIGH; device-wake-gpios gpio4 RK_PB2 GPIO_ACTIVE_HIGH; clocks rk3568_slow_osc; clock-names extclk; }; };reg 1表示这个蓝牙设备在串口上的地址没有实际意义但compatible匹配时会被驱动用到。max-speed是主机和模块协商后切换到的最高波特率这里设3MAP6256这类模块是支持的。shdn-gpios对应BT_REG_ON控制模块使能host-wake-gpios对应HOST_WAKE_BTdevice-wake-gpios对应BT_WAKE_HOST。这句配套在根节点附近要把慢时钟定义好模块的32.768kHz时钟一般由RK3568的rk3568_slow_osc输出或者外接晶振产生clk32k_bluetooth: clk32k-bluetooth { compatible fixed-clock; clock-frequency 32768; #clock-cells 0; };3.3 内核配置项一条条核对内核配置里蓝牙相关的选项比较多但都是互相依赖的打开的时候注意别漏我列一下我的配置CONFIG_BTy CONFIG_BT_BREDRy CONFIG_BT_LEy CONFIG_BT_HCIUARTy CONFIG_BT_HCIUART_H4y CONFIG_BT_HCIUART_BCMy CONFIG_BT_HCIUART_RTLy CONFIG_SERIAL_DEV_BUSy CONFIG_SERIAL_DEV_CTRL_TTYPORTy CONFIG_BT_BNEPy CONFIG_BT_RFCOMMy CONFIG_BT_MSFTy CONFIG_BT_LE_AUDIOyCONFIG_BT_BREDR是经典蓝牙没有它连BR/EDR耳机和SPP都用不了CONFIG_BT_LE是低功耗蓝牙现在的可穿戴设备、ibeacon都靠它CONFIG_BT_HCIUART是总开关下面分的H4、BCM、RTL这些按模块厂商选。如果不确定厂商建议把H4、BCM、RTL、INTEL全打开反正内核里只按设备树或者btattach参数匹配多开了不影响正常加载。如果只是临时调试不想重新编译整个内核也可以把蓝牙相关配置编译成模块CONFIG_BTm CONFIG_BT_HCIUARTm然后用modprobe bluetooth、modprobe hci_uart加载但注意CONFIG_SERIAL_DEV_BUS必须在编内核时就选上它是串口核心的一部分不能模块化。3.4 为什么我建议先在设备树里做、再用btattach调试调试初期我强烈建议先不要依赖设备树自动探测而是按老办法来先保证UART本身没问题然后手动在命令行用btattach挂上蓝牙。原因是设备树自动探测如果失败报错信息可能被串口驱动吞掉不好定位。手动btattach一次就能看到明确报错是open串口失败、还是固件加载失败、还是HCI无响应。等手动方式完全调通了再回设备树里补全蓝牙子节点这样等于把问题分成了两个阶段硬件和UART一个阶段、HCI和固件一个阶段。4. 驱动移植与固件加载实战记录4.1 从串口测试开始排除硬件问题把内核编好烧进板子后第一步别急着搞蓝牙先测串口通不通。方法很简单设备树里先把uart1的status okay打开pinctrl配好然后启动后看/dev/ttyS1是否存在。在板子上执行stty -F /dev/ttyS1 115200 raw echo hello /dev/ttyS1然后用逻辑分析仪或者示波器抓TXD脚能看到输出说明UART发送路径OK。接收路径测试可以在模块未上电时把主控TXD和RXD短接发出去的数据自己收回来cat /dev/ttyS1能看到回显。短接测试通过就说明串口控制器到接口之间的通路没问题。如果串口都通不过大概率是引脚复用配错或者设备树里status还是disabled。4.2 手动挂载HCI并观察启动日志串口没问题后开始挂蓝牙。先把模块的BT_REG_ON拉高让模块上电。如果你还没设备树节点可以用gpio命令操作echo 148 /sys/class/gpio/export echo out /sys/class/gpio/gpio148/direction echo 1 /sys/class/gpio/gpio148/valueGPIO编号要根据RK3568的GPIO bank算GPIO4_B0按每组32个算4328136不对按RK平台的计算bank 0基址32则GPIO4_B0 432 8 136GPIO4_B1137GPIO4_B2138。用gpiodetect、gpioinfo查一下就知道了。驱动起来之后不要再用这种sysfs方式操作GPIO它跟设备树里的gpio请求会冲突。手动挂载用的是btattach命令如下btattach -B /dev/ttyS1 -S 115200-B表示Broadcom模块-S指定初始波特率。执行后立刻看dmesgBluetooth: hci0: BCM: chip id 87 Bluetooth: hci0: BCM4438A1 Bluetooth: hci0: BCM: patch brcm/BCM4345C0.hcd not found出现hci0说明HCI层已经建立模块芯片id也读到了后面报固件找不到是正常的因为固件文件还没放进去。如果把chip id那行都看不到多半是串口不通或者模块没上电。如果提示command 0x1003 tx timeout一般是波特率不匹配或者CTS/RTS流控没生效。4.3 固件下载与路径放置规范Broadcom方案模块必须下载固件才能正常工作固件文件是一个.hcd格式的二进制补丁包。不同模块对应不同文件名常见的有模块固件文件AP6212BCM43438A1.hcdAP6256BCM4345C0.hcdAP6236BCM43430A1.hcdRTL8821CSrtl8821cs_fw / rtl8821cs_config把这些文件放到内核固件搜索路径一般是/lib/firmware/brcm/下。注意内核里有一个称为FW_LOADER_USER_HELPER的配置项如果被打开固件加载会调用用户空间的hotplug脚本如果没配好会导致固件加载失败。一般嵌入式系统建议直接关掉CONFIG_FW_LOADER_USER_HELPERn放好固件后重新触发固件下载。如果之前hci0已经注册失败可以重启板子再走一次btattach如果hci0还在可以用rfkill block bluetooth再unblock或者直接重新执行btattach。正常加载完日志会多出Bluetooth: hci0: BCM: Patch brcm/BCM4345C0.hcd loaded Bluetooth: hci0: BCM: firmware patch successful然后用hciconfig看状态hciconfig -a能看到BD Address和Manufacturer说明驱动已经跑通了。4.4 用bluetoothctl验证主机外设功能驱动通了之后验证主机功能最直接的方式是扫描和连接。先起bluetoothdbluetoothd -n -d也可以用systemd管理systemctl start bluetooth。然后进bluetoothctl工具bluetoothctl power on scan on扫描列表里能看到周围的手机、耳机设备。要连接BLE设备时用bluetoothctl connect 设备的MAC地址要连接经典蓝牙耳机还需要先配置trust和配对bluetoothctl trust AA:BB:CC:DD:EE:FF bluetoothctl pair AA:BB:CC:DD:EE:FF bluetoothctl connect AA:BB:CC:DD:EE:FF连接成功之后可以再用hcitool做更细的链路层验证hcitool dev hcitool cc AA:BB:CC:DD:EE:FF hcitool rssi AA:BB:CC:DD:EE:FF这里有几个坑我之前踩过。一是很多模块默认广播间隔比较长scan on后半天看不到设备别急等十来秒二是扫描到设备但连接失败先看是不是配对模式不对经典蓝牙要enable SSPBLE要看白名单三是连接成功后马上断开常见原因是天线匹配不好、信号太差或者供电不足导致模块射频发射异常。4.5 开机自动化从btattach到服务脚本一切手调正常后可以把它做成开机自启。不想依赖设备树自动探测的话写个systemd服务最省事[Unit] DescriptionBluetooth attach script Aftersystemd-modules-load.service Beforebluetooth.service [Service] Typeoneshot ExecStartPre/bin/sleep 1 ExecStart/usr/bin/btattach -B /dev/ttyS1 -S 3000000 RemainAfterExityes [Install] WantedBymulti-user.target如果你内核配置了CONFIG_SERIAL_DEV_BUS并且设备树里已经有蓝牙子节点其实不需要这个服务串口驱动在初始化阶段就会自动把hci0注册好。哪个方案更可靠取决于你的内核版本和驱动模块。我建议两边都跑通然后实际观察哪个在冷启动时更稳定。5. 常见坑位排查与性能调优心得5.1 典型问题速查表我把这次开发遇到的典型问题整理成一张表方便后面的人遇到类似情况直接对着排查现象可能原因排查与解决办法hci0注册成功但扫描无设备天线接触不良、模块供电不足、蓝牙地址无效检查天线IPEX接口、测量模块3.3V电压、确认BD Address是否存在command timeoutUART波特率不匹配、流控没生效用逻辑分析仪抓UART波形确认CTS/RTS电平降低波特率测试固件下载失败固件路径不对、内核版本不匹配、模块型号判断错核对dmesg里的chip id选对应.hcd确认/lib/firmware路径连接即断信号差、配对模式问题、profile注册缺失观察RSSI值用bluetoothctl配置trust和pair确认bluetoothd正常蓝牙和WiFi互相干扰2.4GHz共存问题、天线隔离度差优先确认模块共存引脚接法开关WiFi测试蓝牙是否恢复模块不休眠导致整板功耗高HOST_WAKE/BT_WAKE GPIO没配置确认设备树host-wake-gpios和device-wake-gpios确认驱动支持suspend5.2 蓝牙吞吐量优化经验数据类场景比如用BLE或SPP做透传吞吐量不达标往往是传输速率卡在115200。我的实测数据是115200波特率下SPP透传实测只能跑到10KB/s左右提升到3M波特率后能到200KB/s以上。切换波特率是在固件加载阶段由驱动完成的设备树里max-speed就是干这个用的。还有一个影响吞吐量的因素是UART接收的DMA模式。RK3568的串口可以在设备树里配DMA通道dmas dmac0 4, dmac0 5; dma-names tx, rx;开了DMA之后高波特率下CPU占用低很多数据不容易丢。但要注意如果DMA配置有误会导致串口接收乱码这种情况可以先把dmas注释掉对比测试。5.3 关于BT/WiFi共存的几个实操结论我手头模块是WiFiBT二合一這兩者在2.4GHz频段是共享天线的。第一版测试发现蓝牙连上耳机后WiFi速率掉得厉害后来分析是模块的共存引脚没接对。二合一模块上通常有WL_BT_ANT_CTL这类共存控制脚需要接到主控或者模块内部的射频开关。如果原理图上没接这个脚两个射频前端会互相抢时间性能自然差。在没有共存引脚可用的情况下软件上也有折中方案。可以在驱动层配置蓝牙和WiFi的时隙分配但实际调下来效果有限。最有效的还是硬件上把共存控制脚接对并且在PCB布局时保证天线区域净空不要在模块天线底下铺地铜。5.4 低功耗唤醒策略的经验补充如果你的产品是电池供电HOST_WAKE_BT和BT_WAKE_HOST这两个信号就非常重要。蓝牙模块在工作时可以保持主机这边的UART休眠等有数据要传的时候通过HOST_WAKE_BT拉高唤醒主控。设备树里配好host-wake-gpios之后驱动会把它注册成中断源。调试唤醒时我遇到一个情况模块能正常连接但主机suspend后蓝牙就彻底失联醒不过来。查到最后是UART的时钟在suspend时被关了HOST_WAKE中断上来了但UART无法收发。解决方法是把UART的电源域配置好或者用irq wake的方式让UART在低功耗模式保持接收使能。提示这一步和具体内核版本关系很大RK3568在不同内核版本里串口驱动对suspend/resume的处理不完全一样最好用原厂最新BSP的内核避免踩到已经修复的旧bug。写在最后整个RK3568 UART蓝牙主机外设驱动项目跑下来我最大的感受是蓝牙驱动的难点其实不在“写代码”而在“把硬件链路和内核协议栈的每一个环节都想明白”。从UART引脚复用、模块上电时序到HCI线路规程、固件加载再到BlueZ的配对连接每一步都有对应的日志和工具可以验证。碰到问题时不要急着改设备树先分清楚是硬件层、传输层还是协议栈层的问题一层一层剥开答案自己就出来了。最后再分享一个小技巧调试蓝牙时dmesg日志里如果只看到HCI数据可以用hcidump或者btmon抓HCI流量配合hcitool cmd主动发厂商命令能挖出很多内核日志里看不到的细节。做嵌入式驱动手里有个靠谱的逻辑分析仪和频谱仪是真的能省掉一半熬夜时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价