Skip to content

Optional stand-alone spectrum/waterfall displays (M5Stack Din Meter, ESP32-C3 OLED) - #17

Open
andrewsharmon wants to merge 3 commits into
ESPARGOS:mainfrom
andrewsharmon:upstream-displays
Open

andrewsharmon wants to merge 3 commits into
ESPARGOS:mainfrom
andrewsharmon:upstream-displays

Conversation

@andrewsharmon

Copy link
Copy Markdown

Hi Florian and the ESPARGOS team,

First, thank you for open-sourcing ESP-SDR. Finding a raw I/Q path in a chip that
costs a few dollars, and then publishing it with this much documentation, is a real
gift. Projects like this are how future RF engineers get hooked: a student can watch
the 2.4 GHz band breathe on a board they already own, with nothing else to buy.

That is exactly what I want to use it for. I'm putting together education outreach
around it, and a board that shows a live waterfall on its own screen, with no laptop,
is a great hook in a classroom. This PR is the code behind that.

Please feel free to reject it if it isn't on your path. I saw in #8 that you prefer
generic solutions over board-specific ones, and this is mostly board-specific UI code.
If you'd rather keep that out of tree, the small generic hooks described below may
still be worth having on their own, and I'm happy to split them out. Or I'll just keep
it all in my fork. Either way, thank you for the project.

Relates to #7 (displaying a waterfall on the ESP32 itself).


What it adds

Two optional builds that draw a live spectrum and waterfall on the board itself.
Both are off by default; stock builds for every chip are unchanged.

M5Stack Din Meter (ESP32-S3, 240×135 colour LCD, rotary encoder):
sdkconfig.defaults.dinmeter

  • Each waterfall line is a 45 ms run of the continuous S3 capture. The on-chip FFT
    frames that SPEC would send over USB go to the display instead (~12 lines/s).
  • The knob tunes 2210–2700 MHz in 1 kHz steps (via the FOFS PLL offset). A press
    switches it to span adjustment (80, 60, 40, 20, 16, 10, 5, 2 MHz) and back. A long
    press toggles peak hold / average.
  • The encoder is counted by PCNT, so no clicks are lost while a run masks interrupts.

ESP32-C3 SuperMini with 0.42" 72×40 OLED (SSD1306): sdkconfig.defaults.c3oled

  • A dithered spectrum + waterfall over a 67.5 MHz span from 80 MS/s snapshots. Each
    snapshot is scanned for block energy, and only its strongest blocks are transformed,
    so short Wi-Fi/Bluetooth bursts show up much more often.
  • The BOOT button steps the centre frequency and cycles the view.

Demo video: Din Meter and C3 OLED running the waterfall

Demo video (39 s): the Din Meter tuning and changing span, then the C3 OLED.

How it fits in

  • The host always wins. Any host command pauses the display ("USB HOST"). The
    protocol is unchanged, and the display resumes when the five-second lease expires.
    On the Din Meter, the UI frees its ~22 KiB of display buffers while a host is
    connected, so IQS keeps its run-time allocations.
  • Generic hooks (small, useful beyond these two boards):
    • ring_capture_local_sink (S3): an on-device consumer for SPEC frames instead of
      the serial port. Host input still ends the run, without being consumed.
    • burst_serial_set_idle(): lets a receiver do display work from the shared serial
      loop, and tells it when a host holds the lease.
    • burst_serial_set_passive(): read-only commands (e.g. a display buffer dump) that
      neither take the lease nor pause the display.
  • Board pins are removed from browser GPIO control when a display is built in.
  • Din Meter build: UART0 is disabled (not wired out) and the SPI ISR stays in flash,
    both so the UI fits in SRAM next to the RF ring.
  • Docs: docs/displays.md (layout, bin widths, controls, build commands, debug
    commands).

Testing

  • Builds with no warnings: stock C3, stock S3, C3 + OLED, Din Meter (pinned IDF 25fe69f).
  • python3 -m unittest discover -s tests: same results as main. The 10 errors I see
    on macOS (the linker has no --wrap) happen on an unmodified checkout too.
  • On hardware, with the display built in:
    • CAP16 (CRC OK), SPEC 1024 bins (status 0) and IQS (status 0) all work.
    • The display resumes after the host lease expires.
    • Din Meter: no failed runs, no bad frames; knob and press checked by hand.
    • C3: OLED placement and orientation look right.

A note on how this was written

In the same spirit as the project's own README: this was written with heavy AI
assistance (Claude). I tested it on both boards and read through it, but I'm not deep
in the capture internals, so please review the ring_capture and burst_serial
changes with that in mind. I'm happy to rework anything.

🤖 Generated with Claude Code

andrewsharmon and others added 3 commits October 17, 2026 18:02
M5Stack Din Meter (ESP32-S3, CONFIG_ESP_SDR_DINMETER):
- 240x135 ST7789 colour spectrum + waterfall fed by the continuous dual-core
  SPEC capture; each line is a 45 ms run whose frames go to the UI through a
  new ring_capture_local_sink hook instead of USB.
- Knob tunes (2210-2700 MHz, 1 kHz steps via the PLL offset); a press
  switches it to span adjustment (80 MHz down to 2 MHz) and back; a long
  press toggles peak hold / average. The encoder is read by PCNT, so counts
  survive the interrupt-masked runs. A host FREQ clears a knob-set offset.
- Display buffers are released while a USB host holds the lease, so host
  modes that allocate at run time (IQS) keep their heap.

ESP32-C3 SuperMini 0.42" OLED (CONFIG_ESP_SDR_OLED_ENABLED):
- 72x40 SSD1306 dithered spectrum + waterfall from 80 MS/s snapshots; only
  the strongest blocks of each snapshot are transformed, so short bursts are
  caught far more often. BOOT button steps frequency / view.

Shared: burst_serial_set_idle() lets a receiver run display work from the
common serial loop; it is told when a host holds the lease, including just
before each host command. Both features are off by default
(sdkconfig.defaults.dinmeter, sdkconfig.defaults.c3oled), pause while a host
is connected and leave the serial protocol unchanged; their pins are
excluded from GPIO control. See docs/displays.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a read-only "passive" command path to burst_serial (tried before the lease
logic, so it neither takes the serial lease nor pauses the display), and use it
for DMWF on the Din Meter: dump the 65x240 waterfall ring as hex palette levels,
newest line first. Lets a host read back exactly what is on screen for bench
verification without disturbing the live display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Jeija

Jeija commented Oct 22, 2026

Copy link
Copy Markdown
Member

Thanks! I am considering adding some special firmware profiles LCD waterfall display devices, since many people have asked for that. On the other hand, the challenge is that supporting a large range of LCD devices is a nightmare to maintain.

I have a "CI-like" setup that can automatically test all firmware changes across the whole range of esp32 family chips, but if I ever add support for LCD devices, I would like to also be able to actually test them whenever there is a change to the firmware, and I obviously cannot test all of them. I am considering adding support for some of the more common (like M5Stack) or "Espressif official" LCD dev kits out there, but I would like to actually have all of them in my CI setup in that case. So thanks for your contribution, I will keep this PR open for a while and then see which LCD waterfall / spectrogram devices are most in demand / most common.

@andrewsharmon

Copy link
Copy Markdown
Author

I totally understand the difficulties on CI'ing on the generic 'esp32 w/display' breath of HW. I went with an M5stack (digikey/mouser PO able) and the cheapest generic display esp32 board currently on Amazon as my baseline. You do bring up an interesting CI endpoint need (I'll noodle on that).

I may also add this code explicitly does not include any transmit code for valid esp-now packets to graphically display foxes on spectral plots for fox hunting activities.

@doctormord

Copy link
Copy Markdown

Thanks for ESP-SDR! Since LCD builds came up here: FYI, I keep two handheld display
builds out of tree, so there is nothing to merge or maintain on your side. They grew out
of Skydev0h/esp-sdr-glow:

https://github.com/doctormord/ESP32-SDR-Handheld-scanner-with-LCD

  • LILYGO TTGO T-Display (ESP32) and Guition JC3248W535 (ESP32-S3, 3.5" touch)
  • Wi-Fi waterfall, network list with measured channel airtime, and a "sniffer" mode for
    finding active transmitters nearby (channelizer detector, bearing, BLE list on the S3)
  • ready-made images in the repo and in the v1.0.0 release, flashable with esptool-js

One finding that may matter to anyone combining the receiver with a normal Wi-Fi scan:
after rx_recalibrate() at 2442 MHz, a scan heard nothing on channel 1 (even a
600 ms passive scan), while channel 13 still worked; only the first scan after power-on
saw everything. Calibrating at channel 1 before the scan (in the idle promiscuous state;
in STA mode the PHY's DC calibration never finished) fixed it on the ESP32. The S3
additionally needed a second calibration for channels 8–13. Details in the
commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants