4、HCI层调试:hcitool、hciconfig、hcidump工具实战

HCI层,说白了就是主机和蓝牙控制器之间的“翻译官”。

你想想看,上层协议栈要发数据,底层芯片要收数据,中间全靠HCI命令、事件和数据包来沟通。我做了这么多年蓝牙驱动,最头疼的问题十有八九都出在这一层——不是命令发错了,就是事件没响应。

所以,掌握HCI层的调试工具,是每个蓝牙开发者的基本功。今天我就带你把 hcitoolhciconfighcidump 这三个家伙彻底搞明白。

4.1 hciconfig:先看看你的蓝牙长啥样

我个人习惯,拿到一块新的蓝牙模组,第一件事就是跑 hciconfig

它就像Linux下的 ifconfig,专门用来查看和配置蓝牙设备的状态。

# 查看所有蓝牙设备
hciconfig -a

# 输出示例:
hci0:   Type: Primary  Bus: USB
        BD Address: 00:1A:7D:DA:71:13  ACL MTU: 1021:8  SCO MTU: 64:1
        UP RUNNING
        RX bytes:12345 acl:100 sco:0 events:500 errors:0
        TX bytes:67890 acl:200 sco:0 commands:300 errors:0

这里有几个关键信息你要盯紧了:

  • UP RUNNING:设备正常启动并运行。如果显示DOWN,说明蓝牙没打开。
  • BD Address:蓝牙地址,每个设备唯一。我遇到过两次地址全是0的情况,那基本是固件没烧好。
  • ACL MTU:最大传输单元。这个值太小会影响吞吐量,我调音频传输时经常要改它。
  • errors:错误计数。如果这个数字一直涨,赶紧查硬件连接。
小技巧:hciconfig hci0 up 启动设备,hciconfig hci0 down 关闭设备。调试时经常需要重启蓝牙,我习惯写个脚本一键搞定。

4.2 hcitool:发命令、扫设备、查信息

hcitool 是我用得最多的工具。它可以直接向蓝牙控制器发送HCI命令,也能扫描周围的设备。

4.2.1 扫描周围设备

# 扫描蓝牙设备
hcitool scan

# 输出示例:
Scanning ...
        00:11:22:33:44:55       MyPhone
        AA:BB:CC:DD:EE:FF       Speaker

嗯,这里要注意:hcitool scan 只扫描经典蓝牙(BR/EDR)。如果你想扫低功耗蓝牙(BLE),得用 hcitool lescan

我曾经在一个项目里,怎么都扫不到某个BLE设备。折腾了半天,才发现自己一直在用 scan 而不是 lescan。这种低级错误,犯一次就记住了。

4.2.2 查看连接信息

# 查看当前连接
hcitool con

# 输出示例:
Connections:
        < ACL 00:11:22:33:44:55 handle 12 state 1 lm MASTER

handle 是连接句柄,后面调试数据流的时候会频繁用到。state 1 表示已连接,如果是0就是正在连接中。

4.2.3 发送原始HCI命令

这是最硬核的部分。有时候工具不提供某个功能,你就得自己拼命令。

# 发送HCI命令:读取本地版本信息
# 命令格式:hcitool cmd <ogf> <ocf> [参数]
# OGF=0x04(控制与基带命令组),OCF=0x0001(读取本地版本信息)
hcitool cmd 0x04 0x0001

# 输出示例:
< HCI Command: ogf 0x04, ocf 0x0001, plen 0
> HCI Event: 0x0e plen 12
    01 01 04 00 01 08 00 00 00 00 00 00

返回的数据怎么解析?我一般对照蓝牙核心规范(Core Spec)来查。上面这个例子,第4个字节 01 表示HCI版本号,第5个字节 08 表示LMP版本号。

警告: 发送原始HCI命令时,一定要确认OGF和OCF的值是正确的。我曾经手抖发错了参数,直接把开发板的蓝牙控制器搞挂了,只能重新烧录固件。

4.3 hcidump:抓包分析,真相只有一个

hcidump 是HCI层的抓包工具。它能捕获主机和控制器之间所有的HCI数据包,包括命令、事件、ACL数据和SCO数据。

说白了,它就是蓝牙驱动的“Wireshark”。

4.3.1 基本用法

# 抓取所有HCI数据包
hcidump -i hci0

# 输出示例:
> HCI Event: Connect Complete (0x03) plen 11
    status 0x00 handle 12 bdaddr 00:11:22:33:44:55 link_type ACL encrypt_mode 0x00

看到 status 0x00 就放心了,表示连接成功。如果返回非零值,比如 0x04(硬件故障),那就要检查硬件了。

4.3.2 保存到文件并分析

# 保存为文本格式
hcidump -i hci0 -w dump.txt

# 保存为二进制格式(可以用Wireshark打开)
hcidump -i hci0 -w dump.pklg

我个人习惯先用 -w 保存成二进制文件,然后用Wireshark打开分析。Wireshark的图形界面比命令行友好太多了,还能自动解析各种协议。

4.3.3 过滤特定类型的数据包

# 只抓取ACL数据
hcidump -i hci0 -t acl

# 只抓取SCO数据(音频)
hcidump -i hci0 -t sco

# 只抓取命令和事件
hcidump -i hci0 -t cmd
hcidump -i hci0 -t event

调试音频传输时,我经常用 -t sco 来过滤。有一次客户反馈通话有杂音,我抓了SCO数据一看,发现每隔几个包就丢一个。最后定位到是USB传输的延迟太大,换了个USB口就解决了。

4.4 实战案例:排查蓝牙连接失败

光说不练假把式。我拿一个真实案例来演示这三个工具的配合使用。

问题现象: 手机扫描不到某个蓝牙设备。

排查步骤:

  1. 先用 hciconfig 检查设备状态

    hciconfig -a
    # 发现 hci0 是 DOWN 状态

    嗯,问题找到了。设备都没启动,怎么可能扫得到?

  2. 启动设备,再次扫描

    hciconfig hci0 up
    hcitool scan
    # 还是扫不到!

    这就奇怪了。设备启动了,但就是扫不到目标。

  3. 用 hcidump 抓包看看

    hcidump -i hci0 -t cmd
    # 发现扫描命令已经发出去了
    # 但没有任何 Inquiry Result 事件返回

    这说明蓝牙控制器确实在执行扫描,但周围没有设备响应。要么是目标设备没开广播,要么是距离太远。

  4. 检查目标设备

    我拿着手机靠近开发板,再次扫描——成功了!原来是距离问题。客户把设备放在10米外的金属柜子里,信号被屏蔽了。

核心思路: 从设备状态 → 命令发送 → 事件响应,一步步缩小范围。HCI层的调试,本质上就是在验证“命令是否发出”和“事件是否返回”这两个关键点。

4.5 避坑指南

最后,分享几个我踩过的坑:

  • 权限问题: 运行这些工具通常需要root权限。如果提示权限不足,记得加 sudo
  • 多设备冲突: 如果系统里有多个蓝牙适配器(比如内置蓝牙+USB蓝牙),用 -i hci0 指定设备,别搞混了。
  • hcidump 卡死: 有时候抓包会卡住不动,按 Ctrl+C 退出就行。如果还不行,killall hcidump 强制结束。
  • 日志太多: 生产环境不要一直开着hcidump,日志文件会暴涨。我见过有人把嵌入式设备的Flash写爆了。

好了,HCI层的三个调试工具就讲到这里。下一章我们会深入ACL数据流,看看蓝牙数据到底是怎么在主机和控制器之间穿梭的。