ESP-SDR turns the ESP32's built-in 2.4 GHz Wi-Fi radio into a software-defined radio (SDR). It lets you capture the radio signal itself and process it in software, so you can view the spectrum and study signals beyond ordinary Wi-Fi packets. No separate SDR hardware is needed. The ESP32-C5 also supports reception in the 5 GHz band.
With the help of LLMs, we discovered an undocumented debug path that bypasses the chip's fixed-function Wi-Fi modem. This gives software access to raw radio samples, called I/Q samples, from the built-in receiver. ESP-SDR captures short bursts of these samples and sends them to your computer over USB or UART for analysis. It can also compute spectra on-device, including continuous RF capture on selected chips.
The diagram shows the hardware's receive and transmit paths; this firmware currently implements reception only.
Project overview · Browser SDR viewer · Browser firmware installer
Parts of the firmware code are AI-generated. While we have a very good understanding of how the IQ sampling functionality works on the ESP32-C61 chip (used in our ESPARGOS One array), making IQ sampling work on the whole range of ESP32 family chips would have been too much work without LLM support.
| Chip | Status | Native USB | UART0 TX / RX | Minimum flash | Special profiles |
|---|---|---|---|---|---|
| ESP32 | ✅ | — | GPIO1 / GPIO3 | 2 MB | — |
| ESP32-C2 | ✅ (26 MHz crystal) | — | GPIO20 / GPIO19 | 2 MB | — |
| ESP32-C3 | ✅ | Serial/JTAG | GPIO21 / GPIO20 | 2 MB | — |
| ESP32-C5 | ✅ | Serial/JTAG | GPIO11 / GPIO12 | 2 MB | — |
| ESP32-C6 | ✅ | Serial/JTAG | GPIO16 / GPIO17 | 2 MB | — |
| ESP32-C61 | ✅ | Serial/JTAG | GPIO11 / GPIO10 | 2 MB | — |
| ESP32-H2 | ✅ | Serial/JTAG | GPIO24 / GPIO23 | 2 MB | — |
| ESP32-H21 | 🚧 | — | — | — | — |
| ESP32-H4 | 🚧 | — | — | — | — |
| ESP32-P4 | ❌ | — | — | — | — |
| ESP32-S2 | ✅ | USB-OTG CDC | GPIO43 / GPIO44 | 4 MB | — |
| ESP32-S3 | ✅ | Serial/JTAG | GPIO43 / GPIO44 | 2 MB | — |
| ESP32-S31 | ✅ | Serial/JTAG | GPIO58 / GPIO59 | 2 MB | High Speed USB / Ethernet streaming at up to 40 MSa/s (experimental) |
✅ Supported · 🚧 Not yet supported · ❌ Unsupported (no integrated radio).
USB, UART, and flash requirements above refer to the standard profiles; see each special profile for its board requirements.
Experimental: The S31 streaming mode is under development. Signal quality, including the remaining DC peak, still needs improvement.
The separate esp32s31-stream profile targets the ESP32-S31 Function-CoreBoard
with Gigabit Ethernet and native high-speed USB. It streams receive-only I/Q to
SoapyESPSDR and includes an on-device web page for receiver controls and
status, with rates up to 20 MSa/s over USB and 40 MSa/s over Ethernet using
8-bit I plus 8-bit Q. The ordinary esp32s31 profile remains the serial burst/FFT firmware
for ESP-WebSDR. See streaming build, architecture, and protocol.
The firmware can compute FFTs on the device and send compact spectra instead of raw I/Q. The viewer queries each device's supported rates, FFT sizes and transport before offering this mode.
| Chip | Continuous RF capture with on-chip FFT | Snapshot FFT |
|---|---|---|
| ESP32 | — | 256–2048 bins; 16/40/80 MS/s; UART |
| ESP32-C2 | — | 256–2048 bins; 80/40/16 MS/s; UART |
| ESP32-C3 | 256 bins; 80 MS/s; native USB | 512–2048 bins over USB; 256–2048 over UART |
| ESP32-C5 | — | 256–2048 bins; 4/8/10/20/40/80 MS/s |
| ESP32-C6 | 256 bins; 80 MS/s; native USB | 512–2048 bins over USB; 256–2048 over UART |
| ESP32-H2 | — | 256–2048 bins; 6.4/10.667/16/32 MS/s; USB or UART |
| ESP32-C61 | 256 bins; 4/8/10/20/40/80 MS/s; native USB | 512/1024 bins over USB; 256–1024 over UART |
| ESP32-S2 | — | 256–2048 bins; 16/40/80 MS/s; USB or UART |
| ESP32-S3 | 256–2048 bins; 16/40/80 MS/s; native USB | — |
| ESP32-S31 | 256–2048 bins; 4/8/10/20/40/80 MS/s; native USB | Same FFT sizes and rates over UART |
Continuous capture keeps the RF writer running, but the CPU analyzes only selected FFT windows. It does not deliver every sample or guarantee that every short RF event will be visible. Snapshot FFT stops reception between captures; the viewer labels these gaps explicitly. Raw I/Q capture remains available separately.
The original S3 Turbo Mode was developed by Zoltan Doczi from Z2Labs. The shared implementation extends it with C6/C61 bank rotation, C3 live-bank reads, S31 dual-core SIMD processing with continuous bank rotation, and portable snapshot FFTs. See spectrum protocol and hardware validation for the wire format, limitations and test results. The S3 ring diagnostic host tool is tools/s3_ring.py.
Connect over native USB or a 3.3 V USB-to-UART adapter with crossed TX/RX and common ground, using the pins above. Both interfaces carry the same request/response protocol: send newline-terminated ASCII commands and read text replies. Capture replies also include a binary I/Q payload.
Query INFO and CAPS to identify the firmware and supported features.
LIMITS? reports receive-control limits, RANGE? reports the tuning range,
and TRANSPORT? identifies the active interface. Configure reception with
FREQ <MHz>, BANDWIDTH <MHz> and GAIN commands.
Request a snapshot with CAP16 <samples> <rate-index> for signed 8-bit I/Q
or CAP20 <samples> <rate-index> for packed signed 10-bit I/Q. The reply is
DATA <samples> <crc32-hex> <capture-microseconds>, followed by exactly
ceil(samples × bits-per-component × 2 / 8) binary bytes. Verify the
payload CRC32 before using the samples. Rate indices 0–6 select 80, 40, 20, 10,
8, 4 or 16 MS/s respectively; use only rates advertised by LIMITS?.
Finish reading each reply before sending another command. Failures return
ERR <reason>. SYNC <nonce> echoes the nonce to let clients resynchronize
after an incomplete transfer. One client controls the radio at a time;
RELEASE or five seconds of idle time releases it, while other clients
receive ERR busy.
firmware-targets.json lists the supported profiles and
pins their ESP-IDF commits, including the preview SDK for S31. Check out the
matching SDK, initialize its submodules, run install.sh <target>, and source
export.sh.
Use a separate build directory and configuration for each chip:
idf.py -B build-s3 -DIDF_TARGET=esp32s3 \
-DSDKCONFIG=sdkconfig.s3 \
-DSDKCONFIG_DEFAULTS=sdkconfig.defaults.esp32s3 build
idf.py -B build-s3 -p /dev/ttyACM0 flashSubstitute the target and paths for your chip. S31 also requires idf.py --preview.
The C2 profile uses a 26 MHz crystal, as on the tested ESP8684H board.
For a 40 MHz board, select CONFIG_XTAL_FREQ_40=y in menuconfig and rebuild;
the packaged browser image requires 26 MHz. C2 uses UART0 at 2 Mbaud by
default. With a CH340 bridge, use the viewer's Switch to 1 Mbaud warning
when transfers lose bytes; baud changes are session-only. C2 provides raw
IQ8/IQ10 captures, hardware/manual gain, and snapshot FFTs. Continuous capture
is not advertised; approximate analog bandwidth covers 12–20 MHz.
See C2 backend and validation.
The esp32h2 profile uses the Bluetooth PHY capture engine and supports
both native USB Serial/JTAG and UART0. It supports 32, 16, approximately 10.667 and 6.4 MS/s hardware sampling,
plus approximately 4–11 MHz analog bandwidth control.
See H2 backend and validation.
main/targets/<target>/: chip receiver or adapter, tuning helpers, and the linker guard for its capture SRAM. CMake selects only the requested target.main/targets/esp32s31/burst/: serial IQ capture and on-chip FFT firmware.main/targets/esp32s31/streaming/: continuous USB/Ethernet IQ application for SoapyESPSDR. Both S31 profiles sharemain/targets/esp32s31/tuning.h.main/families/c5_c6_c61/: receiver shared by C5, C6, and C61; itschip.hcomes from the selected target directory.main/common/: burst serial transport, gain control, limits, and bandwidth helpers. The gain-table wrapper is linked only for C61 and S31.main/diagnostics/: optional register probes, excluded from release exports.platform/esp32s2/: pinned ROM USB CDC compatibility component.
The application component and UART configuration stay in main/. Target SDK
defaults stay at the repository root for the build tools and ESP-IDF defaults
lookup. The firmware uses the burst protocol over UART/native USB; the separate
main/targets/esp32s31/streaming/ application implements the receive-only Ethernet and
vendor USB streaming profile.
Run python3 -m unittest discover -s tests for host checks. Build every profile
with tools/build_firmware.py and its pinned SDK before distributing a change;
the CI matrix does this automatically. Preserve the target SRAM guards and
gain-table linker wrappers when moving or refactoring receiver code.
Hardware AGC is the default. GAIN MANUAL <index> sets manual gain;
GAIN HARDWARE restores AGC. LIMITS? reports available gain indices,
bandwidths, sample rates and bit depths. BANDWIDTH <MHz> sets approximate
analog bandwidth; zero selects the widest setting.
All supported burst targets accept tuning attempts from 100–6000 MHz in 1 MHz steps. The viewer shows an informational warning outside 2400–2483.5 MHz, with 5150–5895 MHz also treated as the supported 5 GHz Wi-Fi band on C5. The warning never blocks tuning. These are software attempt limits; the expanded range has not been hardware validated.
- ESP32: 80/40/16 MS/s.
- C2: 80/40/16 MS/s; up to 8,190 complex samples; approximately 12–20 MHz analog bandwidth.
- H2: 32/16/10.667/6.4 MS/s; approximately 4–11 MHz analog bandwidth; up to 16,380 complex samples.
- C3: 80 MS/s; 14–62 MHz analog bandwidth.
- C5: 11–48 MHz bandwidth; selects its 5 GHz RF path above 3000 MHz.
- C61: 80/40/20/10/8/4 MS/s; 13–54 MHz bandwidth.
- C6: 80 MS/s; 12–54 MHz bandwidth.
- S2: 80/40/16 MS/s; 15–60 MHz bandwidth; up to 12,284 complex samples.
- S3: 13–69 MHz bandwidth.
- S31: 80/40/20/10/8/4 MS/s; 13–54 MHz bandwidth.
Captures have gaps; nominal sample rates exceed sustained serial throughput. Gain and power are uncalibrated. Extended tuning does not guarantee PLL lock or reception; the viewer uses the ISM-band warning described above.
![]() Florian Euchner |
![]() Zoltan Doczi |
ESP-SDR is licensed under the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your option)
any later version (GPL-3.0-or-later). See LICENSE for the full terms.
It is provided without any warranty, including implied warranties of
merchantability or fitness for a particular purpose.
We chose the GPL because we want improvements to ESP-SDR to make their way back to the community. When you distribute modified versions, the GPL requires you to make the corresponding source available to recipients under the GPL, so they can study, share, and build on those improvements. We encourage you to contribute changes upstream, but the GPL does not require upstream submissions or publication of private modifications. See the GNU GPL FAQ.
The discovery of the capture mechanism itself is not protected by copyright: copyright covers the code and other copyrightable expression, not the underlying facts, ideas, or methods. You are free to independently implement the mechanism in your own projects under a license of your choice.
Third-party components retain their own licenses and copyright notices,
including the Apache-2.0 ESP-IDF compatibility code in
platform/esp32s2/esp_usb_cdc_rom_console/, the pinned
ESP-DSP component, and the derived FFT kernels
in main/targets/esp32s3/s3_fft_rnd.S and main/targets/esp32s31/burst/s31_fft_rnd.S.




