ESP32用ESP-IDF扫描iBeacon并基于RSSI测距的实战指南
2026/9/19 13:57:50 网站建设 项目流程

先把结论放在前面:这一讲咱们用一个周末就能做完,效果是ESP32在VSCode里用ESP-IDF扫描低功耗蓝牙beacon,解析出iBeacon广播包里的UUID、Major、Minor和发射功率,再结合接收信号强度RSSI算出距离,串口打印出来。整个过程不需要额外的蓝牙模块,ESP32自带的BLE控制器就够了。

这篇文章适合已经跑通过ESP-IDF环境、点过灯的读者。你要是还卡在环境搭建,建议先回去把前面几讲的基础过一遍,再来啃这个。当然如果你用的是Arduino,思路也通用,只是API不同,我把ESP-IDF的实现细节讲清楚之后,你也能照葫芦画瓢。

1. 为什么用蓝牙beacon测距:方案选型前的三个判断

1.1 室内定位的几种常见技术怎么选

做室内定位或者近场感知,大家第一反应可能是UWB、WiFi RSSI、蓝牙RSSI这几个方向。UWB精度能做到厘米级,但硬件成本高,终端支持还没全面普及;WiFi RSSI依赖现有路由器的部署位置,指纹库维护起来头疼;蓝牙beacon的优势在于低功耗、低成本、生态成熟,而且手机和嵌入式设备天然支持BLE,哪怕不做专门的网关,普通ESP32就能扫描周围信标。

如果你只需要粗糙的位置感知,比如判断“人走进了哪个房间”“设备靠近了哪个工位”“导览到哪个展品附近”,蓝牙beacon测距完全够用。它的精度通常在2到5米范围内,达不到厘米级,但胜在布点灵活,一个CR2032电池能撑半年以上,改造现有场地也不用拉线。

1.2 ESP32做接收端的底气在哪

ESP32系列芯片从出厂就内置了完整的蓝牙协议栈,支持BLE 4.2甚至BLE 5.0(不同型号有差异),不需要外挂蓝牙芯片。这点对做产品原型特别友好,一个模组解决WiFi和蓝牙两件事,PCB面积和BOM成本都省下来了。

另外ESP32在ESP-IDF框架下对蓝牙GAP层的接口封装得比较清晰,扫描、广播、连接管理都有现成的API。底层逻辑虽然复杂,但我们做应用层调用并不需要深究HCI命令。尤其是它的RSSI是硬件上报的,扫描事件里直接带出来,省去了很多中间转换。

1.3 ESP-IDF比Arduino强在哪

用Arduino开发蓝牙beacon扫描也能做,库很成熟,几行代码就能拿到广播包。但项目一旦复杂起来,比如要同时跑MQTT、OTA、传感器采集和蓝牙扫描,Arduino的FreeRTOS后台机制有时候就不够清晰了。

ESP-IDF是乐鑫官方的物联网开发框架,基于FreeRTOS,任务调度、内存管理、事件循环、日志系统都是工程级的。最关键的是,ESP-IDF允许你精确控制BLE扫描参数,比如扫描窗口、扫描间隔、主动扫描还是被动扫描、重复过滤策略,这些对beacon扫描的实时性和功耗影响非常大。Arduino的BLE库也能设置一部分,但可调范围不如原生API。后续你要是想把这套代码迁移到ESP32-C3或者ESP32-S3上,IDF工程基本可以无缝切换。

2. 开发环境准备:VSCode+ESP-IDF从零到位

2.1 安装ESP-IDF插件的最省事路径

现在新用户开发ESP32,我的建议是直接用VSCode的Espressif IDF插件,不要再去手动折腾命令行工具链了。你去VS Code扩展市场搜“ESP-IDF”,装那个由Espressif官方发布的插件,版本号前缀是1.x的就行。

装完插件,按F1打开命令面板,输入“ESP-IDF: Configure ESP-IDF Extension”。这里有个容易踩坑的点:如果你电脑上之前装过ESP-IDF命令行版本,插件会问你选择已有的IDF还是下载新的。建议直接用“Express”模式让插件帮你下载一个集成好的工具链,省得环境变量各种冲突。

下载过程中如果卡住,多半是网络问题,需要重试几次,或者换成设置HTTPS代理的方式。路径上不要有中文和空格,我习惯放在C:\esp\这类纯英文短路径下。网上有人反馈IDF文件装到了C盘用户目录半天找不到,其实插件右下角状态栏会显示当前IDF版本和Workspace文件夹,点开就能看到具体路径。另外补充一句,最简单直接的下载途径仍然是去乐鑫官网拿到离线安装器,国内网络环境下成功率更高。

2.2 创建工程和menuconfig里的三个关键配置

命令面板输入“ESP-IDF: Create Project”,选择一个模板工程,选“hello_world”就行,后面我们自己改代码。芯片型号在左下角设置里选,ESP32、ESP32-S3、ESP32-C3都可以,前提是你板子上确实那颗芯片。

编译之前,打开menuconfig,这里能用UI方式配置蓝牙相关选项。三个关键配置务必确认:

  • Component config -> Bluetooth -> Bluetooth,广播并选择Bluedroid
  • Component config -> Bluetooth -> Bluedroid Options,勾选SPP之外的Classic Bluetooth可以不要,但我们这边只要BLE,所以这套配置里确认BLE相关项被启用即可。
  • Component config -> Bluetooth -> Bluedroid Options -> BLE确保勾选了GATTGAP等基础选项。

实际工程里,如果你用的官方模板,默认可能只启用了WiFi,蓝牙没开。在menuconfig里把蓝牙总开关打开后,编译时会多出很多蓝牙相关源文件,链接时间会变长,属正常现象。

2.3 工程结构里和蓝牙相关的文件

一个ESP-IDF工程的核心目录结构差异不大,我们需要关心的文件有这些:

  • main/:我们自己写的业务代码放这里,比如main.c
  • CMakeLists.txt:用于告知构建系统需要编译的源文件和依赖组件。
  • sdkconfig:由menuconfig生成,记录了所有配置项,不要手动去改它。
  • managed_components:如果你后面加载了组件管理器托管的第三方组件,会出现在这里。

蓝牙beacon扫描这个功能不需要引入额外的第三方组件,只要ESP-IDF自带的bt组件即可。你在main目录下的CMakeLists.txt里确认requires字段包含了bt就行。

3. iBeacon广播协议与RSSI测距原理

3.1 iBeacon广播包逐字节拆解

苹果定义的iBeacon数据格式是BLE广播包里的一个厂商自定义数据段。一个完整的BLE广播包由多个AD structure组成,每个structure的结构是:长度 + 类型 + 数据

iBeacon嵌入在类型为0xFF的厂商自定义数据里,厂商ID是苹果的0x004C。整体数据布局是这样的:

  • 厂商ID:2字节,4C 00(注意小端序,实际显示为0x004C表示Apple)。
  • iBeacon类型标识:1字节,02
  • iBeacon数据长度:1字节,15(十进制21,表示后面21字节是iBeacon数据)。
  • Proximity UUID:16字节,用来区分不同的beacon部署项目,比如同一个商场所有信标共用一个UUID。
  • Major:2字节,用于区分一组信标,比如同一UUID下的不同楼层。
  • Minor:2字节,用于区分同一个Major下的不同信标,比如同一楼层的具体点位。
  • Measured Power(TX Power):1字节,带符号整数,表示距离信标1米处测得的接收信号强度值,单位dBm。

所以拿到一个广播包,你只要在AD structure里定位到4C 00 02 15这串特征字节,后面的字段就能按固定偏移解出来。

3.2 RSSI测距公式到底怎么用

蓝牙beacon测距的本质是:信号在空气中传播时会衰减,距离越远,接收端测到的RSSI值越小(负得越多)。理论和实测经验之间常用一个对数距离路径损耗模型来表达:

这个公式里几个变量的含义如下:

公式的分母上有两个关键参数:AnA表示参考距离为1米时测得的RSSI理想值,也就是iBeacon广播里那个TX Power字段附近的一个实测参考值;n是环境衰减因子,开阔空间约2.0到2.5,普通办公室隔断环境下2.5到3.5,工业场景或金属货架密集区域可以到3.5以上。

为什么强调标定?因为每颗beacon的实际发射功率都存在个体差异,你放在1米处测出来的A未必等于广播里写的TX Power。我之前遇到过一批信标,广播里写的TX Power是-59,实际1米处均值是-64,差5个dB,就是这5个dB导致计算距离直接偏离了将近一倍。所以生产环境部署前,一定要拿信号接收器对每个点位做现场校正。

3.3 滤波:测距精度靠它拉回来

RSSI是所有无线信号里最不稳定的物理量之一。多径反射、人体遮挡、收发天线朝向、同频段干扰,都会让RSSI在短时间内跳动10到15个dB。跳这么一下,算出来的距离可能从1米蹦到4米。

应对手段主要有三种:

  • 滑动平均滤波:维护一个固定长度的RSSI历史窗口,每次进来新值就剔除最旧的值,然后求平均。实现简单,适合大部分场景,窗口长度通常在10到20。
  • 中值滤波:取历史窗口中间值,能把偶尔出现的极端跳变剔除得更彻底。
  • 卡尔曼滤波:状态模型建好后效果最好,但实现复杂度高,还要调协方差参数。

我实际用下来,滑动平均加合理范围内剔除异常值已经能满足绝大多数应用。卡尔曼滤波更适合运动目标追踪或者连续定位输出,如果你只是判断几个固定点位的远近,没必要上。

4. 代码实现:从扫描到测距的完整流程

4.1 初始化BLE并启动扫描

代码里最先要做的是初始化蓝牙协议栈。

esp_err_t ret; esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); // 禁掉经典蓝牙,只保留BLE esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if (ret) { ESP_LOGE(TAG, "ESP32蓝牙控制器初始化失败: %s", esp_err_to_name(ret)); return; } ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); if (ret) { ESP_LOGE(TAG, "蓝牙控制器使能失败: %s", esp_err_to_name(ret)); return; } ret = esp_bluedroid_init(); if (ret) { ESP_LOGE(TAG, "Bluedroid初始化失败: %s", esp_err_to_name(ret)); return; } ret = esp_bluedroid_enable(); if (ret) { ESP_LOGE(TAG, "Bluedroid使能失败: %s", esp_err_to_name(ret)); return; }

这里有个细节:esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)可以释放经典蓝牙占用的内存。beacon扫描只需要BLE,经典蓝牙是多余的,调用这行能让后续内存分配宽松不少。

接着注册GAP回调函数,并设置扫描参数:

esp_ble_gap_register_callback(esp_gap_cb); 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 = 0x50, // 扫描间隔(单位625us),0x50约50ms .scan_window = 0x30, // 扫描窗口(单位625us),0x30约30ms .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE // 关闭重复过滤 }; esp_ble_gap_set_scan_params(&ble_scan_params);

扫描间隔和扫描窗口是一组联动参数。窗口决定了单次扫描持续多久,间隔决定了下一次扫描什么时候开始。窗口越大,能捕获到的广播包概率越高,但功耗也越高。beacon的广播间隔通常是100ms到1s,我这边扫描窗口设了30ms、间隔50ms,配合长时间的扫描周期,实际测试下来一次完整的beacon广播事件不会漏掉。

scan_duplicate建议设成DISABLE,不然同一设备重复上报会被过滤掉。我们做滤波需要对同一个beacon连续采样,如果开了重复过滤,每秒才拿一两个RSSI样本,滤波就没意义了。

初始化完成后,在GAP回调里启动扫描是标准姿势:

case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: esp_ble_gap_start_scanning(0); // 第二个参数0表示持续扫描,不设时长 break;

4.2 解析广播包中的iBeacon字段

GAP回调里,事件类型ESP_GAP_BLE_SCAN_RESULT_EVT就代表拿到了一个广播事件。判断子事件为ESP_GAP_SEARCH_INQ_RES_EVT后,广播数据、RSSI、设备地址都在param->scan_rst结构体里。

完整解析函数我建议单独写一个,方便复用。逻辑上就是遍历所有AD structure,逐个判断类型和特征字节:

typedef struct { uint8_t uuid[16]; uint16_t major; uint16_t minor; int8_t tx_power; } ibeacon_info_t; bool parse_ibeacon(const uint8_t *adv, uint8_t len, ibeacon_info_t *info) { uint8_t i = 0; while (i < len) { uint8_t ad_len = adv[i]; if (ad_len == 0) { break; } uint8_t ad_type = adv[i + 1]; if ((ad_type == 0xFF) && (ad_len >= 0x1A)) { const uint8_t *d = &adv[i + 2]; if ((d[0] == 0x4C) && (d[1] == 0x00) && (d[2] == 0x02) && (d[3] == 0x15)) { memcpy(info->uuid, &d[4], 16); info->major = (d[20] << 8) | d[21]; info->minor = (d[22] << 8) | d[23]; info->tx_power = (int8_t)d[24]; return true; } } i += ad_len + 1; } return false; }

可能有读者要问:为什么判断条件里要求ad_len >= 0x1A?因为iBeacon的完整厂商数据是Apple公司ID的2字节加类型和长度的2字节加21字节数据,合计25个字节,十六进制是0x19。但有些beacon厂商会额外塞一点别的数据,比如设备名称或电量信息,所以判断只要这段AD structure的长度够容纳iBeacon必要字段就行,宁可多不能少。如果你的beacon是其它格式,比如Google的Eddystone,判断逻辑就要换成0xAA 0xFE的特征码,原理一模一样。

4.3 RSSI平滑滤波与距离计算

RSSI滤波我用了一个简单的环形缓冲区加滑动平均:

#define RSSI_HISTORY_SIZE 15 #define RSSI_ABNORMAL_THRESHOLD 15 static int8_t rssi_history[RSSI_HISTORY_SIZE]; static uint8_t rssi_idx = 0; static uint8_t rssi_count = 0; int8_t rssi_smooth_filter(int8_t new_rssi) { // 异常值剔除:和当前均值差值超过阈值就丢弃 if (rssi_count > 0) { int32_t sum = 0; for (int i = 0; i < rssi_count; i++) { sum += rssi_history[i]; } int8_t avg = (int8_t)(sum / rssi_count); int diff = abs(new_rssi - avg); if (diff > RSSI_ABNORMAL_THRESHOLD) { return avg; // 丢弃这次异常跳变 } } rssi_history[rssi_idx] = new_rssi; rssi_idx = (rssi_idx + 1) % RSSI_HISTORY_SIZE; if (rssi_count < RSSI_HISTORY_SIZE) { rssi_count++; } int32_t sum = 0; for (int i = 0; i < rssi_count; i++) { sum += rssi_history[i]; } return (int8_t)(sum / rssi_count); }

异常值剔除的逻辑有必要说明一下。室内环境下,蓝牙信号偶尔会因为人体靠近或者金属物体遮挡,瞬间衰减到-90甚至-100,但下一秒又恢复正常。如果直接把异常值扔进滑动平均,滤波结果会被带偏好几秒。我设置了和当前均值的差值阈值,超过15dB就丢弃,实测下来RSSI输出的稳定性提高了一个档次。

距离计算直接套对数距离模型:

#include <math.h> float calc_distance(int8_t rssi, int8_t tx_power, float env_factor) { return powf(10.0f, (tx_power - rssi) / (10.0f * env_factor)); }

这里tx_power就是iBeacon里的Measured Power字段,rssi是滤波后的信号强度,env_factor就是环境衰减因子n,默认传2.5。如果广播播放的是iBeacon标准协议,这个字段通常和我们实测1米处的RSSI接近,直接用它做A值在大多数室内场景够用。

4.4 完整代码与串口输出效果

组合起来,扫描回调的完整处理逻辑是这样的:

static void handle_ble_adv_data(uint8_t *adv, uint8_t adv_len, int8_t rssi, uint8_t *bda) { ibeacon_info_t beacon; if (!parse_ibeacon(adv, adv_len, &beacon)) { return; // 不是iBeacon广播包,忽略 } int8_t smooth = rssi_smooth_filter(rssi); float dist = calc_distance(smooth, beacon.tx_power, 2.5f); ESP_LOGI(TAG, "MAC: " MACSTR, MAC2STR(bda)); ESP_LOGI(TAG, "UUID: %02x%02x%02x%02x-%02x%02x-%02x%02x", beacon.uuid[0], beacon.uuid[1], beacon.uuid[2], beacon.uuid[3], beacon.uuid[4], beacon.uuid[5], beacon.uuid[6], beacon.uuid[7]); ESP_LOGI(TAG, "Major: %d Minor: %d", beacon.major, beacon.minor); ESP_LOGI(TAG, "RSSI: %d dBm, Distance: %.2f m", smooth, dist); }

串口的输出格式我故意设计成了“一行闭合键值对”的样式,后面你如果要把数据传到MQTT或者阿里云物联网平台,解析这个字符串会非常方便。实际调试时,用ESP_LOGI输出的日志在ESP-IDF Monitor里可以直接看到全局系统日志时间戳,哪天你加了多个任务要排查时序问题,这个时间戳能救命。

顺带提一嘴,如果你想同时扫描多个beacon并分别输出距离,建议维护一张beacon表,以MAC地址为key,存每个beacon的滤波状态、最近距离和更新时间。单机同时扫描几十个beacon完全没问题,但要注意串口日志刷太快会拖慢系统,调试的时候输出放在循环里加个节流,比如每秒最多打印3次。

5. 实测数据与误差分析

5.1 测试场景与标定方法

测试场地我选了一个普通办公区走廊,宽度约2米,两侧有金属门框,一端放了3个beacon,高度1.2米,间隔约3米。接收端ESP32放在小推车上,从0.5米开始往后移动,每到固定距离停下来采样30秒取平均。

正式测距前,先做一步简单标定:在离beacon正好1米的位置,连续采集100个RSSI样本,取平均值。比如测出来是-63.5,而广播包里的TX Power是-62,那就以实测值为准,重新调calc_distance里的参考值。同时在5米处再采一组数据,反推一下环境因子n,公式稍作变形就行:

这个值可以从测试数据的对数坐标图里拟合出来,但我通常在5米处采到RSSI后,直接代入求n

如果你手头没有5米这种大距离的场地条件,那就用默认值2.5起步,然后拿实测值不断校正。经验是n在室内一般落在2.2到3.2之间,误差容忍度不要太苛刻。

5.2 实测数据记录

标定后,我在1米、2米、3米、5米、8米这几个距离点上做了静止采样,滤波后取均值,结果大概是这样:

距离的误差表现让我有个明显感受:3米以内相对误差控制在20%左右,超出5米后误差快速扩大。这符合对数路径损耗模型的特性——距离越远,相同RSSI波动对应的距离误差越大。也就是说这个方案做近距离姿态判断或者区域级定位够用,但你要精确报出“距离3.1米”这类数字,得叠加多个参考节点做三边定位才有意义。

5.3 提升精度的四个小技巧

实测下来,几个细节对精度影响很大:

  • 固定接收端高度和天线朝向。ESP32板载天线是全向天线,但人体遮挡对信号衰减很明显,手握住板子测出的RSSI直接往下掉5到8dB,实验时把设备架起来别拿手里。
  • 增加采样次数。采样时间从10秒拉长到30秒,均值更稳。对静态场景来说这不算缺点,但如果你的应用目标是移动中实时测距,那就要在实时性和精度之间做权衡。
  • 用多包平均再加滤波。我的程序里滤波窗口是15,测试时发现如果windows窗口从15减到5,输出的跳动幅度会明显上升;从15加到30,回复变慢但输出非常平稳。具体窗口大小你可以写配置文件动态调,方便在不同现场切换参数。
  • 部署密度不要过低。做定位系统时,单点beacon覆盖半径建议控制在5米内,beacon间距3到5米布一个,这样即使个别点RSSI跳变,周围节点还能互相纠偏。

6. 常见问题与排查技巧实录

6.1 扫不到beacon的检查顺序

这是问得最多的问题。我的排查顺序是从底层往上层一层层看:

  • 确定beacon在正常工作。手机上下载一个nRF Connect,先看看手机能不能扫到那个beacon。如果手机也扫不到,大概率是beacon没配置好或者没电了。
  • 确定ESP32蓝牙初始化成功。看启动日志里有没有报错,特别留意esp_bt_controller_initesp_bluedroid_enable的返回码。
  • 检查扫描参数。如果你用的是主动扫描,有些beacon设备不支持扫描响应包,但广播包本身一定能扫到;真不行就把scan_type改成PASSIVE或者两种都试试。
  • 确认广播过滤没有误伤。如果你的代码里做了UUID过滤,先试一下不过滤,只要能扫到设备再慢慢加过滤逻辑。

补一个常见坑:ESP32的扫描事件里,广播类型和广播地址类型是需要关心的,但一般做beacon扫描不需要连接设备,所以不用纠结这个地址是不是公共地址。如果你在回调里读设备名没读到,大概率是因为广播包本身没包含设备名,beacon为了省电通常只发厂商自定义数据,不附带设备名。

6.2 RSSI忽大忽小怎么办

RSSI跳动是常态,不是故障。先确认你采集的数据源是不是经过滤波的。如果滤波后仍有明显毛刺,检查是不是有无线设备在附近,比如USB 3.0接口、微波炉、隔壁房间的大功率WiFi路由器,都会对2.4GHz频段造成干扰。

另外注意接收天线的摆放位置。ESP32的天线区域位于板子一端,这部分贴到金属物体上信号会剧烈变化。把板子放在塑料支架上,天线区域朝上或者朝外,能有效减少环境对天线的影响。

还有一个容易忽略的点:你的beacon如果同时支持连接和广播,而某个设备已经和它建立了连接,那么beacon的广播间隔会自动拉长,甚至暂停广播。这在调试时容易被误判成扫描问题,实际是beacon被别的东西占用了。

6.3 编译、烧录与VSCode环境问题

开发环境方面的报错,我挑几个高频的整理一下。

  • The path for ESP-IDF is not valid: /tools/idf.py not found.
    这通常是ESP-IDF插件设置里填的IDF路径不对,或者你选择的路径指向了esp-idf子目录而不是真正的IDF根目录。打开VSCode的设置,搜idf.espIdfPath,确认路径直接包含了tools/idf.py这个文件所在的层级。如果是用Express模式安装的,插件会把路径自动配置好,尽量不要手动去改它。

  • 编译到一半报Error: ...fqbn: esp32:esp32:esp32s3 using board...
    这个信息有时在PlatformIO或Arduino环境里出现,说明编译系统是通过FQBN指定开发板的。如果你在ESP-IDF扩展里看到这个,多半是你不小心用了Arduino扩展的命令来编译IDF工程。记得编译时用命令面板里的ESP-IDF: Build,不要在Arduino的编译按钮里点。

  • 烧录时连接不上串口
    先确认串口号在设备管理器里存在。如果存在但一直连接失败,按住板子的BOOT键,再点烧录,看到进入下载模式后再松手。老款ESP32开发板经常有这个问题,新板子加了自动下载电路就不用管了。

  • VSCode下离线安装ESP-IDF选好了路径,但工作时组件还是被装到C盘
    这个坑很隐性。Espressif的IDF组件管理器会把下载的managed components放到工程目录,但工具链本身会和IDF放一起。如果你不想装到C盘,一个办法是安装时选择自定义路径,一个办法是装完了把整个.espressif文件夹挪到D盘,然后用idf_tools.py脚本重新设置环境变量。命令行处理起来不难,但新手容易绕进去,所以我通常建议安装阶段就一次性规划好路径。

6.4 几个我踩过的大坑

写代码过程中,我遇到过几个挺迷的问题,在这里一并说下。

第一个是esp_ble_gap_register_callback必须在esp_bluedroid_enable之后调用。顺序反了,回调注册会失败,扫描事件永远到不了你的处理函数,但程序不会崩,你会看到一个“扫描启动成功但就是没结果”的怪现象。

第二个是esp_ble_scan_params_t里的scan_duplicate字段,不同IDF版本默认值存在差异。旧版本默认是BLE_SCAN_DUPLICATE_ENABLE,新版本默认是DISABLE。如果你要做连续RSSI采样,建议显式赋值,不要靠默认。

第三个是日志输出速率。如果你在扫描回调里直接打印每一包数据,而周围beacon数量又多,串口会被刷爆,导致整个系统响应变慢甚至看门狗超时。解决方法是加打印节流,比如每100ms才输出一次最新值,中间的数据只更新内存变量。

第四个和硬件有关。ESP32开发板种类很多,有些板子的板载USB转串口芯片不稳定,特别是高波特率下,烧录和日志会断断续续。遇到烧录失败,优先降波特率到115200试试,别一上来就怀疑代码。还有,如果你的板子同时接了外部电源又插着USB,容易造成串口芯片电平混乱,这个问题我在用劣质充电宝供电时遇到过多次,后来统一用USB口供电,问题就没再出现。

我在实际项目里最后落地的时候,把beacon测距的代码放在了一个独立任务里,设置好优先级和栈深度,再用消息队列把测距结果发给主控任务处理。这样即使蓝牙扫描偶发问题,也不会影响其它业务逻辑。后续如果你要做多beacon定位、违规进入区域告警或设备巡检打卡,这套结构都能直接扩展。测距精度不满足需求时,记得回来重新标定一遍环境因子,我用这个公式处理过很多现场问题,最有效的步骤永远是标定、标定、再标定。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询