第2章 BLE广播与扫描:广播报文结构、扫描参数配置、设备过滤与回调处理

2.1 BLE广播原理与报文结构

BLE 设备在物理层使用 3 个专用广播信道(37/38/39)来发送广播报文,避免与 Wi‑Fi 的 2.4 GHz 信道冲突。广播者(Advertiser)周期性地在三个信道上发送相同的广播 PDU;扫描者(Scanner)则在信道上监听并接收这些报文。广播报文是 BLE 连接建立的起点,也是设备发现、服务广播和数据透传的基础载体。

BLE 广播与扫描全景图 BLE 广播者 广播报文 PDU 结构 前导码 1 B 访问地址 4 B PDU 头 2 B MAC + 数据 6~37 B CRC 3 B 信道 37/38/39 无线广播 BLE 扫描者 扫描参数配置 设备过滤 回调处理 💡 广播报文在 3 个信道上同时发送,扫描者只需在一个信道上监听即可发现设备 扫描参数、过滤条件、回调函数三者协同完成设备发现与数据采集

广播报文的核心是 PDU(Protocol Data Unit),其结构如上图中间展开部分所示。PDU 之前还包含前导码(用于接收端同步)和访问地址(固定为 0x8E89BED6),PDU 之后附加 3 字节 CRC 校验。PDU 本身由 2 字节头部、6 字节广播者 MAC 地址以及 0~31 字节广播数据构成。广播 PDU 类型决定了设备的可连接性、可扫描性以及响应行为:

PDU 类型 可连接 可扫描 典型场景
ADV_IND是是通用可连接广播
ADV_DIRECT_IND是否定向快速重连
ADV_NONCONN_IND否否纯广播(信标)
ADV_SCAN_IND否是可扫描但不连接

关键理解:广播报文是 BLE 协议栈中唯一不需要建立连接就能收发的数据载体。嵌入式开发中,设备发现、服务宣告、低功耗传感器数据上报都依赖广播报文的结构化设计。掌握 PDU 字段布局是后续配置扫描过滤器、解析广播数据的基石。

2.2 扫描参数配置

扫描者通过配置 扫描参数 控制监听行为。主要参数包括扫描类型、扫描间隔、扫描窗口、过滤策略和重复过滤选项。以 Zephyr RTOS 为例,扫描参数结构体定义如下:

struct bt_le_scan_param {
    uint8_t  type;       /* BT_LE_SCAN_TYPE_ACTIVE 或 PASSIVE */
    uint16_t interval;   /* 扫描间隔 (0.625ms 单位) */
    uint16_t window;     /* 扫描窗口 (0.625ms 单位) */
    uint8_t  options;    /* 过滤重复、主动扫描等标志位 */
};
参数 典型值 说明
typeBT_LE_SCAN_TYPE_ACTIVE主动扫描会发送扫描请求,被动扫描只监听
intervalBT_GAP_SCAN_FAST_INTERVAL (60 ms)两次扫描启动之间的间隔
windowBT_GAP_SCAN_FAST_WINDOW (30 ms)每次扫描持续时长,必须 ≤ interval
optionsBT_LE_SCAN_OPT_FILTER_DUPLICATE过滤重复设备,减少回调次数

实践建议:扫描窗口与间隔的比值(占空比)直接影响功耗与发现速度。快速扫描(60/30 ms)适用于设备密集发现场景;慢速扫描(1024/512 ms)适合低功耗背景扫描。始终确保 window ≤ interval,否则协议栈会拒绝参数。

参数配置完成后调用扫描启动 API:

struct bt_le_scan_param scan_params = {
    .type       = BT_LE_SCAN_TYPE_ACTIVE,
    .interval   = BT_GAP_SCAN_FAST_INTERVAL,
    .window     = BT_GAP_SCAN_FAST_WINDOW,
    .options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
};

err = bt_le_scan_start(&scan_params, scan_cb);
if (err) {
    printk("扫描启动失败: %d\n", err);
}

2.3 设备过滤机制

原始扫描回调会收到所有广播报文,设备过滤用于筛选出目标设备。嵌入式 BLE 开发中常用的过滤维度有四种,可在回调函数中或通过协议栈自带过滤功能实现:

过滤方式 实现手段 适用场景
RSSI 阈值比较接收信号强度剔除远距离设备
设备名称解析 AD Type 0x09按名称匹配已知设备
服务 UUID解析 AD Type 0x02/0x03/0x06/0x07按服务类型过滤
MAC 地址直接比较 6 字节地址白名单/黑名单

以下示例在回调函数中实现 RSSI 阈值过滤与名称匹配:

static void scan_cb(const struct bt_le_scan_recv_info *info,
                    struct net_buf_simple *ad)
{
    char name[32];
    uint8_t name_len;

    /* 1. RSSI 阈值过滤 */
    if (info->rssi < -70) {
        return;  /* 信号太弱,忽略 */
    }

    /* 2. 解析广播数据中的设备名称 */
    name_len = bt_le_scan_ad_data_get(ad, BT_DATA_NAME_COMPLETE,
                                      name, sizeof(name)-1);
    if (name_len == 0) {
        return;  /* 未携带完整名称,忽略 */
    }
    name[name_len] = '\0';

    /* 3. 按名称匹配 */
    if (strcmp(name, "MySensorTag") != 0) {
        return;
    }

    /* 通过过滤,处理该设备 */
    printk("发现目标设备: %s, RSSI: %d\n", name, info->rssi);
}

常见错误:在回调函数中执行阻塞操作(如延时、打印大量日志、内存分配)会导致协议栈扫描线程阻塞,造成丢包。过滤逻辑应保持轻量,只做条件判断和数据拷贝,耗时处理应递交给工作队列或任务。

2.4 回调处理与数据解析

扫描结果通过回调函数异步返回。每次扫描到广播报文时,协议栈调用注册的回调,传入接收信息结构体和广播数据缓冲区。开发者需要从中提取 RSSI、MAC 地址、广播类型以及自定义广播数据段。

Zephyr 的回调签名:

static void scan_cb(const struct bt_le_scan_recv_info *info,
                    struct net_buf_simple *ad)
{
    bt_addr_le_t *addr = info->addr;
    uint8_t adv_type = info->adv_type;
    int8_t rssi = info->rssi;

    /* 逐段解析 AD Structure */
    while (ad->len > 1) {
        uint8_t len = net_buf_simple_pull_u8(ad);
        if (len == 0) break;
        uint8_t type = net_buf_simple_pull_u8(ad);
        uint8_t *data = net_buf_simple_pull_mem(ad, len - 1);

        switch (type) {
        case BT_DATA_NAME_COMPLETE:
            /* 提取完整设备名称 */
            break;
        case BT_DATA_UUID16_ALL:
            /* 提取 16 位服务 UUID 列表 */
            break;
        case BT_DATA_MANUFACTURER_DATA:
            /* 提取厂商自定义数据 */
            break;
        }
    }
}

广播数据以 AD Structure(Advertising Data Structure) 的形式组织,每个结构包含 1 字节长度、1 字节类型和若干字节数据。解析时按长度字段遍历即可提取所有数据段。

数据流向:物理层 → 链路层过滤 → 协议栈扫描回调 → 用户过滤逻辑 → 用户数据处理。整个链路中,回调函数是开发者唯一可以注入代码的接入点,必须高效处理。

2.5 调试与常见错误

扫描阶段的调试主要依赖协议栈日志和空中抓包工具。以下列出最常遇到的 5 个问题及其根因:

  • 扫描不到任何设备:检查扫描参数 window 是否为 0,确认天线已启用,验证广播者是否在发送可扫描的 PDU 类型。
  • 回调频繁导致系统卡顿:未开启重复过滤(FILTER_DUPLICATE),或回调内执行了阻塞操作。开启过滤 + 回调轻量化即可解决。
  • RSSI 过滤后仍有弱信号设备:RSSI 值在空气中波动较大,建议采用滑动平均或滞后比较,避免单次判决。
  • 名称解析为空:广播者可能未包含完整名称(AD Type 0x09),或使用了缩短名称(0x08)。应同时处理两种类型。
  • 主动扫描无响应:广播者广播类型为 ADV_NONCONN_IND 或 ADV_SCAN_IND 但扫描者未正确发送扫描请求。检查扫描类型是否为 ACTIVE。

调试工具推荐:使用 Nordic nRF Sniffer 或 Ellisys BLE Analyzer 抓取空中包,可以直观观察广播 PDU 字段、扫描请求和响应时序。对于嵌入式端,开启协议栈的 DEBUG 日志(CONFIG_BT_DEBUG_LOG=y)也能定位大部分参数配置问题。

2.6 性能与稳定性优化

在实际产品中,扫描性能和系统稳定性需要精细权衡。以下是经过工程验证的 6 条优化措施:

优化项 操作 收益
1. 占空比控制扫描窗口/间隔 ≤ 0.5降低平均功耗 40~60