
1. 从一块吃灰的ESP32说起为什么我要折腾它的射频通路手里攒了七八块ESP32开发板从最早的ESP32-WROOM-32到后来的S3、C3、C6大部分时间它们都在干些正经活——连WiFi、读传感器、驱动屏幕、跑个小网页。直到有一次我在调一个2.4G频段的无线遥控项目用逻辑分析仪抓SPI总线数据时突然意识到一个问题这颗芯片内部明明有一套完整的射频收发链路能覆盖2400MHz到2483.5MHz的ISM频段可我们平时用它的时候几乎只把它当成一个WiFi/蓝牙模块来使唤。官方手册里关于射频部分的描述基本停留在支持802.11 b/g/n和支持BLE 4.2/5.0这个层面。但如果你翻过ESP32的技术参考手册会发现里面藏着一个叫I2S的外设以及一个叫RMTRemote Control Transceiver的模块这两个东西配合起来能干一些官方文档里压根没提过的事——比如直接对射频前端做基带层面的采样和调制。我说的不是破解什么协议也不是搞什么违规通信。我说的是用ESP32的射频硬件做软件定义无线电SDR的入门实验。这个方向在开源社区里已经有人玩了好几年只是官方从来没把它写进任何一份正式文档里。你搜ESP32 SDR能翻出一堆GitHub仓库和论坛帖子但真正能把原理讲清楚、把坑填明白的中文资料少得可怜。这篇文章就是把我自己从零摸索的过程完整记录下来。从为什么选ESP32而不是专用SDR开发板到怎么绕过官方API直接操作射频寄存器再到实际抓取和解码一个简单的OOK调制信号每一步我都会把为什么这么做讲透。适合有ESP32基础、想往射频方向伸一脚的嵌入式玩家也适合做过Arduino项目、想理解无线通信底层逻辑的爱好者。不需要你有射频工程背景但至少得会用ESP-IDF或者Arduino框架烧个程序。2. ESP32的射频硬件到底藏了什么从WiFi外设到可编程基带2.1 射频前端的物理结构不只是WiFi和蓝牙ESP32系列芯片的射频部分本质上是一个零中频Zero-IF收发机架构。天线进来的信号经过巴伦和匹配网络后直接下变频到基带然后由ADC采样成数字信号交给基带处理器。发射方向反过来DAC把数字基带信号转成模拟上变频到2.4G频段发射出去。这个架构的关键在于基带处理器的输入输出是可编程的。官方固件里基带处理器被配置成专门处理WiFi的OFDM信号和蓝牙的GFSK信号。但硬件本身并不限制你只能跑这两种协议。如果你能绕过上层协议栈直接往基带寄存器里写数据理论上可以生成任意波形的2.4G信号。当然理论上可以和实际能跑通之间隔着巨大的鸿沟。ESP32的射频寄存器大部分是 undocumented 的乐鑫官方只公开了WiFi和蓝牙相关的配置接口。但社区里有人通过逆向工程和实验摸出了一些可用的寄存器地址和配置方法。这些信息散落在GitHub的各个角落我花了大概两周时间才把它们拼凑成一个能跑的方案。2.2 I2S和RMT为什么能碰射频外设复用的隐藏路径ESP32的I2S外设本来是用来接音频编解码器的但它有一个特性支持高速率的并行数据输出。在ESP32上I2S可以配置成最高80MHz的位时钟这意味着它能以很高的采样率往外吐数据。而RMT模块本来是给红外遥控用的能精确控制脉冲的宽度和时序。把这两个东西结合起来就能构造一个简易的任意波形发生器I2S负责产生高速数据流RMT负责精确的时序控制两者配合可以把基带信号喂给射频前端。这个思路最早是社区里一个叫ESP32-SDR的项目提出来的后来被多个开发者验证和改进。注意这条路径需要直接操作射频前端的使能寄存器和基带配置寄存器不同型号的ESP32比如ESP32、S3、C3寄存器地址不一样不能直接照搬代码。2.3 和专用SDR开发板的差距为什么还要用ESP32你可能会问既然要玩SDR为什么不直接买一块AD9361或者RTL-SDR答案很简单成本和门槛。一块AD-FMCOMMS5-EBZ开发板要好几千块RTL-SDR虽然便宜但只能收不能发而且需要接电脑才能用。ESP32开发板几十块钱就能买到自带MCU、WiFi、蓝牙能独立运行还能直接做信号处理。当然ESP32的射频性能跟专用SDR芯片没法比。它的ADC采样率有限带宽窄噪声系数也一般。但对于学习SDR的基本概念——比如采样、调制、解调、频谱分析——完全够用。而且因为资源受限你被迫去理解每一个环节的原理反而比直接用高级工具学得更扎实。对比项ESP32RTL-SDRAD9361方案成本30-80元100-200元3000元以上收发能力收发一体仅接收收发一体最大带宽约20MHzWiFi模式2.4MHz56MHz独立运行支持需接电脑需接FPGA开发难度高寄存器级低有现成API极高适合场景学习原理、简单实验收听广播、解码专业通信研究3. 动手前的硬核准备工具链、固件和寄存器手册3.1 开发环境的选择ESP-IDF还是Arduino我一开始用的是Arduino框架因为上手快。但很快发现Arduino的抽象层太厚了很多底层寄存器根本访问不到。比如你想直接写射频前端的使能寄存器Arduino的WiFi库会把相关的外设全部初始化成WiFi模式你再去改就会冲突。后来换到ESP-IDF情况好很多。ESP-IDF提供了esp_phy和esp_wifi的底层接口虽然官方不推荐直接操作射频寄存器但至少你能通过REG_WRITE和REG_READ宏来访问任意地址。我的建议是用ESP-IDF做底层实验用Arduino做快速验证。两者可以共存通过PlatformIO或者手动配置都能切换。如果你之前一直用Arduino IDE转ESP-IDF最大的障碍是编译速度。Windows下第一次编译ESP-IDF项目可能要十几分钟后面增量编译会快很多。有个技巧是把编译缓存打开在menuconfig里设置CCACHE_ENABLEy能省不少时间。3.2 必须准备的硬件和软件清单硬件方面你需要一块ESP32开发板推荐ESP32-WROOM-32或ESP32-S3前者资料多后者性能强一根2.4G天线大部分开发板自带PCB天线但外接SMA天线效果更好一个逻辑分析仪可选但强烈建议用来抓I2S和RMT的时序一台示波器可选用来观察基带信号波形软件方面ESP-IDF v5.0或更高版本Python 3.8以上用于运行辅助脚本一个串口终端工具我用的minicomWindows下可以用PuTTY频谱分析软件可选我用的是inspectrum开源且好用3.3 寄存器信息的获取途径官方手册之外的路乐鑫官方发布的《ESP32技术参考手册》里射频部分的寄存器描述非常简略只列出了WiFi和蓝牙相关的配置寄存器。但社区里有人通过对比不同固件的二进制文件反推出了一些未公开的寄存器地址。我主要参考了三个来源一是GitHub上ESP32-SDR项目的源码里面有一些注释说明了寄存器的用途二是乐鑫官方论坛里几个老帖子的讨论三是自己用esptool.py读芯片的efuse和寄存器状态通过对比不同配置下的值来推断功能。提示直接操作未公开寄存器有风险可能导致芯片进入不可恢复的状态。建议先用一块便宜的ESP32做实验不要拿主力开发板冒险。4. 打通射频通路的实操步骤从寄存器配置到信号发射4.1 第一步让射频前端进入可编程模式ESP32上电后射频前端默认是关闭的。WiFi或蓝牙初始化时协议栈会调用esp_phy_init来打开射频前端并配置成相应的模式。我们要做的是绕过协议栈手动完成这个初始化过程。关键寄存器是0x3FF45000附近的几个地址不同型号可能不同。具体操作是先写0x3FF45000的bit 0为1使能射频前端的时钟然后写0x3FF45004的bit 4为1打开功率放大器最后写0x3FF45008配置基带采样率。这段代码在ESP-IDF下的写法大概是#define RF_ENABLE_REG 0x3FF45000 #define RF_PA_REG 0x3FF45004 #define RF_BB_REG 0x3FF45008 void rf_frontend_init(void) { REG_WRITE(RF_ENABLE_REG, REG_READ(RF_ENABLE_REG) | 0x01); REG_WRITE(RF_PA_REG, REG_READ(RF_PA_REG) | 0x10); REG_WRITE(RF_BB_REG, 0x0A); // 设置基带采样率 }实测下来这段代码执行后用频谱分析仪能在2.4G频段看到底噪明显抬升说明射频前端确实被打开了。但这时候还没有信号发射只是前端在工作。4.2 第二步用I2S构造基带数据流射频前端打开后需要往基带送数据。ESP32的I2S外设可以配置成主模式、发送方向、并行输出。具体配置参数采样率设置为1MHz这是ESP32 I2S能稳定工作的较高频率数据位宽16位通道数1通道单声道时钟源PLL_D2240MHz分频配置好I2S后往I2S_DATA_REG寄存器写数据数据就会以1MHz的速率从I2S的SD线输出。然后你需要把这个输出连接到射频前端的基带输入。在ESP32内部这个连接是通过GPIO矩阵实现的需要配置GPIO_FUNC寄存器把I2S的SD信号路由到射频基带输入。这一步是整个方案里最玄学的部分。因为官方文档没有说明I2S和射频基带之间的内部连接关系我是通过反复试验GPIO矩阵的配置组合才找到正确的路由。具体来说需要把GPIO矩阵的SIGMADELTA通道配置成I2S输出然后使能RF_BB_IN的输入选择位。4.3 第三步用RMT做精确的时序控制I2S输出的数据流是连续的但很多调制方式比如OOK需要控制载波的通断。这时候RMT就派上用场了。RMT可以产生精确的脉冲序列每个脉冲的宽度可以精确到微秒级。我的做法是用RMT的一个通道产生一个门控信号这个信号控制射频前端的使能端。当RMT输出高电平时射频前端工作发射载波当RMT输出低电平时射频前端关闭不发射。这样就能实现OOK调制。RMT的配置代码大概长这样rmt_config_t rmt_tx { .channel RMT_CHANNEL_0, .gpio_num GPIO_NUM_25, .clk_div 80, // 1MHz时钟 .mem_block_num 1, .rmt_mode RMT_MODE_TX, .tx_config { .carrier_freq 0, .carrier_duty_percent 0, .carrier_level RMT_CARRIER_LEVEL_LOW, .carrier_en false, .idle_level RMT_IDLE_LEVEL_LOW, .idle_output_en true, } }; rmt_config(rmt_tx); rmt_driver_install(rmt_tx.channel, 0, 0);然后往RMT的RMT_DATA_REG写脉冲宽度数据就能控制射频前端的通断时序。4.4 第四步实际发射一个OOK信号把上面三步串起来就能发射一个简单的OOK调制信号。我测试的时候用RMT产生一个1kHz的方波控制射频前端通断然后用另一块ESP32或者RTL-SDR在远处接收。实测结果在1米距离内用RTL-SDR能清楚地看到2.4G频段上有一个被1kHz方波调制的信号。频谱图上能看到载波和两侧的边带边带间隔正好是1kHz。这说明整个通路是通的。但有几个坑要注意一是射频前端的使能响应有延迟RMT的脉冲宽度不能太窄实测最小可靠宽度是2微秒左右二是I2S的数据速率和RMT的时序要匹配否则会出现数据错位三是电源要干净ESP32的射频部分对电源噪声很敏感最好用线性稳压器供电。5. 接收方向用ESP32抓取和解码2.4G信号5.1 接收通路的配置从ADC到基带发射搞通了接收其实更简单一些。ESP32的射频前端在接收模式下会把天线进来的信号下变频到基带然后由ADC采样。官方API里没有直接读取基带ADC数据的接口但可以通过配置I2S为接收模式把ADC数据流读到内存里。具体操作把I2S配置成从模式、接收方向数据来源选择ADC。然后配置射频前端为接收模式使能低噪声放大器LNA。这时候I2S的缓冲区里就会出现基带采样数据。我实测的采样率能稳定跑到500kHz左右再高就会丢数据。对于2.4G频段的信号来说500kHz的采样率远远不够奈奎斯特采样定理要求至少4.8GHz但因为我们只关心基带包络不需要恢复载波所以这个采样率对于解调OOK或FSK信号是够用的。5.2 用ESP32做简单的频谱分析拿到基带采样数据后可以做FFT来看频谱。ESP32的DSP库里有现成的FFT函数1024点的FFT大概需要几毫秒。我把采样数据做FFT后通过串口传到电脑上用Python画出来能看到明显的信号峰值。这个频谱分析的功能虽然简陋但用来观察自己发射的信号、或者看周围WiFi信道的占用情况已经够用了。我经常用它来检查2.4G频段哪些信道比较干净然后把自己的实验信号放在干净的信道上避免干扰。5.3 解码一个实际的无线信号以OOK遥控器为例市面上很多无线遥控器用的是OOK调制载波频率在315MHz或433MHz。ESP32的射频前端覆盖不了这些频段但如果你把遥控器的发射部分换成2.4G的OOK模块就能用ESP32来接收和解码。我拿一个2.4G的无线门铃做实验用ESP32接收它的信号采样后做包络检波然后通过比较器整形最后用GPIO中断来解码。整个过程在ESP32上跑不需要电脑。解码出来的数据通过串口打印能看到门铃的地址码和按键码。这个实验的意义在于它展示了SDR的核心思想——用软件来处理本来需要专用硬件才能处理的信号。虽然ESP32的性能有限但基本的采样、解调、解码流程都能跑通。6. 踩过的坑和绕过的弯那些官方文档不会告诉你的事6.1 射频寄存器写入后芯片死机第一次尝试直接写射频寄存器时芯片直接死机了串口没有任何输出。后来发现是因为写寄存器之前没有关闭WiFi和蓝牙协议栈协议栈在后台会定期访问射频寄存器和我的写入操作冲突了。解决办法在操作射频寄存器之前先调用esp_wifi_stop()和esp_bt_controller_disable()确保协议栈完全停止。然后还要关闭射频前端的自动增益控制AGC否则AGC会不断调整增益导致寄存器值被覆盖。6.2 I2S数据速率上不去I2S配置成1MHz采样率时实际输出速率只有几百kHz。查了很久才发现是DMA缓冲区设置太小数据还没发完就被覆盖了。把DMA缓冲区从默认的8个描述符增加到32个问题解决。另外I2S的时钟源选择也很关键。默认的APLL时钟在射频工作时会不稳定换成PLL_D2后好很多。这个在官方文档里没有明确说明是我对比不同时钟源下的输出波形才发现的。6.3 发射信号被WiFi信道干扰实验初期我发射的信号总是被周围的WiFi信号淹没。后来用频谱分析发现我选的载波频率正好在一个繁忙的WiFi信道上。把载波频率挪到信道边缘比如2412MHz以下或2484MHz以上干扰就小很多。注意在2.4G频段做发射实验时一定要遵守当地的无线电管理规定控制发射功率和占用带宽避免干扰正常的WiFi和蓝牙通信。6.4 电源噪声导致接收灵敏度下降用USB供电时接收灵敏度很差近距离才能收到信号。换成电池供电后灵敏度明显提升。原因是USB电源的开关噪声耦合到了射频前端抬高了底噪。如果要做接收实验建议用线性稳压器或者电池供电。7. 这条路还能怎么走从OOK到更复杂的调制OOK只是最简单的调制方式ESP32的射频通路其实还能做更多。比如用I2S输出任意波形配合RMT的时序控制可以生成FSK甚至PSK信号。社区里有人用类似的方法实现了简单的LoRa风格调制虽然性能比不上专用芯片但作为学习工具非常有价值。另一个方向是接收解码。ESP32的ADC采样率有限但如果你只关心低速信号比如1kbps以下完全可以用软件做匹配滤波和解调。我试过用ESP32解码一个2.4G的FSK信号虽然误码率不低但在近距离下能稳定工作。如果你对边缘AI感兴趣还可以把采样数据喂给ESP32上的神经网络做信号分类。比如区分WiFi、蓝牙和自定义信号这个在ESP32-S3上跑起来完全可行。我目前正在折腾这个方向等有结果了再单独写一篇。最后说一个实际体会ESP32的射频通路就像一间没开灯的房间官方手册只告诉你门在哪但里面的布局得自己摸。摸的过程会踩很多坑但每搞明白一个寄存器的作用你对无线通信的理解就深一层。这种从底层往上打通的感觉比调通一个现成的API要有意思得多。