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 本章知识体系总览
下面这张图把物理层到连接事件的关系串起来了。我画图时习惯把层级和参数放在一起,方便对照。
这张图从左到右、从上到下,把本章的核心知识点串了一遍。你写代码时,遇到功耗问题,先回来看这张图——看看是物理层速率没选对,还是链路层状态机没配好,还是连接间隔太短。大部分问题都能在这张图上找到根因。
好了,这一章的内容就到这儿。物理层和链路层是地基,GAP和GATT是框架,连接间隔和事件是调优的旋钮。把这些搞明白,后面讲休眠唤醒策略时你就能直接上手调参数了。