目录
概述
1 函数功能介绍
1.1 函数原型和参数和主要参数
1.2 主要功能
1.3 扫描参数选择指南
2 基本使用方法
2.1 简单扫描示例
2.2 完整的使用流程
3 高级用法
3.1 被动扫描与主动扫描对比
3.2 使用白名单过滤
3.3 扫描参数计算和优化
3.4 解析特定广播数据(如iBeacon)
4 重要注意事项
4.1 功耗管理
4.2 回调函数注意事项
4.3 内存管理
4.4 错误处理
5 常见问题解决
概述
bt_le_scan_start是 Zephyr Bluetooth API 的核心函数之一,用于启动蓝牙低功耗(BLE)扫描。它允许设备作为中心设备(Central)或观察者(Observer)发现周围的蓝牙外围设备。
1 函数功能介绍
1.1 函数原型和参数和主要参数
1) 函数原型
int bt_le_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb);2) 主要参数
- )param- 扫描参数结构体
struct bt_le_scan_param { uint8_t type; // 扫描类型:被动或主动 uint8_t options; // 扫描选项(位掩码) uint16_t interval; // 扫描间隔(单位:0.625ms) uint16_t window; // 扫描窗口(单位:0.625ms) uint8_t timeout; // 扫描超时(单位:秒),0=持续扫描 };-1)扫描类型(type):
#define BT_LE_SCAN_TYPE_PASSIVE 0x00 // 被动扫描 #define BT_LE_SCAN_TYPE_ACTIVE 0x01 // 主动扫描-2) 扫描选项(options):
#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)cb- 扫描回调函数
typedef void bt_le_scan_cb_t(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, struct net_buf_simple *buf);回调参数:
addr:发现的设备地址
rssi:信号强度指示器(dBm)
adv_type:广播类型
buf:广播数据缓冲区
广播类型(adv_type):
#define BT_GAP_ADV_TYPE_ADV_IND 0x00 // 可连接的非定向广播 #define BT_GAP_ADV_TYPE_ADV_DIRECT_IND 0x01 // 可连接的定向广播 #define BT_GAP_ADV_TYPE_ADV_SCAN_IND 0x02 // 可扫描的非定向广播 #define BT_GAP_ADV_TYPE_ADV_NONCONN_IND 0x03 // 不可连接的非定向广播 #define BT_GAP_ADV_TYPE_SCAN_RSP 0x04 // 扫描响应1.2 主要功能
bt_le_scan_start是 Zephyr Bluetooth API 的核心函数之一,用于启动蓝牙低功耗(BLE)扫描。它允许设备作为中心设备(Central)或观察者(Observer)发现周围的蓝牙外围设备。
功能总结如下:
设备发现:扫描并发现周围的 BLE 广播设备
广播数据收集:获取设备的广播数据(如设备名称、服务 UUID、制造商数据等)
连接准备:为后续的连接操作发现目标设备
信标监控:监控 iBeacon、Eddystone 等蓝牙信标
1.3 扫描参数选择指南
2 基本使用方法
2.1 简单扫描示例
#include <zephyr/bluetooth/bluetooth.h> #include <zephyr/bluetooth/conn.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); // 解析广播数据 parse_advertising_data(buf); } /* 解析广播数据 */ static void parse_advertising_data(struct net_buf_simple *buf) { while (buf->len > 1) { uint8_t len = net_buf_simple_pull_u8(buf); uint8_t type = net_buf_simple_pull_u8(buf); switch (type) { case BT_DATA_NAME_COMPLETE: case BT_DATA_NAME_SHORTENED: { char name[31]; size_t name_len = MIN(len - 1, sizeof(name) - 1); memcpy(name, buf->data, name_len); name[name_len] = '\0'; printk("设备名称: %s\n", name); break; } case BT_DATA_UUID16_SOME: case BT_DATA_UUID16_ALL: { printk("16位UUID服务: "); for (int i = 0; i < (len - 1) / 2; i++) { uint16_t uuid = net_buf_simple_pull_le16(buf); printk("%04x ", uuid); } printk("\n"); break; } case BT_DATA_MANUFACTURER_DATA: { uint16_t company_id = net_buf_simple_pull_le16(buf); printk("制造商数据,公司ID: 0x%04x\n", company_id); break; } } // 跳过剩余数据 net_buf_simple_pull(buf, len - 1); } } /* 启动扫描 */ int start_ble_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("启动扫描失败: %d\n", err); return err; } printk("BLE扫描已启动\n"); return 0; } /* 停止扫描 */ void stop_ble_scan(void) { int err = bt_le_scan_stop(); if (err) { printk("停止扫描失败: %d\n", err); } else { printk("BLE扫描已停止\n"); } }2.2 完整的使用流程
#include <zephyr/kernel.h> #include <zephyr/bluetooth/bluetooth.h> #include <zephyr/bluetooth/conn.h> #define SCAN_DURATION_SECONDS 30 #define TARGET_DEVICE_NAME "MyDevice" static struct k_timer scan_timer; static bool found_target = false; /* 扫描回调 */ static void scan_callback(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)); // 检查是否为扫描响应 if (adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) { printk("收到扫描响应: %s, RSSI: %d\n", addr_str, rssi); } // 解析广播数据查找目标设备 check_for_target_device(addr, rssi, buf); } /* 检查目标设备 */ static void check_for_target_device(const bt_addr_le_t *addr, int8_t rssi, struct net_buf_simple *buf) { char addr_str[BT_ADDR_LE_STR_LEN]; 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) { char name[31]; size_t name_len = MIN(len - 1, sizeof(name) - 1); memcpy(name, buf->data, name_len); name[name_len] = '\0'; if (strcmp(name, TARGET_DEVICE_NAME) == 0) { printk("找到目标设备: %s, 地址: %s, RSSI: %d\n", name, addr_str, rssi); found_target = true; // 停止扫描 bt_le_scan_stop(); } } net_buf_simple_pull(buf, len - 1); } } /* 定时器回调:扫描超时 */ static void scan_timeout(struct k_timer *timer) { if (!found_target) { printk("扫描超时,未找到目标设备\n"); bt_le_scan_stop(); } } /* 初始化蓝牙 */ int init_bluetooth(void) { int err; err = bt_enable(NULL); if (err) { printk("蓝牙初始化失败: %d\n", err); return err; } printk("蓝牙已初始化\n"); return 0; } /* 主函数 */ int main(void) { int err; // 初始化蓝牙 err = init_bluetooth(); if (err) { return 0; } // 初始化定时器 k_timer_init(&scan_timer, scan_timeout, NULL); // 设置扫描参数 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 = 0, // 手动控制超时 }; // 启动扫描 printk("开始扫描目标设备...\n"); err = bt_le_scan_start(&scan_param, scan_callback); if (err) { printk("启动扫描失败: %d\n", err); return 0; } // 启动超时定时器 k_timer_start(&scan_timer, K_SECONDS(SCAN_DURATION_SECONDS), K_NO_WAIT); // 等待扫描完成 while (!found_target) { k_sleep(K_MSEC(100)); } printk("扫描完成\n"); return 0; }3 高级用法
3.1 被动扫描与主动扫描对比
/* 被动扫描配置(低功耗) */ 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_SLOW_INTERVAL_1, // 慢速扫描 .window = BT_GAP_SCAN_SLOW_WINDOW_1, .timeout = 0, // 持续扫描 }; bt_le_scan_start(&scan_param, scan_cb); 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 = 10, // 10秒后自动停止 }; bt_le_scan_start(&scan_param, scan_cb); printk("主动扫描已启动(快速发现模式)\n"); }3.2 使用白名单过滤
/* 设置白名单并扫描 */ void start_whitelist_scan(void) { int err; // 定义白名单设备地址 bt_addr_le_t whitelist[] = { {BT_ADDR_LE_RANDOM, {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}}}, {BT_ADDR_LE_PUBLIC, {{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}}}, }; // 清除现有白名单 bt_le_whitelist_clear(); // 添加设备到白名单 for (int i = 0; i < ARRAY_SIZE(whitelist); i++) { err = bt_le_whitelist_add(&whitelist[i]); if (err) { printk("添加白名单失败 [%d]: %d\n", i, err); } } // 设置白名单扫描参数 struct bt_le_scan_param scan_param = { .type = BT_LE_SCAN_TYPE_ACTIVE, .options = BT_LE_SCAN_OPT_FILTER_WHITELIST | // 只扫描白名单 BT_LE_SCAN_OPT_FILTER_DUPLICATE, .interval = BT_GAP_SCAN_FAST_INTERVAL, .window = BT_GAP_SCAN_FAST_WINDOW, .timeout = 0, }; err = bt_le_scan_start(&scan_param, scan_cb); if (err) { printk("白名单扫描启动失败: %d\n", err); } else { printk("白名单扫描已启动\n"); } }3.3 扫描参数计算和优化
/* 计算扫描占空比和功耗 */ void calculate_scan_parameters(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("扫描参数:\n"); printk(" 间隔: %dms\n", interval_ms); printk(" 窗口: %dms\n", window_ms); printk(" 占空比: %.1f%%\n", duty_cycle); // 估计功耗(示例值,实际值取决于硬件) float current_active = 10.0f; // 扫描时电流(mA) float current_idle = 0.1f; // 空闲时电流(mA) float avg_current = (duty_cycle / 100) * current_active + ((100 - duty_cycle) / 100) * current_idle; printk(" 估计平均电流: %.2fmA\n", avg_current); } /* 根据应用需求选择扫描参数 */ struct bt_le_scan_param get_optimal_scan_params(enum scan_requirement req) { struct bt_le_scan_param param = {0}; switch (req) { case REQUIREMENT_FAST_DISCOVERY: // 快速发现:高占空比 param.type = BT_LE_SCAN_TYPE_ACTIVE; param.interval = 0x0060; // 60ms param.window = 0x0060; // 60ms (100%占空比) param.timeout = 10; break; case REQUIREMENT_LOW_POWER: // 低功耗:低占空比 param.type = BT_LE_SCAN_TYPE_PASSIVE; param.interval = 0x1000; // 2560ms param.window = 0x0012; // 11.25ms (~0.44%占空比) param.timeout = 0; break; case REQUIREMENT_BACKGROUND: // 后台扫描:平衡模式 param.type = BT_LE_SCAN_TYPE_PASSIVE; param.interval = 0x0800; // 1280ms param.window = 0x0018; // 15ms (~1.17%占空比) param.timeout = 0; param.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE; break; } return param; }3.4 解析特定广播数据(如iBeacon)
/* iBeacon数据结构 */ struct ibeacon_data { uint8_t flags[3]; uint8_t length; uint8_t type; uint16_t company_id; uint16_t beacon_type; uint8_t proximity_uuid[16]; uint16_t major; uint16_t minor; int8_t tx_power; }; /* 解析iBeacon数据 */ static void parse_ibeacon(const bt_addr_le_t *addr, int8_t rssi, struct net_buf_simple *buf) { struct ibeacon_data *ibeacon; 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_MANUFACTURER_DATA && len >= 25) { // 检查是否是iBeacon(Apple公司ID) uint16_t company_id = net_buf_simple_pull_le16(buf); if (company_id == 0x004C) { // Apple公司ID uint16_t beacon_type = net_buf_simple_pull_le16(buf); if (beacon_type == 0x0215) { // iBeacon类型 char addr_str[BT_ADDR_LE_STR_LEN]; bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); // 提取UUID uint8_t uuid[16]; for (int i = 0; i < 16; i++) { uuid[i] = net_buf_simple_pull_u8(buf); } // 提取Major和Minor uint16_t major = net_buf_simple_pull_le16(buf); uint16_t minor = net_buf_simple_pull_le16(buf); // 提取发射功率 int8_t tx_power = net_buf_simple_pull_u8(buf); // 计算距离(简化模型) float distance = calculate_distance(rssi, tx_power); printk("iBeacon发现:\n"); printk(" 地址: %s\n", addr_str); printk(" Major: %u, Minor: %u\n", major, minor); printk(" RSSI: %d, TX Power: %d\n", rssi, tx_power); printk(" 估计距离: %.2f米\n", distance); } } } net_buf_simple_pull(buf, len - 1); } }4 重要注意事项
4.1功耗管理
/* 错误的做法:持续高占空比扫描 */ void bad_power_management(void) { struct bt_le_scan_param bad_param = { .interval = 0x0060, // 60ms .window = 0x0060, // 60ms (100%占空比) .timeout = 0, // 持续扫描 }; // 这会快速耗尽电池! } /* 正确的做法:间歇扫描 */ void good_power_management(void) { struct k_work_delayable scan_work; // 工作函数:执行短时间扫描 static void scan_work_handler(struct k_work *work) { struct bt_le_scan_param param = { .type = BT_LE_SCAN_TYPE_PASSIVE, .interval = 0x0060, .window = 0x0030, // 30ms (50%占空比) .timeout = 3, // 只扫描3秒 }; bt_le_scan_start(¶m, scan_cb); // 安排下一次扫描(例如30秒后) k_work_schedule(&scan_work, K_SECONDS(30)); } }4.2回调函数注意事项
/* 错误的做法:在回调中进行耗时操作 */ void bad_scan_callback(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, struct net_buf_simple *buf) { // 错误:可能阻塞系统工作队列 k_sleep(K_SECONDS(1)); // 错误:执行复杂计算 perform_complex_calculation(); // 错误:进行I/O操作 write_to_filesystem(); } /* 正确的做法:快速处理,延迟复杂操作 */ static struct k_workqueue *app_workq; static struct k_work deferred_work; void good_scan_callback(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, struct net_buf_simple *buf) { // 快速复制数据 struct device_data { bt_addr_le_t addr; int8_t rssi; uint8_t data[32]; size_t data_len; } *data = k_malloc(sizeof(*data)); if (!data) return; memcpy(&data->addr, addr, sizeof(bt_addr_le_t)); >4.3内存管理/* 正确处理广播数据缓冲区 */ void handle_adv_data_safely(struct net_buf_simple *buf) { // 错误:保存缓冲区指针(缓冲区会被重用) // static struct net_buf_simple *saved_buf; // 不要这样做! // 正确:复制需要的数据 static uint8_t saved_data[32]; size_t copy_len = MIN(buf->len, sizeof(saved_data)); memcpy(saved_data, buf->data, copy_len); // 如果需要处理原始数据,使用pull函数 while (buf->len > 1) { uint8_t len = net_buf_simple_pull_u8(buf); uint8_t type = net_buf_simple_pull_u8(buf); // 处理数据... net_buf_simple_pull(buf, len - 1); } }
4.4并发和状态管理
static atomic_t scan_state = ATOMIC_INIT(0); #define SCAN_STATE_IDLE 0 #define SCAN_STATE_SCANNING 1 #define SCAN_STATE_STOPPING 2 /* 安全的扫描控制 */ int safe_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) { int expected = SCAN_STATE_IDLE; // 使用原子操作检查状态 if (!atomic_cas(&scan_state, expected, SCAN_STATE_SCANNING)) { printk("扫描已在进行中\n"); return -EBUSY; } int err = bt_le_scan_start(param, cb); if (err) { atomic_set(&scan_state, SCAN_STATE_IDLE); } return err; } void safe_scan_stop(void) { atomic_set(&scan_state, SCAN_STATE_STOPPING); int err = bt_le_scan_stop(); if (err == 0) { atomic_set(&scan_state, SCAN_STATE_IDLE); } else { // 处理错误 } }
4.4错误处理
/* 全面的错误处理 */ int robust_scan_start(const struct bt_le_scan_param *param, bt_le_scan_cb_t cb) { int err; // 检查蓝牙状态 if (!bt_is_ready()) { printk("蓝牙未就绪\n"); return -EAGAIN; } // 检查参数有效性 if (param->window > param->interval) { printk("错误:扫描窗口不能大于间隔\n"); return -EINVAL; } // 尝试启动扫描 err = bt_le_scan_start(param, cb); switch (err) { case 0: printk("扫描启动成功\n"); break; case -EALREADY: printk("扫描已在运行中\n"); // 可以选择先停止再重启 bt_le_scan_stop(); k_sleep(K_MSEC(100)); err = bt_le_scan_start(param, cb); break; case -EINVAL: printk("无效的参数\n"); break; case -ENOMEM: printk("内存不足\n"); // 可以尝试释放内存后重试 cleanup_memory(); err = bt_le_scan_start(param, cb); break; default: printk("未知错误: %d\n", err); break; } return err; }
5 常见问题解决
1)扫描不到设备
void troubleshoot_scan_issues(void) { printk("扫描问题排查:\n"); // 1. 检查蓝牙状态 if (!bt_is_ready()) { printk(" - 蓝牙未初始化或未就绪\n"); return; } // 2. 检查扫描参数 printk(" - 扫描类型: %s\n", bt_dev.scan_param.type ? "主动" : "被动"); // 3. 检查硬件限制 if (CONFIG_BT_MAX_CONN <= 0) { printk(" - 最大连接数配置为0\n"); } // 4. 建议操作 printk("建议:\n"); printk(" 1. 确认目标设备正在广播\n"); printk(" 2. 尝试不同的扫描间隔/窗口\n"); printk(" 3. 禁用重复过滤选项\n"); printk(" 4. 检查信号强度(可能距离太远)\n"); }
2)扫描性能优化
/* 自适应扫描策略 */ struct adaptive_scanner { uint8_t scan_phase; uint32_t devices_found; struct k_work_delayable phase_work; }; static void adaptive_scan_phase(struct k_work *work) { struct adaptive_scanner *scanner = CONTAINER_OF(work, struct adaptive_scanner, phase_work); switch (scanner->scan_phase) { case 0: // 阶段1:快速主动扫描 start_fast_active_scan(); scanner->scan_phase = 1; k_work_schedule(&scanner->phase_work, K_SECONDS(5)); break; case 1: // 阶段2:慢速被动扫描 if (scanner->devices_found > 0) { // 发现设备,保持较慢的扫描 start_slow_passive_scan(); } else { // 未发现设备,返回快速扫描 scanner->scan_phase = 0; } k_work_schedule(&scanner->phase_work, K_SECONDS(30)); break; } }
3)代码组织建议
/* 推荐的代码结构 */ struct ble_scanner { struct bt_le_scan_param params; bt_le_scan_cb_t *callback; atomic_t state; struct k_mutex lock; struct k_workqueue *workq; struct k_work_delayable scan_work; struct k_work_delayable timeout_work; }; // 初始化扫描器 int ble_scanner_init(struct ble_scanner *scanner); // 启动扫描(带错误处理和重试) int ble_scanner_start(struct ble_scanner *scanner); // 停止扫描(安全地) int ble_scanner_stop(struct ble_scanner *scanner); // 处理扫描结果 void ble_scanner_process_result(struct ble_scanner *scanner, const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, struct net_buf_simple *buf);