基于双ESP32的移动射频感知系统:Wi-Fi/蓝牙扫描与多源定位实践
1. 项目概述:从零构建一个移动射频感知节点
如果你对身边的无线信号世界感到好奇,想知道周围有多少Wi-Fi热点、蓝牙设备,并且能精确记录下发现它们的位置,那么这个基于ESP32的无线信号采集与地理定位系统项目,正是为你准备的。我花了几天时间,把HackerBox 0089套件里的零件变成了一台能揣进口袋、边走边“嗅探”的移动信号测绘仪。它的核心价值在于,将复杂的射频信号感知与多源地理定位技术,集成到了一个巴掌大小的开源硬件平台上。
简单来说,这个系统能同时干三件事:第一,用一块ESP32扫描并记录周围的Wi-Fi网络名称(SSID)、信号强度(RSSI)和MAC地址;第二,用另一块ESP32扫描蓝牙设备;第三,通过独立的GPS模块获取经纬度,同时利用GSM模块捕获附近的蜂窝基站信息作为辅助定位。所有这些数据都会实时存储在一张MicroSD卡里。无论是用于业余的无线网络测绘(Wardriving)、信号覆盖分析,还是作为物联网数据采集的前端,它都是一个极具学习和实践价值的项目。接下来,我会带你完整走一遍从硬件焊接、软件配置到系统集成的全过程,并分享我在调试过程中踩过的坑和总结的经验。
2. 核心硬件平台解析与选型思路
2.1 为什么选择双ESP32架构?
项目最核心的设计是采用了双ESP32-DevKitC开发板的并行架构。很多初学者可能会问,ESP32本身不是已经集成了Wi-Fi和蓝牙吗?为什么需要两块?这里的关键在于实时性与资源隔离。
一块ESP32确实可以同时运行Wi-Fi扫描和蓝牙扫描任务,但在密集信号环境下(例如繁华的商业街),同时进行两种扫描、处理GPS串口数据、写入SD卡,并且还要通过GSM模块查询基站信息,这对单核MCU的任务调度和内存管理是极大的挑战。很容易导致数据丢失、扫描周期不稳定,甚至系统卡死。
采用双ESP32方案,本质上是将任务进行了物理层面的分离:
- ESP32 A(主控与记录单元):负责运行Wi-Fi扫描任务、读取GPS数据、管理SD卡文件系统,并协调整个系统。它作为“大脑”,处理更复杂的逻辑和存储I/O。
- ESP32 B(专用扫描单元):专职负责蓝牙扫描,并将扫描结果通过串口发送给ESP32 A。同时,它也负责驱动SIM800L GSM模块,进行蜂窝网络扫描。
这种架构保证了每种射频扫描任务都能获得尽可能多的CPU时间和射频前端资源,数据流的处理也更加清晰。在实际测试中,双机架构的扫描成功率和数据完整性远高于单ESP32尝试进行多任务处理的方案。
2.2 关键模块选型与功能剖析
除了ESP32,套件中的几个核心模块决定了系统的能力边界。
1. ATGM336H GPS模块这是一个基于北斗/GPS/GLONASS多星系统的定位模块。选择它而非常见的NEO-6M或NEO-8M,主要看中其高更新率与冷启动性能。它的定位数据更新率最高可达10Hz,对于移动中的快速定位很有帮助。其典型的2.5米CEP(圆概率误差)精度,对于民用级信号测绘完全足够。模块通过UART(串口)与ESP32通信,使用标准的NMEA-0183协议,兼容性极好。
2. SIM800L GSM/GPRS模块这是一个经典的2G通信模块。这里需要明确一个非常重要的点:我们使用它并非为了上网或发短信,而是利用其“小区识别”功能进行辅助定位。即使不插入SIM卡,模块也能扫描到周围移动基站广播的公共信息,包括MCC(国家代码)、MNC(运营商网络代码)、LAC(位置区码)和Cell ID(基站编号)。这些信息可以上传到OpenCellID等开源数据库进行查询,从而获得一个大致的地理位置。在GPS信号丢失的室内或城市峡谷,这是非常有效的备用定位手段。SIM800L功耗相对较高,在扫描时峰值电流可达2A,因此电源设计需要特别注意。
3. MicroSD卡模块与存储方案数据存储选择了最通用的MicroSD卡(TF卡)方案,通过SPI接口与ESP32连接。选择16GB或更小容量的卡,并格式化为FAT32文件系统,可以最大限度地保证兼容性。对于这种持续写入小数据包(每条记录可能只有几百字节)的应用,选择高速卡(Class10或UHS-I)的意义不大,反而应该优先考虑卡的稳定性和兼容性。一些品牌的大容量、高速卡可能因为功耗或初始化时序问题导致初始化失败。实测下来,像SanDisk Ultra或Kingston的普通速度卡最为稳定。
4. 天线系统:从全向到定向的配置天线是射频系统的“耳朵”,选对天线,信号捕获能力能提升数倍。
- ESP32 Wi-Fi/蓝牙天线:开发板板载的PCB天线性能一般。套件提供了带IPEX接口的外接天线,建议更换为2.4GHz全向天线,能显著提升接收灵敏度。
- GPS有源陶瓷天线:GPS模块配套的是一款有源(内置LNA低噪声放大器)的陶瓷天线。有源天线能补偿线缆损耗,但对供电电压敏感,必须连接到模块的“VCC_RF”或“V_ANT”引脚(通常为3.3V),不能接错。
- GSM螺旋天线:SIM800L模块工作于850/900/1800/1900MHz频段,套件提供的单极螺旋天线在这些频段有较好的谐振特性。安装时让其“悬浮”在模块上方数毫米,能获得最佳性能。
- PCB Yagi定向天线(选配):这是一款为2.4GHz频段设计的印刷电路板八木天线。它的增益高、方向性强,非常适合用于定向探测或远距离连接测试。当你需要探查某个特定方向的信号源时,它会非常有用。
3. 硬件组装全流程与避坑指南
硬件组装是项目的基础,焊接和连接的正确性直接决定了后续软件调试的难度。我按照功能模块的依赖关系,建议采用以下顺序进行组装。
3.1 第一步:核心控制器ESP32的初步测试
在将任何模块焊接到主PCB之前,务必先单独测试每一块ESP32开发板。这是一个能节省你大量后期排查时间的好习惯。
- 软件环境搭建:在Arduino IDE中安装ESP32开发板支持。打开“文件”->“首选项”,在“附加开发板管理器网址”中添加:
https://espressif.github.io/arduino-esp32/package_esp32_index.json。然后在“工具”->“开发板”->“开发板管理器”中搜索并安装“esp32”。 - 基础功能测试:使用USB线连接一块ESP32到电脑。在Arduino IDE中选择开发板为“ESP32 Dev Module”。打开示例“文件”->“示例”->“01.Basics”->“DigitalReadSerial”。将代码中的
int pushButton = 2;修改为int pushButton = 0;,因为开发板上的BOOT按钮连接的是GPIO0。上传代码,打开串口监视器(波特率9600),正常情况下会持续打印“1”。按下BOOT按钮,打印值应变为“0”。这个测试验证了MCU的基本运行、编程接口和GPIO输入功能。 - 重复测试:对第二块ESP32板子执行完全相同的测试流程。确保两块板子在独立状态下都是完好的。
注意:ESP32开发板上的EN(使能)引脚有时会因静电或误触进入不稳定状态。如果遇到板子无法识别或程序无法上传,可以尝试短按一下“EN”按键进行硬件复位。
3.2 第二步:焊接主控与GPS定位模块
测试完ESP32后,可以开始在主PCB上进行焊接。
- 焊接ESP32 A:将第一块ESP32模块对准PCB上标有“A”位置的焊盘。务必注意方向,PCB丝印上会标明USB接口和天线接口应朝向哪一侧。先焊接对角的两个引脚固定位置,检查无误后再焊接所有引脚。
- 焊接ATGM336H GPS模块:将GPS模块焊接到指定位置,其IPEX天线座应朝向PCB外侧。焊接时温度不宜过高(建议350°C左右),时间要短,避免损坏模块内部的敏感射频电路。
- 连接GPS天线:将那个米白色的有源陶瓷天线,通过IPEX跳线轻柔地连接到GPS模块的天线座。听到轻微的“咔哒”声即表示连接到位。切忌用力拉扯或扭转电缆,IPEX连接器的核心非常脆弱。
此时,你可以再次通过USB给ESP32 A上电,运行之前的按钮测试程序,确保在焊接后主板功能依然正常。
3.3 第三步:集成GSM蜂窝定位模块
这是硬件部分最容易出错的环节,需要仔细处理电源和天线。
- 焊接稳压二极管:在SIM800L模块的电源路径上,有一个1N4001或类似型号的二极管。它的作用是将来自ESP32的5V电源降压到约4.3V(5V - 二极管压降0.7V),以满足SIM800L的供电要求(3.7V-4.2V最佳)。二极管的方向至关重要:黑色环标记应对准PCB丝印上标有白色环标记的一端。焊反了会导致电源无法接通。
- 焊接ESP32 B:将第二块ESP32模块焊接到标有“B”的位置。同样注意方向。
- 焊接SIM800L模块:这是最需要耐心的一步。模块需要两组排针(一组6Pin,一组5Pin)。特别注意:模块上有两个6Pin的焊盘排,其中一个焊盘已经被我们用作天线焊点(NET)。因此,你需要准备一个6Pin排针(焊在另一排)和一个5Pin排针(焊在包含NET脚的那一排,但NET脚不插排针)。先将排针焊接到SIM800L模块上,再将模块整体插到PCB上焊接固定。
- 焊接GSM螺旋天线:将螺旋天线的引脚直接焊接到SIM800L模块上标有“NET”的过孔。按照指南,让天线底部距离模块的金属屏蔽罩大约4mm,这个间隙有助于天线阻抗匹配。
- 上电测试:通过USB给ESP32 B供电。此时,SIM800L模块上的红色LED应该开始闪烁(大约每秒一次)。如果LED不亮,首先检查二极管方向是否正确,其次检查USB电源是否充足(最好直接连接电脑主板后置端口,避免使用前端接口或未经供电的USB Hub)。
3.4 第四步:存储与显示扩展接口
- 焊接MicroSD卡模块:将模块焊接到PCB边缘指定位置。为了方便焊接,可以先用钳子小心地将模块排针上的塑料卡扣滑掉。焊接时确保模块与PCB贴合平整。
- 组装天线转接与显示板:
- 将OLED显示屏(通常是128x64的I2C SSD1306)焊接到“天线安装板”上。注意屏幕应朝向有丝印的一面。
- 在天线安装板的背面,焊接一个4Pin的排针座。
- 使用套件提供的螺丝和螺母,将两根SMA转IPEX的射频跳线固定在天线安装板上。SMA头从板子正面(有屏幕的一面)伸出,跳线从背面穿出。拧紧螺母时一定要用两只扳手对向固定,避免旋转力传导到脆弱的跳线电缆上,导致内部芯线扭断。
- 连接系统:
- 在主RF采集PCB上,找到标有“OLED”的4Pin焊盘,焊接上对应的排针座。
- 使用4根母对母杜邦线,将天线安装板背面的4Pin排针与主板上的4Pin排针连接起来。连接顺序必须正确:通常顺序是VCC、GND、SDA、SCL。请对照OLED模块和主板的原理图或丝印确认。接反可能烧毁屏幕。
- 最后,将两根射频跳线的IPEX端子,分别小心地按压到两块ESP32模块的IPEX天线座上。听到清脆的“咔”声即可。
3.5 第五步:供电设计与系统整合
双ESP32的供电需要特别注意。两块ESP32的5V电源在PCB上是连通的。这意味着,你只需要给其中任意一个ESP32的USB口供电,就能同时带动两块板子。理论上,由于电源路径上有防反接二极管,即使两个USB口都接上电源也不会短路,但为了避免任何潜在的电源冲突风险,强烈建议只连接一个USB供电口。
对于移动使用场景,你可以用一个USB充电宝给系统供电。选择充电宝时,需要注意其输出能力。虽然系统待机时功耗不高,但在GSM模块扫描的瞬间会有约2A的峰值电流。因此,一个能提供至少2.5A输出的充电宝是必要的。
4. 软件配置与核心功能实现详解
硬件组装完毕后,就进入了软件赋予系统灵魂的阶段。我们将分步为两个ESP32烧录不同的固件。
4.1 ESP32 A:GPS数据获取与SD卡日志
首先处理负责GPS和存储的ESP32 A。
库安装:在Arduino IDE中,点击“工具”->“管理库”,搜索并安装以下两个库:
TinyGPSPlus-ESP32:一个优秀的GPS NMEA语句解析库。EspSoftwareSerial:用于在ESP32上创建额外的软件串口(虽然ESP32有3个硬件串口,但其中一个通常用于USB通信,用软件串口可以更灵活地分配引脚)。
GPS测试代码与修改:打开
TinyGPSPlus-ESP32库的示例DeviceExample。你需要修改两处关键配置以匹配我们的硬件连接:// 原代码: // static const int RXPin = 4, TXPin = 3; // static const uint32_t GPSBaud = 4800; // 修改为(根据PCB设计,GPS的TX接ESP32的GPIO16,RX接GPIO17): static const int RXPin = 16, TXPin = 17; static const uint32_t GPSBaud = 9600; // ATGM336H默认波特率是9600将修改后的代码上传到ESP32 A。打开串口监视器(波特率115200),将设备置于户外开阔地带。等待几分钟,你就能看到解析后的经纬度、时间、卫星数等信息。首次定位(冷启动)可能需要较长时间(1-3分钟)。
SD卡功能测试:可以使用Arduino自带的
SD库示例CardInfo或Datalogger来测试SD卡模块是否正常工作。确保在代码中正确初始化SPI引脚(通常SD卡模块的CS引脚连接GPIO5)。
4.2 ESP32 B:蓝牙扫描与GSM基站捕获
接下来是功能更复杂的ESP32 B,它需要驱动蓝牙和GSM。
GSM功能测试与库安装:首先安装
TinyGSM库。然后,你需要一个专门的测试代码来验证SIM800L模块。核心步骤如下:#include <TinyGsmClient.h> #define TINY_GSM_MODEM_SIM800 // 必须明确定义模块型号 #define SerialAT Serial1 // 使用硬件串口1与SIM800L通信 TinyGsm modem(SerialAT); void setup() { Serial.begin(115200); // 用于调试输出 SerialAT.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17 delay(3000); Serial.println("Initializing modem..."); modem.restart(); String modemInfo = modem.getModemInfo(); Serial.print("Modem: "); Serial.println(modemInfo); // 执行网络扫描(无需SIM卡) Serial.println("Scanning for networks..."); String networkList = modem.getNetworkList(); Serial.println(networkList); }这段代码会初始化模块并扫描可用网络。如果串口输出类似
“T-Mobile”,MCC:310,MNC:260,...的信息,说明GSM模块工作正常。实操心得:很多朋友在这一步遇到问题,输出卡在“Initializing modem...”。除了检查接线和电源,最关键的是检查
TINY_GSM_MODEM_SIM800这个宏定义是否被正确设置。TinyGSM库支持多种模块,必须通过这个宏指定型号,否则编译会报错或初始化失败。蓝牙扫描功能:ESP32的蓝牙库
BluetoothSerial是内置的。一个简单的扫描循环如下:#include "BluetoothSerial.h" BluetoothSerial SerialBT; void setup() { Serial.begin(115200); SerialBT.begin("ESP32_Scanner"); // 蓝牙设备名 Serial.println("Bluetooth Started!"); } void loop() { Serial.println("Scanning..."); BTScanResults *results = SerialBT.discover(10); // 扫描10秒 int count = results->getCount(); Serial.printf("Found %d devices\n", count); for (int i = 0; i < count; i++) { BTAdvertisedDevice device = results->getDevice(i); Serial.printf("Device: %s, RSSI: %d dBm\n", device.getAddress().toString().c_str(), device.getRSSI()); } delete results; delay(5000); }
4.3 系统集成:Wardriver.uk固件部署
当独立功能都测试通过后,就可以使用项目原作者Joseph Hewitt提供的集成化固件了。这能让你快速得到一个功能完整的系统。
- 准备环境:访问Wardriver.uk的GitHub仓库,下载
Portable Wardriver Rev3固件。根据仓库说明,你需要为Arduino IDE安装五个必要的库:TinyGPSPlus-ESP32、TinyGSM、SD、ESP32-BLE-Scanner(一个更高效的蓝牙扫描库)以及ArduinoJson。 - 烧录双固件:下载的固件包内通常包含两个
.ino文件:Sketch_A.ino和Sketch_B.ino。- 将
Sketch_A.ino上传至ESP32 A。这个固件负责GPS、Wi-Fi扫描、SD卡日志和系统主控。 - 将
Sketch_B.ino上传至ESP32 B。这个固件专职负责蓝牙扫描和GSM基站扫描,并通过串口将数据发送给ESP32 A。
- 将
- 运行与数据收集:将格式化为FAT32的MicroSD卡插入卡槽。通过USB或充电宝给系统供电。OLED屏幕会显示系统状态(如GPS卫星数、扫描到的Wi-Fi/蓝牙数量、SD卡状态)。系统会自动在SD卡根目录创建以日期命名的日志文件(如
WARDRIBE-20231015.CSV)。你可以带着设备移动,它会自动记录所有数据。
5. 数据解读、应用与高级调试技巧
系统运行起来后,产生的数据才是最终价值所在。理解这些数据并能解决运行中的问题,是项目从“搭建成功”到“实用可靠”的关键。
5.1 日志文件解析与可视化
打开SD卡上的CSV文件,你会看到类似以下格式的数据行:
2023-10-15T14:30:25Z, 48.8584, 2.2945, 12, -65, WPA2, AA:BB:CC:DD:EE:FF, MyHomeWiFi 2023-10-15T14:30:26Z, 48.8584, 2.2945, , -45, , 11:22:33:44:55:66, iPhone 2023-10-15T14:30:27Z, 48.8584, 2.2945, 310, 260, 0x09E1, 0x74E2, T-Mobile各列通常代表:时间戳、纬度、经度、信号强度(RSSI)、信道、加密方式、MAC地址、SSID/设备名/运营商名。
- Wi-Fi数据:可以导入到
WiGLE.net(一个著名的无线网络地图网站)或Google Earth中进行可视化,生成热力图或点状分布图,直观展示无线网络的密度和覆盖情况。 - 蓝牙数据:可用于分析蓝牙信标(Beacon)的分布,例如在商场、博物馆内的设备部署情况。请注意,仅收集公开广播的MAC地址,并确保你的行为符合当地法律法规和道德规范。
- 蜂窝数据:MCC、MNC、LAC、Cell ID这组数据,可以提交到
OpenCellID.org或Unwired Labs等定位服务API,反向查询出基站的大致经纬度。这在GPS失效时非常有用。
5.2 常见问题排查实录
在调试和运行过程中,你几乎一定会遇到下面这些问题。这里是我的排查记录:
问题1:GPS长时间显示“INVALID FIX”(无效定位)。
- 原因分析:GPS模块需要清晰的天空视野来接收卫星信号。室内、窗边、树下都可能无法定位。
- 排查步骤:
- 检查硬件:确认GPS模块的TX/RX与ESP32的RX/TX是否交叉连接(模块TX接MCU RX)。
- 检查软件:确认串口波特率设置为9600(ATGM336H默认)。
- 查看原始数据:在代码中,除了使用
TinyGPSPlus库解析,还可以直接打印原始的NMEA语句(如$GPGGA,$GPRMC)。如果能看到原始语句但解析失败,是库的问题;如果连原始语句都没有,是硬件或连接问题。 - 耐心等待:冷启动或长时间未使用后,首次定位可能需要5-10分钟。将设备置于户外静止不动。
问题2:GSM模块扫描不到任何网络,或初始化失败。
- 原因分析:这是最常见的问题。可能原因包括:地区2G网络退网、电源不足、AT指令不匹配。
- 排查步骤:
- 确认网络制式:
SIM800L是2G模块。首先确认你所在的区域是否还有2G网络覆盖。在北美、加拿大、澳大利亚等地,许多运营商已关闭2G网络。在中国,2G网络(GSM)目前仍然广泛存在。 - 检查电源:使用万用表测量SIM800L的VCC引脚电压,应在3.8V-4.2V之间。如果低于3.7V,模块可能无法启动。确保供电USB口能提供2A以上的峰值电流。
- 发送AT指令手动调试:最直接的排查方法。在代码中,初始化后直接通过
SerialAT.println("AT");发送AT指令,并读取回复。正常应返回“OK”。然后发送AT+CNETSCAN=1启动网络扫描,再发送AT+CNETSCAN?查询结果。通过手动指令可以精确判断是模块问题、电源问题还是代码逻辑问题。 - 检查天线:GSM天线接触不良或型号不匹配会导致信号极差。确保螺旋天线焊接牢固,并尝试在室外开阔地测试。
- 确认网络制式:
问题3:SD卡初始化失败或文件写入错误。
- 原因分析:SPI通信失败、卡格式不兼容、卡损坏或功耗问题。
- 排查步骤:
- 换一张卡试试:优先使用容量较小(如4GB、8GB、16GB)且品牌可靠的MicroSD卡,并格式化为FAT32格式(簇大小32KB)。
- 检查接线:确认SD卡模块的CS(片选)、MOSI、MISO、SCK引脚与代码中定义的一致。
- 降低写入频率:如果是在移动中频繁写入小文件,可以改为在内存中缓存一定数量的数据后,再一次性写入SD卡,减少文件系统的操作负担。
- 增加电源去耦:在SD卡模块的VCC和GND之间并联一个100μF的电解电容,可以平滑供电,避免因ESP32射频部分工作时产生的电流尖峰导致SD卡掉线。
问题4:两块ESP32之间串口通信混乱。
- 原因分析:在集成固件中,ESP32 B通过串口将蓝牙和GSM数据发送给ESP32 A。如果波特率、引脚定义不一致,会导致乱码或数据丢失。
- 排查步骤:
- 确认物理连接:根据PCB设计图,找到连接两块ESP32的串口引脚(例如,B的TX接A的RX)。
- 统一波特率:在
Sketch_A.ino和Sketch_B.ino中,用于板间通信的串口初始化波特率必须完全相同,通常为115200。 - 设计通信协议:简单的文本协议如“BLT,AA:BB:CC:DD:EE:FF,-65\n”和“GSM,310,260,0x09E1,0x74E2\n”,并在接收端(ESP32 A)根据前缀(BLT/GSM)来解析数据。确保每条数据以换行符
\n结束,便于使用serial.readStringUntil('\n')来读取。
5.3 性能优化与扩展思路
当系统稳定运行后,可以考虑以下优化和扩展:
- 扫描策略优化:Wi-Fi和蓝牙扫描会消耗大量电量。可以设置为移动时(通过GPS速度判断)高频扫描,静止时低频扫描或休眠。
- 数据过滤与脱敏:在存储前,可以对MAC地址进行哈希处理(如只保留前24位OUI或整体加盐哈希),以保护隐私。同时过滤掉信号强度极弱(如RSSI > -90dBm)的无用数据。
- 增加惯性导航单元:在隧道、地下停车场等GPS完全失效的区域,可以增加一个MPU6050(六轴陀螺仪加速度计)或更高级的IMU,通过惯性导航算法进行短时航位推算,弥补定位空白。
- 无线数据传输:除了本地SD卡存储,可以增加一个4G Cat.1或NB-IoT模块,将加密后的数据包定时上传到云端服务器,实现远程实时监控。
- 外壳与电源管理:为整个系统设计一个3D打印的外壳,将天线、屏幕外露。内部集成一块大容量锂电池和充电管理电路,实现真正的长时间户外独立工作。
这个项目的魅力在于,它不仅仅是一个按照教程组装套件的练习。从硬件调试的波折,到软件集成的挑战,再到数据应用的思考,每一个环节都充满了嵌入式开发、射频通信和位置服务技术的实践要点。当你亲手组装的小设备开始源源不断地记录下数字世界的“地理印记”时,那种连接虚拟信号与物理世界的成就感,正是硬件创客乐趣的核心。
