3、扫描参数详解:扫描窗口、扫描间隔、扫描模式(被动/主动)、扫描过滤器

各位好,我是老周。做蓝牙开发这些年,我见过太多人在扫描这步翻车了。要么扫不到设备,要么功耗高得离谱,要么数据收不全。说白了,很多人根本没搞懂那几个扫描参数到底在控制什么。

今天这一章,咱们就把扫描参数掰开揉碎了讲。你把这些参数吃透了,后面做低功耗设计、做快速发现,才能游刃有余。

3.1 扫描窗口与扫描间隔:一对儿“冤家”

先看两个最基础的参数:扫描窗口(Scan Window)和扫描间隔(Scan Interval)。

扫描窗口,就是接收机每次打开射频的时间长度。扫描间隔,则是两次扫描起始点之间的时间间隔。你想想看,如果窗口等于间隔,那接收机就一直开着,这叫连续扫描。如果窗口小于间隔,中间就有空闲时间,这叫间歇扫描。

我习惯用一个比喻来理解:
扫描间隔就像你每隔多久去公交站看一眼。
扫描窗口就像你每次在公交站等多久。

等的时间越长(窗口大),越容易看到车来。但你也得花更多时间在站台上耗着(功耗高)。

核心公式:

  • 扫描窗口 ≤ 扫描间隔(这是协议规定的硬约束)
  • 窗口越接近间隔 → 扫描越密集 → 功耗越高
  • 窗口远小于间隔 → 扫描越稀疏 → 功耗越低,但可能漏包

我在项目中遇到过最典型的场景:一个穿戴设备需要扫描附近的手机。一开始我把扫描窗口设成 30ms,间隔 100ms,结果功耗超标。后来我把窗口缩到 10ms,间隔拉到 200ms,功耗降下来了,但手机要走近到 1 米内才能发现。嗯,这就是典型的“省电省过头了”。

参数 典型值范围 对功耗影响 对发现速度影响
扫描窗口 10ms ~ 10.24s 窗口越大,功耗越高 窗口越大,越容易收到包
扫描间隔 10ms ~ 10.24s 间隔越小,功耗越高 间隔越小,发现越快

我的调优经验:如果你不确定怎么配,先试试 30ms 窗口 + 60ms 间隔。这个组合在大多数场景下功耗和性能比较均衡。然后再根据实际测试结果微调。

3.2 扫描模式:被动 vs 主动

扫描模式就两种:被动扫描(Passive Scanning)和主动扫描(Active Scanning)。区别其实就一句话:收到广播包后,要不要主动去问更多信息。

被动扫描:只听不说。收到广播包就完事了,不回复任何东西。这种模式功耗低,但只能拿到广播包本身携带的数据。

主动扫描:收到广播包后,如果发现是“可扫描”类型的,就会发一个扫描请求(SCAN_REQ),然后等对方回复扫描响应(SCAN_RSP)。这样你能拿到更多数据,但多了一次收发,功耗自然上去了。

我曾经在一个项目中,用被动扫描死活拿不到设备的完整名称。后来才发现,设备把完整名称放在了扫描响应里,而不是广播包里。换成主动扫描后,数据就全了。

注意:主动扫描不是万能的。如果广播设备设置了“不可扫描”类型(比如只发不可连接广播),你发 SCAN_REQ 过去,对方根本不会理你。白费电。

特性 被动扫描 主动扫描
功耗 低 高(多一次收发)
数据量 仅广播包(31字节) 广播包 + 扫描响应(最多62字节)
适用场景 只需发现设备、信标场景 需要获取完整名称、服务UUID等

3.3 扫描过滤器:别让无关设备打扰你

现实世界中,BLE 信道里广播包多得很。你打开扫描,可能一秒收到几十上百个包,但其中 90% 跟你没关系。这时候就需要扫描过滤器(Scan Filter)。

BLE 协议栈通常支持以下几种过滤方式:

  • 按设备地址过滤:只扫描特定 MAC 地址的设备。最直接,但需要提前知道地址。
  • 按广播类型过滤:比如只处理可连接广播,忽略不可连接广播。
  • 按服务 UUID 过滤:只扫描包含特定 16-bit 或 128-bit UUID 的设备。这个很实用。
  • 按 RSSI 过滤:信号太弱的设备直接扔掉,省得处理。

我个人的习惯是:先粗筛,再精筛。比如先按 RSSI 过滤掉 -90dBm 以下的设备,再按服务 UUID 匹配目标设备。这样能大幅减少上层应用的处理负担。

避坑指南:我曾经在一个项目中,只用了服务 UUID 过滤,结果发现有些设备虽然广播了正确的 UUID,但 RSSI 已经低到 -95dBm,根本连不上。后来我加了 RSSI 阈值过滤,才把那些“看得见摸不着”的设备排除掉。

3.4 参数之间的联动关系

这几个参数不是孤立的。它们之间会互相影响。我画了一张图,帮你理清它们的关系:

扫描参数联动关系图 扫描参数配置 扫描窗口 扫描间隔 扫描模式 扫描过滤器 功耗 发现速度 数据完整性 调优原则 • 窗口 + 间隔 → 决定扫描占空比 → 直接影响功耗 • 模式 → 决定能否获取扫描响应 → 影响数据完整性 • 过滤器 → 减少无效数据 → 间接降低处理功耗

3.5 实战配置示例

说了这么多理论,咱们来点实际的。下面是一个典型的扫描初始化代码,我用的是 Nordic nRF5 SDK 的接口,但思路是通用的:

// 扫描参数配置结构体
ble_gap_scan_params_t scan_params;

// 设置扫描模式:主动扫描
scan_params.active = 1;  // 1=主动扫描,0=被动扫描

// 设置扫描窗口和间隔(单位:0.625ms)
scan_params.window = 0x30;   // 30 * 0.625ms = 18.75ms
scan_params.interval = 0x60; // 96 * 0.625ms = 60ms

// 设置扫描过滤器策略
scan_params.filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL;
// 可选值:
// BLE_GAP_SCAN_FP_ACCEPT_ALL - 接受所有广播包
// BLE_GAP_SCAN_FP_ACCEPT_ONLY_WLST - 只接受白名单中的设备
// BLE_GAP_SCAN_FP_ACCEPT_ALL_EXCEPT_WLST - 接受除白名单外的所有设备

// 启动扫描
sd_ble_gap_scan_start(&scan_params);

小提示:如果你用的是主动扫描,记得检查广播设备的类型。只有 ADV_IND 和 ADV_SCAN_IND 类型的广播才会响应 SCAN_REQ。ADV_NONCONN_IND 和 ADV_DIRECT_IND 是不会回复的。

3.6 常见问题与排查思路

最后,我整理了几个我在项目中踩过的坑,希望能帮你少走弯路:

  • 扫不到设备?先检查扫描窗口是不是太小了。我试过 5ms 窗口,基本啥都扫不到。至少 10ms 起步。
  • 功耗太高?看看扫描间隔是不是设得太短了。把间隔拉大,或者缩小窗口,都能降功耗。
  • 数据不全?确认一下是不是用了被动扫描。换成主动扫描试试。
  • 收到一堆无关设备?加过滤器。按服务 UUID 过滤是最常用的方法。

重要提醒:BLE 协议规定,扫描窗口不能大于扫描间隔。如果你不小心设反了,协议栈通常会直接报错,或者默默帮你修正。但别依赖这个,自己写代码时一定要检查。

好了,扫描参数这块就讲到这里。这些参数看着简单,但组合起来变化很多。我的建议是:先理解每个参数的作用,再根据实际场景去调。别指望一套参数打天下,不同的产品、不同的使用场景,最优配置是不一样的。