第1章:BLE基础知识回顾

各位同学,欢迎来到《Zephyr低功耗蓝牙协议栈开发与调试实战》的第一课。

在正式开始写代码之前,我觉得有必要先带大家回顾一下BLE的基础知识。你可能会问:「老师,这些概念我都知道,能不能直接上手?」嗯,我理解这种心情。但说实话,我在Zephyr上踩过的坑,十有八九都是因为对协议栈分层理解不够深。所以,咱们花点时间把地基打牢,后面写代码会顺畅很多。

1.1 BLE协议栈分层

BLE协议栈,说白了就是一套分层的通信规范。每一层各司其职,上层依赖下层提供的服务。我个人习惯把协议栈想象成一个「俄罗斯套娃」——最里面是物理层,最外面是应用层。

下面这张表,是我自己整理的分层概览,建议你收藏一下:

层级 全称 主要职责
PHY 物理层 负责射频收发,调制解调
LL 链路层 控制连接状态、数据包重传
HCI 主机控制器接口 Host与Controller之间的通信桥梁
L2CAP 逻辑链路控制与适配协议 数据包分段重组、多路复用
SM 安全管理层 配对、绑定、加密
ATT 属性协议 读写对端设备的属性数据
GATT 通用属性协议 基于ATT定义服务、特征值的框架
GAP 通用访问协议 定义设备角色、广播、连接流程

咱们挑几个重点层聊一聊。

PHY层

PHY层就是最底层的射频硬件。BLE工作在2.4GHz ISM频段,有40个信道。其中37、38、39是广播信道,其余37个是数据信道。我记得刚开始做BLE项目时,总以为广播只在37信道发,后来用抓包工具一看,才发现三个广播信道是轮着跳的。这个细节,在调试连接稳定性时特别重要。

LL层

链路层是BLE的「大脑」。它负责管理连接状态机——待机、广播、扫描、发起连接、已连接。LL层还处理数据包的重传和确认。说白了,你发出去的数据能不能到对端,全靠LL层在背后默默干活。

重点:LL层有一个「连接事件」的概念。每次连接事件,主从设备会交换一次数据。连接间隔决定了功耗和吞吐量的平衡。我在项目中遇到过,连接间隔设得太短,设备发热严重;设得太长,数据延迟又受不了。这个后面实战章节会细讲。

HCI层

HCI是Host和Controller之间的接口。在Zephyr中,Host跑在CPU上,Controller可以是内置的,也可以是外挂的蓝牙芯片。HCI通过UART、USB或SPI传输命令和事件。如果你用的是nRF52840这样的SoC,HCI是内部通信,你基本感觉不到它的存在。但如果你像我一样,曾经把外挂的BLE模块接错UART引脚……嗯,那HCI层的调试会让你印象深刻。

L2CAP层

L2CAP负责把上层的大数据包拆成LL层能处理的小包,反过来也负责重组。它还提供多路复用——多个应用可以同时使用同一个BLE连接。举个例子,你的设备可以一边传传感器数据,一边跑OTA升级,这就是L2CAP的功劳。

SM层

安全管理层处理配对和加密。BLE的配对方式有三种:Just Works、Passkey Entry、Out of Band。Just Works最简单,但安全性最低。我建议你在产品中至少用Passkey Entry,除非你的设备真的不涉及任何敏感数据。

ATT与GATT

这两个层经常被混在一起说。ATT定义了如何读写属性——属性就是一小块数据,比如「当前温度值」。GATT则定义了这些属性怎么组织成服务和特征值。你想想看,如果没有GATT,每个厂商都自己定义数据格式,那不同品牌的设备根本没法互操作。GATT就是那个「通用语言」。

GAP层

GAP定义了设备的行为模式。它告诉别人:「我是谁,我能干什么,我怎么被发现。」GAP层还管理广播数据的格式。我见过不少新手,在广播包里塞了太多数据,导致广播包超过31字节的限制,结果扫描端收不全。这个坑,咱们后面会专门讲怎么避。

1.2 BLE角色

BLE定义了四种角色。记住,一个设备可以同时扮演多个角色。比如你的手机,既可以作为Central连接手环,也可以作为Peripheral被电脑连接。

  • Broadcaster(广播者):只发广播,不连接。典型应用是信标(Beacon)。
  • Observer(观察者):只扫描广播,不连接。比如扫描周围设备的App。
  • Peripheral(外设):发出广播,等待连接。比如心率带、温度传感器。
  • Central(中心):扫描并主动发起连接。比如手机、网关。

小提示:在Zephyr中,角色是通过配置Kconfig来设定的。你可以在prj.conf里设置CONFIG_BT_PERIPHERAL或CONFIG_BT_CENTRAL。我建议初学者先从Peripheral角色入手,因为广播和连接流程更直观。

1.3 BLE通信流程

BLE的通信流程,可以概括为四个步骤:广播、扫描、连接、数据传输。咱们一步步拆开看。

广播

Peripheral设备周期性地在三个广播信道上发送广播包。广播包包含设备地址、设备名称、以及一些自定义数据。广播间隔通常设在20ms到10.24s之间。间隔越短,被发现得越快,但功耗也越高。

我曾经做过一个项目,客户要求设备在按键后1秒内被手机发现。我一开始把广播间隔设成100ms,结果发现有时候扫描端要等好几秒才能搜到。后来查资料才发现,广播有随机延迟——每次广播前会加一个0到10ms的随机延时,目的是避免多个设备同时广播时碰撞。这个随机延迟,在某些场景下会显著拉长发现时间。解决办法是把广播间隔再缩短一点,同时配合主动扫描。

扫描

Central设备在广播信道上监听广播包。扫描分为被动扫描和主动扫描。被动扫描只收广播包;主动扫描在收到广播包后,会发送一个扫描请求,Peripheral收到后回复扫描响应数据。扫描响应可以携带额外的31字节数据,相当于广播包的「扩展包」。

注意:主动扫描会增加功耗,也会增加空中的无线冲突。如果你的设备对功耗敏感,尽量用被动扫描。我在调试一个低功耗传感器时,就是因为开了主动扫描,导致电池续航从3个月掉到了2周。后来改成被动扫描,问题解决。

连接

当Central扫描到目标设备后,会发送一个连接请求。连接请求里包含了连接间隔、从设备延迟、监督超时等参数。Peripheral收到后,双方就进入连接状态。

连接建立后,Central成为Master,Peripheral成为Slave。Master控制连接时序——每个连接事件由Master发起,Slave在指定时间窗口内回复。这个机制保证了低功耗:Slave可以在非连接事件期间进入休眠。

数据传输

连接建立后,双方通过ATT协议读写属性。GATT定义了数据的组织方式:一个服务包含多个特征值,每个特征值有属性(可读、可写、可通知等)。

数据传输有两种模式:

  • 写请求/写命令:Central主动写数据到Peripheral。
  • 通知/指示:Peripheral主动推数据给Central。通知不需要确认,速度快;指示需要确认,可靠性高。

我个人习惯用通知来传传感器数据,因为速度快、延迟低。但如果你传的是关键指令(比如「打开阀门」),那还是用指示更稳妥。

小结

这一章我们回顾了BLE协议栈的八层结构、四种角色,以及广播、扫描、连接、数据传输的完整流程。这些概念是后续所有实战内容的基础。你可能会觉得有些枯燥,但请相信我——当你真正在Zephyr上调试连接失败、数据丢包、功耗异常时,你会感谢今天花时间把这些基础搞清楚了。

下一章,我们会搭建Zephyr开发环境,然后写第一个BLE广播程序。到时候,这些理论知识就会变成实实在在的代码了。

课后思考:如果你要设计一个智能门锁,它应该扮演什么角色?广播包里应该放哪些数据?连接后如何保证指令的可靠性?带着这些问题,咱们下章见。