3. 主动扫描与被动扫描
任务说明
本案例基于nRF52832平台与Zephyr RTOS,实现BLE中心设备的主动扫描与被动扫描两种模式,通过对比扫描结果中是否包含扫描响应包(Scan Response)来理解两种扫描机制的本质区别。主动扫描会额外发送扫描请求(SCAN_REQ)以获取从设备的扫描响应数据,被动扫描则仅监听广播包。
实现原理
BLE扫描分为两种模式:被动扫描只监听广播信道上的ADV_IND/ADV_NONCONN_IND等广播包;主动扫描则在收到可连接广播包后,自动向从设备发送SCAN_REQ,从设备收到后回复SCAN_RSP(扫描响应包)。扫描响应包通常携带从设备的完整名称、附加服务UUID等额外数据。本案例通过配置Zephyr的BLE扫描参数,分别以两种模式扫描同一从设备,并打印收到的广播数据与扫描响应数据,从而直观对比差异。
代码实现
/* main.c - BLE主动扫描与被动扫描对比 */
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/printk.h>
/* 扫描参数:被动扫描(不发送SCAN_REQ) */
static const struct bt_le_scan_param scan_param_passive = {
.type = BT_HCI_LE_SCAN_PASSIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
};
/* 扫描参数:主动扫描(发送SCAN_REQ) */
static const struct bt_le_scan_param scan_param_active = {
.type = BT_HCI_LE_SCAN_ACTIVE,
.options = BT_LE_SCAN_OPT_NONE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
};
/* 解析并打印AD结构中的指定类型数据 */
static void parse_ad(struct bt_data *data, uint8_t len)
{
for (uint8_t i = 0; i < len; i++) {
if (data[i].type == BT_DATA_NAME_COMPLETE) {
printk(" 完整名称: %.*s\n", data[i].data_len, data[i].data);
} else if (data[i].type == BT_DATA_NAME_SHORTENED) {
printk(" 短名称: %.*s\n", data[i].data_len, data[i].data);
} else if (data[i].type == BT_DATA_UUID16_SOME) {
printk(" UUID16(部分): ");
for (uint8_t j = 0; j < data[i].data_len; j += 2) {
printk("%04x ", sys_get_le16(&data[i].data[j]));
}
printk("\n");
}
}
}
/* 扫描回调:打印广播包与扫描响应包 */
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
uint8_t adv_type, struct bt_data *ad,
uint8_t len)
{
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("设备: %s, RSSI: %d dBm\n", addr_str, rssi);
if (adv_type == BT_GAP_ADV_TYPE_ADV_IND) {
printk(" 广播包(ADV_IND):\n");
} else if (adv_type == BT_GAP_ADV_TYPE_ADV_SCAN_RSP) {
printk(" 扫描响应包(SCAN_RSP):\n");
} else {
printk(" 广播类型: 0x%02x\n", adv_type);
}
parse_ad(ad, len);
printk("\n");
}
/* 执行一次扫描,mode: 0=被动, 1=主动 */
static void do_scan(const struct bt_le_scan_param *param, const char *mode_name)
{
int err;
printk("===== %s扫描开始 =====\n", mode_name);
err = bt_le_scan_start(param, scan_cb);
if (err) {
printk("扫描启动失败: %d\n", err);
return;
}
/* 扫描5秒 */
k_sleep(K_SECONDS(5));
err = bt_le_scan_stop();
if (err) {
printk("扫描停止失败: %d\n", err);
}
printk("===== %s扫描结束 =====\n\n", mode_name);
}
int main(void)
{
int err;
printk("BLE主动/被动扫描对比示例启动\n");
err = bt_enable(NULL);
if (err) {
printk("蓝牙初始化失败: %d\n", err);
return err;
}
printk("蓝牙已就绪\n\n");
/* 先执行被动扫描 */
do_scan(&scan_param_passive, "被动");
/* 再执行主动扫描 */
do_scan(&scan_param_active, "主动");
printk("示例完成\n");
return 0;
}
工程配置
# prj.conf
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_SCAN_FILTER_ENABLE=n
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
验证方法
使用另一块nRF52832开发板运行任意BLE广播示例(如Zephyr的peripheral_hr示例),确保广播包中包含完整设备名称。将本示例编译烧录到中心设备后,通过串口终端观察输出。预期结果:被动扫描阶段只打印ADV_IND广播包;主动扫描阶段会先打印ADV_IND广播包,随后打印SCAN_RSP扫描响应包。对比两种模式下同一从设备的打印信息,主动扫描会额外显示扫描响应包中携带的完整名称或附加UUID数据,而被动扫描无法获取这些信息。
判定标准
- 串口输出中被动扫描阶段无
SCAN_RSP类型数据包打印; - 主动扫描阶段出现
SCAN_RSP类型数据包,且其中包含广播包中未出现的附加数据(如完整名称); - 两种模式均能正常扫描到目标从设备,且RSSI值合理(通常在-30~-90 dBm范围)。