4、BLE协议栈架构:物理层、链路层、HCI、L2CAP、ATT、GATT,各层职责与数据流
各位同学,咱们今天聊聊BLE协议栈。说实话,我刚入行那会儿,看到这七层八层的协议栈,头都大了。但干久了你会发现,这玩意儿就像剥洋葱,一层一层剥开,其实没那么玄乎。
BLE协议栈,说白了就是一套规矩。它规定了数据怎么从你的手机APP,一路跑到那个小小的蓝牙信标里。我习惯把它分成三大部分:Controller(控制器)、Host(主机)和Application(应用)。咱们今天重点讲中间的Host层,因为这是做嵌入式开发最常打交道的部分。
4.1 物理层(PHY)—— 最底层的“搬运工”
物理层是协议栈的最底层,它负责最原始的数据收发。你想想看,数据在空气中传播,靠的就是电磁波。物理层就是干这个的——把0和1变成无线电信号发出去,再把收到的无线电信号变回0和1。
BLE工作在2.4GHz ISM频段,一共40个信道。其中3个是广播信道(37、38、39),剩下37个是数据信道。为什么这么设计?我刚开始也纳闷,后来做项目才明白——广播信道专门用来发现设备和建立连接,数据信道用来传业务数据。这样分工明确,效率高。
- 调制方式:GFSK(高斯频移键控)
- 数据速率:1Mbps(BLE 4.x/5.0),2Mbps(BLE 5.0+)
- 发射功率:通常-20dBm到+10dBm
- 接收灵敏度:典型值-90dBm到-97dBm
嗯,这里要注意。物理层的性能直接决定了你的通信距离和稳定性。我曾经在一个智能货架项目里,因为天线匹配没做好,导致通信距离只有5米。后来重新调了匹配电路,距离直接翻倍。所以,别小看物理层,它是一切的基础。
4.2 链路层(LL)—— 连接的“调度员”
链路层在物理层之上,它负责管理设备之间的连接状态。说白了,就是决定什么时候发数据、什么时候收数据、什么时候睡觉省电。
链路层定义了五种状态:
- Standby(待机):啥也不干,最省电
- Advertising(广播):向外喊话,“我在这儿,快来连我”
- Scanning(扫描):竖起耳朵听,看周围有没有设备在广播
- Initiating(发起连接):听到广播后,主动发起连接请求
- Connection(已连接):连接建立成功,开始传数据
我记得有一次调试一个货架标签,标签老是连不上网关。查了半天,发现是广播间隔设得太短,导致网关来不及扫描。后来把广播间隔从20ms调到100ms,问题就解决了。这就是链路层参数调优的典型场景。
4.3 HCI(主机控制器接口)—— 上下沟通的“翻译官”
HCI是Controller和Host之间的桥梁。Controller负责物理层和链路层,Host负责上层协议。这两部分通常在不同的芯片上,或者在同一芯片的不同核心上。HCI就是它们之间的通信协议。
HCI定义了三种数据传输方式:
- 命令(Command):Host发给Controller的指令
- 事件(Event):Controller回复Host的通知
- 数据(Data):实际传输的应用数据
实际项目中,如果你用的是单芯片方案(比如Nordic的nRF52832),HCI是内部实现的,你基本感觉不到它的存在。但如果你用双芯片方案(比如主控MCU + 蓝牙模块),那HCI就是通过UART或USB传输的。我建议你在调试时,把HCI日志打开,能看到很多底层细节。
4.4 L2CAP(逻辑链路控制与适配协议)—— 数据的“分拣员”
L2CAP在HCI之上,它负责把上层的数据拆分成适合底层传输的小包,或者把底层的小包组装成上层需要的大包。它还负责多路复用——让多个上层协议共用同一条链路。
L2CAP的主要职责:
- 分段与重组:上层数据太大?拆成小包发。收到小包?拼回去。
- 多路复用:ATT、SMP等协议都通过L2CAP传输,靠CID(通道ID)区分。
- 流量控制:防止发送太快,接收方来不及处理。
你想想看,如果没有L2CAP,上层协议就得自己处理分包和重组,那得多麻烦。L2CAP把这些脏活累活都干了,上层只管发数据就行。
4.5 ATT(属性协议)—— 数据的“读写器”
ATT是BLE协议栈里最核心的协议之一。它定义了一套客户端-服务器模型,用来读写“属性”。属性是什么?说白了就是一段数据,比如温度值、开关状态、设备名称等。
ATT定义了六种操作:
| 操作类型 | 说明 | 实际场景 |
|---|---|---|
| Read Request/Response | 客户端读服务器属性 | 手机读取货架标签的温度值 |
| Write Request/Response | 客户端写属性,需要确认 | 手机设置标签的广播间隔 |
| Write Command | 客户端写属性,不需要确认 | 批量下发商品价格更新 |
| Notify | 服务器主动通知客户端 | 标签检测到货架震动,主动上报 |
| Indicate | 服务器主动指示,需要确认 | 标签电量低,需要确保手机收到 |
我个人习惯用Notify来传周期性数据,比如温度、湿度。用Indicate传重要事件,比如报警、错误。为什么?因为Notify不需要确认,效率高;Indicate需要确认,可靠性高。但Indicate会慢一些,因为要等确认。
4.6 GATT(通用属性协议)—— 数据的“组织者”
GATT在ATT之上,它定义了数据的组织方式。ATT只管读写属性,但属性怎么组织、怎么分类、怎么描述,是GATT的事。
GATT引入了几个核心概念:
- Service(服务):一组相关属性的集合。比如“电池服务”包含电量百分比、电池状态等属性。
- Characteristic(特征):一个具体的属性值,包含值、描述符、配置等。
- Descriptor(描述符):对特征的额外描述,比如单位、范围、配置信息。
举个例子,智能货架上的温湿度传感器:
- 服务:环境监测服务(UUID: 0x181A)
- 特征1:温度值(UUID: 0x2A6E),可读、可通知
- 特征2:湿度值(UUID: 0x2A6F),可读、可通知
- 描述符:客户端特征配置(CCCD),用来开启或关闭通知
4.7 数据流全景——从APP到芯片,数据怎么走?
好了,各层都讲完了。咱们串起来看看,一条数据从手机APP到货架标签,到底经历了什么?
- APP层:你调用了一个API,比如“写入温度阈值”。
- GATT层:把“写入温度阈值”封装成一个GATT Write Request,找到对应的Characteristic。
- ATT层:把GATT请求转成ATT Write Request,加上属性句柄。
- L2CAP层:把ATT包拆分成适合底层传输的L2CAP包,加上CID。
- HCI层:把L2CAP包封装成HCI数据包,通过UART或USB发给Controller。
- 链路层:把HCI数据包封装成链路层数据包,加上连接句柄、序列号等。
- 物理层:把链路层数据包调制成无线电信号,通过天线发出去。
反过来,标签收到数据后,沿着同样的路径反向解析,最终APP就能读到标签的响应。
我刚开始做BLE开发时,总觉得这些层很抽象。后来在调试一个货架项目时,用抓包工具(比如Ellisys或nRF Sniffer)看了一次空中数据包,才真正理解了各层的关系。我建议你也试试,亲眼看到数据包在各层之间的流转,比看十遍文档都管用。
- PHY:负责物理信号的收发
- LL:负责连接管理和状态机
- HCI:负责Host和Controller的通信
- L2CAP:负责数据的分段、重组和多路复用
- ATT:负责属性的读写操作
- GATT:负责数据的组织和服务定义
下一章,咱们聊聊实际项目中怎么配置这些协议栈参数,以及常见的坑有哪些。到时候我会分享一个我踩过的雷——因为GATT服务配置错误,导致手机端扫描不到设备,折腾了两天才找到原因。嗯,到时候细说。