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;
}
相关推荐
mftang14 小时前
Zephyr RTOS中bt_conn_le_create 函数用法详细介绍
zephyr·ble central
mftang10 天前
Zephyr RTOS 下 bt_le_scan_start 函数详解
被动扫描·zephyr rtos·主动扫描
mftang11 天前
Zephyr RTOS 中 BT_CONN_CB_DEFINE 详解
zephyr·central·peripheral·注册回调
mftang1 个月前
Nordic nRF52805 Zephyr OS下低功耗模式应用详细介绍-D
watchdog·低功耗·zephyr
硬汉嵌入式1 个月前
Zephyr 十周年报告:开源嵌入式创新的十年历程
zephyr
嵌入式小企鹅1 个月前
蓝牙学习系列(七):BLE GATT 数据模型详解
学习·蓝牙·ble·蓝牙协议栈·蓝牙开发·gatt
fitpolo1 个月前
串行通讯(I2C)
zephyr
wsoz1 个月前
服务发现协议(SDP)
嵌入式·sdp·蓝牙协议栈·服务发现协议
wsoz1 个月前
串口仿真协议(RFCOMM)
单片机·嵌入式·蓝牙协议栈·rfcomm