尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

ESP32蓝牙Beacon测距实战:RSSI原理、代码实现与参数标定

ESP32蓝牙Beacon测距实战:RSSI原理、代码实现与参数标定 1. 蓝牙beacon测距的总体设计思路开讲之前先说个事这个系列的每一讲我都在尽量控制篇幅结果每讲写出来都能赶上小论文。倒不是因为废话多而是ESP-IDF这套框架里很多看起来“一行搞定”的功能真要讲清楚它背后的机制就得把协议栈、事件循环、内存管理这些前置知识一并说透。这一讲蓝牙beacon测距更是如此它涉及的不仅是API调用还有信号传播模型、天线增益补偿、多径效应等一系列射频层面的问题所以我会尽量按照“原理→代码→实测→调参”的顺序来写保证你不仅能把例程跑起来还能在项目里真正用上。1.1 为什么选用BLE beacon方案做测距先回答一个我经常被问到的问题ESP32本身支持Wi-Fi、经典蓝牙BR/EDR和低功耗蓝牙BLE三种无线方式为什么测距偏偏选beacon经典蓝牙虽然也有RSSIReceived Signal Strength Indicator接收信号强度指示接口但它的工作模式和连接机制决定了它的RSSI采样并不适合做连续测距。经典蓝牙建立连接后设备之间是点对点通信测距的前提是“先配对成功”这天然限制了应用场景。你想做一个商铺室内定位的信息推送总不能让顾客先跟你的广播盒子配对再收消息吧Wi-Fi的RSSI也在ESP32上轻松可得但Wi-Fi是高频大带宽系统受环境影响更剧烈而且Wi-Fi扫描一次需要几百毫秒到数秒做连续测距的时间粒度很差。BLE采用了广播Advertising模式设备不需要建立连接扫描端就能持续收到广播报文并从物理层直接拿到RSSI值。广播报文本身还带时间戳、设备地址、自定义数据段这对做定位系统尤其友好。也就是说BLE beacon天然就是为“低功耗、单向、广播”场景设计的。配合ESP32自带的BLE控制器扫描和广播可以分时复用一个模组既能当信标又能当扫描器做原型验证成本极低。1.2 RSSI测距的原理与短板RSSI测距的根在一个对数距离路径损耗模型工程上最常见的公式是距离d 10 ^ ((A - RSSI) / (10 * n))其中A是距离发射端1米处测得的RSSI参考值n是路径损耗指数。这个公式把无线信号在空间中的衰减近似成对数线性关系离得越近RSSI越大离得越远RSSI越小。这个模型听起来很简单实际用起来问题很大。室内环境下多径效应、人体遮挡、墙体反射都会导致RSSI剧烈波动同一个位置静止不动1秒钟内采到的RSSI都可能出现±6dBm的抖动。按公式反算距离可能3米的实际距离一会儿算成1.8米一会儿算成4.5米。所以我在这一讲的实现里没有直接拿单次RSSI去求距离而是引入了滑动窗口滤波和加权修正。这一套组合下来静态测距的稳定性能提升到一个勉强可用的程度。纯RSSI测距的绝对精度确实有限但它的价值在于“相对距离的趋势判断”和“区域内粗定位”用在防丢提醒、人员接近检测、区域分割这些场景里是完全够用的。2. 开发环境准备ESP-IDF与VSCode的工程配置2.1 从零搭建ESP-IDF VSCode环境的关键步骤这一讲先假设你已经装好了ESP-IDF如果还没装好的同学建议先去乐鑫官网下载离线安装包Windows用户直接一键安装选一个没有中文和空格的路径比如D:\esp\esp-idf。装完之后系统会生成一个“ESP-IDF Command Prompt”快捷方式先跑一下这个命令行工具确认idf.py可用。VSCode这边的配置我个人强烈建议安装乐鑫官方提供的Espressif IDF扩展而不是手动改JSON配置文件。因为这个扩展会自动识别你本机的ESP-IDF安装路径同时在VSCode里集成编译、烧录、串口监视器、GDB调试等功能省去了大量手工配置。你装好扩展后打开命令面板CtrlShiftP搜索“ESP-IDF: Configure ESP-IDF Extension”会弹出两个选项“USE EXISTING SETUP”和“DOWNLOAD ESP-IDF TOOLS”。如果你刚才已经装过完整工具链直接选USE EXISTING SETUP然后在输入框里定位到esp-idf文件夹下的export.bat文件即可。扩展会读取环境变量自动绑定编译器、烧录器、OpenOCD等工具。2.2 创建工程与配置menuconfig打开VSCode后按F1输入“ESP-IDF: Show Example Projects”可以基于官方例程创建工程。不过beacon测距并不是官方例程的标准模板我们需要用空工程起手。先创建空工程然后在main目录下改CMakeLists.txtidf_component_register(SRCS main.c INCLUDE_DIRS .)之后在主程序中包含#include stdio.h #include esp_log.h #include nvs_flash.h #include esp_bt.h #include esp_bt_main.h #include esp_gap_ble_api.h #include esp_gattc_api.h然后在menuconfig中打开两个关键配置idf.py menuconfig进入Component config → Bluetooth → Bluedroid勾选Bluedroid Enable然后把Classic Bluetooth取消或者保留也行不影响BLE单独使用。再进入Controller选项把BLE Scan Duplicate Filter关掉。这个重复过滤在beacon扫描场景下是个坑打开之后你会发现同一个广播地址的包会被过滤掉导致扫描频率比预期低很多测距数据点根本不够用。3. 广播端与扫描端的核心代码实现3.1 广播端构造标准的iBeacon或自定义Beacon报文beacon有多种格式最经典的是苹果的iBeacon但iBeacon的数据结构固定Payload里包含UUID、Major、Minor、TxPower四个字段。如果你只需要简单测距而不需要跟iOS生态交互完全可以用自定义格式的beacon广播把RSSI校准值直接写进厂商自定义段扫描端解析更灵活。广播端的核心代码其实就三部分static esp_ble_adv_params_t adv_params { .adv_int_min 0x20, // 最小广播间隔 20ms .adv_int_max 0x20, // 最大广播间隔 20ms .adv_type ADV_TYPE_NONCONN_IND, .own_addr_type BLE_ADDR_TYPE_PUBLIC, .channel_map ADV_CHNL_ALL, .adv_filter_policy ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, }; static uint8_t adv_data[31] { 0x02, 0x01, 0x06, // Flags 0x1A, 0xFF, 0x00, 0x16, // 厂商自定义段头 0x02, 0x01, 0x02, 0x03, 0x04, 0x05, // 自定义标识 0x64, // 1米处RSSI校准值十进制100即-100dBm // 后面是你自己的业务数据 };这里有个细节广播数据最长31字节超出部分要放到scan_response_data里扫描端通过主动扫描active scan才能拿到。如果你用的是NONCONN_IND这种不可连接的广播类型扫描端只能被动接收广播包所以所有关键数据必须塞进广播段本身。3.2 扫描端注册GAP回调并提取RSSI扫描端的核心是注册事件回调函数接收ESP_GAP_BLE_SCAN_RESULT_EVT事件static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: { esp_ble_gap_cb_param_t *scan_result (esp_ble_gap_cb_param_t *)param; switch (scan_result-scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: process_beacon(scan_result-scan_rst); break; default: break; } break; } default: break; } }注意scan_rst结构体里有个rssi字段单位是dBm范围一般是-100到0。取到这个值之后还需要同时拿到广播地址和广播数据才能判断这个包是不是你要找的那个beacon。有一点需要提醒扫描回调是在Bluetooth Controller的任务上下文中执行的不要在回调里做滤波、算法运算、日志打印等耗时操作。我见过很多新手直接把一个ESP_LOGI扔进回调里结果日志一多扫描直接卡死重启。正确做法是回调里只拷贝数据用队列xQueueSend把数据丢给一个低优先级任务去处理。3.3 距离换算从RSSI到米的完整计算拿到RSSI之后按前面的对数损耗模型反算距离float rssi_to_distance(float rssi, float tx_power, float n) { return powf(10.0f, (tx_power - rssi) / (10.0f * n)); }tx_power是广播端1米处测得的RSSI参考值A值一般取-59到-65之间这个需要实测标定不能照抄别人例程里的数值。n是路径损耗指数开阔空间取2.0办公室环境取2.5~3.0金属货架密集的环境取3.5以上。算出来之后千万别急着用。单次RSSI算出来的距离抖动非常夸张我建议至少做两层处理第一层是滑动窗口均值滤波维护一个长度为10的环形缓冲区每次来新数据先把最旧的丢弃然后求平均值#define FILTER_WINDOW 10 static float rssi_history[FILTER_WINDOW]; static uint8_t rssi_index 0; static uint8_t rssi_count 0; float rssi_filter(float new_rssi) { rssi_history[rssi_index] new_rssi; rssi_index (rssi_index 1) % FILTER_WINDOW; if (rssi_count FILTER_WINDOW) rssi_count; float sum 0; for (int i 0; i rssi_count; i) { sum rssi_history[i]; } return sum / rssi_count; }第二层是距离域的中值滤波把滤波后的RSSI还原成距离再取连续5次距离的中位数。中值滤波对突发的信号跳变比如有人从中间走过有很强的抑制作用和均值滤波配合使用效果显著。4. 参数标定与实测数据复盘4.1 实测场景办公室走廊的线性测距我拿这套实现做了几轮实测测试场景是普通办公楼走廊宽度大概2.5米两侧有金属门的门框头顶是石膏板吊顶。广播端是ESP32模组固定在走廊尽头扫描端是另一块ESP32开发板沿着走廊走动记录RSSI。测试时我用了三种方法来估算距离方法1米处实测3米处实测5米处实测8米处实测单次RSSI直接换算0.94m2.35m4.01m6.89m滑动窗口均值滤波1.05m2.91m4.56m7.12m均值中值双层滤波1.02m2.96m4.88m7.54m可以看到距离越远误差越大。这符合对数模型的数学特性RSSI单位误差在远处对应的距离误差更大。所以如果你要做5米以上的测距建议适当增加滤波窗口长度但窗口过长又会引入明显的响应延迟这个取舍要结合你的应用场景来定。4.2 标定A值和n值的实操心得前面提过A值是1米处的RSSI参考值这个值我建议你在目标环境下实地采集而不是用数据手册或者网上例程里的默认值。具体做法是把广播端固定好扫描端放在距离1米整的位置连续采集50个RSSI取平均值作为A值。n值的标定复杂一些你需要采集至少两个距离点的RSSI实测值然后用公式反推n (A - RSSI_d) / (10 * log10(d))例如我采集到1米处RSSI均值为 -62 dBm3米处RSSI均值为 -75 dBm代进去n (-62 - (-75)) / (10 * log10(3)) 13 / 4.77 ≈ 2.72这个n值表示该环境的衰减系数偏高和办公室走廊这种多反射环境是吻合的。标定完成后把这组参数固化到代码里再辅助滤波测距效果会好很多。另外还有一个实操细节ESP32开发板的天线方向会影响RSSI。PCB板载天线的增益在某个方向上有明显凹陷我在实测中把广播天线和扫描天线都朝向同一方向时整体RSSI约高出4~6dBm。所以固定安装时尽量保持天线方向一致测试时也要注意这一点。5. 踩坑记录与排查经验速查5.1 只扫到自己address过滤逻辑写反了第一次调试扫描端时我把beacon的MAC地址判断写成了“跳过该地址”结果看到的是自己的广播不停报出来目标beacon一个都没有。排查半天才发现是把过滤条件取反了。如果你也遇到类似数据异常情况先别急着怀疑射频问题用日志把每个包的MAC地址、广播数据段、RSSI都打印出来人眼确认一遍数据流再分析。5.2 广播间隔与扫描窗口的关系我把广播端的广播间隔设成了20msADV_INT_MIN0x20对应20ms扫描端的扫描窗口设成了标准值。实际测试中部分扫描到的广播报文RSSI抖动特别剧烈我发现这和ESP32内部采样时刻有关广播包在射频端被检测到的时间和扫描窗口的边界重合时采样可能不完整。后来我把扫描窗口周期调成与广播间隔不成整数倍的关系比如固定扫描窗口为60ms扫描周期为260ms数据波动就明显改善了。5.3 日志打印导致扫描卡顿扫到的beacon数量一多比如周围有其他手机和设备的BLE广播如果每个包都打日志串口输出会成为瓶颈反而把Controller的任务拖慢。我后来的做法是只对目标beacon的MAC地址匹配通过后才打日志其他包直接丢弃。这样日志量可控且不影响扫描性能。5.4 常见问题速查表现象可能原因解决方案扫描不到任何beaconBLE未初始化或重复过滤器开启检查nvs_flash初始化关闭duplicate filterRSSI值固定在某个值不变滤波窗口数据未刷新或回调里阻塞检查队列处理避免长时间打印距离计算结果为负数A值大于RSSI导致指数为正检查A值标定确认单位是dBm两台设备互相找不到广播类型选择错误使用ADV_TYPE_NONCONN_IND确认参数设置正确只有靠近到1米内才触发n值偏大或A值偏小重新标定环境参数手机能看到ESP32但ESP32扫不到手机手机广播周期太长手机锁屏后广播会停止属正常现象5.5 关于ESP-IDF版本差异的提醒ESP-IDF从4.x到5.xBLE相关的API有一些调整。最典型的是原来的esp_ble_gap_set_scan_params()函数在5.x版本里加了参数检查如果传入的扫描窗口大于扫描周期会直接返回错误码。另外新版IDF对Stack内存管理做了更严格的校验比如CONFIG_BT_BLUEDROID_PINNED_TO_CORE默认值变了多核环境下回调任务所在核会发生迁移。我写这讲的时候用的ESP-IDF v5.1.2代码里涉及的API在这版本上测试通过。如果你用的版本比较旧编译报错时优先查一下对应版本的esp_gap_ble_api.h头文件看看函数签名是否有变化这比在网上搜报错信息来得快得多。仅凭这点实现beacon测距在室内定位的局限性还是清楚的单点RSSI做不出多高的绝对精度但配合三点定位或多点指纹就能搭出一套比较实用的室内位置感知系统。后面如果大家有兴趣我再单独写一讲基于beacon的三点定位实现把多个扫描端的RSSI数据汇聚到服务器端做位置解算那就是另一个层面的工程问题了。
返回列表