ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

RK3568 UART蓝牙调试全攻略:从硬件到协议栈

RK3568 UART蓝牙调试全攻略:从硬件到协议栈 1. 项目背景与整体方案选型1.1 为什么是RK3568加UART蓝牙RK3568是瑞芯微推出的四核Cortex-A55平台这两年出现在大量工控板、边缘计算盒子和智能终端上。它的接口资源很丰富尤其是串口——芯片本身集成多个UART控制器而且大多数都能配硬件流控CTS/RTS这给蓝牙主机外设提供了非常合适的物理通道。我说的“蓝牙主机外设”指的是把一颗蓝牙Controller芯片比如AP6212、AP6256、RTL8821系列通过UART接口挂到RK3568这颗SoC上。RK3568作为Host跑内核里的蓝牙协议栈和BlueZ用户态工具通过HCI命令去控制蓝牙模块完成扫描、连接、配对、数据收发。这种架构下蓝牙模块本身不做协议栈它只负责射频收发和链路层控制所以叫“主机外设”。为什么不用USB蓝牙因为USB蓝牙虽然即插即用、驱动也简单但它要占用一个USB Host口USB控制器和Hub走高速差分信号PCB布线和电源要求都不低。而且很多工控板的USB口本来就紧张做产品时还得考虑成本——一颗USB蓝牙模组和一颗UART蓝牙模组在BOM成本上差不少。UART蓝牙还有一个优势它跟SoC之间的连接非常直接功耗低待机电流控制得好尤其适合电池供电或对功耗敏感的设备。另外RK3568的串口控制器在全功能模式下支持硬件流控这很重要。蓝牙HCI流量不能靠简单的单线串口死等必须有RTS/CTS流控来防止数据溢出丢包。所以项目一开始我就把目标锁定在了“全功能UART 硬件流控 蓝牙模块”这条路上。1.2 方案定型前的几个判断在决定方案的时候有几个点是必须先想清楚的否则后面调试会非常痛苦。第一确认板卡上引出的UART是否带流控。RK3568的UART控制器虽然多但不少开发板为了省管脚实际只引出TX/RX两条线。没有RTS/CTSH4协议能跑但高速率下非常容易丢包连接后断流更是家常便饭。我这个项目用的是UART1板子上明确引出了CTS/RTS所以可以放心上2M甚至4M波特率。第二确认蓝牙模块是走H4还是H5。H4就是最简单的四线UARTTX/RX/RTS/CTS一字节开头标明包类型适合硬件流控可靠的场景。H5是三线UART协议在TX/RX/GND之上加了滑帧重传机制能做到无流控环境下的可靠传输但吞吐会打折。现在主流模块默认都是H4只有某些老模块或特殊场景才用H5。我这边用的是H4后面内容也主要围绕H4展开。第三确认内核版本和蓝牙子系统。RK3568官方SDK的内核一般都在Linux 4.19或5.10以上蓝牙协议栈net/bluetooth已经非常成熟BlueZ用户态工具也齐全。我建议直接用SDK自带的内核配置在此基础上打开蓝牙相关选项不要从零开始裁剪否则很容易踩到依赖缺失的坑。方案定下来之后整个实现路径就非常清晰硬件连接确认 - 内核配置和驱动选择 - 设备树描述UART和蓝牙芯片 - 固件加载 - 用户态验证。前面每一步没做好后面都会以各种玄学问题暴露出来所以我建议你也按这个顺序走。2. 硬件链路与协议分层2.1 蓝牙模块的硬件连接先看硬件。RK3568和蓝牙模块之间的连接最核心的是四根线TX、RX、CTS、RTS另外还需要供电和使能控制。以我手里这块板子为例蓝牙模块是AP6212主控端的连接是这样的UART1_TX - 模块的UART_RXUART1_RX - 模块的UART_TXUART1_CTS - 模块的UART_RTSUART1_RTS - 模块的UART_CTSGPIO这里用的GPIO4_PA7- 模块的BT_WAKEUP或BT_REG_ON控制蓝牙核心供电注意TX/RX要交叉CTS/RTS也要交叉这个连线错一个都不行。很多新人第一次接TX接TX结果数据完全不通还以为是驱动问题其实纯硬件接错。供电上AP6212这类模块一般需要VDDIO和VBAT两路VBAT通常是3.3VVDDIO可以1.8V或3.3V具体看模块的参考设计。模块如果支持SDIO WiFi UART蓝牙的复合形态那么蓝牙部分只走UARTWiFi部分另接SDIO。我这个项目只关心蓝牙部分WiFi那一路先不管。还有一个容易被忽略的天线。AP6212一般用板载天线或外接IPEX天线天线周围不要走高频信号也不要被金属外壳完全包住否则后面扫描距离会非常短你会怀疑人生。2.2 从UART到HCI的协议分层蓝牙从体系上分两部分Controller和Host。Controller跑链路层也就是射频收发、跳频、链路管理这些底层的活。Host跑L2CAP、SMP、ATT、GATT这些上层协议。两者之间通过HCIHost Controller Interface通信。当HCI跑在UART上时就要有一个传输层协议来识别“这是一条HCI命令这是一条ACL数据这又是一条事件”。H4协议的做法是每一个HCI数据包前面加一个字节的指示符0x01表示命令包0x02表示ACL数据包0x03表示同步数据包0x04表示事件包0x05表示ISO数据包。这个设计很简单但它要求UART传输必须可靠因为一旦丢字节整个包边界就乱了。硬件流控在这里的作用就是让发送方对端知道“我的FIFO满了你先停一下”从而保证不丢字节。这也是为什么我前面强调必须用带RTS/CTS的UART。在RK3568这一侧Linux内核的蓝牙子系统已经把这些处理好了。你打开CONFIG_BT和CONFIG_BT_HCIUART之后内核里的hci_uart驱动会注册为一个线路规程line discipline通常N_HCI15负责从串口收字节流、解包成HCI帧并提交给蓝牙核心反方向则是把HCI帧打包成H4流写到串口。协议栈本身不在驱动里是在net/bluetooth目录下用户态还能通过BlueZ来控制。2.3 设备树里的UART节点配置RK3568的设备树里UART节点并不是简单把status改成okay就完了。你要确保pinctrl正确、时钟稳定、流控使能。我项目里的设备树关键部分如下uart1 { pinctrl-names default; pinctrl-0 uart1_xfer uart1_cts uart1_rts; status okay; };uart1_xfer是TX/RX引脚配置uart1_cts和uart1_rts是流控引脚配置。SDK的dtsi里面一般已经把复用关系定义好了你只需要在板级dts里引用。如果蓝牙芯片挂在UART节点下面直接声明一个子节点交给serdev驱动去做自动绑定代码会长这样uart1 { pinctrl-names default; pinctrl-0 uart1_xfer uart1_cts uart1_rts; status okay; bluetooth { compatible brcm,bcm43438-bt; max-speed 4000000; shutdown-gpios gpio4 RK_PA7 GPIO_ACTIVE_HIGH; vbat-supply vcc3v3_sys; vddio-supply vcc_1v8; }; };这里shutdown-gpios就是控制蓝牙模块电源使能的引脚vbat-supply和vddio-supply是对应电源轨。max-speed建议先设成115200或921600验证通路确认稳定后再往2M、4M提不要一上来就上4M不然排查起来很难分清是驱动问题还是信号完整性问题。还有一点pinctrl状态里如果其他外设复用了同一个引脚在设备树里不会直接报错但引脚就是不出波。所以确认UART引脚的复用状态一个很有效的办法是在内核启动后检查/sys/kernel/debug/pinctrl/pinctrl-handles看当前uart1节点是否占用到了正确引脚。3. 驱动实现的关键环节3.1 内核配置与蓝牙子系统选型编译内核之前先打开蓝牙相关的配置项。在RK3568 SDK的内核源码目录执行make ARCHarm64 menuconfig需要确保以下选项开启CONFIG_BTy蓝牙核心协议栈CONFIG_BT_HCIUARTyHCI UART传输层驱动CONFIG_BT_HCIUART_H4yH4协议支持CONFIG_BT_HCIUART_BCMy博通/正基系列模块的hciattach或serdev支持CONFIG_SERIAL_DEVy或CONFIG_SERIAL_DEV_BUSyserdev总线支持CONFIG_BT_BNEPm、CONFIG_BT_HIDPm如果需要蓝牙网络或HID设备这是后话CONFIG_PINCTRL_RK3568y引脚控制驱动正常SDK已经默认开启值得留意的是CONFIG_BT_HCIUART_BCM它让内核的hci_uart驱动识别博通系蓝牙芯片并在加载固件、设置波特率时走专用流程。如果你的模块是瑞昱的RTL8821、RTL8761等注意还要打开CONFIG_BT_HCIUART_RTL。不同厂家的加载时序差异很大选错驱动最典型的现象是hci0能注册成功但firmware一直load失败。配置完成后重新编译make ARCHarm64 rk3568_defconfig make ARCHarm64 -j16RK3568的内核镜像通常叫boot.img或者kernel.img具体看SDK的编译脚本。我习惯把新内核和dtb一起打成boot.img烧录这样设备树改动和内核改动一起生效一次开机就能验证。3.2 serdev和hciattach两种驱动路径怎么选这里有个容易搞混的地方蓝牙驱动到底是内核自动识别还是用户态脚本去挂载。传统做法是hciattach。你编译一个带CONFIG_BT_HCIUART的内核启动后用命令行工具打开串口设备并挂载蓝牙协议hciattach /dev/ttyS1 any 1500000 flowany告诉驱动自动识别模块类型如果是博通系会自动走BCM协议加载固件如果识别不了可以显式写bcm43xx或rtl_h5等参数。1500000是初始波特率很多模块固件加载后还会再切到max-speed指定的高速率。flow表示启用硬件流控建议必加。hciattach的优点是从内核到用户态链路清晰出问题时日志容易定位缺点是要你在启动脚本里手动处理还得保证固件文件已经放到/lib/firmware对应路径。另一个方式是serdev即设备树里把蓝牙作为UART子节点内核通过compatible匹配到对应驱动直接在驱动里面完成固件加载、波特率切换和协议初始化不需要用户态干预。对于量产设备我强烈建议走serdev因为少了用户态脚本出错的机会开机就自动注册hci0非常干净。在RK3568上如果你的内核和SDK驱动支持serdev匹配比如brcm,bcm43438-bt或realtek,rtl8821-bt这种compatible就优先用serdev。如果SDK没有现成serdev驱动也不用纠结hciattach方案完全能用很多量产固件也是这么干的。这里给一个实用经验如果你用hciattach建议写成systemd服务[Unit] DescriptionBluetooth attach Aftersystemd-modules-load.service [Service] Typeoneshot ExecStartPre/usr/bin/hciattach /dev/ttyS1 any 115200 flow ExecStart/usr/bin/hciattach /dev/ttyS1 any 1500000 flow RemainAfterExityes [Install] WantedBymulti-user.target开机后检查一下服务状态和服务日志能省掉你不少排查时间。3.3 蓝牙电源控制与复位时序很多蓝牙驱动问题其实不是UART通信问题而是电源时序问题。AP6212、AP6256这类模块对供电和复位时序是有要求的先供VDDIO再供VBAT然后拉高BT_REG_ON。如果你的模块用GPIO直接控制BT_REG_ON那设备树里shutdown-gpios或者enable-gpios的极性一定要对。有些模块是低有效——GPIO拉低时模块使能有些是高有效。这个极性搞反了模块看起来有电但主控就是跟它通信不上dmesg里通常是hci_uart_tty_open正常但发命令没响应。你可以先用示波器或者万用表量一下BT_REG_ON电平再对照电路图确认GPIO极性。没有示波器的话先在用户态把GPIO导出来手动翻转通过模块的上下电电流变化来判断是否真的使能了。另外如果模块有主控唤醒线Host Wake/BT_WAKE_HOST记得接到SoC的一个普通GPIO并配置为中断输入这样蓝牙模块有事件时能主动唤醒主控避免主控在低功耗睡眠时响应延迟或丢事件。这个不是必须的但做低功耗产品必须处理否则你会看到蓝牙明明连着主机休眠后就断线。3.4 内核蓝牙协议栈的注册流程硬件和驱动都到位后内核蓝牙子系统的注册流程大概是这样的hci_uart驱动初始化注册一个line discipline。某个用户态进程hciattach或serdev驱动打开串口设备设置波特率、流控。驱动根据模块类型BCM/RTL/其他走对应的初始化序列通常是下载固件 - 模块重启 - 重新设置高速波特率。带宽协商完成后蓝牙核心接收到来自模块的HCI Command Complete事件此时会注册一个hci0设备。用户态BlueZ的bluetoothd监听到hci0出现开始控制它。所以判断蓝牙链路是否通了第一个信号就是/sys/class/bluetooth/hci0/是否存在或者运行hciconfig -a看有没有设备列表。如果hci0出现了但状态是DOWN不要慌先执行hciconfig hci0 up再运行hciconfig -a看状态。如果是UP但扫描不到设备那就是另一个层面的问题了。关于这些我放在第5节详细说。4. 实操从编译到上电调试4.1 设备树修改与UART打开设备树是RK3568平台绕不开的一环UART能不能用蓝牙能不能注册一半取决于设备树。我项目的板级dts路径一般是arch/arm64/boot/dts/rockchip/rk3568-xxx.dts。在里面找到UART1节点确认状态uart1 { status okay; };默认SDK里很多UART是disabled你不打开的话后面哪怕蓝牙驱动全编进去了串口也没有波形。如果你在设备树里已经把蓝牙子节点也写了并且驱动支持serdev那么这一步不需要额外用户态脚本。如果暂时不想动serdev可以先不管子节点直接在用户态用hciattach。两种方式选一种就够了不要两个都用否则同一个串口会被两个进程抢后果就是莫名其妙的Device or resource busy。编译并烧录boot.img之后启动系统先确认串口设备节点是否存在ls -l /dev/ttyS*RK3568的UART1通常对应/dev/ttyS1但也不一定具体看SDK的aliases。如果/dev下看不到说明内核没把这个串口注册上回头检查设备树status和pinctrl。再做一个非常关键的验证把UART1的TX和RX短接起来在用户态做回环测试。stty -F /dev/ttyS1 115200 raw echo hello /dev/ttyS1然后另开一个终端读cat /dev/ttyS1如果TX/RX短接后能读到hello说明串口通路没问题。注意这里不能短接蓝牙模块的TX/RX要让UART独占回路这样测才不会跟模块相互干扰。这个测试能筛掉一半的硬件问题。4.2 蓝牙固件加载方法蓝牙模块上电默认是低速运行并不能马上对外提供完整蓝牙服务。它需要主控把固件下载到模块内部RAM这时模块才会进入正常工作状态。固件加载是蓝牙驱动最关键的环节。如果你的内核里用了brcm,bcm43438-bt这类serdev驱动固件路径一般在/lib/firmware/brcm/下面比如BCM43438A1.hcd、BCM4345C0.hcd。实际加载哪个文件由驱动根据模块型号和/sys/class/bluetooth/hci0/的信息决定。你只要确保固件文件和compatible对应模块型号匹配即可。如果是hciattach方案博通系的加载一般这样hciattach /dev/ttyS1 bcm43xx 115200 flow它会在驱动内部完成“下载hcd固件 - 模块重启 - 切换波特率”的流程。注意bcm43xx不是指芯片一定是BCM43438正基AP6212的蓝牙部分也是博通内核协议走的是同一个。所以用bcm43xx兼容性很强。瑞昱系模块则不太一样比如RTL8821它需要两个固件文件rtl_bt/rtl8821cu_rom.bin和rtl_bt/rtl8821cu_fw.bin。加载流程不是hciattach直接触发而是驱动和模块握手后驱动自动从/lib/firmware读取。你在用户态能看到的是dmesg里出现类似Bluetooth: hci0: RTL: firmware file rtl_bt/rtl8821cu_fw.bin loaded之类的日志。如果固件加载失败最常见的是固件文件缺失或版本不匹配。排查时先用dmesg | grep -i firmware看内核日志再用ls /lib/firmware/brcm /lib/firmware/rtl_bt确认文件是否存在。文件放好后可能需要重启蓝牙模块——最简单的方式是重启系统。4.3 用bluetoothctl、btmgmt验证整条链路固件加载成功、hci0注册且UP之后就要验证整条链路是否真的能工作。我一般按顺序跑下面几个命令。先看设备基本信息和控制状态hciconfig -a输出里能看到BD Address、Manufacturer、State。如果BD Address全是00:00:00:00:00:00说明固件没加载或者驱动没拿到MAC地址这时候扫描功能大概率不正常。再看控制器索引btmgmt info这条命令比hciconfig更现代能显示固件版本、厂商、支持的蓝牙规范版本。如果报错先看是No such device还是Operation not permitted后者多半是权限问题需要root或加入bluetooth组。然后扫描周围设备先来经典蓝牙扫描hcitool scan再来低功耗扫描hcitool lescan如果扫描出若干设备说明射频、协议栈、驱动全部通了。如果hcitool卡住不返回或者一直空转就要进入下一节的排查了。我用的时候更习惯bluetoothctl因为它交互式比较直观bluetoothctl进入后依次执行power on scan onscan on后能看到一大堆周边设备说明链路完全正常。接下来还可以做配对和连接测试这些就不是驱动层的问题了而是应用层行为。5. 常见问题与排查方法5.1 蓝牙设备不出现的排查链路这是遇到最多的问题开机后dmesg一点蓝牙痕迹都没有ls /sys/class/bluetooth也是空的。我的排查套路是三步走。第一步查UART是否真的打开。运行cat /proc/tty/driver/serial看对应串口的tx/rx计数有没有变化。如果TX有计数、RX为0说明主控发出去了模块没回如果两个都是0说明根本没在通信——大概率是UART节点没打开或者引脚没配。第二步查使能GPIO。确认设备树里shutdown-gpios、vbat-supply、vddio-supply这些属性都被驱动正常获取。在驱动里如果失败日志会留在dmesg所以也建议加打印调试。没有日志的时候直接用万用表量GPIO电平按设备树设的极性看是否处于使能状态。第三步查固件加载。如果hci0出现了但后面的服务起不来就用dmesg | grep -i -E bluetooth|hci|firmware把所有相关日志拉出来大多数固件加载问题都会在日志里留下明确提示比如Direct firmware load failed。这三步走完90%“设备不出现”的案例都能定位到具体环节。5.2 蓝牙数据传输错乱波特率与流控问题有时候hci0能注册但连接后数据传输不稳定经常断或者传文件非常慢。这种时候我第一反应是波特率太高或者流控没配对。UART跑蓝牙波特率不是越高越好。模块和主控都要支持同一个高速波特率而且PCB走线、电容耦合都会影响高速信号质量。AP6212一般推荐2M或3M4M也能跑但要求主控UART时钟稳定走线不能太长。我踩过的坑是设备树里写了max-speed 4000000但模块初始固件加载用的是115200加载完后总线切到4M结果偶尔在连接大流量时会触发内核日志里的hci0: command 0x0c29 tx timeout。后面把max-speed降到20000002M这个问题就消失了。流控方面确认驱动打开串口时设置了CRTSCTSstty -F /dev/ttyS1 -a输出里要有crtscts关键字。没有的话在hciattach命令行加flow或者如果是serdev驱动在设备树和驱动里检查是否把CRTSCTS标志设置到了uart_port。硬流控没打开高速传输必然丢字节H4的包边界一旦错位整个HCI链路就废了。还有一个细节容易被忽略蓝牙模块的RTS/CTS方向。模块RTS要求接主控CTS、模块CTS接主控RTS交叉接错的情况下低速偶尔能通高速必现错误。这种硬件接错在芯片引脚间距小的板子上特别容易发生建议用万用表导通档顺着PCB走线确认。5.3 扫描不到设备的射频与协议因素如果hci0正常、UP正常但hcitool scan扫不到任何设备先别急着怀疑驱动先解决两个问题天线和发射功率。天线没接好或者天线周围有屏蔽罩会导致灵敏度极差隔着30厘米都扫不到设备。比如我的板子用IPEX外接天线最初调试时天线没卡到位扫描列表一片空白卡了半天最后才发现是天线接头松了。这个环节一定先自查硬件。再一个是蓝牙地址。扫描阶段主控要发广播和查询请求如果模块的MAC地址是全0有些协议栈或对端设备不会响应。可以尝试用btmgmt public-addr或hciconfig hci0 bdaddr重新设置一个合法的本地地址。另外蓝牙规范版本不匹配也会影响扫描结果。比如模块是蓝牙5.0但你用的是一个蓝牙4.0测试仪两边经典蓝牙的连接参数不匹配可能出现偶尔能扫到、点对点就是连不上的情况。这时候换一个手机或蓝牙耳机做对端测试能快速区分是主控问题还是对端兼容性问题。如果以上都没问题还可以用发射功率排查。有些模块支持用hcitool cmd 0x08 0x0009这类HCI厂商命令调整发射功率但不同芯片差异大不建议在生产环境乱调。正常情况默认发射功率足够覆盖几米内的设备。5.4 常见问题速查表现象直接原因排查方向hci0不出现串口未使能/GPIO未配置/固件缺失查设备树、查使能GPIO、dmesghci0出现但DOWN模块未完成初始化hciconfig hci0 up看报错扫描无结果天线、MAC、发射功率查硬件连接、设合法BD Address配对后掉线波特率过高/流控没开/电源噪声降低max-speed、强制CRTSCTSdmesg报tx timeoutHCI链路超时查UART电压、走线长度、波特率systemd服务启动失败串口被占用/用户态脚本错误确认无其他进程打开ttySx这个表基本覆盖了我做UART蓝牙调试遇到的大部分问题你可以保存下来。6. 经验与扩展建议做RK3568 UART蓝牙调试我最大的体会是不要迷信驱动代码先把硬件链路打通。这里的“硬件链路”指的是从主控引脚到模块引脚之间所有连接的连通性、电平和时序。很多时候你觉得是内核驱动问题最后发现是GPIO极性反了或者天线没接好这一类问题用驱动代码怎么查都查不出来必须回到万用表和示波器上。另一个实用建议是尽量早地在用户态把串口通信验证了再往内核和协议栈推。先stty设置串口然后直接往串口写几个字节用逻辑分析仪或者示波器看波形确认TX有信号、波特率对不对。这一步可能只需要十分钟但它能把驱动层和物理层的问题彻底分开省下后面一大把排查时间。如果你想在这个项目上继续深挖有几个方向值得做把蓝牙和WiFi的共存机制做起来。很多模块是二合一蓝牙和WiFi共用天线或需要协同避让这部分需要在设备树和固件层面配置否则两个功能同时工作时吞吐会互相拖垮。做低功耗待机方案。UART蓝牙在主机睡眠时会走特定的HCI命令进入休眠需要配置主机唤醒引脚和系统Wakeup Source这是一个完整的电源管理课题。量产阶段的自动校验。建议写一个启动脚本在系统起来后自动ping一次hci0扫描一个固定的BLE广播设备结果上报给测试程序。这样产线能快速判断蓝牙功能是否正常比人工看日志高效得多。最后再分享一个小技巧调蓝牙驱动的时候一定把内核日志级别调到最高并且加上时间戳。dmesg -T看当期日志dmesg -w实时跟日志拿到完整的时间线后再分析问题你会有一种豁然开朗的感觉。我已经不止一次靠日志里的毫秒级时间线定位到了是电源时序晚了几毫秒导致的偶发初始化失败。这个项目做完之后我对UART蓝牙的整个链路算是彻底摸透了。从设备树到协议栈再到用户态工具每一层都踩过坑也把这个过程中好用的命令和排查思路沉淀成了自己的清单。希望这篇文章能让你少走一些弯路。
返回列表