Zephyr RTOS 下 BLE 主动扫描和被动扫描详解

目录

概述

[1 核心概念对比](#1 核心概念对比)

[1.1 扫描类型概述](#1.1 扫描类型概述)

[1.2 数据获取差异](#1.2 数据获取差异)

[1.3 选择指南](#1.3 选择指南)

[2 API 详解](#2 API 详解)

[2.1 扫描参数结构体](#2.1 扫描参数结构体)

[2.2 扫描类型常量](#2.2 扫描类型常量)

[2.3 扫描选项常量](#2.3 扫描选项常量)

[3 具体用法示例](#3 具体用法示例)

[3.1 基础扫描设置](#3.1 基础扫描设置)

[3.2 完整示例:智能选择扫描模式](#3.2 完整示例:智能选择扫描模式)

[3.3 扫描响应数据处理](#3.3 扫描响应数据处理)

[3.4 停止扫描](#3.4 停止扫描)

[4 扫描参数配置建议](#4 扫描参数配置建议)

[4.1 扫描间隔和窗口计算](#4.1 扫描间隔和窗口计算)

[4.2 不同场景的推荐配置](#4.2 不同场景的推荐配置)

[4.3 高级功能:编码PHY扫描 (BLE 5.0)](#4.3 高级功能:编码PHY扫描 (BLE 5.0))

[5 最佳实践和注意事项](#5 最佳实践和注意事项)

[5.1 功耗优化建议](#5.1 功耗优化建议)

[5.2 错误处理](#5.2 错误处理)


概述

在Zephyr RTOS中,蓝牙低功耗(BLE)扫描分为主动扫描和被动扫描。这两种扫描模式在Zephyr的蓝牙API中通过bt_le_scan_param结构体的type字段来指定。本文主要介绍Zephyr RTOS 下 BLE 主动扫描和被动扫描的相关接口和两者的使用方法。

1 核心概念对比

1.1 扫描类型概述

在 Zephyr RTOS 的蓝牙协议栈中,扫描分为两种类型:

类型 枚举值 说明
被动扫描 BT_LE_SCAN_TYPE_PASSIVE 只接收广播包,不发送扫描请求
主动扫描 BT_LE_SCAN_TYPE_ACTIVE 接收广播包并发送扫描请求获取扫描响应

1.2 数据获取差异

1.3 选择指南

在 Zephyr RTOS 中,通过合理配置扫描参数,可以在功耗、发现速度和数据完整性之间找到最佳平衡点,满足不同应用场景的需求。

1) 使用被动扫描

  • 只需要发现设备存在

  • 设备广播数据已包含所有需要的信息

  • 对功耗要求极高

  • 设备密度高,需要减少无线通信

2) 使用主动扫描

  • 需要完整的设备信息(如完整设备名)

  • 快速设备发现和配对

  • 需要获取扫描响应中的额外数据

  • 功耗不是首要考虑因素

3) 关键参数配置

cpp 复制代码
// 平衡功耗和发现速度的推荐配置
struct bt_le_scan_param balanced_param = {
    .type = BT_LE_SCAN_TYPE_ACTIVE,  // 或 PASSIVE
    .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
    .interval = 0x0400,  // 1024ms (适中)
    .window = 0x0030,    // 30ms
    .timeout = 0,        // 持续扫描
};

2 API 详解

2.1 扫描参数结构体

cpp 复制代码
struct bt_le_scan_param {
    uint8_t  type;        // 扫描类型: BT_LE_SCAN_TYPE_PASSIVE 或 BT_LE_SCAN_TYPE_ACTIVE
    uint8_t  options;     // 扫描选项 (位掩码)
    uint16_t interval;    // 扫描间隔 (单位: 0.625ms)
    uint16_t window;      // 扫描窗口 (单位: 0.625ms)
    uint8_t  timeout;     // 扫描超时时间 (单位: 秒),0 = 持续扫描
};

2.2 扫描类型常量

cpp 复制代码
#define BT_LE_SCAN_TYPE_PASSIVE  0x00  // 被动扫描
#define BT_LE_SCAN_TYPE_ACTIVE   0x01  // 主动扫描

2.3 扫描选项常量

cpp 复制代码
#define BT_LE_SCAN_OPT_NONE              0x00  // 无选项
#define BT_LE_SCAN_OPT_FILTER_DUPLICATE  0x01  // 过滤重复广播
#define BT_LE_SCAN_OPT_FILTER_WHITELIST  0x02  // 只扫描白名单设备
#define BT_LE_SCAN_OPT_CODED             0x04  // 使用编码PHY (BLE 5.0)

3 具体用法示例

3.1 基础扫描设置

1) 扫描示例

cpp 复制代码
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>

/* 扫描回调函数 */
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
                    uint8_t adv_type, struct net_buf_simple *buf)
{
    char addr_str[BT_ADDR_LE_STR_LEN];
    
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    printk("设备地址: %s, RSSI: %d, 广播类型: 0x%02X\n", 
           addr_str, rssi, adv_type);
}

/* 启动被动扫描 */
void start_passive_scan(void)
{
    struct bt_le_scan_param scan_param = {
        .type       = BT_LE_SCAN_TYPE_PASSIVE,  // 被动扫描
        .options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,  // 过滤重复
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,  // 快速扫描间隔
        .window     = BT_GAP_SCAN_FAST_WINDOW,    // 快速扫描窗口
        .timeout    = 0,  // 0 = 持续扫描,非0 = 超时后停止(秒)
    };
    
    int err = bt_le_scan_start(&scan_param, scan_cb);
    if (err) {
        printk("启动扫描失败 (err %d)\n", err);
    } else {
        printk("被动扫描已启动\n");
    }
}

/* 启动主动扫描 */
void start_active_scan(void)
{
    struct bt_le_scan_param scan_param = {
        .type       = BT_LE_SCAN_TYPE_ACTIVE,    // 主动扫描
        .options    = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,
        .window     = BT_GAP_SCAN_FAST_WINDOW,
        .timeout    = 30,  // 扫描30秒后自动停止
    };
    
    int err = bt_le_scan_start(&scan_param, scan_cb);
    if (err) {
        printk("启动扫描失败 (err %d)\n", err);
    } else {
        printk("主动扫描已启动,30秒后停止\n");
    }
}

2) 预设的扫描参数

Zephyr 提供了一些预设的扫描参数:

cpp 复制代码
// 快速扫描参数(高频率,功耗高)
#define BT_GAP_SCAN_FAST_INTERVAL 0x0060  // 60ms = 96 * 0.625ms
#define BT_GAP_SCAN_FAST_WINDOW   0x0030  // 30ms = 48 * 0.625ms

// 慢速扫描参数(低频率,功耗低)
#define BT_GAP_SCAN_SLOW_INTERVAL_1 0x0800  // 1280ms
#define BT_GAP_SCAN_SLOW_WINDOW_1   0x0012  // 11.25ms

// 超低功耗扫描
#define BT_GAP_SCAN_SLOW_INTERVAL_2 0x1000  // 2560ms
#define BT_GAP_SCAN_SLOW_WINDOW_2   0x0012  // 11.25ms

3.2 完整示例:智能选择扫描模式

cpp 复制代码
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>

static struct k_timer scan_timer;
static bool found_target_device = false;

/* 扫描回调 - 解析广播数据 */
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
                    uint8_t adv_type, struct net_buf_simple *buf)
{
    char addr_str[BT_ADDR_LE_STR_LEN];
    char name[30] = {0};
    
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    
    // 解析广播数据中的设备名
    while (buf->len > 1) {
        uint8_t len = net_buf_simple_pull_u8(buf);
        uint8_t type = net_buf_simple_pull_u8(buf);
        
        if (type == BT_DATA_NAME_COMPLETE || 
            type == BT_DATA_NAME_SHORTENED) {
            size_t name_len = min(len - 1, sizeof(name) - 1);
            memcpy(name, buf->data, name_len);
            name[name_len] = '\0';
            
            printk("发现设备: %s, 地址: %s, RSSI: %d, 名称: %s\n",
                   (adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) ? 
                   "[扫描响应]" : "[广播]", 
                   addr_str, rssi, name);
            
            // 如果是目标设备,切换到被动扫描以省电
            if (strstr(name, "TARGET_DEVICE")) {
                found_target_device = true;
                printk("找到目标设备,切换到被动扫描\n");
            }
        }
        
        // 跳过剩余数据
        net_buf_simple_pull(buf, len - 1);
    }
}

/* 切换扫描模式 */
static void switch_scan_mode(bool active)
{
    struct bt_le_scan_param scan_param = {
        .type = active ? BT_LE_SCAN_TYPE_ACTIVE : BT_LE_SCAN_TYPE_PASSIVE,
        .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
        .interval = active ? 
            BT_GAP_SCAN_FAST_INTERVAL : 
            BT_GAP_SCAN_SLOW_INTERVAL_1,
        .window = active ? 
            BT_GAP_SCAN_FAST_WINDOW : 
            BT_GAP_SCAN_SLOW_WINDOW_1,
        .timeout = 0,
    };
    
    bt_le_scan_stop();
    bt_le_scan_start(&scan_param, scan_cb);
    
    printk("已切换到%s扫描模式\n", 
           active ? "主动(快速)" : "被动(慢速)");
}

/* 定时器回调 */
static void scan_timeout_cb(struct k_timer *timer)
{
    if (!found_target_device) {
        // 如果没找到设备,切换回主动扫描以获取更多信息
        switch_scan_mode(true);
    }
}

/* 主函数 */
int main(void)
{
    int err;
    
    // 初始化蓝牙
    err = bt_enable(NULL);
    if (err) {
        printk("蓝牙初始化失败 (err %d)\n", err);
        return 0;
    }
    
    // 先使用主动扫描快速发现设备
    printk("启动主动扫描搜索设备...\n");
    switch_scan_mode(true);
    
    // 设置定时器,10秒后检查是否找到设备
    k_timer_init(&scan_timer, scan_timeout_cb, NULL);
    k_timer_start(&scan_timer, K_SECONDS(10), K_SECONDS(10));
    
    while (1) {
        k_sleep(K_SECONDS(1));
    }
    
    return 0;
}

3.3 使用白名单过滤

cpp 复制代码
/* 配置白名单并扫描 */
void start_whitelist_scan(void)
{
    struct bt_le_scan_param scan_param = {
        .type = BT_LE_SCAN_TYPE_ACTIVE,
        .options = BT_LE_SCAN_OPT_FILTER_WHITELIST,  // 只扫描白名单设备
        .interval = BT_GAP_SCAN_FAST_INTERVAL,
        .window = BT_GAP_SCAN_FAST_WINDOW,
        .timeout = 0,
    };
    
    // 添加设备到白名单
    bt_addr_le_t whitelist_addrs[2] = {
        {.type = BT_ADDR_LE_RANDOM, 
         .a = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}},
        {.type = BT_ADDR_LE_PUBLIC, 
         .a = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
    };
    
    // 设置白名单
    bt_le_whitelist_clear();
    for (int i = 0; i < ARRAY_SIZE(whitelist_addrs); i++) {
        bt_le_whitelist_add(&whitelist_addrs[i]);
    }
    
    // 启动扫描
    int err = bt_le_scan_start(&scan_param, scan_cb);
    if (err) {
        printk("启动白名单扫描失败 (err %d)\n", err);
    }
}

3.3 扫描响应数据处理

cpp 复制代码
/* 处理主动扫描获取的完整数据 */
static void process_scan_data(const bt_addr_le_t *addr, 
                              int8_t rssi, uint8_t adv_type,
                              struct net_buf_simple *buf)
{
    static char last_addr[BT_ADDR_LE_STR_LEN] = {0};
    char curr_addr[BT_ADDR_LE_STR_LEN];
    
    bt_addr_le_to_str(addr, curr_addr, sizeof(curr_addr));
    
    // 如果是扫描响应,与之前的广播数据合并处理
    if (adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) {
        printk("接收到扫描响应,来自: %s\n", curr_addr);
        // 这里可以处理完整的62字节数据
    } 
    // 如果是广播数据
    else if (adv_type == BT_GAP_ADV_TYPE_ADV_IND || 
             adv_type == BT_GAP_ADV_TYPE_ADV_NONCONN_IND) {
        
        // 检查是否需要发送扫描请求(如果是主动扫描)
        if (strcmp(curr_addr, last_addr) != 0) {
            strncpy(last_addr, curr_addr, sizeof(last_addr)-1);
            printk("新设备广播: %s, RSSI: %d\n", curr_addr, rssi);
            
            // 主动扫描会在这里自动发送扫描请求
            // 被动扫描则不会
        }
    }
}

3.4 停止扫描

cpp 复制代码
/* 停止扫描 */
void stop_scanning(void)
{
    int err = bt_le_scan_stop();
    if (err) {
        printk("停止扫描失败 (err %d)\n", err);
    } else {
        printk("扫描已停止\n");
    }
}

4 扫描参数配置建议

4.1 扫描间隔和窗口计算

cpp 复制代码
/* 计算实际的扫描时间 */
void calculate_scan_times(void)
{
    // 时间 = 参数值 × 0.625ms
    
    uint16_t interval_ms = BT_GAP_SCAN_FAST_INTERVAL * 625 / 1000;  // 60ms
    uint16_t window_ms = BT_GAP_SCAN_FAST_WINDOW * 625 / 1000;      // 30ms
    
    float duty_cycle = (float)window_ms / interval_ms * 100;  // 50%
    
    printk("扫描占空比: %.1f%%\n", duty_cycle);
}

4.2 不同场景的推荐配置

cpp 复制代码
/* 不同应用场景的扫描配置 */
struct bt_le_scan_param get_scan_config(enum scan_scenario scenario)
{
    struct bt_le_scan_param param = {0};
    
    switch (scenario) {
        case SCAN_FAST_DISCOVERY:
            // 快速设备发现
            param.type = BT_LE_SCAN_TYPE_ACTIVE;
            param.interval = BT_GAP_SCAN_FAST_INTERVAL;  // 60ms
            param.window = BT_GAP_SCAN_FAST_WINDOW;      // 30ms
            param.timeout = 30;  // 30秒
            break;
            
        case SCAN_LOW_POWER_MONITOR:
            // 低功耗监控
            param.type = BT_LE_SCAN_TYPE_PASSIVE;
            param.interval = BT_GAP_SCAN_SLOW_INTERVAL_2;  // 2560ms
            param.window = BT_GAP_SCAN_SLOW_WINDOW_2;      // 11.25ms
            param.timeout = 0;  // 持续
            break;
            
        case SCAN_BACKGROUND_SCANNING:
            // 后台扫描
            param.type = BT_LE_SCAN_TYPE_PASSIVE;
            param.interval = 0x0640;  // 1000ms
            param.window = 0x0030;    // 30ms
            param.timeout = 0;
            param.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE;
            break;
            
        case SCAN_WHITELIST_ONLY:
            // 只扫描白名单设备
            param.type = BT_LE_SCAN_TYPE_ACTIVE;
            param.interval = 0x0C80;  // 2000ms
            param.window = 0x0018;    // 15ms
            param.timeout = 0;
            param.options = BT_LE_SCAN_OPT_FILTER_WHITELIST | 
                           BT_LE_SCAN_OPT_FILTER_DUPLICATE;
            break;
    }
    
    return param;
}

4.3 高级功能:编码PHY扫描 (BLE 5.0)

cpp 复制代码
#ifdef CONFIG_BT_LE_5_0
/* 使用编码PHY进行远距离扫描 */
void start_coded_phy_scan(void)
{
    struct bt_le_scan_param scan_param = {
        .type = BT_LE_SCAN_TYPE_PASSIVE,
        .options = BT_LE_SCAN_OPT_CODED,  // 使用编码PHY
        .interval = 0x2000,  // 5120ms (长距离需要更长的窗口)
        .window = 0x0800,    // 1280ms
        .timeout = 0,
    };
    
    // 设置PHY参数
    struct bt_le_scan_param *phy_params = &scan_param;
    
    printk("启动编码PHY扫描(远距离模式)\n");
    int err = bt_le_scan_start(phy_params, scan_cb);
    if (err) {
        printk("编码PHY扫描启动失败 (err %d)\n", err);
    }
}
#endif

5 最佳实践和注意事项

5.1 功耗优化建议

cpp 复制代码
/* 动态调整扫描策略以节省功耗 */
void adaptive_scanning(void)
{
    static int scan_phase = 0;
    
    switch (scan_phase) {
        case 0:  // 阶段1:快速主动扫描(10秒)
            start_active_scan_fast();
            k_sleep(K_SECONDS(10));
            scan_phase = 1;
            break;
            
        case 1:  // 阶段2:慢速被动扫描(50秒)
            start_passive_scan_slow();
            k_sleep(K_SECONDS(50));
            scan_phase = 0;  // 回到阶段1
            break;
    }
}

5.2 错误处理

cpp 复制代码
/* 健壮的扫描控制函数 */
int robust_scan_control(bool enable, enum scan_type type)
{
    int ret;
    
    if (enable) {
        struct bt_le_scan_param param = {
            .type = (type == SCAN_TYPE_ACTIVE) ? 
                    BT_LE_SCAN_TYPE_ACTIVE : 
                    BT_LE_SCAN_TYPE_PASSIVE,
            .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
            .interval = BT_GAP_SCAN_FAST_INTERVAL,
            .window = BT_GAP_SCAN_FAST_WINDOW,
            .timeout = 0,
        };
        
        ret = bt_le_scan_start(&param, scan_cb);
        if (ret) {
            printk("扫描启动失败: %d,1秒后重试\n", ret);
            k_sleep(K_SECONDS(1));
            ret = bt_le_scan_start(&param, scan_cb);
        }
    } else {
        ret = bt_le_scan_stop();
        if (ret) {
            printk("扫描停止失败: %d\n", ret);
        }
    }
    
    return ret;
}
相关推荐
ScilogyHunter4 天前
Zephyr串口驱动开发及构建完全指南
驱动开发·uart·zephyr
ScilogyHunter4 天前
Zephyr Hello World应用开发构建完全指南
zephyr·hello world
ScilogyHunter4 天前
Zephyr Twister测试框架完全指南
zephyr·twister
ScilogyHunter5 天前
west init 命令详解
init·zephyr·west
ScilogyHunter5 天前
使用Kconfig配置Zephyr工程完全指南
kconfig·zephyr
ScilogyHunter5 天前
Zephyr设备树完全指南
zephyr
ScilogyHunter6 天前
Zephyr项目按需配置完全指南
zephyr
ScilogyHunter6 天前
Zephyr最简工程配置指南
zephyr
ScilogyHunter6 天前
Zephyr主仓库目录结构完全指南
zephyr
ScilogyHunter6 天前
Zephyr工程配置完全指南
zephyr