3. Zephyr 蓝牙初始化流程:从 HCI 驱动到 Enable 回调
蓝牙协议栈跑起来,第一步是什么?很多人上来就调 bt_enable(),结果发现要么卡死,要么回调没触发。我刚开始接触 Zephyr 蓝牙时也踩过这个坑——说白了,初始化不是一行 API 的事,而是一条完整的链路。
这条链路从硬件 HCI 驱动开始,经过 Controller 初始化、Host 层启动,最后才落到你注册的 bt_ready_cb 回调。今天我们就把它拆开,看看每一层到底干了什么。
3.1 初始化全景图
先看整体流程。我习惯把它分成四个阶段:
- HCI 驱动加载——让 CPU 能和蓝牙芯片通信(UART/USB/SPI)
- Controller 初始化——下发 HCI Reset,等待 Controller 就绪
- Host 层初始化——L2CAP、ATT、GATT、SM 等协议模块依次启动
- Enable 回调——通知应用层:协议栈已就绪,可以开始干活了
下面这张图把整个流程串起来了,建议你保存下来对照着看代码。
3.2 HCI 驱动加载:藏在设备树里的秘密
Zephyr 的 HCI 驱动加载是自动的。你只需要在设备树里配好节点,Kconfig 选对驱动类型,剩下的交给系统。
举个例子,如果你用的是 nRF52840 搭配 UART HCI:
// 设备树片段:uart0 节点上挂载 HCI
&uart0 {
compatible = "nordic,nrf-uarte";
current-speed = <1000000>;
hci-uart {
compatible = "zephyr,bt-hci-uart";
hci-uart-rts = <24>;
hci-uart-cts = <23>;
};
};
系统启动时,bt_hci_driver_init() 会被自动调用。它做了三件事:
- 初始化 UART/SPI/USB 外设
- 注册 HCI 传输层回调(收发包接口)
- 将驱动挂到蓝牙协议栈的 HCI 总线上
bt_hci_driver_init() 里加个打印,或者看系统日志里有没有 "HCI: driver registered" 字样。没有这个,后面全是白搭。
3.3 Controller 初始化:第一次握手
驱动加载完,协议栈会发第一条 HCI 命令——HCI Reset。这是蓝牙芯片的"你好,在吗?"
流程是这样的:
- Host 构造 HCI Reset 命令包
- 通过 HCI 传输层发给 Controller
- Controller 回复 Command Complete 事件
- Host 收到后,更新状态为 "Controller Ready"
这里有个容易忽略的点:HCI Reset 是同步的。Zephyr 内部用信号量等待回复,超时时间默认 5 秒。如果芯片没回应,bt_enable() 会直接返回错误。
CONFIG_BT_HCI_RESET_TIMEOUT。
3.4 Host 层初始化:协议栈的"拼图"
Controller 就绪后,Host 层开始初始化。这部分是异步的,也是整个流程里最复杂的。
Zephyr 的 Host 层初始化顺序是固定的:
| 步骤 | 模块 | 作用 |
|---|---|---|
| 1 | L2CAP | 建立逻辑链路控制层,为上层提供通道 |
| 2 | ATT | 属性协议层,处理读写请求 |
| 3 | GATT | 通用属性协议层,管理 Service/Characteristic |
| 4 | SM | 安全管理层,负责配对和加密 |
| 5 | HCI Event 处理 | 启动事件循环,开始处理 Controller 上报的事件 |
每个模块初始化时,会注册自己的回调函数到 HCI 事件分发器。比如 GATT 注册了 bt_gatt_init(),SM 注册了 bt_sm_init()。
你想想看,如果某个模块初始化失败,比如 L2CAP 没起来,那 GATT 和 SM 就算注册了也没用。所以 Zephyr 内部做了依赖检查——前一个模块初始化成功,才会继续下一个。
3.5 Enable 回调:终于轮到你了
所有模块初始化完成后,协议栈调用你注册的 bt_ready_cb。这个回调是在系统工作队列里执行的,不是中断上下文,所以你可以放心做耗时操作。
典型的回调实现:
static void bt_ready_cb(int err)
{
if (err) {
printk("蓝牙初始化失败: %d\n", err);
return;
}
printk("蓝牙协议栈已就绪\n");
// 开始广播
bt_le_adv_start(...);
}
注意:bt_enable() 返回 0 不代表蓝牙可用。必须等回调里的 err == 0,才能调用其他蓝牙 API。我见过有人在这上面栽跟头——bt_enable() 返回后立刻调 bt_le_adv_start(),结果系统崩溃。
bt_ready_cb 回调触发之后调用。在此之前,协议栈还没准备好。
3.6 初始化时序图:谁先谁后
为了让你看得更清楚,我把整个时序画出来:
3.7 常见问题与避坑指南
做项目这么多年,我总结了几条初始化阶段的常见问题:
| 现象 | 原因 | 解决办法 |
|---|---|---|
| bt_enable() 返回 -ETIMEDOUT | HCI Reset 超时,Controller 没响应 | 检查硬件连接、波特率、电源 |
| bt_enable() 返回 0 但回调没触发 | 系统工作队列没跑,或者回调被阻塞 | 检查 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE |
| 回调触发后调用 API 崩溃 | 在中断上下文调用了蓝牙 API | 确保所有蓝牙操作在线程上下文中执行 |
| 蓝牙广播不出去 | Controller 初始化成功,但 Host 层 GATT 没注册 | 检查 CONFIG_BT_GATT 是否启用 |
3.8 小结
Zephyr 蓝牙初始化,说白了就是一条从硬件到应用的链路。HCI 驱动是基础,Controller 握手是前提,Host 层拼图是核心,回调通知是终点。
我个人建议,每次开始一个新项目,先跑通初始化流程。用逻辑分析仪抓一下 HCI 数据,或者加几个打印,确认每一层都正常。这一步走稳了,后面的广播、连接、GATT 操作才有意义。