Skip to content

TTeuber/StompLink

Repository files navigation

StompLink

CI

A two-microcontroller guitar pedal: a Daisy Seed (STM32H750) runs the audio effects inside a Hothouse pedal enclosure, and an ESP32-S3 acts as a USB host that bridges the pedal to a WiFi web UI and mirrors every knob, switch, and footswitch on an OLED — live, in both directions.

The rig: Hothouse pedal (Daisy Seed inside, J-Link on SWD) wired over USB to an ESP32-S3 DevKitC driving an SSD1306 OLED

Turning a physical knob: the OLED and the web UI follow in real time

The two boards

The Daisy Seed is a general-purpose STM32 board: an STM32H750 (Cortex-M7 @ 480 MHz) system-on-module with 64 MB SDRAM, 8 MB QSPI flash, and a stereo audio codec wired to the MCU's SAI peripheral. Audio here is just a bidirectional DMA stream — the external codec does the analog conversion and clocks 48 kHz stereo samples through the H7's SAI (Serial Audio Interface), and libDaisy hands them to an interrupt-context callback in 1 ms blocks of floats. The "effects" are ordinary fixed-budget math inside that ISR; the knobs are plain STM32 ADC channels and the switches GPIOs, wired out by the Hothouse carrier board. Everything else — USB-OTG device, timers, GPIO — is stock STM32 peripheral work.

The ESP32-S3 (dual-core Xtensa LX7 @ 240 MHz, WiFi) plays the opposite role: no audio at all, but USB host, an embedded web server, a WebSocket broadcaster, and an I²C display. The split is the point — hard-real-time DSP stays on the M7 where a missed deadline is audible, and everything bursty (WiFi, HTTP, USB enumeration) lives on the S3.

                        ┌───────────────────────────┐
   guitar ──► in        │  Hothouse pedal           │        out ──► amp
                        │  ┌─────────────────────┐  │
                        │  │ Daisy Seed          │  │  overdrive → tremolo → delay
                        │  │ (STM32H750, 48 kHz) │  │  6 knobs · 3 toggles · 2 footswitches
                        │  └──────────┬──────────┘  │
                        └─────────────┼─────────────┘
                                USB CDC-ACM
                     newline ASCII protocol, both ways
                        ┌─────────────┴─────────────┐
                        │  ESP32-S3 DevKitC         │
                        │  USB host · WiFi · I²C    │──── SSD1306 OLED
                        └─────────────┬─────────────┘     (live control mirror)
                            WiFi (SoftAP + STA)
                        WebSocket, same ASCII protocol
                        ┌─────────────┴─────────────┐
                        │  Browser: web control UI  │
                        └───────────────────────────┘

The same newline-terminated ASCII protocol (common/stomplink_proto.h) travels over every hop — USB and WebSocket — so any hop can be debugged with a serial terminal or the browser dev console. The protocol library is a single dependency-free C header shared by both firmwares and covered by host-side unit tests.

What it demonstrates

  • USB device (Daisy/libDaisy CDC-ACM) and USB host (ESP32-S3/esp-idf usb_host + cdc_acm_host) on the same wire, with hot-plug reconnect on the host side.
  • Real-time-safe control: the audio callback never takes a lock; parameters cross from USB/main-loop context as single-word volatile writes and are de-zippered per sample.
  • Bidirectional state with ownership arbitration: a web write marks a parameter web-owned; nudging the physical knob reclaims it. The owner mask travels in every state line, and the web UI tags hardware-owned params HW.
  • FreeRTOS task architecture (ESP32): USB event pump and bridge pinned to core 1 away from WiFi, a mutex-guarded state hub with event-group dirty bits fanning out to the OLED task and a coalescing (≤20 Hz) WebSocket broadcaster.
  • Embedded web serving: gzipped single-page UI embedded in flash, served with Content-Encoding: gzip; WebSocket both ways; WiFi provisioning form writing STA credentials to NVS; always-on SoftAP + mDNS.
  • I²C display driver: hand-rolled 1-bpp framebuffer + public-domain 8×8 font over esp_lcd, no LVGL.
  • Host-tested protocol: make -C common/tests test runs the round-trip / fuzz-ish suite on your dev machine, compiled both as C99 and as C++.
  • CI for firmware: GitHub Actions cross-builds both firmwares (arm-none-eabi toolchain + ESP-IDF v5.5 container) and runs the protocol suite on every push.

Hardware

Piece Notes
Hothouse pedal kit with Daisy Seed powered by its own 9 V pedal supply
ESP32-S3-DevKitC (two USB ports) UART port → computer, native USB-OTG port → Daisy
SSD1306 128×64 I²C OLED SCL → GPIO40, SDA → GPIO41, 3V3, GND
USB-C → micro-USB cable ESP32 OTG port → Daisy Seed onboard USB
J-Link debugger flashes/debugs the Daisy (SWD pins)

Wiring notes:

  • The DevKitC's OTG connector does not source 5 V VBUS. The pedal supply powers the Daisy, and the USB cable carries only D+/D−/GND — but stock libDaisy enables VBUS sensing on the Seed's onboard port, so the Daisy ignores a host that supplies no 5 V (symptom on the ESP32: HUB: Root port reset failed). make -C daisy patch-libdaisy applies a two-line libDaisy patch disabling VBUS sensing; the Daisy is self-powered here, so nothing is lost.
  • GPIO19/20 are the S3's fixed internal USB PHY — don't repurpose them.
  • While the ESP32 occupies the Daisy's USB port, USB-DFU flashing of the Daisy is unavailable; flash it over SWD with the J-Link (or unplug the ESP32 and use DFU).

Repo layout

common/     stomplink_proto.h — shared protocol + host unit tests
daisy/      Daisy Seed firmware (effects + USB CDC device)
esp32/      ESP-IDF project (USB host bridge, OLED, WiFi, web server)
esp32/web/  web UI source; pack.sh regenerates the embedded .gz
libDaisy/   \ submodules pinned to known-good revisions
DaisySP/    /

Build & flash

0. Protocol tests (any machine, no hardware)

make -C common/tests test

1. Daisy firmware

Requires the ARM GNU toolchain (arm-none-eabi-gcc) and OpenOCD for J-Link flashing.

git submodule update --init --recursive
make -C daisy patch-libdaisy # disable VBUS sensing (see wiring notes)
make -C libDaisy -j
make -C DaisySP -j
make -C daisy -j
make -C daisy program        # flash via J-Link/OpenOCD (SWD)

Sanity-check without the ESP32: plug the Daisy's micro-USB into your computer and screen /dev/tty.usbmodem* 115200 — you'll see S/K/T lines as you move controls, and you can type b 0, p dl mix 0.8, ? etc.

2. ESP32 firmware

Requires ESP-IDF v5.5.

cd esp32
. $IDF_PATH/export.sh
idf.py build
idf.py -p /dev/tty.usbmodem*   flash monitor   # the UART-labelled USB port

After editing web/index.html, run web/pack.sh and rebuild.

3. Use it

  1. Power the pedal; connect the ESP32's OTG port to the Daisy's micro-USB. The OLED shows USB when the CDC link is up.
  2. Join the StompLink WiFi network (WPA2, password letsstomp) and open http://192.168.4.1/ — or use the WiFi form to join your home network, then open http://stomplink.local/. The OLED shows the STA IP.
  3. Move web sliders → the pedal reacts instantly and the OLED tracks. Move physical knobs → the web UI bars and sliders follow, and reclaimed params show the HW tag.

Controls

Control Function
Knobs 1–6 OD drive, OD level, trem rate, trem depth, delay time, delay mix
Toggle 1 overdrive on (up) / off
Toggle 2 tremolo waveform: sine / square / ramp
Toggle 3 delay on (up) / off
Footswitch 1 global bypass (LED 1)
Footswitch 2 tap tempo (LED 2 blinks the delay time)
Web only delay feedback, per-effect enable incl. tremolo

Hold both footswitches ≥ 2 s to reboot the Daisy into DFU mode.

Protocol

Downstream (browser/ESP32 → Daisy): p <fx> <name> <0..1>, e <fx> <0|1>, b <0|1>, t (tap), ? (state request), v (version request).

Upstream (Daisy → ESP32/browser): S <bypass> <enable-mask> <owner-mask> <8 params> (on change, ≤30 Hz, plus a 1 Hz heartbeat), K <idx> <val>, T <idx> <pos>, F <idx> <event>, V <proto> <fw>.

The ESP32 adds bridge-local L lines on the WebSocket only (USB link, IP, firmware version, footswitch counter). Full details in common/stomplink_proto.h.

Three bugs this project surfaced

Bring-up notes — each of these passed every desk check and failed only on real hardware, which is rather the point of a two-MCU project.

  1. The link was dead because of USB VBUS sensing. The Daisy enumerated fine on a Mac but the ESP32 logged HUB: Root port reset failed forever. The one electrical difference: the Mac supplies 5 V VBUS and the DevKitC's OTG port doesn't. Stock libDaisy enables VBUS sensing on the Seed's USB-FS port, so the (self-powered) Daisy silently ignored any bus reset from a host that provides no 5 V. Fix: a two-line patch to usbd_conf.c disabling VBUS sensing, shipped as daisy/patches/ + a make patch-libdaisy target so the fix survives a fresh submodule checkout.

  2. printf("%.3f") prints nothing on the target. State lines arrived with blank parameter fields — only on hardware. The Daisy links against newlib-nano, which omits float formatting by default, and 278 host-side protocol tests can't catch a target libc gap. Fix: the shared header formats floats with integer math (v*1000"%d.%03d"), so both firmwares are immune by construction.

  3. Burst writes silently dropped lines. The full-state replay (?) arrived with most of its 10 lines missing. libDaisy's CDC transmit returns immediately while the IN endpoint is still busy with the previous packet — no queue, no error surfaced up the stack. Fix: a bounded retry-with-delay around the send, which is honest about the constraint (a 1 kHz main loop can afford 1 ms; the audio ISR never touches USB).

Known quirks

  • In APSTA mode the SoftAP follows the STA onto its channel when the STA connects, briefly dropping SoftAP clients (esp-idf documented behavior).
  • The SoftAP is WPA2 with a published password — fine for a demo pedal, not an access-control boundary.
  • Once this firmware runs, the S3's native USB port stops enumerating as USB-Serial-JTAG — the USB host stack owns the PHY. To re-flash over that port, hold BOOT while plugging in (download mode); day-to-day, flash and monitor over the UART-labelled port instead.
  • daisy/Makefile defaults PGM_DEVICE to interface/jlink.cfg; override it for other probes (make program PGM_DEVICE=interface/stlink.cfg). Note that SEGGER's own tools (JLinkExe) refuse non-matching targets on J-Link-OB probes ("Specific core setup failed") — OpenOCD via make program drives the same probe as a generic SWD adapter and works.

License

GPL-3.0-or-later. daisy/hothouse.{h,cpp} are vendored from the Hothouse examples (GPL-3.0-or-later, © Cleveland Music Co.); esp32/components/oled_ui/font8x8_basic.h is public domain (Daniel Hepper).

About

Two-MCU guitar pedal: STM32H750 (Daisy Seed) audio effects bridged over USB CDC to an ESP32-S3 USB host serving a live WiFi web UI and OLED control mirror

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors