目录
[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(¶m, scan_cb);
if (ret) {
printk("扫描启动失败: %d,1秒后重试\n", ret);
k_sleep(K_SECONDS(1));
ret = bt_le_scan_start(¶m, scan_cb);
}
} else {
ret = bt_le_scan_stop();
if (ret) {
printk("扫描停止失败: %d\n", ret);
}
}
return ret;
}
