2. BLE协议栈架构:从物理层到应用层的七层解读
BLE协议栈,说白了就是一套分层的通信规则。我刚开始接触蓝牙时,总觉得这七层结构太抽象,直到亲手调过几个项目,才明白每一层都有它存在的道理。今天咱们一层层拆开来看。
核心认知:BLE协议栈分为Controller(控制器)和Host(主机)两大部分。PHY、LL、HCI属于Controller,L2CAP、ATT、GATT、SM属于Host。这种分层设计,让硬件和软件可以独立演进。
2.1 物理层(PHY)—— 信号的起点
PHY层负责最原始的比特流收发。它工作在2.4GHz ISM频段,把数字信号调制成无线电波发出去,再把收到的无线电波解调回数字信号。
BLE在PHY层有三大关键参数:
- 工作频段:2.400 - 2.4835 GHz,共40个信道
- 调制方式:GFSK(高斯频移键控),简单可靠
- 速率:1Mbps(LE 1M PHY)或2Mbps(LE 2M PHY,蓝牙5.0引入)
我的经验:做低功耗产品时,我习惯优先选LE 1M PHY。虽然2M速率更快,但接收灵敏度会差3-4dBm,传输距离反而缩短。如果你的产品不追求高速率,1M更省电、更稳定。
2.2 链路层(LL)—— 连接的指挥官
LL层是协议栈里最忙的一层。它负责设备发现、连接建立、数据包收发、重传、加密等核心任务。说白了,LL层决定了两个蓝牙设备能不能「对上话」。
LL层定义了五种设备角色:
| 角色 | 说明 | 典型场景 |
|---|---|---|
| Advertiser(广播者) | 发送广播包,宣告自己的存在 | 传感器、信标 |
| Scanner(扫描者) | 监听广播包,发现设备 | 手机、网关 |
| Initiator(发起者) | 主动发起连接请求 | 手机连接设备 |
| Master(主设备) | 连接后控制时序 | 手机、中心节点 |
| Slave(从设备) | 连接后跟随主设备时序 | 传感器、外设 |
LL层还有一个关键概念——连接事件(Connection Event)。主从设备在约定的时间点交换数据包,其余时间可以休眠。这就是BLE低功耗的根基。
避坑指南:我曾经在一个项目中,把连接间隔设成了7.5ms(最小值),结果设备功耗飙升到2mA以上。后来改成50ms,功耗降到30μA,功能完全不受影响。记住:连接间隔越小,功耗越大,吞吐量也越大。要根据实际需求权衡。
2.3 主机控制接口(HCI)—— 软硬件的桥梁
HCI是Controller和Host之间的通信接口。它定义了一套标准命令和事件,让上层软件可以控制底层硬件。
HCI的传输方式常见有三种:
- UART:最常用,简单可靠,速率可达4Mbps
- USB:用于蓝牙dongle,即插即用
- SPI/SDIO:用于高速场景,比如音频传输
实际开发中,我们很少直接操作HCI层。芯片厂商的SDK已经封装好了。但理解HCI有助于排查问题——比如用HCI log抓取空中包,是调试蓝牙连接的利器。
小技巧:我调试蓝牙连接问题时,经常用HCI的LE_ReadRemoteFeatures命令查看对方设备支持哪些特性。这比猜来猜去高效多了。
2.4 逻辑链路控制与适配协议(L2CAP)—— 数据的分包与重组
L2CAP层位于Host的最底层。它负责把上层的大数据包拆分成LL层能处理的小包,反过来把收到的小包重组回大包。
L2CAP的核心功能:
- 分段与重组(SAR):MTU(最大传输单元)默认23字节,但L2CAP可以协商更大的MTU,比如247字节
- 协议多路复用:区分上层是ATT、SM还是其他协议
- 流控与重传:增强型L2CAP支持可靠传输
举个例子:你的应用要发一个100字节的数据包。L2CAP会把它拆成多个23字节的片段,交给LL层逐个发送。接收端再拼回去。
关键参数:MTU大小直接影响吞吐量。我建议在连接建立后,主动协商MTU到247字节(BLE 4.2+支持)。这样一次连接事件可以传更多数据,减少唤醒次数,反而更省电。
2.5 属性协议(ATT)—— 数据的读写规则
ATT层定义了设备之间如何读写数据。它把数据组织成「属性(Attribute)」,每个属性有句柄、UUID、权限和值。
ATT有六种操作类型:
| 操作 | 方向 | 说明 |
|---|---|---|
| Request | Client → Server | 请求数据 |
| Response | Server → Client | 回复请求 |
| Command | Client → Server | 写数据,不需要回复 |
| Notification | Server → Client | 通知数据,不需要确认 |
| Indication | Server → Client | 指示数据,需要确认 |
| Confirm | Client → Server | 确认指示 |
ATT定义了两种角色:
- Client(客户端):发起读写请求的一方
- Server(服务端):存储属性并响应请求的一方
一个设备可以同时是Client和Server。比如手机连接心率带,手机是Client,心率带是Server;但手机也可能作为Server提供配置信息。
2.6 通用属性配置文件(GATT)—— 数据的组织规范
GATT在ATT之上定义了数据的组织方式。它把属性按层级组织成:Service(服务)→ Characteristic(特征)→ Descriptor(描述符)。
举个例子,一个温度传感器GATT结构:
服务:温度测量服务 (UUID: 0x1809)
├── 特征:温度值 (UUID: 0x2A1C)
│ ├── 属性:可读、可通知
│ └── 描述符:客户端特征配置 (CCCD)
│ └── 值:0x0001(启用通知)
└── 特征:温度类型 (UUID: 0x2A1D)
└── 属性:可读
GATT定义了两种角色:
- GATT Server:提供服务和特征的一方
- GATT Client:发现和读写特征的一方
我的习惯:设计GATT服务时,我尽量用标准UUID(16位),兼容性好。自定义服务用128位UUID,但要注意避免冲突。另外,CCCD(0x2902)是通知/指示的开关,很多新手忘了配置它,导致收不到数据。
2.7 安全管理器(SM)—— 安全的守护者
SM层负责配对、绑定和加密。它确保蓝牙通信不会被窃听或篡改。
SM定义了三种配对方式:
| 配对方式 | 安全等级 | 适用场景 |
|---|---|---|
| Just Works(直接工作) | 低 | 无显示/输入能力的设备 |
| Passkey Entry(密码输入) | 中 | 有键盘或显示屏的设备 |
| Numeric Comparison(数字比较) | 高 | 双方都有显示屏 |
配对完成后,SM会生成短期密钥(STK)或长期密钥(LTK)。LTK可以存储下来,下次连接时直接绑定,不需要重新配对。
曾经踩过的坑:我做过一个门锁项目,配对后没有保存LTK,结果每次断连都要重新配对。用户反馈体验极差。后来加上绑定存储,问题解决。记住:配对是过程,绑定是结果。绑定数据要写入Flash,不能只存在RAM里。
2.8 七层协作:一次完整的数据交互
咱们串起来看一个完整流程。假设手机(GATT Client)要读取温度传感器的温度值:
- 应用层调用API读取温度特征
- GATT层构造Read Request,交给ATT
- ATT层封装成ATT PDU,交给L2CAP
- L2CAP层加上L2CAP头,可能分段,交给HCI
- HCI层封装成HCI数据包,通过UART传给Controller
- LL层在下一个连接事件中,封装成LL数据包,交给PHY
- PHY层调制成GFSK信号,通过天线发出去
温度传感器收到后,逆向处理,最终返回温度值。整个过程在几毫秒内完成。
总结:BLE协议栈的七层分工明确。PHY管信号,LL管连接,HCI做桥梁,L2CAP管数据,ATT管读写,GATT管组织,SM管安全。每一层都不可或缺。理解它们,你就能从「调库工程师」升级为「蓝牙系统工程师」。