2. BLE协议栈基础:物理层与链路层、GAP与GATT角色、连接间隔与事件

好,咱们正式开始啃协议栈。很多初学者一上来就扎进GATT profile的代码里,结果遇到休眠唤醒功耗降不下来,回头才发现是底层没吃透。我个人习惯,不管做什么蓝牙项目,先把物理层和链路层那点事理清楚,后面写应用代码心里才有底。

2.1 物理层(PHY)—— 信号怎么在空中跑

BLE工作在2.4GHz ISM频段,这个频段其实挺挤的——Wi-Fi、Zigbee、微波炉都在这里。BLE把它分成了40个信道,每个信道2MHz宽。

你可能会问:为什么是40个?不是80个?嗯,这里有个设计取舍。2MHz带宽比经典蓝牙的1MHz宽了一倍,数据速率能跑上去,但信道数量就少了。BLE把这40个信道又分了两类:

  • 3个广播信道(37/38/39):专门用来发广播包、做连接前的握手。这三个信道刻意避开了Wi-Fi的1/6/11信道,减少干扰。我在项目中遇到过,有些模块在Wi-Fi密集的办公环境里广播成功率低,后来发现是广播信道被Wi-Fi挤了,换了个信道偏移策略才解决。
  • 37个数据信道(0-36):连接建立后,数据就在这些信道上跳来跳去——这就是跳频机制。

核心要点:BLE物理层支持三种速率模式——1Mbps(LE 1M)、2Mbps(LE 2M)、以及125kbps/500kbps(LE Coded)。低功耗场景下,LE Coded模式虽然速率慢,但接收灵敏度能提升6-8dB,传输距离翻倍。我建议,如果你的产品需要穿墙或远距离,优先考虑Coded PHY。

2.2 链路层(LL)—— 连接是怎么建立和维护的

链路层是BLE的大脑。它负责五件事:广播、扫描、发起连接、数据收发、以及休眠唤醒。说白了,链路层决定了你的设备什么时候该醒,什么时候该睡。

链路层有五种状态机:

  • Standby:啥也不干,最省电
  • Advertising:发广播包,告诉别人我在这儿
  • Scanning:听广播包,找周围设备
  • Initiating:主动发起连接
  • Connection:连接状态,收发数据

这里有个容易踩的坑:很多开发者以为设备连上后链路层就一直醒着。其实不是。连接状态下,链路层只在约定的时间窗口内醒来收发数据,其余时间都在睡觉。这个时间窗口就是下面要讲的连接事件。

注意:链路层的休眠是硬件级别的,和你的MCU睡眠模式是两码事。我曾经在一个项目里,MCU已经进了deep sleep,但链路层因为配置了过短的扫描窗口,每隔几毫秒就醒来一次,整机功耗根本降不下去。后来把扫描间隔拉到100ms以上,功耗才正常。

2.3 GAP与GATT角色——谁负责发现,谁负责数据

很多新手把GAP和GATT搞混。我打个比方:GAP是社交礼仪,决定谁先打招呼、谁回应;GATT是聊天内容,决定聊什么、怎么聊。

GAP(Generic Access Profile)

GAP定义了四种角色:

角色 说明 典型设备
Broadcaster 只发广播,不连接 信标、温度标签
Observer 只扫描,不连接 扫描仪、网关
Peripheral 可被连接的外设 手环、传感器
Central 主动连接的中心设备 手机、平板

在低功耗休眠场景下,Peripheral角色最常用。它广播一段时间,然后休眠,等Central来连。我建议广播间隔不要短于100ms,否则电池撑不了多久。

GATT(Generic Attribute Profile)

GATT定义了连接后的数据交互方式。它有两种角色:

  • GATT Server:提供数据的一方,比如传感器把温度值放在某个特征值里
  • GATT Client:读取或写入数据的一方,比如手机App去读温度特征值

注意:一个设备可以同时是GAP Peripheral和GATT Server,也可以同时是GAP Central和GATT Client。但也可以混搭——比如一个设备是Peripheral但作为GATT Client去读另一个设备的数据。嗯,这种情况不常见,但协议栈是允许的。

经验之谈:设计休眠唤醒方案时,尽量让Peripheral做GATT Server。这样Central可以主动来读数据,Peripheral只需要在连接事件里回复,不用主动发通知,省电。如果你非要用Notification(通知),记得把CCCD(客户端特征配置描述符)配好,否则设备会一直发,功耗翻倍。

2.4 连接间隔与连接事件——休眠唤醒的核心参数

好,终于到了最关键的参数。连接建立后,Central和Peripheral会在一个固定的时间间隔内交换数据,这个间隔就叫连接间隔(Connection Interval),单位是1.25ms的整数倍。

举个例子:连接间隔设为30(即30×1.25ms=37.5ms),那么每37.5ms,Central和Peripheral会同步一次,交换数据。这个同步时刻就叫连接事件(Connection Event)。

连接间隔的范围是7.5ms到4s。你可能会问:间隔越长越省电,为什么不直接设4s?因为应用有实时性要求。比如一个心率监测器,如果连接间隔是4s,心率数据要等4s才能传一次,用户肯定不干。

我一般这样选:

  • 实时控制类(如遥控器):7.5ms - 15ms
  • 数据采集类(如温湿度计):100ms - 500ms
  • 低功耗传感器(如门磁):1s - 4s

避坑指南:我曾经在一个门锁项目里把连接间隔设成了4s,结果用户开门后App要等4s才能收到状态更新,体验极差。后来改成1s,功耗只多了不到10μA,但用户体验好了很多。所以别一味追求低功耗,要平衡。

另外,还有一个参数叫从机延迟(Slave Latency)。它允许Peripheral在连续几个连接事件里不响应,继续睡觉。比如从机延迟设为3,那么Peripheral可以跳过最多3个连接事件,只在第4个事件里醒来。这样平均功耗能再降一大截。

但注意:从机延迟不能设太大,否则Central会认为连接断了。协议规定,Peripheral最多可以跳过(连接间隔 × (从机延迟+1))超过监督超时(Supervision Timeout)的时间。监督超时默认6s,所以如果你连接间隔是100ms,从机延迟最多设59——但实际没人这么干,一般设3-5就够了。

2.5 本章知识体系总览

下面这张图把物理层到连接事件的关系串起来了。我画图时习惯把层级和参数放在一起,方便对照。

BLE协议栈核心结构图 BLE协议栈核心结构(物理层 → 连接事件) 物理层(PHY) 40个信道(3广播 + 37数据) | 2MHz带宽 | 1M/2M/Coded速率 链路层(LL) 状态机:Standby → Advertising → Scanning → Initiating → Connection GAP(通用访问规范) Broadcaster / Observer / Peripheral / Central GATT(通用属性规范) Server / Client | Service / Characteristic / Descriptor 连接参数(Connection Parameters) 连接间隔(7.5ms-4s) | 从机延迟(0-499) | 监督超时(100ms-32s) 连接事件(Connection Event)—— 数据交换 + 休眠唤醒 说明:物理层决定信号怎么传,链路层决定什么时候醒,GAP/GATT决定数据怎么组织,连接参数决定功耗和实时性

这张图从左到右、从上到下,把本章的核心知识点串了一遍。你写代码时,遇到功耗问题,先回来看这张图——看看是物理层速率没选对,还是链路层状态机没配好,还是连接间隔太短。大部分问题都能在这张图上找到根因。

好了,这一章的内容就到这儿。物理层和链路层是地基,GAP和GATT是框架,连接间隔和事件是调优的旋钮。把这些搞明白,后面讲休眠唤醒策略时你就能直接上手调参数了。