BLE协议栈架构:分层模型与数据流
聊BLE协议栈之前,我先问大家一个问题:为什么蓝牙这么复杂的通信协议,最终能被几十亿设备用起来?
答案其实就四个字——分层设计。每一层只干自己的事,层与层之间通过标准接口通信。这样硬件厂商可以专注做底层,应用开发者只管上层API,互不干扰。
我个人习惯把BLE协议栈想象成一栋三层小楼:Controller(控制器层)、Host(主机层)、Application(应用层)。今天我们就一层一层拆开看。
核心观点:BLE协议栈的分层模型,本质上是把「无线收发」和「数据业务」解耦。Controller管物理信号,Host管逻辑连接,Application管用户场景。
一、协议栈三层总览
先看一张整体架构图,我亲手画的,帮你建立全局印象:
这张图我建议你保存下来。每次调试BLE问题,先定位是哪一层出的问题,能省一半时间。
二、Controller层:最底层的硬功夫
Controller层是离硬件最近的一层,通常由蓝牙芯片固件实现。它包含两个核心模块:PHY(物理层)和LL(链路层)。
2.1 PHY(物理层)
PHY负责最原始的比特流收发。说白了,就是决定用什么频率、什么调制方式、多大功率把0和1发出去。
- 工作频段:2.4GHz ISM频段,40个信道(37个数据信道 + 3个广播信道)
- 调制方式:GFSK(高斯频移键控),简单可靠
- 速率:1Mbps(LE 1M PHY)或2Mbps(LE 2M PHY,蓝牙5.0引入)
- 发射功率:典型0dBm到+10dBm,视芯片而定
避坑指南:我曾经在一个穿戴项目里,PHY层发射功率设到了+8dBm,结果功耗超标。后来发现场景只需要3米通信距离,降到0dBm完全够用,功耗直接砍半。别盲目开大功率。
2.2 LL(链路层)
LL层是BLE协议栈的大脑。它负责:
- 状态机管理:Standby、Advertising、Scanning、Initiating、Connection五个状态
- 信道选择:自适应跳频(AFH),避开干扰信道
- 包处理:前导码、访问地址、PDU、CRC的组装与解析
- 连接管理:连接建立、连接参数更新、断开连接
你想想看,两个BLE设备怎么知道什么时候该听、什么时候该说?就是LL层在背后调度。它用跳频+时间槽的方式,让主从设备在约定好的时间和频率上碰头。
三、HCI:连接Controller和Host的桥梁
HCI(Host Controller Interface)不是一层协议,而是一个标准接口。它定义了Host和Controller之间怎么通信。
实际项目中,HCI的物理形式有三种:
| 接口类型 | 典型场景 | 特点 |
|---|---|---|
| UART(H4) | 双芯片方案(Host MCU + 蓝牙芯片) | 简单、通用、速率受限 |
| USB(H2) | PC蓝牙适配器、开发板 | 高速、即插即用 |
| SPI/SDIO | 高性能嵌入式系统 | 吞吐量大、延迟低 |
注意:如果你用的是单芯片方案(比如Nordic nRF52、TI CC26xx),HCI是内部接口,不需要你操心。但双芯片方案里,HCI的波特率、流控、缓冲区大小经常是坑。我遇到过UART波特率设太高导致丢包,降到115200就稳了。
四、Host层:协议栈的中枢
Host层跑在MCU上,通常由蓝牙协议栈软件实现。它包含五个关键模块:L2CAP、SM、ATT、GATT、GAP。
4.1 L2CAP(逻辑链路控制与适配协议)
L2CAP是Host层的交通调度员。它负责:
- 协议复用:区分上层是ATT流量还是SM流量
- 分段重组:大包拆小、小包拼大
- MTU管理:协商最大传输单元(默认23字节,可协商到更大)
嗯,这里要注意:BLE的L2CAP和经典蓝牙的L2CAP不完全一样。BLE的L2CAP简化了很多,不支持面向连接的信道(除了LE Credit Based Flow Control模式)。
4.2 SM(安全管理)
SM负责配对、绑定、加密。它定义了三种配对方式:
- Just Works:无用户交互,安全性最低
- Passkey Entry:输入6位数字PIN码
- Out of Band(OOB):通过NFC等带外方式交换密钥
我记得有一次做门锁项目,客户要求必须用Passkey Entry。结果发现用户每次开门都要输6位密码,体验极差。后来改成了OOB+手机NFC碰一下,用户满意度直线上升。选配对方式一定要考虑实际场景。
4.3 ATT(属性协议)
ATT定义了客户端-服务器通信模型。服务器端暴露属性(Attribute),客户端读写这些属性。每个属性包含:
- 句柄(Handle):16位唯一标识
- 类型(UUID):16位或128位,标识属性含义
- 权限:读、写、加密读、加密写等
- 值(Value):实际数据
4.4 GATT(通用属性规范)
GATT在ATT之上定义了数据组织方式。它把属性组织成:
- Service(服务):一组相关功能的集合(如电池服务、心率服务)
- Characteristic(特征):一个具体的数据点(如电池电量百分比)
- Descriptor(描述符):特征的附加信息(如单位、配置)
说白了,GATT就是一张数据表格。设备A告诉设备B:「我的电池服务在句柄0x0010,你读它就能拿到电量。」
4.5 GAP(通用访问规范)
GAP定义了设备如何被发现、如何建立连接。它规定了四种角色:
| 角色 | 职责 | 典型设备 |
|---|---|---|
| Broadcaster(广播者) | 只发广播,不连接 | 信标(Beacon) |
| Observer(观察者) | 只扫描广播,不连接 | 扫描仪 |
| Peripheral(外设) | 发广播,等待连接 | 心率带、温度传感器 |
| Central(中心) | 扫描并发起连接 | 手机、平板 |
五、数据流在协议栈中的传递过程
理解了各层职责,我们来看一个完整的数据流。假设手机(Central)要读取心率带(Peripheral)的心率值。
整个流程可以概括为「层层封装,层层解析」:
- 手机App调用GATT API,发起「读取心率特征」请求
- GATT层找到对应Service和Characteristic,交给ATT层
- ATT层构造Read Request PDU(包含句柄0x0010)
- L2CAP层给ATT PDU加上L2CAP头(CID=0x0004表示ATT)
- HCI层封装成HCI数据包,通过UART/SPI发给Controller
- LL层组装链路层包(加前导码、访问地址、CRC),交给PHY
- PHY层GFSK调制,通过天线发射出去
- 心率带收到后,逆向解析:PHY解调→LL校验→HCI拆包→L2CAP提取→ATT解析→GATT查表→App返回心率值72 bpm
关键点:每一层只关心自己的头信息。LL层不看ATT数据,ATT层不管L2CAP分段。这种「各扫门前雪」的设计,让协议栈的维护和升级变得非常灵活。
六、实际项目中的分层思维
最后分享一个我自己的经验。有一次调试一个BLE门锁,发现手机连上后经常断连。一开始我怀疑是射频干扰,折腾了三天天线匹配。后来静下心来看日志,发现是LL层的连接间隔(Connection Interval)设得太小,手机端处理不过来。
你看,如果不懂分层,你可能会在PHY层瞎忙活。但如果你知道连接参数属于LL层,直接改连接间隔从7.5ms到30ms,问题就解决了。
我的建议:遇到BLE问题,先问自己三个问题:
- 是连不上?(大概率GAP/LL层问题)
- 是连上后数据读不到?(大概率GATT/ATT层问题)
- 是连上后频繁断连?(大概率LL层连接参数问题)
定位到层,再深入查,效率翻倍。
好了,协议栈的分层模型就讲到这里。下一层一层拆开看,你会发现BLE其实没那么神秘。后面几章我们会深入每个模块的细节,包括怎么配广播包、怎么写GATT服务、怎么处理配对加密——都是实战干货。