基于UWB与ESP-NOW的DIY高精度定位追踪器制作指南

UWBESP-NOWESP32
于 2026-05-31 13:05:28 修改
·本内容遵循CC 4.0 BY-SA版权协议

1. 项目概述与核心思路

如果你和我一样,是个喜欢折腾硬件的玩家,同时又对苹果那个小巧但价格不菲的AirTag感到好奇,那么这个项目绝对能让你兴奋起来。我们没法复制苹果依托全球十亿台设备构建的“查找”网络,那是它的护城河。但抛开这个,AirTag最核心、最实用的两个功能是什么?第一,是能告诉你“目标离我到底有多远”的精准测距;第二,是能让目标物“哔哔”作响,帮你快速定位的声音提醒。这两个功能,我们完全可以用开源硬件和不算太复杂的代码来实现一个本地化、高精度的版本。

这个DIY追踪器的核心,在于两项关键技术的结合:UWB(超宽带)ESP-NOW。UWB不是什么新鲜玩意儿,它在工业领域已经应用多年,但直到最近几年,随着芯片成本的下降,才开始在消费电子领域崭露头角。它的原理不像蓝牙那样通过信号强度(RSSI)来粗略估算距离,那种方法在复杂环境下误差动不动就好几米。UWB采用的是飞行时间法,简单来说,就是精确测量无线电波在两个设备之间“飞”一个来回所花的时间。光速是恒定的,时间测得准,距离自然就准,轻松实现厘米级的测距精度,这正是我们实现“精准追踪”的物理基础。

而ESP-NOW,则是乐鑫为ESP32系列芯片开发的一种高效的无线通信协议。你可以把它想象成设备之间的“对讲机”,它们可以直接对话,完全不需要Wi-Fi路由器这个“基站”来中转。这对于我们这个需要快速、稳定触发蜂鸣器的场景来说再合适不过了——低延迟,连接简单,功耗也比一直连着Wi-Fi要低。所以,我们这个项目的架构就很清晰了:两块ESP32 UWB开发板,一块作为“追踪器”贴在物品上,另一块作为“查找器”拿在手里。它们之间通过UWB持续测量精确距离并显示在OLED上;当你按下查找器上的按钮,它通过ESP-NOW发送一个指令,让追踪器上的蜂鸣器响起,实现声音寻物。

2. 硬件选型与物料清单解析

工欲善其事,必先利其器。硬件选型直接决定了项目的可行性、精度和最终体验。下面我结合自己的踩坑经验,详细拆解每一部分的选择逻辑和注意事项。

2.1 核心主控与UWB模块:ESP32 UWB开发板

这是整个项目的心脏。市面上常见的方案是ESP32芯片搭配DW1000或更新的DW3000系列UWB射频芯片。对于DIY玩家,最省事的选择是直接购买集成好的ESP32 UWB开发板。

  • 为什么是集成板? 因为UWB电路(特别是射频部分)的布线要求非常严格,天线设计、阻抗匹配稍有偏差,性能就会大打折扣。自己用ESP32模组和DW1000芯片画板,对绝大多数爱好者来说门槛太高,失败率惊人。集成板厂商已经帮你解决了这些难题。
  • 主流型号推荐:我实测过几款,像“Makerfabs ESP32 UWB”和“Pozyx”的板子都比较稳定。它们通常基于DW1000,在开源社区(如Arduino)的驱动支持也最完善。选择时注意板载天线类型(PCB天线或陶瓷天线),室内使用区别不大,但陶瓷天线性能通常更稳定一点。
  • 关键参数检查
    1. 供电:确保是3.3V逻辑电平,大部分ESP32开发板都是。直接用USB供电最方便。
    2. 引脚引出:确认板子是否将所有需要的GPIO(用于OLED、按钮、蜂鸣器)都引出来了,有些板子可能只引出了UWB相关引脚。
    3. 固件与库支持:优先选择在Arduino IDE中已有成熟库(如arduino-dw1000库)支持的板型,这会为你后续编程省去无数麻烦。

注意:购买时一定要问清楚卖家是否提供示例代码和库文件。有些廉价板子资料不全,会让你在软件调试阶段陷入困境。

2.2 显示单元:OLED屏幕的选择

OLED屏幕用于实时显示测量出的距离,是项目的“眼睛”。选择0.96寸或1.3寸的I2C接口OLED屏是最佳选择。

  • I2C vs SPI:I2C接口只需要2根数据线(SDA, SCL),而SPI需要4根或更多。对于只是显示一些数字和简单字符的应用,I2C的速率完全足够,而且能节省宝贵的GPIO口,让接线更清爽。
  • 分辨率:128x64像素是甜点级选择。分辨率足够清晰显示“Distance: 2.35m”这样的信息,且驱动简单,市面上相关的Adafruit_SSD1306U8g2库非常成熟。
  • 供电:同样是3.3V,与ESP32完美兼容。注意,有些屏幕模块自带3.3V/5V电平转换芯片,接5V也可以,但为了统一和安全,建议全部接入ESP32板上的3.3V引脚。

2.3 声学反馈与触发:蜂鸣器与按钮

这是实现“声音提醒”功能的关键执行端和触发端。

  • 蜂鸣器:推荐使用有源蜂鸣器。有源蜂鸣器内部自带振荡电路,给它一个直流电压(3.3V)就会持续发声,频率固定;而无源蜂鸣器需要你通过MCU的PWM引脚输出特定频率的方波才能驱动它发声。对于我们这个“通电就响”的简单需求,有源蜂鸣器只需一个GPIO口控制通断,程序实现上简单得多。
  • 按钮:选择一个普通的轻触开关即可。这里涉及一个重要的硬件知识:消抖。机械按钮在按下和弹起的瞬间,金属触点会发生物理抖动,导致MCU在几毫秒内检测到多次通断,从而可能误触发多次。虽然我们可以在软件中做消抖处理,但在硬件上并联一个0.1uF的电容到地,是更可靠的做法。不过对于ESP32来说,其内部上拉和软件消抖通常已足够应付。

2.4 完整物料清单与连接规划

基于以上分析,一份可靠的物料清单如下:

元件名称 规格/型号 数量 用途说明 关键注意事项
ESP32 UWB开发板 集成DW1000芯片,如Makerfabs款 2块 核心主控与UWB测距 确认引脚布局,优先选择资料齐全的型号
OLED显示屏 0.96寸,128x64,I2C接口 1块 显示实时距离 仅接在“查找器”(主机)端
有源蜂鸣器 工作电压3.3V 1个 发出寻物提示音 接在“追踪器”(从机)端,注意正负极
轻触开关 6x6mm 四脚轻触 1个 触发寻物声音 接在“查找器”(主机)端,需考虑消抖
杜邦线 公对公、母对母 若干 连接各元件 准备不同规格以便连接
USB数据线 Micro-USB 或 Type-C 2根 供电与程序下载 根据你的ESP32板接口选择
面包板 中号或大号 1块 原型搭建与测试 可选,但强烈建议用于初期验证

接线规划是项目成功的第一步。在动手焊接或插线前,最好画一个简单的接线图。核心原则是:电源(VCC/GND)先走,信号线后连。务必确保所有元件的GND最终都连接到ESP32的GND引脚,共地是电路正常工作的基础。

3. 系统架构与通信协议深度剖析

在把零件连起来之前,我们必须从逻辑上理解整个系统是如何协同工作的。这不仅仅是接线,更是数据流和控制流的规划。

3.1 双设备角色定义:主机与从机

我们的系统包含两个完全对称的ESP32 UWB设备,但在功能上我们赋予它们不同的角色,这会让程序逻辑更清晰:

  1. 主机(查找器):这是用户手持的设备。它负责:

    • 主动发起UWB测距请求。
    • 计算并显示实时距离到OLED屏幕。
    • 监听按钮状态,当按钮被按下时,通过ESP-NOW向从机发送“鸣叫”指令。
    • 硬件:连接了OLED屏幕和按钮。
  2. 从机(追踪器):这是附着在钥匙、背包等物品上的设备。它负责:

    • 响应主机的UWB测距请求,配合完成距离测量。
    • 监听ESP-NOW通道,收到主机的指令后,驱动蜂鸣器发出声音。
    • 硬件:连接了蜂鸣器。

为什么这么设计?因为UWB测距需要一个“发起者”和一个“响应者”,角色固定能简化测距流程。而触发声音的动作显然是单向的(从查找器到追踪器),因此由主机主动发送指令最为合理。

3.2 UWB测距原理与实现流程

UWB实现厘米级精度的核心是双向测距。市面上很多模块的单向测距精度会受时钟偏差影响,而双向测距通过两次消息交换,巧妙地抵消了这种偏差。其流程(以最常见的SS-TWR为例)如下:

  1. Poll消息:主机(A)在时间T1发送一个Poll报文给从机(B)。
  2. Response消息:从机(B)在收到Poll后,于时间T2发送一个Response报文回给主机(A)。这个T2是B根据自己的时钟记录的。
  3. Final消息:主机(A)在时间T3收到Response报文。然后,主机在时间T4再发送一个Final报文给从机(B)。
  4. 计算:从机(B)在时间T5收到Final报文。至此,A掌握了T1和T3(来自自己的时钟),B掌握了T2和T5(来自自己的时钟)。通过一个包含这四个时间戳的公式,可以计算出无线电波在A、B之间飞行的时间,从而得到距离。这个计算过程通常由UWB芯片的驱动库在后台完成,我们只需要调用getRange()之类的函数读取结果即可。

实操心得:在代码中,你需要为两个设备分别设置一个唯一的“短地址”(如0x0001和0x0002),并在初始化时指定谁作为“发起者”,谁作为“响应者”。库函数会帮你处理底层复杂的报文交换时序。

3.3 ESP-NOW协议在项目中的应用

ESP-NOW是乐鑫的“黑科技”,它让两个ESP32设备像对讲机一样直接通信。在这个项目中,我们用它来传递“响铃”指令。

  • 优势

    • 低延迟:指令发出到接收通常在毫秒级,按下按钮几乎能立刻听到蜂鸣器响。
    • 无需网络:不依赖Wi-Fi路由器,甚至在完全没网络的环境下(比如地下室)也能工作。
    • 配置简单:你只需要知道对方ESP32的MAC地址,就能建立连接。MAC地址是每块ESP32硬件唯一的标识符。
  • 工作流程

    1. 初始化:在setup()函数中,初始化ESP-NOW,并注册一个接收回调函数。这个函数就像是一个信箱的收件员,一旦有数据送来,它就会被自动调用。
    2. 配对:在主机端,你需要将从机的MAC地址添加到对等设备列表中。这意味着主机“认识”从机,知道该把消息发给谁。
    3. 发送:当主机检测到按钮被按下时,它构造一个简单的数据包(比如一个字节的命令0x01代表“响铃”),然后通过ESP-NOW发送出去。
    4. 接收与执行:从机的回调函数收到这个数据包,解析出命令,然后执行digitalWrite(buzzerPin, HIGH)来驱动蜂鸣器。通常我们会让蜂鸣器响一段时间(如2秒)后自动关闭。
  • 注意事项

    • MAC地址获取:你需要先单独为每块ESP32烧录一个简单的程序来读取并打印出其MAC地址,记录下来,然后硬编码到对方的程序中。
    • 数据包结构:虽然我们只发一个简单命令,但良好的习惯是定义一个小型结构体,比如struct Command { uint8_t action; },这样以后如果想增加其他指令(如改变蜂鸣模式)会非常容易扩展。
    • 通信可靠性:ESP-NOW本身不提供可靠传输保证。在干扰严重的环境,偶尔可能丢包。对于这个项目,如果按一下没反应,多按一下即可。如果要求高,可以加入简单的应答重传机制。

4. 软件开发与环境搭建全指南

硬件准备就绪后,我们就进入了软件的战场。清晰的开发环境和正确的库文件是成功的一半。

4.1 Arduino IDE配置与核心库安装

虽然你也可以用PlatformIO,但Arduino IDE对新手更友好。确保你安装的是较新版本。

  1. 添加ESP32板支持

    • 打开Arduino IDE,进入“文件”->“首选项”。
    • 在“附加开发板管理器网址”中,填入:https://espressif.github.io/arduino-esp32/package_esp32_index.json
    • 然后进入“工具”->“开发板”->“开发板管理器”,搜索“esp32”,找到由Espressif Systems提供的版本并安装。这个过程可能需要下载一些资源,请保持网络通畅。
  2. 安装必需的库

    • UWB库:在“项目”->“加载库”->“管理库”中,搜索“DW1000”。通常会找到“arduino-dw1000”这个库,作者是thotro。安装它。这是驱动DW1000芯片的核心。
    • OLED库:同样在库管理中,搜索“SSD1306”,安装“Adafruit SSD1306”库。安装时,它会提示你依赖“Adafruit GFX Library”,一并安装。
    • ESP-NOW库:好消息是,ESP-NOW功能已经集成在ESP32的Arduino核心中,无需额外安装库。你只需要包含esp_now.hWiFi.h头文件即可。

4.2 双设备代码结构与逻辑分解

你需要创建两个独立的Arduino工程,一个给主机(Finder),一个给从机(Tag)。

主机(Finder)代码核心逻辑:

CPP
// 1. 包含头文件
# include <DW1000.h>
# include <Wire.h>
# include <Adafruit_SSD1306.h>
# include <esp_now.h>
# include <WiFi.h>
 
// 2. 定义引脚、对象、变量
# define BUTTON_PIN 4
# define OLED_RESET -1
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
 
// 3. 声明从机的MAC地址(需要替换成你实际的地址)
uint8_t tagMacAddress[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
 
// 4. 定义ESP-NOW数据结构
typedef struct command_message {
uint8_t action; // 0: 停止, 1: 鸣叫
} command_message;
command_message myCommand;
 
// 5. ESP-NOW发送回调函数(用于调试发送状态)
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
// 可以在这里打印发送成功或失败,便于调试
}
 
void setup() {
Serial.begin(115200);
// 初始化UWB,设置为主机/发起者模式
DW1000.begin(/* IRQ pin */, /* RST pin */);
DW1000.newConfiguration();
DW1000.setDeviceAddress(0x0001); // 主机短地址
DW1000.setNetworkId(10); // 网络ID,两个设备需相同
DW1000.enableMode(DW1000.MODE_SHORTDATA_FAST_ACCURACY); // 快速精准模式
DW1000.commitConfiguration();
// 初始化OLED
Wire.begin(/* SDA pin */, /* SCL pin */);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
// 初始化按钮引脚为输入模式,并启用内部上拉电阻
pinMode(BUTTON_PIN, INPUT_PULLUP);
// 初始化ESP-NOW
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
Serial.println("ESP-NOW初始化失败!");
return;
}
esp_now_register_send_cb(OnDataSent); // 注册发送回调
// 添加对等设备(从机)
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, tagMacAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
esp_now_add_peer(&peerInfo);
myCommand.action = 0; // 初始化为停止
}
 
void loop() {
// 6. UWB测距循环
static uint32_t lastRangeTime = 0;
if (millis() - lastRangeTime > 100) { // 每100ms测一次距
float distance = DW1000.getRange(); // 获取距离,单位通常是米
lastRangeTime = millis();
// 在OLED上显示距离
display.clearDisplay();
display.setCursor(0,0);
display.print("Dist:");
display.setCursor(0, 30);
display.print(distance, 2); // 显示两位小数
display.print(" m");
display.display();
// 也可以通过串口打印,用于调试
Serial.print("Distance: ");
Serial.print(distance);
Serial.println(" m");
}
// 7. 按钮检测与ESP-NOW发送
static bool lastButtonState = HIGH;
bool currentButtonState = digitalRead(BUTTON_PIN);
if (lastButtonState == HIGH && currentButtonState == LOW) { // 检测下降沿(按下)
delay(50); // 简单软件消抖
if (digitalRead(BUTTON_PIN) == LOW) { // 确认按下
myCommand.action = 1; // 设置为鸣叫指令
esp_now_send(tagMacAddress, (uint8_t *) &myCommand, sizeof(myCommand));
Serial.println("Send BEEP command!");
}
}
lastButtonState = currentButtonState;
// 可选:如果按钮是点动式,发送指令后可以延时一下避免连续触发
}

从机(Tag)代码核心逻辑(简化版):

CPP
# include <DW1000.h>
# include <esp_now.h>
# include <WiFi.h>
 
# define BUZZER_PIN 5
 
// 声明ESP-NOW接收数据结构(与主机一致)
typedef struct command_message {
uint8_t action;
} command_message;
command_message receivedCommand;
 
// ESP-NOW接收回调函数
void OnDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
memcpy(&receivedCommand, incomingData, sizeof(receivedCommand));
if (receivedCommand.action == 1) {
digitalWrite(BUZZER_PIN, HIGH); // 蜂鸣器响
delay(2000); // 响2秒
digitalWrite(BUZZER_PIN, LOW); // 关闭蜂鸣器
// 注意:在实际项目中,建议用非阻塞的定时方式控制蜂鸣器时长,避免阻塞loop
}
}
 
void setup() {
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// 初始化UWB,设置为从机/响应者模式
DW1000.begin(/* IRQ pin */, /* RST pin */);
DW1000.newConfiguration();
DW1000.setDeviceAddress(0x0002); // 从机短地址
DW1000.setNetworkId(10); // 必须与主机相同
DW1000.enableMode(DW1000.MODE_SHORTDATA_FAST_ACCURACY);
DW1000.commitConfiguration();
// 注意:从机代码中,UWB部分通常配置为自动响应模式,库会处理响应报文
// 初始化ESP-NOW为接收模式
WiFi.mode(WIFI_STA);
if (esp_now_init() != ESP_OK) {
return;
}
esp_now_register_recv_cb(OnDataRecv); // 注册接收回调
}
 
void loop() {
// 从机的主循环通常很简单,UWB响应和ESP-NOW接收都由中断或回调处理
// 可以在这里添加一些状态指示灯闪烁,表示设备正在运行
delay(1000);
}

4.3 代码烧录与关键配置点

  1. 分别烧录:用USB线连接主机ESP32,在Arduino IDE中选择正确的端口和开发板型号(如ESP32 Dev Module),将主机代码烧录进去。完成后,拔下线,再连接从机ESP32,烧录从机代码。
  2. 获取并填写MAC地址:烧录一个简单的程序到每块板子上,读取其MAC地址。
    CPP
    #include <WiFi.h>
    void setup(){
    Serial.begin(115200);
    WiFi.mode(WIFI_MODE_STA);
    Serial.println(WiFi.macAddress());
    }
    void loop(){}
    在串口监视器中查看打印出的地址,形如AA:BB:CC:DD:EE:FF,将其转换成数组格式{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},分别更新到对方代码的peerInfo.peer_addr或接收判断逻辑中。
  3. 引脚配置:代码中的/* IRQ pin *//* RST pin *//* SDA pin *//* SCL pin */等注释处,需要根据你使用的具体ESP32 UWB开发板的原理图或引脚标注,填写正确的GPIO编号。这是最常见的错误来源之一。

5. 硬件连接、组装与系统调试

当代码准备就绪,就到了激动人心的实体搭建环节。遵循正确的步骤和顺序,可以避免很多低级错误。

5.1 分步焊接与连接指南

我强烈建议你先在面包板上完成所有连接和初步测试,确认一切工作正常后,再进行焊接,制作成更牢固的成品。

第一步:为每块ESP32 UWB板供电 这是最基本也最重要的一步。使用USB线为每块开发板单独供电。确保电源指示灯正常点亮。

第二步:连接“查找器”(主机)

  1. 连接OLED屏幕
    • OLED的VCC -> ESP32的3.3V引脚。
    • OLED的GND -> ESP32的GND引脚。
    • OLED的SDA -> ESP32的GPIO 21(这是I2C SDA的常用引脚,但请以你的板子为准)。
    • OLED的SCL -> ESP32的GPIO 22(这是I2C SCL的常用引脚)。
  2. 连接按钮
    • 按钮的一端连接到ESP32的某个GPIO(如代码中的GPIO 4)。
    • 按钮的另一端连接到GND。同时,在代码中我们使用了INPUT_PULLUP模式,这意味着我们利用了ESP32的内部上拉电阻,将GPIO 4在平时拉高到3.3V。当按钮按下时,引脚被短接到GND,变为低电平,从而被检测到按下。这是最简单的连接方式,无需外部电阻。

第三步:连接“追踪器”(从机)

  1. 连接有源蜂鸣器
    • 蜂鸣器的正极(通常有“+”标记或较长的引脚) -> ESP32的某个GPIO(如代码中的GPIO 5)。
    • 蜂鸣器的负极 -> ESP32的GND引脚。
    • 重要提示:GPIO口的驱动能力有限(通常~40mA)。如果蜂鸣器工作电流较大,直接驱动可能导致ESP32重启或损坏。稳妥的做法是,在GPIO和蜂鸣器正极之间加一个NPN三极管(如S8050) 或一个MOS管来驱动,将GPIO作为控制信号。对于小型有源蜂鸣器,通常可以直接驱动,但务必查看蜂鸣器规格书。

5.2 上电测试与系统联调

连接完成后,不要急于封装,先进行系统化测试。

  1. 分模块测试

    • UWB测距:先不接OLED和按钮/蜂鸣器,只给两块ESP32上电,打开串口监视器(波特率115200)。你应该能看到两块板子之间不断打印出距离信息。尝试移动一块板子,观察距离值是否平滑变化。这是验证UWB硬件和基础代码是否正常的关键。
    • OLED显示:单独测试主机,将测距得到的距离值打印到串口的同时,也尝试用简单的display.println("Hello")测试OLED是否正常显示。
    • ESP-NOW通信:编写一个最简单的测试程序,一块板子发,一块板子收,并在串口打印状态,确保无线链路畅通。
  2. 集成测试

    • 烧录完整的主机、从机代码。
    • 为主机上电,观察OLED是否开始显示距离。移动从机,看距离显示是否更新。
    • 按下主机上的按钮,听从机的蜂鸣器是否立即响起。测试不同距离下的响应情况(理论上在ESP-NOW有效范围内都应响应,室内通常几十米没问题)。

5.3 外壳设计与电源考量

原型工作正常后,可以考虑为它制作一个外壳,特别是“追踪器”部分,需要附着在物品上。

  • 查找器(主机):可以找一个合适大小的塑料盒,将ESP32主板、OLED屏幕(在盒子上开窗)和按钮固定进去。可以考虑增加一个锂电池和充放电管理模块,使其脱离USB线使用。
  • 追踪器(从机):这是需要迷你化的部分。目标是尽可能小巧。你可以考虑使用更小的ESP32模组(如ESP32-S2 Mini)搭配独立的DW1000模块,但这需要自己焊接和画板,难度较大。对于初版,可以先用现有的开发板,找一个扁平的盒子封装。电源是关键:为了长期贴在物品上使用,必须考虑电池供电。一颗小容量的锂电池(如500mAh)配合高效的降压模块,可以工作数天甚至数周。需要在代码中深度优化功耗(如让ESP32和DW1000在大部分时间进入睡眠模式,定期唤醒测距),这又是一个复杂的主题,但却是产品化的必经之路。

6. 性能优化、问题排查与扩展思路

项目能跑起来只是第一步,让它跑得稳、跑得好,才是体现功力的地方。

6.1 提升UWB测距稳定性与精度

实测中,你可能会发现距离数值偶尔跳变,或者在非视距(NLOS)环境下误差变大。

  • 天线摆放:确保UWB天线周围没有大的金属物体遮挡,尽量保持两个设备天线之间是视距(LOS)传播。金属会严重反射和屏蔽UWB信号。
  • 校准:UWB模块可能存在天线延迟偏差。高级的驱动库通常支持校准功能。你可以将两个设备固定在已知的精确距离上(如1.000米),运行校准程序,让库计算出修正值。这能有效消除系统误差。
  • 滤波算法:在代码中对读取到的距离值进行软件滤波。最简单的是移动平均滤波,即存储最近N次测量值,取平均值作为输出。这可以平滑掉偶然的跳变。
    CPP
    #define FILTER_SIZE 5
    float distanceBuffer[FILTER_SIZE];
    int bufferIndex = 0;
    float filteredDistance = 0;
     
    // 在获取到原始距离 rawDistance 后
    distanceBuffer[bufferIndex] = rawDistance;
    bufferIndex = (bufferIndex + 1) % FILTER_SIZE;
     
    filteredDistance = 0;
    for (int i=0; i<FILTER_SIZE; i++) {
    filteredDistance += distanceBuffer[i];
    }
    filteredDistance /= FILTER_SIZE;
    // 使用 filteredDistance 进行显示
  • 模式选择DW1000.enableMode()可以设置不同的速率和精度模式。MODE_LONGDATA_RANGE_LOWPOWER模式距离更远但精度稍差,MODE_SHORTDATA_FAST_ACCURACY精度高但耗电快。根据你的场景选择。

6.2 常见问题与故障排除速查表

遇到问题不要慌,大部分都是常见错误。

现象 可能原因 排查步骤
串口无任何输出 1. USB线或端口问题
2. 板子型号选错
3. 代码未成功上传
1. 换线、换端口试试。
2. 在IDE中确认选择的开发板型号与手中硬件完全一致。
3. 上传时观察IDE下方进度条和提示信息。
OLED屏幕不显示 1. 电源接反或没接
2. I2C地址不对
3. SDA/SCL接错引脚
1. 用万用表检查VCC和GND电压是否为3.3V。
2. 运行I2C扫描程序,确认OLED的地址(通常是0x3C)。
3. 核对原理图,确认SDA、SCL是否接到了ESP32对应的I2C引脚。
UWB不显示距离 1. 两块板子UWB配置不一致
2. 天线损坏或接触不良
3. 距离过远或遮挡严重
1. 检查两段代码中的setNetworkIdsetDeviceAddress是否配对(网络ID相同,设备地址不同)。
2. 检查天线是否焊接牢固。
3. 将两设备靠近至1米内,确保无遮挡,再测试。
距离数值固定不变或为0 1. 角色配置错误
2. 测距流程未成功启动
1. 确认一块板子初始化为DW1000.MODE_INITIATOR(发起者),另一块为DW1000.MODE_RESPONDER(响应者)。
2. 检查主机的loop中是否定期调用了发起测距的函数(如startRange())。
按下按钮蜂鸣器不响 1. ESP-NOW配对失败
2. MAC地址错误
3. 蜂鸣器接线或GPIO错误
1. 检查串口,看主机是否打印了“Send BEEP command!”。
2. 最可能的原因:MAC地址填写错误。重新获取并核对。
3. 用digitalWrite(BUZZER_PIN, HIGH);单独测试蜂鸣器是否会响。
蜂鸣器一直响或不停止 1. 代码逻辑错误
2. 硬件连接问题(如上拉电阻)
1. 检查从机接收回调函数,确认驱动蜂鸣器后是否有delay或定时关闭逻辑。
2. 检查按钮是否损坏或一直处于短路状态。

6.3 项目扩展与进阶玩法

这个基础框架有很大的扩展潜力:

  • 多从机追踪:ESP-NOW支持一对多通信。你可以让一个查找器主机,同时配对多个追踪器从机(每个有唯一MAC地址和短地址)。在主机OLED上通过按钮切换显示不同追踪器的距离,或者同时显示多个(如果屏幕够大)。
  • 加入姿态传感器:在追踪器上增加一个MPU6050这样的六轴传感器。当物品被移动时,传感器产生中断,唤醒ESP32并通过ESP-NOW向主机发送警报,实现“防丢提醒”功能。
  • 数据上报云端:在主机端,除了本地显示,还可以通过Wi-Fi将距离数据上传到物联网平台(如阿里云、ThingsBoard),实现远程查看物品位置历史。
  • 低功耗优化:这是产品化的核心。让追踪器绝大部分时间处于深度睡眠状态,每隔一段时间(如10秒)唤醒,快速完成一次UWB测距和通信检查,然后继续睡眠。这可以将待机时间从几天延长到数月。
  • 路径记录与寻找:结合手机App(通过蓝牙与主机通信),在手机上显示一个简单的雷达界面,指示追踪器的方向和距离变化,引导你走向物品,体验更接近真正的AirTag。

这个项目从想法到实现,贯穿了无线通信、嵌入式编程、传感器应用等多个知识点。它最迷人的地方在于,你用相对低廉的成本和开源的生态,亲手搭建了一个功能实用、精度可观的位置感知系统。每一次按下按钮听到蜂鸣器响起,每一次在OLED上看到精确到厘米的距离变化,都是对动手创造能力最好的奖励。