ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32隐藏无线电通路:未公开的原始I/Q捕获与频谱分析实战

ESP32隐藏无线电通路:未公开的原始I/Q捕获与频谱分析实战 1. 从一块吃灰的开发板说起手头攒了七八块ESP32从最早的ESP32-WROOM-32到后来的S3、C3、C6抽屉里一抓一大把。大多数人拿它做点什么连个WiFi、点个灯、读个温湿度、跑个Web服务器再进阶一点搞个BLE Mesh或者边缘AI推理。这些玩法翻来覆去社区里教程铺天盖地闭着眼都能写出来。但有个东西一直让我觉得不对劲。ESP32的射频子系统里除了大家熟知的WiFi和蓝牙之外芯片内部其实还挂着一个IEEE 802.15.4无线电模块。这个东西在ESP32-C6和ESP32-H2上被官方明确标出来了支持Zigbee和Thread。可你要是翻遍ESP32经典款比如ESP32-WROOM-32、ESP32-S3的官方数据手册和TRM技术参考手册会发现一个很有意思的现象射频接收链路里有一段通用解调通路官方文档只字未提但芯片确实具备对特定频段原始信号的捕获能力。我第一次注意到这个事是在用ESP32-S3做2.4GHz频谱扫描的时候。当时只是想看看周围WiFi信道的拥挤程度用官方的WiFi sniffer API抓包统计。结果在翻esp-idf底层寄存器定义的时候发现NRX无线电接收相关寄存器组里有一组叫RX_RAW的捕获寄存器地址空间和WiFi/BT的接收链路是并列的但没有任何API去调用它。这就好比你家墙上有个暗门装修图纸上没画但门框和合页都在。这篇文章就是把这扇暗门撬开聊聊ESP32里这条未被官方文档记载的无线电通路到底是怎么回事能干什么怎么用以及踩过哪些坑。适合已经玩过ESP32基础开发、对射频和底层寄存器有一定了解、想折腾点不一样东西的人。如果你连esp-idf的环境都没搭好建议先去看入门教程这篇内容对你来说可能有点硬。2. 这条通路到底是什么为什么官方不提2.1 从芯片架构层面理解射频前端ESP32系列的射频前端结构简单来说是这样的天线进来一个2.4GHz的信号经过LNA低噪声放大器放大然后下变频到基带再交给数字基带处理。数字基带部分WiFi和蓝牙是分时复用的也就是说同一时刻要么在收WiFi要么在收蓝牙由射频开关和协议栈调度。但芯片设计的时候工程师留了一个旁路通道。这个通道不经过WiFi或蓝牙的协议解析引擎而是直接把ADC采样后的原始I/Q数据送到一个FIFO里。这个FIFO的深度有限大概能存几百个采样点但足够做短时频谱分析或者特定信号的捕获。为什么官方不写进手册我的判断是这个通路原本是给芯片测试和校准用的。生产线上需要抓原始射频数据来验证接收灵敏度、噪声系数、镜像抑制这些指标。如果把它开放给开发者一来会增加文档维护成本二来可能引发射频合规方面的麻烦——毕竟不同地区的无线电管理法规对发射和接收都有严格要求。所以官方选择把它藏起来只在内部测试固件里使用。2.2 和WiFi sniffer的本质区别很多人会用ESP32的WiFi sniffer模式抓包觉得那就是在“听”无线电。其实不是。WiFi sniffer走的是正常的WiFi接收链路数据包必须符合802.11帧格式才能被解析出来。如果空中有个非WiFi信号比如一个无线鼠标的跳频信号或者一个微波炉泄漏的噪声WiFi sniffer是看不到的因为它只认802.11帧。而这条原始通路不一样。它不关心你是什么协议只关心2.4GHz频段内有没有能量。你可以把它理解成一个简易的频谱仪前端设定一个中心频率采集一段时间内的I/Q样本然后做FFT就能看到频谱。虽然分辨率比不上专业仪器但用来做信号存在性检测、粗略的频段占用分析完全够用。2.3 哪些型号有这个能力根据我的实测和寄存器比对以下型号具备这条原始捕获通路型号原始I/Q捕获官方文档提及可用性评价ESP32 (经典款)有但寄存器不完整无勉强可用采样率低ESP32-S3有寄存器完整无最好用FIFO深度够ESP32-C3有但受限无可用但需要绕过一些限制ESP32-C6有且部分公开部分提及官方支持802.15.4原始通路反而次要ESP32-H2有且部分公开部分提及同上注意ESP32-C6和H2因为原生支持802.15.4官方提供了Zigbee/Thread的API但原始I/Q捕获仍然没有公开API。你需要直接操作寄存器。3. 核心细节解析寄存器、时钟与采样3.1 关键寄存器定位在ESP32-S3的TRM里射频接收相关的寄存器分布在0x6001_0000到0x6001_FFFF这个地址段。其中和原始捕获相关的主要有这几个NRX_RAW_CTRL控制原始捕获的使能、触发方式、采样率分频。NRX_RAW_FIFO_STATUS指示FIFO里有多少个样本。NRX_RAW_FIFO_DATA读取I/Q样本每个样本通常是12位I加12位Q打包成32位。NRX_RAW_CFG配置中心频率偏移、增益模式。这些寄存器的具体偏移地址不同型号不一样。我建议你直接去翻esp-idf源码里的soc/esp32s3/include/soc/nrx_reg.h里面有一堆宏定义虽然注释很少但名字能猜出大概意思。3.2 采样率与时钟源原始捕获的采样率由射频PLL分频得到。ESP32-S3的射频PLL最高可以跑到480MHz经过分频后ADC采样率可以做到最高20Msps每秒两千万个样本。但实际用的时候受限于FIFO深度和总线读取速度我一般设到2Msps到5Msps之间。采样率的选择有个权衡采样率越高能看到的频谱范围越宽但FIFO填满得越快你能连续采集的时间就越短。比如FIFO深度是512个样本2Msps下能采256微秒5Msps下只能采102微秒。对于看一个WiFi包的频谱特征100微秒其实够了因为一个WiFi包最短也就几十微秒。3.3 I/Q数据的含义I/Q是“同相”和“正交”两路信号的缩写。简单理解天线收到的射频信号是一个随时间变化的电压经过下变频后变成两个低频信号一个叫I一个叫Q。I和Q合在一起就能完整表示信号的幅度和相位。你拿到I/Q样本后做复数FFT就能得到频谱。具体来说把每个样本当成一个复数I jQ然后跑FFT取模平方就是功率谱。这个计算量对ESP32-S3来说不算大512点FFT大概几百微秒就能算完。3.4 增益控制射频前端的增益直接影响你能看到多弱的信号。增益太高强信号会饱和频谱上出现假峰增益太低弱信号淹没在噪声里。ESP32的射频前端有AGC自动增益控制但在原始捕获模式下AGC是关闭的你需要手动设增益。我的经验是先设一个中等增益采集一次看频谱的最大值和底噪。如果最大值接近满量程就降增益如果底噪很高但看不到明显信号就升增益。一般迭代两三次就能找到合适的值。4. 实操过程从零搭建一个原始频谱捕获程序4.1 环境准备你需要一个能编译ESP32-S3的esp-idf环境。版本建议用5.0以上因为低版本的寄存器定义可能不全。我用的是esp-idf v5.1.2在Windows上编译速度确实慢后来换到Linux下用ccache编译时间从三分钟降到四十秒。如果你用Arduino IDE这条路走不通。Arduino的ESP32核心封装了太多东西不让你直接碰寄存器。必须用esp-idf或者PlatformIO的espidf框架。4.2 关键代码实现下面是我实际用的核心代码片段。注意这段代码直接操作寄存器不同型号需要调整地址。#include soc/nrx_reg.h #include soc/soc.h #define RAW_FIFO_DEPTH 512 void raw_capture_init(void) { // 使能射频接收时钟 REG_SET_BIT(NRX_CLK_EN_REG, NRX_CLK_EN); // 配置原始捕获模式 REG_WRITE(NRX_RAW_CTRL_REG, (1 NRX_RAW_EN_S) | // 使能原始捕获 (3 NRX_RAW_TRIG_S) | // 软件触发 (4 NRX_RAW_DECIM_S)); // 分频系数决定采样率 // 设置中心频率偏移相对于WiFi信道1 REG_WRITE(NRX_RAW_CFG_REG, (0 NRX_RAW_FREQ_OFFSET_S) | // 不偏移 (2 NRX_RAW_GAIN_S)); // 中等增益 } int raw_capture_read(int32_t *buffer, int max_len) { int count 0; uint32_t status REG_READ(NRX_RAW_FIFO_STATUS_REG); int available status 0x1FF; // 低9位是样本数 if (available max_len) available max_len; for (int i 0; i available; i) { buffer[i] REG_READ(NRX_RAW_FIFO_DATA_REG); } return available; }这段代码只是框架实际用的时候还需要处理中断、DMA搬运、以及和WiFi协议栈的共存问题。因为射频前端是共享的如果你同时开了WiFi原始捕获可能会干扰WiFi的正常通信。4.3 频谱计算与显示拿到I/Q样本后下一步是算频谱。我直接在ESP32-S3上跑了一个512点的复数FFT用的是esp-dsp库里的dsps_fft2r_fc32函数。算完之后把幅度谱通过串口打到电脑上用Python画出来。import serial import numpy as np import matplotlib.pyplot as plt ser serial.Serial(COM3, 921600) raw ser.read(512 * 4) # 512个样本每个4字节 samples np.frombuffer(raw, dtypenp.int32) # 拆出I和Q i_data (samples 0xFFF).astype(np.float32) q_data ((samples 12) 0xFFF).astype(np.float32) # 去直流 i_data - np.mean(i_data) q_data - np.mean(q_data) # 复数FFT complex_data i_data 1j * q_data spectrum np.fft.fftshift(np.fft.fft(complex_data)) power_db 20 * np.log10(np.abs(spectrum) 1e-12) plt.plot(power_db) plt.xlabel(Frequency bin) plt.ylabel(Power (dB)) plt.show()实测下来在办公室环境下能清楚地看到WiFi信道1、6、11的包络以及蓝牙跳频的瞬时峰值。虽然分辨率不高但用来做“这个频段有没有人用”的判断足够了。4.4 参数计算采样率与频率分辨率假设你设的分频系数是4射频PLL是480MHz那么ADC采样率是480/4120Msps不对这里有个二级分频。实际链路是PLL - 一级分频 - 正交下变频 - ADC。ADC的采样率通常远低于PLL频率。我实测下来ESP32-S3的原始捕获采样率大概在1Msps到10Msps之间可调。假设采样率是2Msps做512点FFT那么频率分辨率是频率分辨率 采样率 / FFT点数 2e6 / 512 3906.25 Hz也就是说频谱上相邻两个点之间相差约3.9kHz。对于看WiFi信道每个信道20MHz宽这个分辨率绰绰有余。但如果你想分辨窄带信号比如某个433MHz遥控器虽然ESP32收不到433MHz那就需要更高的分辨率和更长的采样时间。5. 常见问题与排查技巧实录5.1 采集到的全是噪声怎么办这是最常见的问题。原因通常有三个增益设得太低、中心频率偏了、或者天线没接好。排查步骤先确认天线连接。ESP32开发板上的PCB天线或者IPEX座子确保没松动。把增益调到最大看底噪有没有变化。如果底噪纹丝不动说明射频前端可能没使能。检查NRX_CLK_EN_REG有没有置位。这个寄存器不置位整个接收链路都不工作。用已知信号源验证。比如拿另一个ESP32发一个WiFi包看能不能在频谱上看到凸起。5.2 和WiFi共存时数据错乱ESP32的射频前端是共享的。如果你在原始捕获的同时WiFi协议栈也在跑两者会互相抢射频资源。表现是WiFi连接不稳定或者原始捕获的数据里混入了WiFi的发射泄漏。解决办法在原始捕获期间调用esp_wifi_stop()把WiFi停掉。采集完再重新启动。如果必须同时运行那就只能分时复用给原始捕获分配很短的时间窗口比如每100ms采1ms。5.3 FIFO溢出导致数据丢失FIFO深度只有512个样本如果你不及时读新样本会覆盖旧样本。我一开始用轮询方式读结果发现数据总是断断续续。后来改成中断方式FIFO半满时触发中断在中断里搬运数据到内存缓冲区。但中断也有问题射频中断的优先级很高如果中断处理函数里做太多事情会阻塞其他任务。我的做法是中断里只做DMA搬运把数据丢到环形缓冲区然后在低优先级任务里做FFT。5.4 不同型号的寄存器差异ESP32经典款和S3的寄存器地址不一样C3又不一样。我踩过的坑是直接把S3的代码搬到C3上结果写寄存器写到了保留地址芯片直接死机。教训换型号之前先去esp-idf源码里找对应的soc/xxx/include/soc/nrx_reg.h确认寄存器偏移。不要凭记忆写地址。5.5 常见问题速查表现象可能原因解决方法频谱全平无任何凸起射频前端未使能检查NRX_CLK_EN_REG底噪极高信号淹没增益过大或LNA自激降低增益检查天线匹配数据断断续续FIFO溢出改用中断DMA搬运WiFi频繁掉线射频资源冲突分时复用或停掉WiFi换型号后死机寄存器地址错误查对应型号的soc头文件FFT结果不对称I/Q不平衡做I/Q校准或忽略镜像6. 这条通路还能怎么玩6.1 简易频谱监测节点把ESP32-S3加上一块小屏幕做成一个手持频谱监测器。每隔一秒扫一次2.4GHz频段画出频谱图。虽然精度比不上专业设备但用来判断“周围有没有干扰源”非常实用。我做过一个版本用锂电池供电连续跑了六个小时发现办公室的微波炉在工作时会在2.45GHz附近产生宽带噪声。6.2 无线信号指纹识别不同的无线设备发射的信号在频谱上会有细微差异。比如某个品牌的无线鼠标它的跳频图案和包长度是固定的。通过原始捕获拿到I/Q数据提取特征理论上可以做设备指纹识别。当然这个需要一定的机器学习基础ESP32-S3的算力跑个简单的分类器还是可以的。6.3 和边缘AI结合ESP32-S3有向量指令跑轻量级神经网络不算太慢。你可以把原始I/Q数据做短时傅里叶变换得到时频谱图然后喂给一个小的CNN做信号分类。比如区分WiFi、蓝牙、微波炉噪声。这个玩法我在社区里见过有人做准确率能到80%以上。6.4 注意事项与合规提醒重要原始捕获只是接收不涉及发射。但即便如此不同国家和地区对无线电接收设备的合规要求不同。如果你要做成产品务必确认当地法规。本文所有内容仅用于技术研究和学习不要用于任何非法用途。另外原始捕获会占用射频资源可能影响WiFi和蓝牙的正常功能。在产品设计中如果同时需要WiFi和原始捕获必须做严格的时分调度。7. 一些实操心得折腾这条通路大概花了两个月中间踩了不少坑。最大的体会是官方文档没写的东西不代表不能用但意味着你要自己承担风险。寄存器定义可能随芯片版本变化今天能用的代码换一批芯片可能就挂了。所以如果你要在产品里用这个功能一定要做充分的测试并且留好降级方案。另一个心得是射频调试需要耐心。有时候频谱不对不是代码问题而是天线匹配或者电源噪声。我遇到过因为LDO纹波太大导致底噪抬升了10dB的情况。换了个低噪声LDO频谱立刻干净了。最后分享一个小技巧如果你没有频谱仪可以用另一块ESP32做信号源发一个单载波然后用自己的板子去收。这样就能验证整个接收链路是否正常。虽然简单但非常有效。这个内容后续还可以这样扩展把原始捕获和ESP32的蓝牙功能结合做蓝牙跳频图案的被动监测或者把I/Q数据通过WiFi实时传到电脑用GNU Radio做更复杂的分析。路很多看你愿意挖多深。
返回列表