4. GAP广播机制详解
广播,是低功耗蓝牙最核心的机制之一。说白了,就是设备在不停地喊:“我在这里!我能连!我有这些功能!”
我刚开始做蓝牙项目时,对广播的理解就是“发数据包”。后来踩了不少坑才发现——广播的水很深。广播类型、数据格式、间隔设置,每一个细节都直接影响功耗和连接体验。
今天咱们就把广播机制彻底讲透。
4.1 广播类型:可连接、不可连接、扫描响应
GAP定义了四种广播类型。我个人习惯把它们分成两类:能连的和不能连的。
| 广播类型 | 可连接 | 可扫描 | 典型场景 |
|---|---|---|---|
| 可连接可扫描广播 | 是 | 是 | 普通蓝牙外设(手环、传感器) |
| 可连接不可扫描广播 | 是 | 否 | 定向连接(快速配对) |
| 不可连接可扫描广播 | 否 | 是 | 信标(Beacon) |
| 不可连接不可扫描广播 | 否 | 否 | 纯广播数据(极少用) |
可连接可扫描广播——这是最常用的。设备广播自己,手机扫描到后可以发起连接。我做的智能手环项目,默认就用这种。
不可连接可扫描广播——这就是Beacon的工作模式。设备只发数据,不响应连接请求。你想想看,商场里的蓝牙信标,总不能让人连上去吧?
扫描响应是什么?
嗯,这里要注意。广播包只有31字节,很多时候不够用。扫描响应就是“第二包”——当扫描设备发送扫描请求后,广播设备可以回复更多数据。
4.2 广播数据格式:AD Structure
广播数据不是随便塞的。BLE协议规定了严格的格式——AD Structure(Advertising Data Structure)。
每个AD Structure由三部分组成:
- Length(1字节):表示AD Data的长度(不包含Length本身)
- AD Type(1字节):表示数据类型
- AD Data(N字节):具体数据
举个例子,我要广播一个16位的Service UUID:
// 假设UUID是0x180D(心率服务)
// 广播数据:02 01 06 03 02 0D 18
// 解析:
// 02 01 06
// Length=2, Type=0x01 (Flags), Data=0x06
// 03 02 0D 18
// Length=3, Type=0x02 (16-bit UUID列表), Data=0x0D 0x18
我在项目中遇到过一个问题:广播包塞了太多数据,导致手机扫描不到。后来发现是AD Structure长度算错了。记住,Length字段只算AD Data,不算自己。
- 0x01 — Flags(广播标志)
- 0x02 — 16-bit Service UUID列表
- 0x03 — 128-bit Service UUID列表
- 0x08 — 设备名称(缩短版)
- 0x09 — 设备名称(完整版)
- 0xFF — 厂商自定义数据
4.3 广播间隔与功耗的关系
广播间隔,就是设备两次广播之间的时间。单位是0.625ms。
公式很简单:实际间隔 = 间隔值 × 0.625ms
比如设置间隔值为160,实际就是100ms。
广播间隔对功耗的影响有多大?
我做过实测:
| 广播间隔 | 平均电流(3V供电) | 适用场景 |
|---|---|---|
| 20ms | ~300μA | 快速连接、低延迟 |
| 100ms | ~80μA | 普通外设 |
| 1000ms | ~15μA | 信标、低功耗场景 |
| 4000ms | ~5μA | 超低功耗(纽扣电池) |
为什么会这样?因为每次广播,射频都要打开一段时间(约1-3ms)。间隔越短,射频打开频率越高,功耗自然就上去了。
4.4 配置广播UUID
广播UUID,就是告诉扫描设备:“我支持这些服务”。
配置方式因芯片而异,但逻辑是一样的。以Nordic nRF5 SDK为例:
// 配置广播数据
static void advertising_init(void)
{
uint32_t err_code;
ble_advdata_t advdata;
// 16位UUID列表
ble_uuid_t adv_uuids[] = {
{BLE_UUID_HEART_RATE_SERVICE, BLE_UUID_TYPE_BLE}
};
memset(&advdata, 0, sizeof(advdata));
advdata.name_type = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = true;
advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
advdata.uuids_complete.uuid_cnt = 1;
advdata.uuids_complete.p_uuids = adv_uuids;
err_code = ble_advdata_set(&advdata, NULL);
APP_ERROR_CHECK(err_code);
}
这里要注意:UUID有16位和128位两种。16位是标准服务(比如心率0x180D),128位是自定义服务。
我建议:能用16位就用16位,省空间。自定义服务才用128位。
4.5 主动扫描与被动扫描
扫描,就是设备在监听广播。分两种模式:
- 被动扫描:只监听广播包,不发送扫描请求
- 主动扫描:监听到广播后,发送扫描请求,获取扫描响应数据
说白了,被动扫描就是“只听不说”,主动扫描是“听了还要问”。
主动扫描能拿到更多数据(扫描响应包),但功耗更高。因为要额外发送扫描请求,还要等待响应。
我在做室内定位项目时,就用了主动扫描。因为Beacon的广播包只放UUID,位置信息放在扫描响应里。手机必须主动扫描才能拿到完整数据。
- 如果只需要知道“有没有设备”,用被动扫描就够了
- 如果需要设备详细信息(名称、电量等),用主动扫描
- 扫描窗口和扫描间隔也要平衡,一般设扫描窗口=扫描间隔的一半
4.6 知识体系总览
下面这张图,把广播机制的核心逻辑串起来了:
广播机制,说白了就是一套“喊话-监听”协议。你喊得快(间隔短),别人能更快发现你,但你也更累(功耗高)。你喊的内容(AD Structure)要精简,别把废话塞进去。
嗯,掌握了这些,广播这块基本就通了。剩下的就是根据你的产品需求,去平衡连接速度、数据量和功耗。
公众号:蓝海资料掘金营,微信deep3321