4、扫描机制详解:主动扫描与被动扫描、扫描请求与扫描响应、扫描窗口与扫描间隔、扫描白名单

扫描,说白了就是接收机在嘈杂的射频环境里,找到那些正在广播的设备。很多人觉得扫描就是“打开蓝牙等着收数据”,其实没那么简单。我刚开始做 BLE 项目时,就被扫描参数坑过——设备明明在广播,手机就是搜不到。后来才发现,是扫描窗口和间隔没配好。

这一章,我们把扫描的四个核心概念掰开揉碎讲清楚。你理解透了,以后调 BLE 连接成功率会顺手很多。

4.1 主动扫描 vs 被动扫描

先问一个问题:扫描设备收到广播包之后,要不要主动去问广播设备更多信息?

这个问题的答案,决定了扫描模式。

对比项 被动扫描 主动扫描
行为 只监听广播信道 监听 + 发送扫描请求
数据量 仅接收广播包 可额外获得扫描响应数据
功耗 较低 较高(多发一个包)
典型场景 信标、低功耗传感器 手机发现设备、连接前握手

被动扫描:扫描设备只打开接收窗口,等着广播设备发 ADV_IND 或 ADV_NONCONN_IND。收到什么算什么,不主动问。我做过一个 iBeacon 项目,信标每秒发一次广播,手机端用被动扫描就够了。省电,而且不需要交互。

主动扫描:扫描设备收到广播包后,会在同一个广播信道上发一个 SCAN_REQ(扫描请求)。广播设备收到后,回复一个 SCAN_RSP(扫描响应)。这个响应里可以携带更多数据,比如设备名称、自定义服务数据等。

核心区别:主动扫描能拿到“额外数据”,但代价是多了一次空中交互。在密集场景下,SCAN_REQ 和 SCAN_RSP 会增加信道冲突概率。

4.2 扫描请求与扫描响应

这两个 PDU 是主动扫描的灵魂。我们来看看它们长什么样。

SCAN_REQ(扫描请求)

由扫描设备发出,目标地址是广播设备的随机地址或公共地址。格式很简单:

SCAN_REQ PDU 结构:
- PDU 类型:0x03(SCAN_REQ)
- TxAdd:发送地址类型
- 发起设备地址:扫描设备自己的地址
- 目标设备地址:广播设备的地址

注意:SCAN_REQ 不携带任何应用数据。它就是一个“你是谁?能多给我点信息吗?”的询问包。

SCAN_RSP(扫描响应)

广播设备收到 SCAN_REQ 后,在同一个信道上回复 SCAN_RSP。它的 PDU 类型是 0x04,数据域结构和广播包完全一样,最多 31 字节。

SCAN_RSP PDU 结构:
- PDU 类型:0x04(SCAN_RSP)
- AdvA:广播设备地址
- AdvData:最多 31 字节的响应数据

我的经验:SCAN_RSP 的 31 字节很宝贵。我一般把设备名称放在 SCAN_RSP 里,广播包里放核心服务 UUID。这样被动扫描的设备也能看到服务,主动扫描的设备还能拿到名字,两全其美。

4.3 扫描窗口与扫描间隔

这两个参数决定了扫描设备的“监听节奏”。很多人搞混,我画个图你就明白了。

扫描窗口与扫描间隔示意图 扫描窗口 扫描窗口 扫描窗口 扫描窗口 扫描间隔 空闲时间 扫描间隔 = 扫描窗口 + 空闲时间 窗口越大,监听越密集,功耗越高

扫描窗口(scanWindow):每次扫描持续的时间。单位是 0.625ms 的倍数。比如窗口设 10ms,就是 16 个时间单位。

扫描间隔(scanInterval):两次扫描开始之间的时间间隔。同样以 0.625ms 为单位。

关键公式:扫描间隔 ≥ 扫描窗口。如果窗口等于间隔,那就是连续扫描;如果窗口小于间隔,中间就有空闲时间。

我曾经踩过的坑:在一个低功耗门锁项目里,我把扫描窗口设得很大(接近间隔),结果扫描设备功耗飙升,电池撑不到一周。后来把窗口缩小到间隔的 1/3,功耗降了 60%,设备发现时间只多了 200ms。这个 trade-off 一定要根据场景来调。

4.4 扫描白名单

白名单,说白了就是一个地址过滤器。扫描设备只处理白名单里的广播设备发来的包,其他的直接忽略。

为什么要用白名单?

  • 减少干扰:在设备密集的环境里,白名单能过滤掉 90% 以上的无关广播。
  • 降低功耗:CPU 不用处理每个广播包,省电。
  • 提高安全性:只与已知设备交互,防止恶意设备伪装。

白名单的配置流程:

  1. 把目标设备的地址(公共地址或随机地址)写入扫描设备的白名单。
  2. 设置扫描参数时,把 filterPolicy 设为 0x02(使用白名单)。
  3. 启动扫描。扫描设备只处理白名单内的广播包。
// 伪代码示例:配置扫描白名单
ble_gap_scan_params_t scan_params;
scan_params.filter_policy = BLE_GAP_SCAN_FP_WHITELIST; // 使用白名单
scan_params.scan_phys = BLE_GAP_PHY_1M;
scan_params.scan_window = 100;  // 100 * 0.625ms = 62.5ms
scan_params.scan_interval = 200; // 200 * 0.625ms = 125ms

// 添加白名单设备
ble_gap_whitelist_add(&peer_addr);

我建议:白名单不要加太多设备。BLE 协议规定白名单最多 128 个,但实际项目中超过 20 个,扫描效率就会明显下降。我一般控制在 8-16 个,够用且稳定。

4.5 综合对比:四种扫描参数的关系

参数 作用 典型值 影响
扫描窗口 每次监听时长 10-100ms 窗口越大,发现越快,功耗越高
扫描间隔 监听周期 20-200ms 间隔越小,监听越密集,功耗越高
主动/被动 是否发送 SCAN_REQ 主动/被动 主动模式数据更多,但增加空中冲突
白名单 地址过滤 8-16 个地址 减少干扰,降低功耗

嗯,到这里扫描机制的核心内容就讲完了。你可能会问:这些参数在实际项目里怎么配?我的建议是——先按芯片厂商的默认值跑通,然后用抓包工具(比如 Ellisys 或 Nordic nRF Sniffer)看实际效果,再根据功耗和发现延迟的要求微调。别一上来就追求最优参数,先跑起来,再优化。