第2章 BLE广播与扫描:广播报文结构、扫描参数配置、设备过滤与回调处理
2.1 BLE广播原理与报文结构
BLE 设备在物理层使用 3 个专用广播信道(37/38/39)来发送广播报文,避免与 Wi‑Fi 的 2.4 GHz 信道冲突。广播者(Advertiser)周期性地在三个信道上发送相同的广播 PDU;扫描者(Scanner)则在信道上监听并接收这些报文。广播报文是 BLE 连接建立的起点,也是设备发现、服务广播和数据透传的基础载体。
广播报文的核心是 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; /* 过滤重复、主动扫描等标志位 */
};
| 参数 | 典型值 | 说明 |
|---|---|---|
| type | BT_LE_SCAN_TYPE_ACTIVE | 主动扫描会发送扫描请求,被动扫描只监听 |
| interval | BT_GAP_SCAN_FAST_INTERVAL (60 ms) | 两次扫描启动之间的间隔 |
| window | BT_GAP_SCAN_FAST_WINDOW (30 ms) | 每次扫描持续时长,必须 ≤ interval |
| options | BT_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 |