目录
前言
一、蓝牙
1.1 蓝牙基础知识
核心特点
两大类型
1.2 蓝牙软件架构
Bluetooth Controller
HCI与传输层
Bluetooth Host
Bluedroid
NimBLE
Bluetooth Profiles
Bluetooth Application
1.3 总结
二、蓝牙数据包
2.1 整体数据包
2.2 PDU Header
2.3 PDU Payload
三、蓝牙测距原理
四、卡尔曼滤波
五、工程实现
5.1 工程总览
5.2 工程结构与依赖
5.3 工作原理
5.3.1 启动事件链
5.3.2 测距数据流
5.3.3 RSSI 的距离换算
5.4 关键函数详解
beacon_measure_init(const char *host_name, const char *target_name)
BT_GAP_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
static void beacon_range_process(const esp_ble_gap_ext_adv_report_t *rpt)
static int rssi_to_distance(int rssi, float k)
static int cmd_rssi_cal(int argc, char **argv)
5.5 操作指南
5.5.1 环境与编译烧录
5.5.2 标定与测距流程
5.6 调参与扩展
注意事项与已知边界
六、实测结果展示
总结
前言
蓝牙是一种广泛应用于设备间短距离无线通信(通常在10米范围内)的无线电技术。与Wi-Fi技术相比,蓝牙的特点是传输距离短、传输速率低、能源消耗低。其中,低功耗特性是蓝牙的一个显著优势,这使得其在某些特定应用场景中成为比Wi-Fi更合适的选择。
一、蓝牙
1.1 蓝牙基础知识
蓝牙(Bluetooth)是一套工作在2.4 GHz 免授权频段的短距离无线通信技术标准,用来让设备直接互联,例如手机连接耳机、键盘、鼠标、音箱或传感器;它通常不依赖 Wi‑Fi 路由器或接入点。
核心特点
- 近距离无线连接:典型用于几米到几十米内的设备通信,实际范围受发射功率、天线、墙体遮挡和环境干扰影响。
- 全球统一标准:蓝牙由 Bluetooth SIG 制定和维护,其核心规范定义了不同厂商设备能够互操作所需的技术要求。
- 低功耗与易配对:尤其适合电池供电设备,如手环、温湿度计、遥控器和各类 IoT 节点。
- 共享 2.4 GHz 频段:它与 Wi‑Fi、部分无线鼠标等共用频谱,因此会通过跳频等机制降低持续受干扰的概率。
两大类型
| 类型 | 常见名称 | 主要用途 | 特点 |
|---|---|---|---|
| 经典蓝牙 | Bluetooth Classic、BR/EDR | 无线耳机、音箱、车载音频、较连续的数据传输 | 面向传统音频和外设连接,支持 BR 与 EDR 两类速率。 |
| 低功耗蓝牙 | Bluetooth Low Energy、BLE | 传感器、手环、智能锁、Beacon、ESP32 物联网设备 | 以低功耗、短数据包和间歇通信为重点,适合长期电池供电设备。 |
注意:经典蓝牙和低功耗蓝牙不是简单的“新旧版本”关系,而是两套不同的无线传输体系。一台设备可能只支持其中一种,也可能同时支持两种,即所谓“双模蓝牙”。
1.2 蓝牙软件架构
Bluetooth Controller
Bluetooth Controller(蓝牙控制器)是协议栈靠近硬件的底层部分。它负责真正的无线收发和实时链路控制,可以将它理解为“蓝牙的无线网卡固件 + 射频控制部分”。
乐鑫文档中,ESP 蓝牙控制器包括 PHY、基带、链路控制器、链路管理器、设备管理器及 HCI 等模块;它直接面向硬件和低级蓝牙协议。
关键点:Controller 不关心你的“温度数据”或“手机按钮”。它只关心“何时在哪个信道发包、是否收到 ACK、连接是否仍然有效”等无线链路问题。
HCI与传输层
HCI是 Host 和 Controller 之间的标准逻辑接口。Host 通过它向 Controller 下达命令,例如“开始广播”“停止扫描”“建立连接”;Controller 则通过它回传事件或数据,例如“扫描到某设备”“连接建立成功”“收到一包数据”。
对于 ESP-IDF,Host 与 ESP 蓝牙 Controller 之间使用的是VHCI(Virtual HCI)接口。无论上层选择 Bluedroid 还是 NimBLE,底层 Controller 本身仍是同一个 ESP Controller。
Bluetooth Host
Bluetooth Host(蓝牙主机协议栈)位于 Application/Profile 与 Controller 之间,主要以软件形式运行。它负责更高层的协议语义:设备如何被发现、如何配对、数据如何组织、属性如何读写、服务如何定义等。Host 常包含或处理以下模块:
| 模块 | 作用 | 直观理解 |
|---|---|---|
| GAP | 扫描、广播、连接、设备角色与连接参数 | “如何发现并连接设备” |
| GATT | 服务、特征值、读写、通知/指示 | “数据以怎样的服务接口暴露” |
| ATT | 属性的访问与传输规则 | “读写一个特征值的底层规则” |
| L2CAP | 上层数据复用、分段与传输适配 | “协议数据的通道和搬运层” |
| SMP | 配对、绑定、密钥与安全 | “双方如何建立可信关系” |
例如,手机读取 ESP32 的温湿度值时,Host 会处理 GATT/ATT 的读请求,把它转换为对应的应用回调或属性访问操作;Controller 则只负责把相关蓝牙数据包可靠地收发出去。
Bluedroid
Bluedroid 是原生 Android 蓝牙协议栈。它同时支持:
- 经典蓝牙(Bluetooth Classic / BR/EDR)
- 低功耗蓝牙(Bluetooth LE / BLE)
ESP-Bluedroid 是基于原生 Android 蓝牙协议栈 Bluedroid 的修改版,由两层组成:蓝牙上层 (BTU) 和蓝牙传输控制器层 (BTC)。BTU 层负责处理 L2CAP、GATT/ATT、SMP、GAP 等底层蓝牙协议以及其他配置文件,提供以 "bta" 为前缀的接口。BTC 层主要负责向应用层提供以 "esp" 为前缀的支持接口,并处理基于 GATT 的配置文件以及其他任务。
ESP-Bluedroid 也同时支持经典蓝牙和低功耗蓝牙,不过目前的ESP32很低版本只能使用低功耗蓝牙,这是控制器硬件决定的。射频/基带没有 BR/EDR。同时提高丰富的API给开发者使用
NimBLE
NimBLE 是另一套 Host 协议栈实现,源自 Apache Mynewt 项目的 NimBLE Host,它的定位是:只做 BLE,但更轻量。
ESP-NimBLE 是基于在 Apache Mynewt 开发的 NimBLE 主机协议栈,针对各系列ESP芯片和 FreeRTOS 进行了移植和优化。但使用的API是原本 NimBLE API,需要其他途径学习。
ESP-IDF 明确指出:NimBLE 仅支持 BLE,不支持经典蓝牙;相较于 Bluedroid,它通常需要更少的代码空间和运行时内存,因此对于 BLE-only 项目更合适。
| 维度 | Bluedroid | NimBLE |
|---|---|---|
| 所处位置 | Host 协议栈 | Host 协议栈 |
| 经典蓝牙 | 支持 | 不支持 |
| BLE | 支持 | 支持 |
| 资源占用 | 通常更高 | 通常更低 |
| 适合场景 | SPP、A2DP、Classic + BLE 双模需求 | 纯 BLE 传感器、GATT、低资源 IoT |
| 底层 Controller | ESP Bluetooth Controller | 同一套 ESP Bluetooth Controller |
下表总结了 ESP-IDF 中支持蓝牙的 ESP 芯片及其蓝牙类型支持情况(Y = 支持,N = 不支持)。
芯片 | 经典蓝牙 (BR/EDR) | 低功耗蓝牙 (LE) |
|---|---|---|
ESP32-S3 | N | Y |
ESP32-C2 | N | Y |
ESP32-C3 | N | Y |
ESP32-C5 | N | Y |
ESP32-C6 | N | Y |
ESP32-C61 | N | Y |
ESP32-H2 | N | Y |
在BLE-only 场景下:NimBLE 在内存、吞吐上限的可调性、BLE 5.x 新特性覆盖上占优;Bluedroid 在"开箱即用"(Profile 封装、文档、跨任务调用便利性)上占优。空口性能天花板两者相同——因为用的是同一个控制器。
Bluetooth Profiles
Bluetooth Profile(蓝牙 Profile,配置文件)不是一段单独的硬件驱动,而是一套面向具体应用场景的互操作规范。它规定设备应使用哪些协议、扮演什么角色、提供哪些服务或数据格式,从而让不同厂商产品能够按一致方式协作。常见 Profile 或基于 Profile 的典型能力:
| 名称 | 类型 | 用途 |
|---|---|---|
| GAP | BLE 通用访问框架 | 广播、扫描、连接、设备角色 |
| GATT | BLE 数据组织框架 | 用 Service 和 Characteristic 建立可读、可写、可通知的数据接口 |
| HID | 人机接口设备 | 键盘、鼠标、游戏手柄 |
| A2DP | 经典蓝牙音频 | 向耳机、音箱传输立体声音频 |
| SPP | 串口配置文件 | 模拟串口的数据透传,常用于 ESP32 经典蓝牙串口 |
| HFP | 免提配置文件 | 蓝牙通话、车载免提 |
| 自定义 GATT Service | BLE 常见做法 | 用自定义 UUID 定义传感器、控制命令和状态数据 |
严格来说,GAP、GATT 等在 BLE 架构里具有通用框架和协议层含义;在日常 ESP32 开发中,人们也常把它们统称为“BLE Profiles”。乐鑫文档说明,Profile 实现在 Host 协议栈之上,应用可以在 Bluedroid 或 NimBLE 提供的能力上构建蓝牙功能。
Bluetooth Application
Bluetooth Application(蓝牙应用层)就是你自己写的工程业务代码。它调用 ESP-IDF 的蓝牙 API,响应蓝牙事件,并将“蓝牙通信”连接到实际产品逻辑,例如 GPIO、传感器、显示屏、Wi‑Fi 或 FreeRTOS 任务。
1.3 总结
Controller 管“电波”,Host 管“协议”,Profile 管“互通规则”,Application 管“产品功能”;Bluedroid 和 NimBLE 是 Host 的两种选择。
对于主机协议栈的选型:
若要 BLE GATT、手机控制、传感器数据上报、低资源占用,优先考虑 NimBLE。
若要 经典蓝牙 SPP 串口、A2DP 音频,或 Classic 与 BLE 双模,选择 Bluedroid。
以上两个主机协议栈,ESP32当前各版本均支持,不过不支持Bluedroid经典蓝牙功能。
二、蓝牙数据包
2.1 整体数据包
BLE 广播包数据结构如下
| Preamble | Access Address | PDU Header | PDU Payload | CRC |
| LE 1M:1 B; LE 2M:2 B | 4 B | 2 B | 6~37 B | 3 B |
Preamble 由 PHY 决定长度,这一段是纯硬件行为:1M PHY 是 1 字节(0xAA或0x55,取决于接入地址最高位),2M PHY 是 2 字节。
Access Address 在广播信道上它是两个固定值之一:使用 公开地址或静态随机地址时是0x8E89BED6,使用可解析/不可解析私有地址(RPA)时是0x8E89BED7。注意按 LSB 在前发送。
帧尾的 CRC 是 3 字节链路层校验,不含接入地址;广播信道的初始值固定为0x555555(数据信道则用两端地址派生)。
2.2 PDU Header
传统广播的头部只有 2 字节,位段划分如下(低位在前):
Byte 0: PDU Type[3:0] | RFU | ChSel | TxAdd | RxAdd
Byte 1: RFU[1:0] | Length[5:0]
各字段含义如下:
| 子字段 | 位宽 | 含义 |
|---|---|---|
PDU Type | 4 bit | 广播 PDU 类型 |
RFU | 1 bit | 保留位,发送端应置 0。 |
ChSel | 1 bit | 信道选择算法提示位;在适用的可连接广播中可指示连接后是否支持 Channel Selection Algorithm #2。 |
TxAdd | 1 bit | 发送方地址类型:0 表示 Public Device Address,1 表示 Random Device Address。 |
RxAdd | 1 bit | 接收/目标地址类型;仅在 PDU 含有目标地址时有意义,例如定向广播或扫描/连接请求。 |
Length | 6 bit | PDU Payload 的字节数,不含2 B Header,也不含 CRC。传统广播通常最大为 37 B。 |
经常使用的广播 PDU 类型如下:
| 值 | 类型 | PDU Payload组成 | 用途 |
|---|---|---|---|
0x00 | ADV_IND | AdvA(6 B) + AdvData(0~31 B) | 可连接、可扫描 |
0x01 | ADV_DIRECT_IND | AdvA(6 B) + TargetA(6 B) | 定向连某个发起者 |
0x02 | ADV_SCAN_IND | AdvA + AdvData | 不可连接、可扫描 |
0x03 | ADV_NONCONN_IND | AdvA + AdvData | 纯 beacon,不可连不可扫 |
0x04 | SCAN_REQ | ScanA(6 B) + AdvA(6 B) | 主机索取扫描响应 |
0x05 | SCAN_RSP | AdvA(6 B) + ScanRspData(0~31 B) | 从机回应,另有 31 字节预算 |
2.3 PDU Payload
整个数据段大小为 6~37 B ,常见可划分为固定 6B 的 AdvA 加最大 31B 的 AdvData(具体取决于PDU Type)。
6字节的 AdvA 是本机的公共地址。LSB在前
0~31字节的 AdvData 结构如下:(一般简称为AD结构)
| Len (1B) | Type (1B) | Data (Len-1 字节) | 下一条 AD ... | 剩余补 0x00 |
- 每条AD结构都由Len、Type和Data组成,Len 只数 Type 之后的字节,即 Len = 1 + Data 长度,不含 Len 自己。
09意味着后面跟 9 字节。 Len = 0x00是列表结束标记,其后全是填充;部分协议栈会直接补0x00到 31 字节。- 同一种 Type 在"广播数据 + 扫描响应"合并视图里只能出现一次,想放两个同类数据就得合成一条 AD(例如多 UUID)或改用扩展广播。
常用 Type如下:
| Type | 含义 | 备注 |
|---|---|---|
0x01 | Flags | 1 字节:bit0 Limited、bit1 General、bit2 No BR/EDR、bit3 Controller 同时支持,06是 BLE-only 常见值 |
0x02/0x03 | 16-bit UUID 列表 | 不完整/完整,每个 UUID 2 字节小端 |
0x06/0x07 | 128-bit UUID 列表 | 每个 16 字节,预算极贵 |
0x08/0x09 | 简短 / 完整 本机名称 | ASCII |
0x0A | 发送信号功率 | int8,dBm,C6= −58 |
0x16 | Service Data(前 2 字节 UUID) | 私有协议首选容器 |
0x19 | Appearance | 2 字节小端 |
0x1B | LE Bluetooth Device Address | 1+6 字节 |
0xFF | Manufacturer Specific Data | 前 2 字节为 SIG 分配的 Company ID |
一个完整的蓝牙数据包如下:
| AA | Preamble(1M PHY) |
| D6 BE 89 8E | Access Address 0x8E89BED6(Public 地址,LSB first) |
| 00 24 | Header:Type=0x00(ADV_IND),Length=0x24=36(AdvA 6 + AdvData 30) |
| F1 2E 3D 4C 5A 6B | AdvA:设备地址 6B:5A:4C:3D:2E:F1(空口反序) |
| 31 字节 AdvData | |
| XX XX XX | CRC-24 |
三、蓝牙测距原理
通常蓝牙测距使用的是蓝牙 Beacon 技术。蓝牙 Beacon(通常指BLE Beacon,低功耗蓝牙信标)是一种利用 Bluetooth Low Energy 广播包,周期性、单向地发送少量标识或状态数据的小型无线节点。附近的手机、网关或其他 BLE 扫描设备接收到广播后,可根据其内容识别“附近有什么设备/处于哪个区域”,再触发应用逻辑。它通常不需要与接收端建立传统蓝牙连接。
Beacon 主要适用于邻近检测和粗粒度室内定位,例如判断设备处于“远、近、很近”或进入某个区域。接收端常利用 RSSI 与路径损耗模型估距。
- RSSI (Received Signal Strength Indicator):接收信号强度指示。它是接收端测量到的无线信号功率值,通常以 dBm 为单位。
- 路径损耗 (Path Loss):无线信号在传播过程中,由于扩散、障碍物遮挡、反射、衍射等原因导致的能量衰减
基本逻辑:在发射功率已知的情况下,接收到的 RSSI 值越小,说明路径损耗越大,也就意味着距离越远。在实际应用中,最常用的是对数距离路径损耗模型 (Log-distance Path Loss Model)。其公式如下:
或者写成更直观的距离计算公式:
| 符号 | 含义 |
|---|---|
| d | 待求的收发端距离。 |
| d0 | 参考距离 |
| RSSI(d) | 距离为d处的接收信号强度指示 |
| RSSI(d0) | 参考距离处的接收功率。 在室内,通常取距离发射端 1 米处的 RSSI 值,需要实际标定 |
| n | 路径损耗指数 。它反映了信号衰减的快慢,与环境密切相关。 自由空间(无遮挡) n≈2.0;室内视距: n≈1.6∼1.8;室内有遮挡/复杂环境: n≈2.7∼4.0; 障碍物极多: n>4.0 |
四、卡尔曼滤波
蓝牙发射的 2.4G 信号会经历墙面、地面和金属物体的反射,人体遮挡也会导致明显衰减;因此 RSSI 不能看作稳定、精确的距离读数。研究中通常会先处理 RSSI 样本,再进行距离估算或定位。常见处理方式:
- 滑动平均:取最近 N 个 RSSI 的均值,简单但对突发异常值较敏感
- 中值滤波:取窗口中位数,对偶发的
-85 dBm这类异常点更稳健 - 卡尔曼滤波:把 RSSI 看成带噪声的观测值,结合上一时刻的估计获得更平滑结果;已有 BLE 定位实现会将其用于 RSSI 数据处理。
因此蓝牙 Beacon 技术它不适合直接作为高精度测距方案。实际中,单个 Beacon 更适合做区域判断;多 Beacon 结合滤波、指纹库或三边定位,才能尝试实现基本的室内定位。上面的滤波算法最重要的就是卡尔曼滤波,我们来学习一下。
卡尔曼滤波(Kalman Filter)是一种利用“过去估计 + 当前测量”持续推断真实状态的递推算法。它不盲目信任某一次传感器读数,而是根据模型和噪声大小,动态决定该更相信历史预测还是新测量值。
对 BLE RSSI 测距场景来说,它的作用就是:把不断抖动的 RSSI 序列变得更平稳,从而让“距离估算”或“靠近/远离判断”少跳变。BLE 室内定位研究中也常先用卡尔曼滤波净化 RSSI,再进行定位或距离推断。
假设 ESP32 连续扫描到同一个 Beacon:
原始 RSSI:-60, -62, -59, -80, -61, -63 dBm
其中-80 dBm可能是人体瞬间遮挡、Wi-Fi 干扰或多径反射导致的异常读数。卡尔曼滤波会认为:“此前一直约为 -61 dBm,现在突然变 -80 dBm,未必真的移动了那么远。”
因此,它会适度采纳当前的-80 dBm,而不是让输出立刻跳到-80 dBm;当后续读数持续接近 -80 dBm时,估计值才会逐步向新水平移动。它通过预测和更新两个循环阶段实现这一点。
两个循环步骤
每来一个新 RSSI 值,滤波器执行:
- 预测:根据上一次的估计,推测当前真实 RSSI 是多少。
- 更新:对比“预测 RSSI”和“实测 RSSI”,再按可信度修正估计结果。
完整卡尔曼滤波的状态空间形式如下:
状态空间形式方程引用自卡尔曼滤波(Kalman Filter)五个公式及其通俗解释_卡尔曼滤波五个公式-CSDN博客
建议去看该博主的原文,解释的很好。我这句不重复解释了
我这实际应用简单一下,直接用下面这个简化公式计算:
五、工程实现
以上完整工程可以进入我的Gitee中下载《https://gitee.com/pai-schoolmate/esp32_project.git》
本工程路径:esp32_project-main\function/BT_bluedroid/Beacon
5.1 工程总览
该程序烧到两块板子上即可互测:每块板都既广播、又扫描。广播端持续发出携带发射功率的 BLE 报文;扫描端锁定指定名称的目标,统计其 RSSI,再用对数路径损耗模型把信号强度换算成距离,通过中文日志输出。
核心特性
- 扩展广播(Extended Advertising):BLE 5.0 LEGACY 可扫描不可连接广播,间隔 20~40 ms,发射功率 +6 dBm,本机名称经扫描响应下发。
- 主动扩展扫描:关闭重复过滤(每个广播包都上报),主动发扫描请求以拿到对方设备名用于识别。
- 单目标测距:通过
beacon_measure_init(host, target)的target参数指定,一次只测一个设备;传NULL则仅广播不测距。 - 现场标定:控制台命令
rssi_cal在已知参考距离处采集 50 个 RSSI 样本取均值,得到模型基准d0_rssi。 - 噪声抑制:测距按 20 个样本分组取均值,再经一阶低通滤波(k=0.75)平滑输出。
广播端和扫描端角色由参数决定
发板与收板烧同一个 bin。区别只在app_main里调用beacon_measure_init()时传入的名称:被测量的一方主要作为广播源,测量方配置target_name指向它。
5.2 工程结构与依赖
组件依赖关系如下:
| 组件 | REQUIRES | 职责 |
|---|---|---|
main | basic, Bluedroid | 应用入口,串联控制台与测距初始化 |
Bluedroid | basicbtesp_timer | BLE 广播/扫描/测距核心逻辑 |
basic | IDF console 示例命令组件 | REPL 环境、系统/分区命令 |
共享状态结构体
struct BT_event_data BT_event_data_ptr(定义于 BT_event.c,声明于 BT_event.h)是 beacon.c 与 BT_event.c 之间的唯一桥梁,承载本机名 AD 数据、目标名、目标地址、参考距离与标定标志。理解它就理解了两个文件如何协作。
5.3 工作原理
5.3.1 启动事件链
Bluedroid 的 GAP 接口是异步的——每个esp_ble_gap_xxx()调用只是「发起请求」,真正完成后通过BT_GAP_callback抛出一个 COMPLETE 事件,下一步必须写在对应事件分支里。这条链是读懂本工程的关键:
5.3.2 测距数据流
每收到一个广播包都会进入beacon_range_process(),但绝大多数包会被层层过滤掉,只有「已锁定目标地址、且非标定期的广播包」才参与测距统计:
为什么名称和地址要分两步锁定
BLE 把「广播数据」和「扫描响应」拆成两条独立 PDU:发射功率等在广播包里,设备名在扫描响应里。代码先从扫描响应解析名称、匹配目标后缓存其地址target_bda,之后只用地址过滤广播包(target_locked置位后不再解析名称),既省 CPU 又避免同名干扰。
5.3.3 RSSI 的距离换算
采用对数路径损耗模型。已知参考距离d0(默认 100 cm)处的基准 RSSI(d0_rssi,由标定得到),即可反推任意 RSSI 对应的距离(换算公式见第三节)
RSSI 测距的固有局限
RSSI 受多径、遮挡、天线方向、人体吸收影响极大,本方案适合区域级近距估计(米级、趋势判断),不要指望厘米级精度。标定环境应尽量贴近实际使用环境。
5.4 关键函数详解
以下五个函数覆盖:初始化入口、事件回调、数据流处理、算法换算、用户命令。
beacon_measure_init(const char *host_name, const char *target_name)
整个测距信标的总入口。预组装本机名 AD 数据与目标名 → NVS 初始化 → 释放经典蓝牙内存 → 控制器/Bluedroid 初始化与使能 → 注册 GAP 回调 → 设设备名 → 配置扩展广播参数(后续步骤全部交给回调链)。
- host_name:本机名称,同时作为 GAP 设备名与扫描响应内容。可为
NULL/空串(此时广播不含名称段,仍正常广播);超过HOST_NAME_MAX_LEN(29) 会截断。 - target_name:测距目标设备名。
NULL/空串 = 仅广播不测距;传名称 = 只对该名称的信标测距(一次一个目标)。 - 返回值:
void。任一环节失败会打印ESP_LOGE并提前 return,调用方无需额外处理。
注意:esp_ble_gap_set_device_name()与广播参数配置必须在esp_bluedroid_enable()之后;若启用了目标名,此处还会注册rssi_cal命令并打印标定提示。
BT_GAP_callback(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
GAP 事件总枢纽,一个大switch串起整条事件链。每个 COMPLETE 事件先判status,失败打错误日志、成功则发起下一步请求。
- event:事件类型,如
ESP_GAP_BLE_EXT_ADV_SET_PARAMS_COMPLETE_EVT、ESP_GAP_BLE_EXT_ADV_REPORT_EVT等。 - param:事件参数联合体,按事件类型取对应成员(如
param->ext_adv_report.params)。
关键分支:EXT_ADV_REPORT_EVT是稳态下唯一高频触发的事件,直接转交beacon_range_process();其余分支只在启动阶段各执行一次。
注意:回调运行在 Bluedroid 任务上下文,不要阻塞,耗时操作应投递到别的任务。
static void beacon_range_process(const esp_ble_gap_ext_adv_report_t *rpt)
测距的核心数据处理。用一组static变量维护跨包状态:样本累加值、标定/测距计数、首次标定完成标志、目标锁定标志。
- rpt:单条扫描报告,含
event_type、addr、rssi、adv_data。
两条主线:① 扫描响应 → 解析名称 → 匹配则锁定target_bda;② 广播包 → 地址过滤 → 标定模式攒满 50 个样本写d0_rssi,测距模式攒满 20 个样本换算距离并输出。
注意:cal_end为假时不输出任何测距结果——即必须先成功标定一次,距离日志才会出现。static状态使其不可重入,但单目标场景下安全。
static int rssi_to_distance(int rssi, float k)
把组平均 RSSI 换算为厘米级估计距离,并做一阶低通平滑(公式见 3.3)。
- rssi:本组平均接收信号强度(dBm,负值)。
- k:低通平滑系数 0~1,越大越跟随最新值;当前调用固定传
0.75。 - 返回值:估计距离(cm),下限截断为 10 cm。
注意:依赖文件级static float d0_rssi(默认 −50 dBm,标定后被覆盖)与共享的d0_dist_cm。dist_old是滤波状态量,跨调用保留。
static int cmd_rssi_cal(int argc, char **argv)
控制台命令rssi_cal的处理函数:触发一次现场标定。它只负责置位cal_in_progress = true,真正的采样在测距回调里完成。
- 返回值:
0= 标定已启动;非0= 未满足条件。
前置校验:未配置目标(纯广播)→ 拒绝;已在标定中 → 拒绝;尚未收到目标广播(target_bda[0]==0)→ 仅提示但允许启动。
用法:把接收板固定在距发送板d0_dist_cm(默认 100 cm) 处保持静止,再在控制台输入rssi_cal,等待「标定完成」日志。
5.5 操作指南
5.5.1 环境与编译烧录
同一 bin 烧到两块板。修改main.c中beacon_measure_init()的两个名称参数以区分角色:被測板用唯一host_name;测量板把target_name设为被测板的名称。当前示例为("esp32C6", "esp32C6_touch")。
5.5.2 标定与测距流程
- 两块板上电,确认日志出现「扩展广播已启动」「扩展扫描已启动」。
- 把接收板固定在距发送板 100 cm 处,保持静止、无遮挡、远离金属与人体。
- 在接收板控制台输入
rssi_cal,等待日志「标定完成:参考距离 100 cm 处实测 RSSI 均值 ≈ −xx dBm」。 - 标定完成后,移动接收板即可看到「测距:… 平均RSSI=… 估计距离≈x.xx 米」周期性输出。
没有标定就没有距离输出
首次上电后若未执行rssi_cal,cal_end为假,测距日志不会出现。这是设计行为,不是 bug。
5.6 调参与扩展
| 参数 | 位置 | 当前值 | 调整影响 |
|---|---|---|---|
rssi_n | BT_event.c | 1.8 | 路径损耗指数。环境多径/遮挡越多调越大,反之调小;直接决定距离换算曲线 |
d0_rssi | BT_event.c | −50(默认) | 参考 RSSI,正常应由rssi_cal现场标定覆盖 |
d0_dist_cm | BT_event.c 结构体初值 | 100 | 标定参考距离,须与实际标定摆放距离一致 |
低通k | rssi_to_distance 调用处 | 0.75 | 越小越平滑但越迟钝,越大越跟手但越抖 |
| 样本组大小 | beacon_range_process | 标定 50 / 测距 20 | 越大越稳但更新越慢 |
tx_power | beacon.c ext_adv_params | +6 dBm | 发射功率,0 dBm 起、3 dB 步进;须与广播数据里的 TX_PWR 段一致 |
interval_min/max | beacon.c | 0x20~0x40 | 广播间隔(×0.625 ms = 20~40 ms);越小越实时越耗电 |
scan_interval/window | BT_event.c | 0x50 / 0x30 | 扫描占空比;window 越接近 interval 收包越密 |
调参建议:换环境后先重做rssi_cal;若整体距离系统性偏大/偏小,微调rssi_n;若读数跳动剧烈,降低k或增大样本组。
注意事项与已知边界
- 单目标设计:当前一次只测一个
target_name。要多目标需扩展为地址→状态的映射表,并去掉target_locked单标志逻辑。 - 不可连接:广播类型为 LEGACY 可扫描不可连接,本机不接受连接,纯做信标。
- 名称容量:本机名 AD 上限 29 字符(31 字节 PDU 预留 2 字节长度/类型)。
六、实测结果展示
双板实测(接收板串口日志)。在 100 cm 处执行rssi_cal完成标定后,手持接收板在近 / 中 / 远三个距离段移动,观察测距输出。
| 阶段 | 平均 RSSI | 估计距离 | 说明 |
|---|---|---|---|
标定(rssi_cal@ 100 cm) | −61.1 dBm | — | 50 样本均值写入d0_rssi,此后才有距离输出 |
| 近距段 | −58 ~ −62 dBm | 0.74 ~ 1.07 m | 与 1 m 参考距离吻合 |
| 中距段 | −63 ~ −66 dBm | 1.13 ~ 1.71 m | 过渡平滑 |
| 远距段 | −67 ~ −70 dBm | 2.04 ~ 2.91 m | 信号衰减后仍保持单调 |
- 单调性良好:RSSI 与距离一一对应(−61 dBm ≈ 0.9 m → −64 dBm ≈ 1.5 m → −67 dBm ≈ 2.2 m → −70 dBm ≈ 2.8 m),移动过程中读数连续过渡、无跳变。
- 平滑有效:静止时段连续输出波动约 ±0.1~0.3 m,分组均值 + 一阶低通抑制了 RSSI 瞬时尖峰。
- 更新节奏:约每 2~2.5 s 输出一条测距日志,与「20 样本/组」的统计窗口一致。
总结
本工程基于 ESP-IDF v6 与 Bluedroid 协议栈,在 ESP32-C6(RISC-V)上实现单固件双角色的 BLE 5.0 测距信标:每块板既周期广播、又主动扫描。广播侧用扩展广播 API 配置 LEGACY 可扫描不可连接报文,间隔 20~40 ms、发射功率 +6 dBm,本机名称经扫描响应下发;扫描侧关闭重复过滤,逐包上报 RSSI。
测距核心是异步 GAP 事件链:beacon_measure_init()完成 NVS、控制器、Bluedroid 使能与回调注册后,由BT_GAP_callback逐级推进"设参数→配广播数据→配扫描响应→启动广播→设扫描参数→启动扫描",稳态下EXT_ADV_REPORT_EVT高频触发beacon_range_process()。该函数先从扫描响应解析名称锁定目标地址,再对目标广播包分组统计:标定模式攒 50 个样本求均值写入d0_rssi,测距模式攒 20 个样本求均值,经rssi_to_distance()的对数路径损耗模型(n=1.8)换算距离,并以 k=0.75 一阶低通平滑后输出中文日志。
实测中 100 cm 处执行rssi_cal标定得 −61.1 dBm,随后近/中/远三段距离读数分别为 0.74~1.07 m、1.13~1.71 m、2.04~2.91 m,RSSI 与距离单调对应、无跳变,约每 2~2.5 s 更新一次。需注意 RSSI 测距受多径与遮挡影响大,仅适合米级趋势估计。