BLE Legacy 广播【扫描配置】

LE Set Scan Parameters command

该指令中,需要 Host 传给 Controller 的参数有:

LE_Scan_Type

LE_Scan_Interval

LE_Scan_Window

Own_Address_Type

Scanning_Filter_Policy

Controller 返回给 Host 的参数只有一个:

复制代码
Status

Description 翻译

原文

This command is used to set the scan parameters.

翻译

这个命令用于设置扫描参数。

也就是说,它不是用来"开始扫描"的,而是用来配置"扫描怎么进行"。


原文

The LE_Scan_Type parameter controls the type of scan to perform.

翻译

LE_Scan_Type 参数用于控制要执行的扫描类型。

这里主要就是两种:

复制代码
Passive Scanning:被动扫描
Active Scanning:主动扫描

被动扫描只接收广播包,不主动发 SCAN_REQ

主动扫描在收到可扫描广播包后,可以发送 SCAN_REQ,然后等待从设备回复 SCAN_RSP


原文

The LE_Scan_Interval and LE_Scan_Window parameters are recommendations from the Host on how long (LE_Scan_Window) and how frequently (LE_Scan_Interval) the Controller should scan (See Vol 6 Part B, Section 4.4.3).

翻译

LE_Scan_IntervalLE_Scan_Window 参数是 Host 给 Controller 的建议,用来说明 Controller 应该扫描多长时间,以及多频繁地进行扫描。

其中:

复制代码
LE_Scan_Window:每个扫描周期中,实际扫描的持续时间
LE_Scan_Interval:扫描周期的间隔

可以这样理解:

复制代码
LE_Scan_Interval = 多久启动一次扫描窗口
LE_Scan_Window   = 每次扫描窗口持续多久

例如:

复制代码
LE_Scan_Interval = 100 ms
LE_Scan_Window   = 50 ms

大概意思就是:

复制代码
每 100 ms 作为一个扫描周期,其中有 50 ms 在扫描,剩下 50 ms 不扫描。

原文

The LE_Scan_Window parameter shall always be set to a value smaller or equal to the value set for the LE_Scan_Interval parameter.

翻译

LE_Scan_Window 参数必须始终设置为小于或等于 LE_Scan_Interval 参数的值。

也就是:

复制代码
LE_Scan_Window <= LE_Scan_Interval

不能出现:

复制代码
LE_Scan_Window > LE_Scan_Interval

因为扫描窗口不可能比扫描周期还长。


原文

If they are set to the same value, scanning should be run continuously.

翻译

如果这两个参数被设置为相同的值,那么扫描应该连续进行。

也就是:

复制代码
LE_Scan_Window = LE_Scan_Interval

表示连续扫描。

例如:

复制代码
LE_Scan_Interval = 100 ms
LE_Scan_Window   = 100 ms

含义就是:

复制代码
整个扫描周期都在扫描,没有空档。

原文

Own_Address_Type parameter indicates the type of address being used in the scan request packets.

翻译

Own_Address_Type 参数表示在扫描请求包中使用的本机地址类型。

这里要注意,它主要影响 Active Scanning 时发出的:

复制代码
SCAN_REQ

因为 SCAN_REQ 里面会带主设备自己的地址,也就是:

复制代码
ScanA

所以 Own_Address_Type 决定的是主设备在 SCAN_REQ 里使用什么类型的地址,比如 Public Device Address 或 Random Device Address。


Errors 翻译

原文

See Section 4.5.2 for a list of error types and descriptions.

翻译

错误类型和描述列表请参见 4.5.2 节。


错误表格翻译

Type Condition Error code
MC Controller 中已经启用了扫描 Command Disallowed,0x0C

意思是:

如果 Controller 当前已经处于扫描开启状态,这时候你再发送:

复制代码
HCI_LE_Set_Scan_Parameters

Controller 可以返回:

复制代码
Command Disallowed,0x0C

因为扫描参数不能在扫描已经开启时修改。

正确流程应该是:

复制代码
LE Set Scan Enable = 0x00   // 先关闭扫描
LE Set Scan Parameters      // 再设置扫描参数
LE Set Scan Enable = 0x01   // 再开启扫描

必要 Tip

Tip 1:这个命令只"配置扫描",不"开启扫描"

LE Set Scan Parameters 只是设置扫描参数。

真正开启扫描的是:

复制代码
LE Set Scan Enable

所以 Legacy Scanning 的基础流程是:

复制代码
LE Set Scan Parameters
        ↓
LE Set Scan Enable
        ↓
Controller 开始监听 37 / 38 / 39 广播信道
        ↓
收到广播包后,通过 LE Advertising Report Event 上报给 Host

Tip 2:LE_Scan_Window 不能大于 LE_Scan_Interval

这是这个命令里非常关键的约束:

复制代码
LE_Scan_Window <= LE_Scan_Interval

可以这样记:

复制代码
Interval 是整个周期
Window 是周期中真正扫描的时间

所以 Window 不能比 Interval 还大。


Tip 3:Window = Interval 表示连续扫描,但不等于一定能收到所有广播包

规范说:

复制代码
If they are set to the same value, scanning should be run continuously.

意思是扫描会尽量连续进行。

但是实际中还要考虑:

复制代码
手机系统调度
Controller 实现
多任务共存
蓝牙和 Wi-Fi 共存
功耗策略
扫描信道切换
广播包碰撞

所以连续扫描并不等于"空口上所有广播包都百分百收到"。


Tip 4:Active Scanning 才会发 SCAN_REQ

LE_Scan_Type 如果设置为 Passive Scanning,那么中心设备只听广播,不会主动问从设备要扫描响应数据。

如果设置为 Active Scanning,中心设备才可能对可扫描广播包发送:

复制代码
SCAN_REQ

然后从设备回复:

复制代码
SCAN_RSP

所以完整名称、厂商自定义数据等如果被放在 Scan Response Data 里,主设备就需要主动扫描才有机会拿到。


Tip 5:Own_Address_Type 主要和 SCAN_REQ 相关

截图里说:

复制代码
Own_Address_Type parameter indicates the type of address being used in the scan request packets.

这里的 scan request packets 指的就是:

复制代码
SCAN_REQ

也就是说,主设备主动扫描时发出的 SCAN_REQ 里面会带自己的地址。

Legacy SCAN_REQ 里有两个重要地址字段:

复制代码
ScanA:Scanner Address,也就是主设备地址
AdvA :Advertiser Address,也就是广播设备地址

Own_Address_Type 影响的就是 ScanA 使用什么地址类型。


Tip 6:正在扫描时不能改扫描参数

这个截图最下面的错误表格非常重要。

如果扫描已经打开:

复制代码
LE Set Scan Enable = 0x01

这时候再发:

复制代码
LE Set Scan Parameters

可能会返回:

复制代码
Command Disallowed,0x0C

所以要养成一个固定流程:

复制代码
先 Disable Scan
再 Set Scan Parameters
再 Enable Scan

也就是:

复制代码
HCI_LE_Set_Scan_Enable(0x00)
HCI_LE_Set_Scan_Parameters(...)
HCI_LE_Set_Scan_Enable(0x01)

Tip 7:这个命令是 Legacy Scanning 用的,不是 Extended Scanning 的新命令

LE Set Scan Parameters 是传统 BLE Legacy Scanning 里的命令。

如果是 Bluetooth 5 之后的 Extended Advertising / Extended Scanning,还会涉及:

复制代码
LE Set Extended Scan Parameters
LE Set Extended Scan Enable

所以你现在看这个 7.8.10 LE Set Scan Parameters command,可以先把它放在 Legacy Scanning 体系里理解。


LE_Scan_Type

Value Parameter Description
0x00 被动扫描。不会发送扫描 PDU,默认值
0x01 主动扫描。可以发送扫描 PDU
All other values 保留,供将来使用

这里的 Scanning PDU,主要就是指主动扫描时发出的:

复制代码
SCAN_REQ

所以可以理解为:

复制代码
LE_Scan_Type = 0x00
只听广播,不主动发 SCAN_REQ

LE_Scan_Type = 0x01
收到可扫描广播后,可以主动发 SCAN_REQ

LE_Scan_Interval

Value Parameter Description
N = 0xXXXX 该参数定义为:从 Controller 开始上一次 LE 扫描,到它开始下一次 LE 扫描之间的时间间隔。

详细说明:

复制代码
Range: 0x0004 to 0x4000
Default: 0x0010 (10 ms)
Time = N × 0.625 ms
Time Range: 2.5 ms to 10.24 s

翻译:

复制代码
取值范围:0x0004 到 0x4000
默认值:0x0010,也就是 10 ms
时间 = N × 0.625 ms
时间范围:2.5 ms 到 10.24 s

也就是说,LE_Scan_Interval 不是直接填毫秒,而是填一个单位值 N

换算公式是:

复制代码
实际时间 = N × 0.625 ms

例如:

复制代码
N = 0x0010 = 16
时间 = 16 × 0.625 ms = 10 ms

所以默认值 0x0010 对应 10 ms。


LE_Scan_Window

Value Parameter Description
N = 0xXXXX LE 扫描的持续时间。LE_Scan_Window 必须小于或等于 LE_Scan_Interval

详细说明:

复制代码
Range: 0x0004 to 0x4000
Default: 0x0010 (10 ms)
Time = N × 0.625 ms
Time Range: 2.5 ms to 10.24 s

翻译:

复制代码
取值范围:0x0004 到 0x4000
默认值:0x0010,也就是 10 ms
时间 = N × 0.625 ms
时间范围:2.5 ms 到 10.24 s

也就是说,LE_Scan_Window 也不是直接填毫秒,也要通过 N × 0.625 ms 换算。

例如:

复制代码
N = 0x0010 = 16
时间 = 16 × 0.625 ms = 10 ms

Tip 1:Interval 是扫描周期,Window 是真正扫描的时间

这两个参数很容易混淆。

可以这样理解:

复制代码
LE_Scan_Interval:扫描周期
LE_Scan_Window:每个扫描周期里面真正扫描的时间

例如:

复制代码
LE_Scan_Interval = 100 ms
LE_Scan_Window   = 50 ms

含义大概是:

复制代码
每 100 ms 一个扫描周期
其中 50 ms 在扫描
剩下 50 ms 不扫描

画成图就是:

复制代码
|<----------- 100 ms ----------->|
|<--- 50 ms scan --->| idle......|

|<----------- 100 ms ----------->|
|<--- 50 ms scan --->| idle......|

Tip 2:Window 必须小于或等于 Interval

规范要求:

复制代码
LE_Scan_Window <= LE_Scan_Interval

不能配置成:

复制代码
LE_Scan_Window > LE_Scan_Interval

因为 Window 是 Interval 里面的一段时间,不能比整个扫描周期还长。


Tip 3:Window = Interval 表示连续扫描

如果:

复制代码
LE_Scan_Window = LE_Scan_Interval

那么表示连续扫描。

例如默认值就是:

复制代码
LE_Scan_Interval = 0x0010 = 10 ms
LE_Scan_Window   = 0x0010 = 10 ms

也就是:

复制代码
扫描周期 10 ms
扫描窗口 10 ms
整个周期都在扫描

可以理解为:

复制代码
|<--- 10 ms scan --->|
|<--- 10 ms scan --->|
|<--- 10 ms scan --->|

Tip 4:Passive Scanning 不会拿到 Scan Response

如果:

复制代码
LE_Scan_Type = 0x00

就是被动扫描。

被动扫描只接收广播包,比如:

复制代码
ADV_IND
ADV_NONCONN_IND
ADV_SCAN_IND

它不会发送:

复制代码
SCAN_REQ

所以它也不会触发从设备回复:

复制代码
SCAN_RSP

因此,如果蓝牙模块把完整名称、部分厂商数据、额外 Service UUID 放在 Scan Response Data 里面,被动扫描可能就看不到这些内容。


Tip 5:Active Scanning 是"可以发",不是"一定发"

截图里面写的是:

复制代码
Scanning PDUs may be sent.

这里的 may be sent 很关键,意思是:

复制代码
可以发送扫描 PDU

不是:

复制代码
一定会发送扫描 PDU

因为 Controller 是否真的发 SCAN_REQ,还要看广播包类型、过滤策略、地址匹配、扫描时机、Controller 实现等因素。

比如收到下面这种广播包时,才有扫描响应的意义:

复制代码
ADV_IND
ADV_SCAN_IND

如果收到的是不可扫描广播,例如:

复制代码
ADV_NONCONN_IND

那么即使你设置了 Active Scanning,也不会正常去要 Scan Response。


Tip 6:Interval / Window 的单位不是 1 ms,而是 0.625 ms

这点很容易踩坑。

规范中的值不是直接写毫秒,而是:

复制代码
Time = N × 0.625 ms

所以:

复制代码
0x0004 = 4 × 0.625 ms = 2.5 ms
0x0010 = 16 × 0.625 ms = 10 ms
0x00A0 = 160 × 0.625 ms = 100 ms
0x4000 = 16384 × 0.625 ms = 10240 ms = 10.24 s

如果你想配置 100 ms,应该填:

复制代码
100 / 0.625 = 160 = 0x00A0

而不是直接填 100。


Tip 7:这三个参数组合起来看,Legacy Scanning 就清楚了

可以把它们合起来理解:

复制代码
LE_Scan_Type
决定是被动扫描还是主动扫描

LE_Scan_Interval
决定多久启动一次扫描窗口

LE_Scan_Window
决定每次扫描窗口持续多久

一个典型的主动连续扫描配置可以是:

复制代码
LE_Scan_Type     = 0x01      // Active Scanning
LE_Scan_Interval = 0x0010    // 10 ms
LE_Scan_Window   = 0x0010    // 10 ms

含义是:

复制代码
主动扫描
扫描周期 10 ms
扫描窗口 10 ms
连续扫描
可以发送 SCAN_REQ
有机会拿到 SCAN_RSP

一句话总结:

复制代码
LE_Scan_Type 决定扫不扫 Scan Response;
LE_Scan_Interval 决定扫描周期;
LE_Scan_Window 决定周期内真正扫描多久。

Own_Address_Type

Value Parameter Description
0x00 Public Device Address,公共设备地址,默认值
0x01 Random Device Address,随机设备地址
0x02 Controller 根据 Resolving List 中本地 IRK 生成 Resolvable Private Address。如果 Resolving List 中没有匹配项,则使用 Public Address
0x03 Controller 根据 Resolving List 中本地 IRK 生成 Resolvable Private Address。如果 Resolving List 中没有匹配项,则使用 LE_Set_Random_Address 设置的 Random Address
All other values 保留,供将来使用

这里的 Own_Address_Type 表示中心设备在扫描请求包里使用什么类型的本机地址。

对于 Legacy Active Scanning 来说,中心设备可能会发送:

复制代码
SCAN_REQ

SCAN_REQ 里面有两个地址字段:

复制代码
ScanA:Scanner Address,扫描设备地址,也就是中心设备自己的地址
AdvA :Advertiser Address,广播设备地址,也就是外设地址

所以 Own_Address_Type 主要决定 ScanA 使用什么地址类型。


Scanning_Filter_Policy

Value Parameter Description
0x00 基础的不过滤扫描策略
0x01 基础的过滤扫描策略
0x02 扩展的不过滤扫描策略
0x03 扩展的过滤扫描策略
All other values 保留,供将来使用

这里的 Scanning_Filter_Policy 用来控制扫描时是否使用过滤策略。

简单理解:

复制代码
0x00:不过滤,正常扫描所有广播设备
0x01:使用基础过滤策略
0x02:扩展的不过滤策略
0x03:扩展的过滤策略

Return parameters

Value Parameter Description
0x00 HCI_LE_Set_Scan_Parameters 命令执行成功
0x01 to 0xFF HCI_LE_Set_Scan_Parameters 命令执行失败。错误码和描述请参考 [Vol 1] Part F, Controller Error Codes

也就是说,Controller 收到 HCI_LE_Set_Scan_Parameters 之后,会返回一个 Status

如果:

复制代码
Status = 0x00

表示设置扫描参数成功。

如果:

复制代码
Status != 0x00

表示设置失败,需要根据错误码进一步判断原因。


Event(s) generated,unless masked away

原文

When the HCI_LE_Set_Scan_Parameters command has completed, an HCI_Command_Complete event shall be generated.

翻译

HCI_LE_Set_Scan_Parameters 命令完成后,应当生成一个 HCI_Command_Complete 事件。

也就是说,这个命令不是通过普通数据包返回结果,而是通过 HCI Event 返回结果。

流程可以理解为:

复制代码
Host 发送 HCI_LE_Set_Scan_Parameters
        ↓
Controller 执行该命令
        ↓
Controller 返回 HCI_Command_Complete Event
        ↓
Event 里面带 Status