ESP32 BLE Beacon测距实战:从RSSI到距离估算的完整指南
发布时间:2026/9/11 5:10:07来源:尧图网络
一直做 ESP32 的联网项目Wi-Fi 相关的应用是熟门熟路了直到有个室内展馆的需求把我按在地上摩擦参观者走近某个展品到一定距离时要触发语音讲解精度不需要厘米级但要稳定且延迟低。GPS 进室内直接废掉Wi-Fi 指纹定位又太重最后我盯上了蓝牙 beacon 方案。于是重新把 VSCode ESP-IDF 这套环境翻出来用 ESP32 扫描 BLE beacon 做 RSSI 距离估算从原理到代码再到实际标定完整跑了一遍。这篇文章就是把这段经历整理出来包括 iBeacon 帧格式、扫描回调处理、距离换算公式、参数标定方法还有一堆只有真机实测才会踩到的坑给做室内定位、防丢提醒、近场交互项目的朋友省点时间。1. Beacon 测距的底层逻辑为什么 RSSI 能换算成距离1.1 室内定位方案对比为什么偏偏是蓝牙 beacon做室内距离触发可选的方案其实有好几个但每个都有明显的短板。GPS 在室内的信号衰减非常严重尤其是混凝土结构的建筑里定位误差可以到几十米精确定位基本不用想。UWB超宽带方案精度确实高能做到厘米级但成本也高beacon 标签几百块一个接收端设备也不便宜对大部分应用来说属于杀鸡用牛刀。Wi-Fi 指纹定位需要先在场地里做密集的指纹采集现场环境一变比如移动了货架、关了扇门指纹数据就失效维护成本太高。蓝牙 beacon 在这个场景下刚好卡在中间beacon 标签几块钱到十几块钱一个广播功耗极低一块纽扣电池能用一年以上手机、嵌入式设备几乎都支持 BLE 扫描。更重要的是ESP32 本身就带 BLE 硬件等于接收端零额外成本。所以最终方案很明确在展品旁边放一个或者多个 BLE beaconESP32 作为扫描接收端持续监听通过接收到的信号强度估算距离达到阈值就触发动作。1.2 信号衰减模型RSSI 和距离不是线性关系蓝牙 beacon 测距的原理说白了就是一件事信号在空中传播时会衰减距离越远接收端的 RSSI 值越低。但这里面有个关键点RSSI 和距离之间不是线性关系而是对数关系。信号在自由空间里衰减的物理模型中接收功率和距离的关系大致是RSSI A - 10 * n * log10(d)其中A 表示距离发射端 1 米时测到的 RSSI 值dBmn 是路径损耗指数和环境密切相关。自由空间里 n 约等于 2室内因为有墙壁反射、人体吸收等干扰n 通常在 2.5 到 4 之间d 是距离米为什么要用对数而不是线性因为信号能量在传播中是按指数规律衰减的。就好比声音你在 1 米外听到的音量和 2 米外不是差一半而是差了大概 6dB。蓝牙信号每远离一倍距离RSSI 大约下降 6dBn 个 dB这个关系用对数画出来是一条接近直线的曲线用线性去拟合反而误差更大。这个公式是后面所有距离换算的基础理解这一点后面标定参数时就不会懵。1.3 iBeacon 帧格式到底长什么样市面上 BLE beacon 协议有两类主流iBeacon苹果提出和 Eddystone谷歌提出。我这次用的是 iBeaconESP-IDF 官方示例也是以它做演示的理解一个另一个也就顺带通了。iBeacon 的广播数据包结构是这样的字段字节数内容Company ID2厂商标识Apple 是 0x004CiBeacon 标识2固定为 0x02 0x15用于识别这是 iBeacon 帧UUID16区分不同应用或项目Major2区分同一应用下的分组比如楼层Minor2区分同一分组下的具体设备比如具体展位TX Power1距发射端 1 米处的 RSSI 参考值有符号数注意这里的字节序很关键Major 和 Minor 是大端模式也就是高字节在前。我在第一次解析时就直接按小端读了结果数字完全对不上查了半天才发现是这个问题。TX Power 字段值得多说两句。它代表的是距离发射端 1 米处应该测到的 RSSI 理论值由 beacon 标签厂家出厂时标定写入。正常来说这个值在 -59dBm 到 -65dBm 之间。有些文章里的公式用 TX Power 代替 A 值直接用但实际上厂家标定的 TX Power 是基于他们自己的测试环境你的使用环境墙面地面材质、天线摆放方向、接收端不同实际 1 米处的 RSSI 都会不一样。所以做项目时最好还是自己实测标定 A 值不要盲目信任 beacon 里写死的那个 TX Power。2. VSCode ESP-IDF 环境准备BLE 扫描器工程搭建2.1 工程创建前必须确认的 ESP-IDF 版本如果你已经装好了 VSCode 和 ESP-IDF 插件可以直接跳过这段。但如果你像我一样是从零开始或者准备新开一个项目有几个版本相关的问题值得先确认。我这边用的是 ESP-IDF v5.4.4VSCode 装的是 Espressif IDF 官方插件。在创建工程之前先把插件安装好然后在 VSCode 的命令面板里输入ESP-IDF: Configure ESP-IDF Extension插件会自动检测或者帮你下载指定版本的 ESP-IDF 工具链。这里有个经验别贪新直接上最新版本ESP-IDF 的 API 在不同版本间是有变动的我就遇到过 v5.2 和 v5.4 在蓝牙回调函数参数类型上做微调的情况。如果项目赶时间建议用官方长期支持的版本比如 v5.3.x 或 v5.4.x配套的示例代码也最稳定。另外要注意ESP-IDF 插件默认会创建一个工作区工程路径里尽量不要包含中文和空格否则编译时可能出现一些莫名其妙的路径问题。2.2 menuconfig 蓝牙协议栈配置创建工程时VSCode 命令面板输入ESP-IDF: Create Project from Template在模板列表里找bluetooth/bluedroid/ble/ble_ibeacon这个示例虽然名字是 iBeacon但它同时包含了广播端和扫描端两种角色做扫描端可以少写不少初始化代码。工程创建完成后第一件事不是写代码而是用ESP-IDF: Menuconfig打开配置界面把蓝牙栈搭好。要修改的配置项Component config → Bluetooth → Bluetooth → Enabled Component config → Bluetooth → Bluetooth host → Bluedroid Component config → Bluetooth → Bluedroid Options → Max Classic Bluetooth Connections → 0如果扫描端只用 BLE不涉及经典蓝牙把经典蓝牙连接数设为 0 可以省下不少内存。同理Max BLE Connections也改成 1因为我们只是扫描端不建立连接。这样编译出来的固件体积更小运行时也更稳定。这里补充一个选择点ESP-IDF 支持两套蓝牙协议栈Bluedroid 和 NimBLE。Bluedroid 功能全、兼容性好官方示例多但内存占用大NimBLE 轻量、代码简洁适合纯 BLE 场景。我这次用的是 Bluedroid因为要参考官方 ble_ibeacon 示例用 esp_ble_gap 系列 API 写起来直接。如果你的项目只有扫描这一个功能NimBLE 也是个不错的选择但 API 体系和 Bluedroid 不同代码没法直接照搬。2.3 扫描模式选择主动扫描是关键配置完协议栈还要在代码里碰到一个容易被忽略的概念扫描模式。BLE 扫描分为主动扫描Active Scan和被动扫描Passive Scan。被动扫描只是干听只接收广播者周期性发的 ADV_IND 广播包主动扫描会在收到广播后额外发送一个 SCAN_REQ 给广播者请求对方再发一个包含更多数据的扫描响应包SCAN_RSP。那 beacon 数据到底在哪大部分 iBeacon 直接把广播数据放在 ADV_IND 的 adv data 里被动扫描也能拿到。但有些广播设备厂商会把一部分数据塞进 SCAN_RSP这时候如果用被动扫描就永远拿不到完整数据。所以我的建议是直接用主动扫描多花一点点功耗换来的数据完整度是值得的。扫描参数结构体里相关配置项static esp_ble_scan_params_t ble_scan_params { .scan_type BLE_SCAN_TYPE_ACTIVE, .own_addr_type BLE_ADDR_TYPE_PUBLIC, .scan_filter_policy BLE_SCAN_FILTER_ALLOW_ALL, .scan_interval 0x100, .scan_window 0x50, .scan_duplicate BLE_SCAN_DUPLICATE_DISABLE };scan_interval和scan_window的单位是 0.625 毫秒所以 0x100256约等于 160ms 的扫描间隔0x5080约等于 50ms 的扫描窗口。窗口越大抓到广播包的概率越高功耗也越高。对 beacon 这种一般 100ms 广播一次的场景这个比例覆盖得比较稳。scan_duplicate这个参数更关键。如果设成 ENABLE硬件会自动过滤掉重复地址的广播beacon 是周期性广播MAC 地址不变很容易被过滤掉导致只触发一次回调就再也不更新了。所以扫描 beacon 时务必设成 DISABLE。3. 扫描与解析从裸广告数据到完整 iBeacon 信息3.1 初始化 BLE 主机并设置扫描参数用 Bluedroid 协议栈初始化流程比想象中要短核心就三步注册回调、设置扫描参数、开始扫描。esp_err_t ret; esp_bluedroid_init(); esp_bluedroid_enable(); esp_ble_gap_register_callback(gap_event_handler); esp_ble_gap_set_scan_params(ble_scan_params); ret esp_ble_gap_start_scanning(30); ESP_LOGI(TAG, start scanning, ret%d, ret);esp_ble_gap_start_scanning的参数是扫描持续秒数传 0 表示持续扫描直到主动调用esp_ble_gap_stop_scanning()。我习惯传 0靠应用逻辑控制扫描生命周期。这套流程几乎不需要修改直接搬进工程就行。要注意的是esp_bluedroid_enable()之后不能立刻调用esp_ble_gap_*系列的 API需要等蓝牙控制器真正 ready。实际开发中如果你发现前几步没问题但扫描从来没开始多半就是这里时序问题。稳妥的做法是在ESP_BT_STATUS相关的事件确认后再触发扫描不过 ESP-IDF 内部对大部分初始化调用做了队列缓冲直接链式调用也基本能跑通只是不推荐。3.2 扫描回调事件处理流程核心逻辑全在gap_event_handler里。扫描到广播帧时系统会触发ESP_GAP_BLE_SCAN_RESULT_EVT事件代码要在这个事件里辨别当前是哪种搜索结构static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch (event) { case ESP_GAP_BLE_SCAN_RESULT_EVT: if (param-scan_rst.search_evt ESP_GAP_SEARCH_INQ_RES_EVT) { // 处理单次扫描结果 handle_ibeacon_adv(param-scan_rst); } else if (param-scan_rst.search_evt ESP_GAP_SEARCH_INQ_CMPL_EVT) { ESP_LOGI(TAG, scan completed); } break; default: break; } }这里容易犯的错是把ESP_GAP_BLE_SCAN_RESULT_EVT和ESP_GAP_SEARCH_INQ_RES_EVT混为一谈实际上前者是事件类型后者是当前扫描结果的具体状态。事件里的scan_rst结构体包含了好几个关键字段bda广播者 MAC 地址rssi本次测得信号强度ble_adv广告数据缓冲区adv_data_lenadv data 的长度scan_rsp_len扫描响应数据的长度注意ble_adv缓冲区里既有 adv data 又有 scan rsp需要根据两个长度字段区分区间。我当时就是想当然地认为整个缓冲区都是 adv data结果解析出了谁都看不懂的乱码。3.3 解析厂商特定数据段拿到广告数据后下一步是从里面把 iBeacon 厂商数据提取出来。BLE 广播数据是按 AD Structure 组织的每个结构是[长度][类型][数据]三元组长度字段包含类型字节但不包含长度字节本身。iBeacon 属于厂商特定数据AD type 是 0xFF。ESP-IDF 提供了现成接口uint8_t *adv_data param-scan_rst.ble_adv; uint8_t len param-scan_rst.adv_data_len; uint8_t manu_len 0; uint8_t *manu_data esp_ble_resolve_adv_data(adv_data, ESP_BLE_AD_TYPE_MANU_FACTURER, manu_len);这个函数会在广告数据里自动遍历查找指定类型的 AD Structure返回数据段指针。找到厂商数据后再按 iBeacon 格式进一步解析typedef struct { uint8_t company_id[2]; // 厂商ID uint8_t ibeacon_type[2]; // iBeacon 固定标识 0x02 0x15 uint8_t uuid[16]; // UUID uint16_t major; // 大端序 uint16_t minor; // 大端序 int8_t tx_power; // 1米处RSSI参考值 } __attribute__((packed)) ibeacon_info_t; if (manu_data ! NULL manu_len 25) { ibeacon_info_t *info (ibeacon_info_t *)manu_data; if (info-company_id[0] 0x4C info-company_id[1] 0x00) { if (info-ibeacon_type[0] 0x02 info-ibeacon_type[1] 0x15) { uint16_t major (info-major 8) | (info-major 8); uint16_t minor (info-minor 8) | (info-minor 8); int8_t tx_power info-tx_power; // 到这里就是一个完整有效的 iBeacon 帧 } } }manu_len 25这个判断是必须的因为一个完整的 iBeacon 厂商数据段最少有 25 字节2 字节厂商 ID 2 字节 iBeacon 标识 16 字节 UUID 2 字节 Major 2 字节 Minor 1 字节 TX Power。我之前写的是 23虽然也能通过判断但容易让后面结构体强制转换时发生越界访问建议直接写成 25。3.4 多 beacon 管理MAC 与 UUID 的双重区分实际项目里不可能只有一个 beacon展馆里几十个展位每个展位放一个扫描端必须能区分谁是谁。区分逻辑有两层第一层是靠 UUID 区分归属。同一个项目的所有 beacon 共享同一个 UUID解析出来以后先和预置的 UUID 比对不一致的直接丢弃这样外来的干扰 beacon 就不会混进来。第二层是靠 Major/Minor 或者 MAC 地址区分具体设备。这里我建议优先用 MAC 地址做设备 ID因为 MAC 在扫描结果里本来就带着不需要额外解析。但要注意beacon 广播是可以改 MAC 地址的随机地址类型如果厂商固件里开了 MAC 随机化MAC 就会周期性变化这时候就不能用它做稳定标识只能靠 Major/Minor 组合来识别。在代码里我维护了一个简单的结构体数组#define MAX_BEACON_NUM 16 typedef struct { uint8_t mac[6]; uint8_t major[2]; uint8_t minor[2]; int8_t rssi; float distance; uint32_t last_seen; } beacon_node_t; static beacon_node_t s_beacons[MAX_BEACON_NUM];每次扫描到有效 iBeacon 帧就按 MAC 或者 Minor 值在数组里查找找到就更新 RSSI 和last_seen没找到就找空槽位插入。定期检查last_seen超过 3 秒没更新的节点判定为离线移除。这个环形缓冲的思路对几十个 beacon 的场景完全够用不需要上复杂的哈希表。4. RSSI 到距离标定、滤波与换算公式实操4.1 路径损耗公式与参数的物理含义有了 RSSI 值下一步就是换算成距离。这里我再强调一遍公式distance 10 ^ ((A - RSSI) / (10 * n))其中 A 是 1 米处测到的 RSSI 参考值n 是路径损耗指数。这个公式是从对数衰减模型反推出来的。为什么 RSSI 和距离的关系能用这个公式因为电磁波在传播中接收功率与距离的 n 次方成反比自由空间里 n2也就是平方反比关系用 dB 表示接收功率就是距离对数的线性函数。反过来知道 RSSI 就能反推距离。这里有个新手最容易犯的错直接把 iBeacon 帧里的 TX Power 当 A 用。TX Power 确实是厂家在 1 米处测出来的 RSSI但那是针对厂家测试环境、厂家接收设备和无遮挡环境来说的。你的场地地面、墙面、金属装饰、无线干扰源都会让实际 1 米处的 RSSI 偏离这个标称值。我实测过同一个 beacon手机在 1 米处测到 -58dBmESP32 在同一位置测到 -61dBm差了 3dB。3dB 看着不大代入公式算距离误差可能超过三成。所以必须实测标定。4.2 实测标定 A 值和 n 值的完整过程标定这事说难不难但要做对。我用的方法分三步第一步标定 A 值。找一个相对空旷的场地把 beacon 和 ESP32 都固定在高度 1 米左右的位置比如用三脚架或者堆箱子距离正好 1 米用卷尺量别目测。连续采集 50 次 RSSI取平均值作为 A。为什么要 50 次因为 RSSI 单次测量波动很大同一位置连续采可能相差 5 到 8dB只有取平均才能把随机起伏消掉。第二步标定 n 值。把 beacon 移到 3 米处同样固定高度连续采 50 次 RSSI 取平均。然后代入公式反求 nn (A - RSSI_3m) / (10 * log10(3))比如实测 A -61dBm3 米处平均 RSSI -74dBm那 n (61 - 74) / (10 * 0.477) ≈ 2.72。这个 2.72 在室内场景算正常区间如果算出来超过 4说明场地遮挡太厉害或者有强反射beacon 测距的可靠性会明显下降。第三步验证。把 beacon 放到 2 米处用标定的参数算距离看误差在多少。我这边实测下来2 米处算出来 1.8 到 2.3 之间波动这个精度对触发类应用已经够用。如果误差超过 0.5 米建议重新确认场地环境是否过于复杂或者多增加几个采样点做分段拟合把场地划分成近距离段和远距离段用不同的 n 值。4.3 滑动平均与异常值剔除的组合滤波RSSI 波动大是测距误差的主要来源尤其在有人走动、有金属架的室内环境单次测量值能跳 10dB 以上。如果不滤波直接代入公式算出来的距离会像心跳一样来回蹦。我试过把原始 RSSI 直接换算距离1 秒内的输出能从 0.8 米跳到 2.7 米完全没法用。最简单的滤波是滑动平均开一个环形缓冲区存最近 N 次 RSSI 值每次取平均#define RSSI_FILTER_SIZE 10 static int8_t s_rssi_buf[RSSI_FILTER_SIZE]; static uint8_t s_rssi_idx 0; static int8_t rssi_filter(int8_t new_rssi) { s_rssi_buf[s_rssi_idx] new_rssi; s_rssi_idx (s_rssi_idx 1) % RSSI_FILTER_SIZE; int32_t sum 0; for (int i 0; i RSSI_FILTER_SIZE; i) { sum s_rssi_buf[i]; } return (int8_t)(sum / RSSI_FILTER_SIZE); }但单纯滑动平均有个问题如果某次测量出现极端值比如人体正好挡住天线RSSI 瞬间掉到 -95dBm平均结果会被拉偏。所以在平均之前我加了一步异常值剔除把当前采样值和上一次滤波输出比较偏差超过 8dB 的就丢弃不进入滑动窗口。这里 8dB 的阈值不能设得太小否则正常波动也会被滤掉导致更新迟滞也不能太大否则起不到剔除作用。实测下来 8 到 10dB 比较合适。这一套组合滤波代码量不大但效果非常明显。加上滤波后距离输出从乱跳变成了平滑爬升对触发类应用来说体验完全不同。5. 数据落地串口可视化与 OLED 实时显示5.1 JSON 格式输出串口方便上位机解析测距结果最终要给业务逻辑用。我这里分了两条路一条是开发调试时用串口输出方便在电脑上实时看数据另一条是接 OLED 屏幕现场直接显示。串口输出我用的是 JSON 格式一行一条记录ESP_LOGI(TAG, {\mac\:\%02X:%02X:%02X:%02X:%02X:%02X\,\major\:%d,\minor\:%d,\rssi\:%d,\distance\:%.2f}, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], major, minor, filtered_rssi, distance_m);这里要强调一个坑ESP-IDF 的ESP_LOGI默认会在日志前面加时间戳和 TAG 前缀如果上位机要直接按 JSON 解析得在 menuconfig 里把日志输出格式改成无前缀的或者上位机解析时跳过前缀部分。我开发时直接用 VSCode 串口监视器看文本倒是无所谓但如果你要对着 Python 脚本解析就得注意这个。Python 端解析脚本很简单用json.loads()逐行读取即可如果日志带了前缀就先用正则把{...}部分抓出来再解析。5.2 OLED 实时显示距离现场演示时不能总拖着电脑我接了一块 0.96 寸 SSD1306 OLED走 I2C 接口。这里顺带一提如果项目里同时挂了其他 I2C 设备比如温湿度传感器ESP-IDF v5.x 支持多个 I2C 控制器可以在 menuconfig 里分别配置两组 SDA/SCL 引脚互不干扰。我就是把显示和其他传感器分到了两个控制器省去了地址冲突和总线占用的问题。OLED 驱动代码用现成的 ssd1306 驱动即可显示逻辑很简单每 1 秒刷新一次第一行显示 Minor 值标识哪个 beacon第二行显示 RSSI 和换算后的距离char line1[32], line2[32]; snprintf(line1, sizeof(line1), beacon:%d, minor); snprintf(line2, sizeof(line2), %.1fm rssi:%d, distance_m, filtered_rssi); ssd1306_draw_string(dev, line1, 0, 0); ssd1306_draw_string(dev, line2, 0, 16);刷新频率不要太高OLED 本身刷新带宽有限而且 100ms 刷一次肉眼也看不出区别。1 秒一次既能直观看到距离变化趋势又不给主循环增加负担。5.3 扩展思路三点定位与防丢提醒beacon 测距最直接的扩展就是三点定位。原理很简单已知三个 beacon 的物理坐标比如在展厅平面图上是固定的手机或移动端分别测到到三个 beacon 的距离就能用三边测量法解出自身位置。前提是三个 beacon 位置别摆成一条直线不然方程组解不出来。ESP32 做接收端时也是一样相当于移动端只需要周期性汇总三个方向的距离即可。另一个我做过的扩展是防丢提醒。给贵重物品贴一个 beacon人身上带一个 ESP32 手表或者手环当距离超过预设阈值比如 5 米就触发振动或者蜂鸣。距离阈值不要设太死因为 RSSI 波动在临界点附近会导致频繁触发建议加迟滞逻辑超过 6 米才报警回到 4 米以内才解除这样不会在边界反复横跳。6. 实测中的坑与调参经验6.1 扫描不到 beacon 的排查链路第一次跑代码时串口啥都没有一个 beacon 都扫不到。我当时按这个顺序排查没走弯路分享出来第一步确认 beacon 本身在广播。用手机装一个 nRF Connect 或者 Beacon Scanner 应用看能不能搜到这个 beacon能搜到就说明硬件没问题搜不到先检查 beacon 电池和开关。第二步确认 ESP32 的配置。menuconfig 里 Bluetooth 有没有 EnableBluetooth host 是不是选对了这些都是最基础但最容易漏的。第三步确认扫描参数。这里最隐蔽的是scan_duplicate设置一旦设成 ENABLEbeacon 的周期广播会被过滤掉表现就是回调事件只触发一次后就不再响应。第四步确认扫描窗口大小。如果scan_window太短比如小于 10msbeacon 广播可能正好落在窗口之外。尤其是 Wi-Fi 也在跑的情况下窗口会被压缩更不容易抓到。我最后用的是 interval 0x100、window 0x50实测覆盖效果不错。6.2 距离跳变严重从硬件到软件的完整排查扫到了 beacon也换算出了距离但距离值像股票走势图一样上蹿下跳这是做测距项目百分之百会遇到的问题。我踩完一圈总结出三个层面的原因硬件层面ESP32 的 PCB 天线是有方向性的。我一开始把 ESP32 板子竖着放在桌面上beacon 放在正前方信号倒是稳定。后来为了现场布线方便把板子平放同样的距离RSSI 差了 6 到 8dB距离直接从 1.5 米跳到 2.5 米。所以做标定和部署时接收天线的朝向和高度必须固定不要换来换去。环境层面人体遮挡和金属反射是两大元凶。展厅现场有人路过和没人路过同一位置的 RSSI 能差 10dB 以上。金属展架对信号的反射会让某些位置信号增强多径效应有些位置信号反而相消变弱。这就是为什么我没法靠一套参数打天下必须实地标定的原因。软件层面滤波参数没有适配到当前环境的波动幅度。前面说的异常值剔除阈值如果设得太小会把正常的波动也滤掉导致输出值一直卡在旧值不动。我建议拿到一组实测数据后先看一下 RSSI 的标准差如果波动在 5dB 以内窗口 10 次就够如果波动超过 8dB窗口拉大到 20 次同时把剔除阈值放宽到 12dB。6.3 Wi-Fi 与 BLE 共存干扰ESP32 一个射频前端要同时服务 Wi-Fi 和 BLE二者共用天线同一时刻只能跑一种协议。Wi-Fi 是抢占式的当 Wi-Fi 在大量收发数据时BLE 的扫描窗口会被压缩甚至完全挤掉表现就是 RSSI 采集变得稀疏距离更新卡顿。如果项目里 Wi-Fi 和 BLE 同时跑比如测距同时还要把数据上传服务器有几个可操作的优化方向降低 Wi-Fi 工作强度比如减少 MQTT 上报频率别一直满速传文件BLE 扫描参数调得更激进窗口加大弥补被抢占的部分实测下来把 Wi-Fi 的广播间隔调大、关闭 WiFi 省电模式下的一些主动扫描行为对 BLE 扫描的干扰会明显减少我做过一组对照实验Wi-Fi 空闲时BLE 扫描到 beacon 的间隔稳定在 120ms 左右Wi-Fi 高速下载时这个间隔抖动到 500ms 以上。所以如果你发现距离刷新卡顿了先别急着怀疑代码看看是不是 Wi-Fi 占了射频。另外ESP32 的 2.4G 射频同时开 Wi-Fi 和 BLE 时建议把 ESP32 和 beacon 的距离控制在 10 米以内超过这个距离接收灵敏度下降RSSI 波动会更大测距结果基本不可用。调参这事我给个土办法先在空旷地标一组 A 和 n然后拿实际场地跑一遍看距离误差往哪个方向偏。如果算出来的距离普遍偏大说明实际环境衰减比标定时厉害就把 n 调大一点反之调小。不用纠结非要一次性标定完美beacon 测距本质上是个够用就好的东西对触发类应用来说阈值附近留够迟滞区间波动就不至于影响功能。
网站建设高端定制企业官网