4. GAP广播机制详解

广播,是低功耗蓝牙最核心的机制之一。说白了,就是设备在不停地喊:“我在这里!我能连!我有这些功能!”

我刚开始做蓝牙项目时,对广播的理解就是“发数据包”。后来踩了不少坑才发现——广播的水很深。广播类型、数据格式、间隔设置,每一个细节都直接影响功耗和连接体验。

今天咱们就把广播机制彻底讲透。

4.1 广播类型:可连接、不可连接、扫描响应

GAP定义了四种广播类型。我个人习惯把它们分成两类:能连的和不能连的。

广播类型 可连接 可扫描 典型场景
可连接可扫描广播 普通蓝牙外设(手环、传感器)
可连接不可扫描广播 定向连接(快速配对)
不可连接可扫描广播 信标(Beacon)
不可连接不可扫描广播 纯广播数据(极少用)

可连接可扫描广播——这是最常用的。设备广播自己,手机扫描到后可以发起连接。我做的智能手环项目,默认就用这种。

不可连接可扫描广播——这就是Beacon的工作模式。设备只发数据,不响应连接请求。你想想看,商场里的蓝牙信标,总不能让人连上去吧?

扫描响应是什么?

嗯,这里要注意。广播包只有31字节,很多时候不够用。扫描响应就是“第二包”——当扫描设备发送扫描请求后,广播设备可以回复更多数据。

我的经验:广播包放核心信息(设备名、UUID),扫描响应放扩展信息(电量、版本)。这样扫描设备可以快速决定要不要进一步交互。

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,不算自己。

常见AD Type:
  • 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)。间隔越短,射频打开频率越高,功耗自然就上去了。

避坑指南:我曾经把广播间隔设成10ms,结果电池两天就耗光了。后来改成200ms,续航直接到一个月。别为了“连接快”牺牲太多功耗。

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 知识体系总览

下面这张图,把广播机制的核心逻辑串起来了:

GAP广播机制核心逻辑 广播设备(Advertiser) 扫描设备(Scanner) 广播包(31字节) 扫描请求(可选) 扫描响应(31字节) 广播数据(AD Structure) 主动扫描时发送 响应扫描请求 广播间隔(20ms~10.24s) 功耗(μA级) 间隔越短 → 功耗越高 核心要点:广播类型决定连接行为,AD Structure决定数据内容,广播间隔决定功耗 主动扫描获取更多数据,但增加功耗

广播机制,说白了就是一套“喊话-监听”协议。你喊得快(间隔短),别人能更快发现你,但你也更累(功耗高)。你喊的内容(AD Structure)要精简,别把废话塞进去。

嗯,掌握了这些,广播这块基本就通了。剩下的就是根据你的产品需求,去平衡连接速度、数据量和功耗。


公众号:蓝海资料掘金营,微信deep3321