2. BLE扫描参数配置
任务说明
本案例要求编写BLE扫描参数配置代码,通过设置不同的扫描窗口(scan window)和扫描间隔(scan interval),实现主动扫描与被动扫描两种模式下的设备发现效果对比。代码基于Zephyr RTOS的BLE控制器API实现,目标是在nRF52840 DK开发板上运行,通过串口日志观察不同参数组合下的扫描结果差异。
扫描参数原理
BLE扫描过程中,扫描窗口决定每次射频开启监听的时间长度,扫描间隔决定两次扫描启动之间的时间跨度。当扫描窗口等于扫描间隔时,射频持续开启,为连续扫描模式;当扫描窗口小于扫描间隔时,射频周期性开关,为占空比扫描模式。主动扫描会额外发送扫描请求获取设备名称等附加信息,被动扫描仅监听广播报文。本案例通过配置三组参数对比发现效果:连续主动扫描、低占空比主动扫描、连续被动扫描。
代码实现
/* main.c - BLE扫描参数配置示例 */
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/printk.h>
#define SCAN_INTERVAL_CONT 0x0010 /* 10ms 连续扫描 */
#define SCAN_WINDOW_CONT 0x0010 /* 10ms */
#define SCAN_INTERVAL_LOW 0x0100 /* 160ms 低占空比 */
#define SCAN_WINDOW_LOW 0x0020 /* 20ms */
#define SCAN_TIMEOUT 30000 /* 30秒 */
static uint8_t scan_mode = 0;
static struct bt_le_scan_param scan_params;
/* 扫描回调函数 */
static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
uint8_t adv_type, struct net_buf_simple *adv_data)
{
char addr_str[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
printk("[%s] RSSI:%d Type:%d DataLen:%u\n",
addr_str, rssi, adv_type, adv_data->len);
if (adv_type == BT_LE_ADV_IND) {
/* 主动扫描时,设备会响应扫描请求并发送附加数据 */
printk(" -> 可连接广播,主动扫描可获取完整数据\n");
}
}
/* 配置并启动扫描 */
static void start_scan(uint16_t interval, uint16_t window,
uint8_t type, uint8_t filter_dup)
{
scan_params.type = type;
scan_params.options = BT_LE_SCAN_OPT_NONE;
scan_params.interval = interval;
scan_params.window = window;
scan_params.filter_dup = filter_dup;
int err = bt_le_scan_start(&scan_params, scan_cb);
if (err) {
printk("扫描启动失败 (err %d)\n", err);
} else {
printk("扫描已启动: interval=%u.%ums window=%u.%ums type=%s\n",
interval * 5 / 8, (interval * 5 * 100) % 800,
window * 5 / 8, (window * 5 * 100) % 800,
type == BT_LE_SCAN_TYPE_ACTIVE ? "主动" : "被动");
}
}
/* 切换扫描模式 */
static void switch_scan_mode(void)
{
int err = bt_le_scan_stop();
if (err) {
printk("停止扫描失败 (err %d)\n", err);
return;
}
scan_mode = (scan_mode + 1) % 3;
switch (scan_mode) {
case 0:
printk("\n=== 模式1: 连续主动扫描 (interval=10ms, window=10ms) ===\n");
start_scan(SCAN_INTERVAL_CONT, SCAN_WINDOW_CONT,
BT_LE_SCAN_TYPE_ACTIVE, BT_LE_SCAN_FILTER_DUP_ENABLE);
break;
case 1:
printk("\n=== 模式2: 低占空比主动扫描 (interval=160ms, window=20ms) ===\n");
start_scan(SCAN_INTERVAL_LOW, SCAN_WINDOW_LOW,
BT_LE_SCAN_TYPE_ACTIVE, BT_LE_SCAN_FILTER_DUP_ENABLE);
break;
case 2:
printk("\n=== 模式3: 连续被动扫描 (interval=10ms, window=10ms) ===\n");
start_scan(SCAN_INTERVAL_CONT, SCAN_WINDOW_CONT,
BT_LE_SCAN_TYPE_PASSIVE, BT_LE_SCAN_FILTER_DUP_ENABLE);
break;
}
}
/* 按键中断回调 - 使用GPIO按键切换模式 */
void button_pressed(void)
{
switch_scan_mode();
}
int main(void)
{
int err;
printk("BLE扫描参数配置示例启动\n");
/* 初始化蓝牙 */
err = bt_enable(NULL);
if (err) {
printk("蓝牙初始化失败 (err %d)\n", err);
return err;
}
printk("蓝牙已初始化\n");
/* 启动默认模式:连续主动扫描 */
switch_scan_mode();
/* 使用定时器周期切换模式(演示用,实际可用按键触发) */
while (1) {
k_sleep(K_SECONDS(30));
switch_scan_mode();
}
return 0;
}
工程配置
# prj.conf
CONFIG_BT=y
CONFIG_BT_OBSERVER=y
CONFIG_BT_SCAN_FILTER_ENABLE=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y
编译与运行
# 在Zephyr工程目录下执行
west build -b nrf52840dk_nrf52840 -d build/scan_demo
west flash -d build/scan_demo
# 查看串口输出(默认115200波特率)
screen /dev/ttyACM0 115200
验证方法与判定标准
准备一个持续广播的BLE信标设备(如手机开启BLE广播或使用另一块开发板运行广播示例),放置于扫描设备1~3米范围内。观察串口输出:
- 模式1(连续主动扫描):应能持续、密集地收到广播报文,且对可连接广播设备会显示“可连接广播,主动扫描可获取完整数据”提示,RSSI值波动小,发现速度快。
- 模式2(低占空比主动扫描):收到广播报文的频率明显降低,间隔约160ms一次,RSSI值波动较大,发现速度变慢,但仍能获取主动扫描附加数据。
- 模式3(连续被动扫描):接收频率与模式1相当,但不会出现“可连接广播,主动扫描可获取完整数据”提示,因为被动扫描不发送扫描请求。
判定标准:三种模式均能发现广播设备;模式1与模式3的报文接收频率明显高于模式2;模式1与模式2能获取主动扫描附加信息而模式3不能。若上述现象均符合,则案例验证通过。
扫描参数与预期效果对照
关键参数说明
| 参数 | 模式1 | 模式2 | 模式3 |
|---|---|---|---|
| 扫描间隔 | 10ms | 160ms | 10ms |
| 扫描窗口 | 10ms | 20ms | 10ms |
| 扫描类型 | 主动 | 主动 | 被动 |
| 占空比 | 100% | 12.5% | 100% |
| 发现速度 | 最快 | 较慢 | 快 |
| 附加数据获取 | 支持 | 支持 | 不支持 |
代码中扫描间隔和窗口的单位为0.625ms,因此0x0010表示10ms,0x0100表示160ms,0x0020表示20ms。实际运行时可观察串口日志中打印的换算后时间值确认参数生效。每30秒自动切换一次模式,便于对比观察。