ESP32 CSI 教程

104 阅读15分钟

WiFi 也能测心跳?不用传感器!ESP32 CSI 教程

GitHub

Author: Ming


看到这个标题,我就知道你是满脸的不相信,觉得这又是那种听起来很唬人的“学术噱头”?说实话,我第一次听说WiFi能感知环境甚至测量心跳时,反应和你一模一样:这太玄幻了,怎么可能呢?

但当我真正深入这个领域,才发现 WiFi 的潜力远不止“上网”这么简单。有些研究甚至让我觉得“离谱”:比如,当你身处一个 WiFi 信号覆盖下,就能判断你是否在睡觉、测量你的呼吸频率;能“数”出一个房间里有几个人;能识别你是在走路、跳跃,还是静止不动;甚至——你或许更想不到——在你使用手机时,还能推测出你正在运行什么APP。

这一切听起来像是“玄学”,但背后其实是一项真实存在且正在快速发展的技术——WiFi 信道状态信息感知,也就是我们常说的 CSI(Channel State Information)

你可能会觉得即使这个技术真实存在,也离我们日常生活很远,其实它没有你想象的那么复杂。要实现它,你并不需要昂贵的设备或高深的专业背景。只需要一个普通的路由器,和一块价格不到 20 元的 ESP32 物联网开发板。没错,就是这两样随处可见的东西,就能让你亲手触摸到这项“黑科技”。

它的原理也非常简单,首先,我们需要改变对Wi-Fi信号的看法。我们通常认为Wi-Fi信号只是一个用来上网的“数据管道”,但实质上,它是在我们空间中穿梭的无线电波。它从路由器发出,并不是直线飞到你的手机上,而是在空间中经历反射、衍射、穿透物体……最终以多条路径、不同时间抵达接收端。正是这些“曲折”的旅程,让 WiFi 信号携带上了丰富的环境信息。

c1.png

而 CSI,就是记录这些路径特征的“身份卡”。它精确描述了每一条路径上信号的振幅和相位——振幅告诉我们信号在这条路上衰减了多少,相位则记录了信号延迟了多久。换句话说,CSI 数据就像是一张“环境指纹”,我们只要学会解读它,就能反推出空间中发生了什么。

在实际应用中,我们接收到的 CSI 数据,是一串随时间变化的数值序列。比如下面这段,就是某一时刻t捕捉到的 CSI 样本:(在一秒钟之内,你可以接收到上百条这样的数据)

CSI[t] = [
   -20, 15, -20, 15, -50, 15,  16,  0, -86, 15, 38,  0,
  -120, 15,  37,  0, 119, 15,  24,  0, 125, 15, -2, 15,
  -107, 15, -19, 15, -73, 15, -14, 15, -44, 15, 14,  0,
   -34, 15,  49,  0, -43, 15,  80,  0, -62, 15, 98,  0,
   -84, 15,  91,  0, -81, 15,  58,  0, -55, 15, 51,  0,
   -26, 15,  52,  0,  -7, 15,  72,  0,  -2, 15, 88,  0,
    -9, 15, 101,  0, -22, 15, 100,  0, -19, 15, 87,  0,
   -14, 15,  73,  0,   2,  0,  62,  0,  18,  0, 61,  0,
    41,  0,  69,  0, 23, 0, 32, 0, -32, 0
]

这些数字并非乱码,它们代表了不同子载波上的振幅和相位信息。每两个数字为一组,共同描绘出信号在某个子载波上的状态。


举个CSI检测心跳的例子:

我们要知道,微小的移动,足以改变无线电波的传播路径。

人体本身就是一个“干扰体”。当 WiFi 信号穿过或被身体反射时,我们的胸腔随着心跳产生的微小起伏——哪怕只有几毫米——都会改变某条传播路径的长度。这一点点变化,反映在 CSI 数据中,就表现为相位的周期性波动。

这个过程,有点像你对墙拍球:如果墙悄悄移动了一点点,球弹回来的时间也会发生微小而有规律的变化。在这里,你的胸腔就是那面“移动的墙”,而 WiFi 信号就是那个“球”。

当然,环境中存在大量干扰:别人的走动、空气流动、设备噪声……这些都会混入 CSI 数据中,形成背景“噪音”。但心跳的频率是有特征的,成年人静息状态下通常介于 0.8~2.0 Hz 之间。通过傅里叶变换等信号处理技术,我们可以将 CSI 数据从时域转换到频域,从而在纷杂的噪声中“揪”出那个代表心跳的频率尖峰。

整个过程可以概括为: WiFi 信号发射 → 在空间中经历多路径传播(其中一路被人体的胸腔反射) → 心跳引起胸腔周期性微动 → 改变反射路径长度 → 导致 CSI 相位周期性变化 → ESP32 捕获原始 CSI 数据 → 通过算法滤除噪声、提取相位规律 → 最终转换为心跳频率。

当然,如果环境复杂、目标行为细微,传统信号处理方法可能就力不从心了。这时,我们可以借助人工智能——比如 LSTM、GRU 或 Transformer 这类时序神经网络,对 CSI 数据进行有监督学习,让模型自己学会从混沌中识别出我们关心的“信号”。


你肯定有一个疑问,就是非得用ESP32作为接收端吗,手机电脑连上网络后不也可以作为接收端接收CSI信息吗?你想得没错,从原理上讲,任何能连接WiFi的设备(手机、电脑、网卡)都在接收CSI数据,因为这是OFDM系统(WiFi的技术基础)进行正常通信所必需的信息。

但是,“能接收”和“能获取”是两回事。这正是ESP32在此领域无可替代的原因

我们的日常设备,如手机和笔记本电脑,它们的WiFi芯片被操作系统“保护”起来了。制造商为了确保网络的稳定和安全,隐藏了像CSI这样的底层原始数据,只向我们开放“能否上网”的结果,而不暴露信号传播的“过程”。

而ESP32则不同,它是一款为开发者设计的开源硬件。我们可以通过编程直接与WiFi芯片“对话”,命令它把每一帧数据包附带的CSI信息都完整地吐出来。这种“上帝视角”的权限,是我们在手机和电脑上难以获得的。

正是这种开放性,加上它极低的成本(不到20元)和活跃的开发者社区,使得ESP32成为了进入WiFi感知领域最理想、也几乎是唯一面向大众的“入场券”。


既然它这么厉害,为什么我们在日常生活中没怎么见到这种技术的应用呢?这可算是问到点子上了,因为它有几个暂时还绕不过去的坎。

CSI数据对环境干扰极为敏感,特别是对微小运动感知至关重要的相位信息,非常容易受到环境干扰。日常环境中的无线电频率干扰,包括同频段的Wi-Fi网络、蓝牙设备及其他无线信号源,都会引入显著的噪声。更复杂的是,室内环境的任何细微变化——如家具移动、门窗开闭甚至空气流动导致的热胀冷缩——都会改变既有的信道特性,造成信号基线的漂移。即使是AI分析,也很难训练。

更棘手的是,这项技术还处在“定制化”阶段,远未达到“通用化”的程度。今天在你家书房里训练好的、能精准识别你坐下动作的模型,明天把它放到我家的客厅里,可能就完全失灵了。因为它已经熟悉了你书房里那面书墙、那张木桌带来的独特信号反射环境。换个空间,所有的“参照物”都变了,它就得从头学起。这种“水土不服”的特性,让它很难像传统传感器那样,出厂设定好就能在任何地方即插即用。

再者,虽然ESP32硬件本身很便宜,但要让整个系统真正可靠地工作,背后的“隐性成本”相当高。你需要专门来规划和布设设备位置、调试AI算法、处理海量数据。而这,显然不如直接安装一个几十块钱的传感器来得简单直接,而且精度还更高。


在了解了它的这些局限性之后,如果你内心的好奇之火并未熄灭,渴望亲手尝试这个技术,那么,接下来就是教程环节。下面会教学如何利用ESP32设备,让这些无形的CSI信号数据能够通过MQTT协议,像源源不断的溪流一样,实时传输到你的电脑上并保存,为后续的数据处理和分析做好准备。注意,本篇教程并不涉及后续的数据处理和分析环节,这就要你自己去探索了。(这需要你有一些数据处理和人工智能基础)

首先就是设备的准备,一个负责持续发射Wi-Fi信号的信源,和一个用于捕捉信道状态信息的接收端。在日常生活中,最便捷的方案就是使用手机开启热点作为发射端;当然,你也可以使用路由器。接收端则采用ESP32开发板。

关于ESP32的选型,乐鑫官方对不同芯片的CSI性能有一个推荐顺序,大致为:ESP32-C5 > ESP32-C6 > ESP32-C3 ≈ ESP32-S3 > ESP32。不过请注意,对于初次实验或基础应用而言,即便是最经典的ESP32型号也完全足够,你无需过分追求最新型号。

c2.png

在实际的实验操作中,我强烈推荐大家采用手机热点的方案。这样做有一个明显的好处:你可以自由且灵活地摆放信号发射源的位置。同时,请务必留意热点的AP频段设置——部分较早的ESP32型号可能不支持连接5GHz网络,因此建议在手机热点设置中将其调整为2.4GHz频段,以确保兼容性。

下图便是一个简单的实物连接演示,你可以用类似的方式布置你的实验环境:

c3.jpg

为了让你对整体数据流有一个清晰的认识,我们先用下面这张架构图来俯瞰整个系统:

整个工作流程非常直观:你的手机开启一个Wi-Fi热点,ESP32开发板和你的电脑都作为客户端连接到这个同一网络。ESP32负责捕捉CSI数据,而你的电脑则作为一个MQTT服务器(或称Broker)运行。一旦系统开始工作,ESP32便会将采集到的数据持续发送至电脑上的这个服务器并进行保存。

c4.jpg

你可能会问,为什么选择MQTT,而不是更常见的串口通信或者HTTP协议呢?这背后有非常实际的考量。

首先,串口通信虽然简单,但它有一条物理“锁链”——ESP32必须通过USB数据线始终与电脑相连。这不仅限制了设备的布置自由,更与未来我们希望的、能够独立部署进行实时数据分析的应用场景相去甚远。

其次,对于HTTP协议而言,它虽然无处不在,但其设计并不适合高频、连续的数据流。每次传输都伴随着冗长的请求头与响应头,就像每次寄送一个小零件,却都要用一个巨大的、填充了泡沫塑料的箱子来包装,显得十分臃肿且效率低下。

MQTT则完美地规避了这些问题。它生来就是为了连接物理世界的传感器与网络,是一种轻量级、低功耗的“机器对话”协议。它专为持续不断的数据流设计,协议开销极小,传输效率高。


一切硬件就绪后,接下来就是给这个ESP32写程序了,这里必须使用乐鑫官方推出的ESP-IDF框架。你可能会问,为什么不能用更简单的Arduino或MicroPython呢?原因在于,ESP-IDF是乐鑫官方的物联网开发框架,提供了最底层的WiFi驱动和CSI数据接口,而其他的框架并没有提供这么底层的接口。

如何在你的电脑上安装ESP-IDF呢,很简单,现在安装ESP-IDF已经变得非常简单。无论你使用的是Windows、macOS还是Linux系统,我都强烈推荐通过在VSCode中安装ESP-IDF插件来完成环境搭建。对,ESP-IDF已经可以作为一个VScode的插件在VSCode中使用了。这个插件将整个工具链、编译器和烧录工具都集成在了一起,大大简化了配置过程。

你可以参考官方安装指南。如果你觉得官方文档不够直观,也可以在网络上搜索“VSCode ESP-IDF 安装教程”,有许多社区开发者制作的优质图文或视频教程,能帮你绕过不少坑。

程序很死板,就是接受CSI数据并通过MQTT转发。,但是要如何把程序烧录进ESP32

安装完成后,就新建一个空项目,按照如下的步骤来

c5.jpg

c6.jpg

c7.jpg

项目创建成功后,我们先来熟悉一下标准的ESP-IDF项目结构,它通常会包含以下关键部分:

  • main 目录:这是核心,我们的应用程序代码主要在这里。
  • CMakeLists.txt 文件:告诉构建系统如何编译你的项目。
  • 其他组件(component)目录:用于存放项目依赖的库。

c8.jpg

接下来,你需要从我提供的Gitee仓库将项目文件下载到本地。请将仓库中的project documents里面的文件直接复制到你刚刚创建的项目文件夹中,覆盖原有的文件即可。

现在,打开 main 目录下的 main.c 源文件,找到第49行到63行附近的WiFi和MQTT配置参数部分,这是整个程序的“控制中枢”,你需要根据你的实际情况进行修改:

// WiFi配置参数
#define WIFI_SSID "你的WIFI网络名"     // 请填写你的手机热点或路由器名称
#define WIFI_PASS "WIFI密码"          // 对应的WiFi密码
#define WIFI_MAX_RETRY 5              // 连接失败的重试次数

// MQTT配置参数
#define MQTT_BROKER_URL "mqtt://192.168.80.102:1883"  // 你的电脑(MQTT服务器)的IP地址
#define MQTT_USERNAME "esp_ikd4s"     // 自定义的设备用户名
#define MQTT_PASSWORD "1234567890"    // MQTT连接密码(若服务器无需密码可忽略)
#define MQTT_CLIENT_ID "ecc1-esp1"    // 此设备在MQTT网络中的唯一ID
#define MQTT_TOPIC "electric/vi"      // 用于发送CSI数据的主题名称

特别注意MQTT_BROKER_URL 中的IP地址需要修改为你运行MQTT服务器的电脑的IP地址,确保ESP32和电脑在同一网络中能够相互通信。

配置完成后,使用VSCode底部状态栏的按钮(或终端命令)进行编译和烧录。如果一切顺利,打开串口监视器,你将看到ESP32尝试连接WiFi并最终成功连接到MQTT服务器的日志。当看到CSI数据开始源源不断地发送时,那么恭喜你,最困难的部分已经完成了!

c9.jpg

现在,CSI数据已经从ESP32发出,我们需要在电脑上搭建一个接收平台。

对于熟悉MQTT的开发者,你可以轻松地搭建一个Mosquitto或其他MQTT服务器,并订阅 electric/vi 主题来接收数据。

如果你不熟悉MQTT但会使用JavaScript/Node.js,我在Gitee仓库中已经为你准备了一个简单易用的MQTT服务器套件,就是那个名为 MQTT_Server.7z 的文件。解压后,你会看到几个JS文件,其中 mqttServer.mjsapp.mjs 是核心。

在运行之前,有两个地方你可能需要自定义:

// 在mqttServer.mjs第45行可以更改MQTT服务端口
const server = createServer(broker.handle);
const port = 1883;  //设置MQTT服务端口
server.listen(port, () => {
  const msg = `MQTT Broker Start, listening port ${port}`;
  logToFile(0,msg);
});
  // 在app.mjs第61行~69行对接收到的数据进行后续的处理
  client.on("message", (topic, message) => {
    let msg = message.toString();
    if (msg) {
      let datas = JSON.parse(msg)
      if (datas) {
        // 这里的datas就是接收到的CSI数据,可以在这里继续后续处理
      }
    }
  });

配置好后,在终端运行 node app.mjs 启动服务。最后提醒一点:请暂时关闭电脑的防火墙,或者为对应的端口添加入站规则,否则可能无法正常接收到数据。

以我使用的ESP32-C5为例,在成功运行后,每秒大约可以接收到120条CSI数据,每条数据都包含一个长度为106的CSI复数数组,这为后续的心跳、呼吸等生理信号分析提供了丰富的数据基础。

最后,如果你感兴趣并且想深入了解这项技术的前沿动态,我推荐你关注GitHub上的 Awesome-WiFi-CSI-Sensing 仓库。这个项目收集了大量关于利用WiFi CSI进行感知的学术论文和有趣项目,从基础的人体检测到精细的手势识别、呼吸心跳监测,甚至情感识别,你会发现这个领域有非常多“神奇”的项目。