Skip to content

Latest commit

 

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CWGV3 — ESP32 Complex Waveform Generator (v3, RMT)

A gated pulse-train / burst generator built on the ESP32 RMT peripheral. The whole waveform (pulses and the inter-burst gate) is generated in RMT hardware at 12.5 ns resolution (clk_div = 1, 80 ticks/µs) and repeated in hardware loop mode — the CPU is completely out of the timing loop, so the output is jitter-free and rock-stable under scope averaging.

Sweet spot: clean, precise output up to ~20 kHz; usable well beyond. (Above ~10 MHz the RMT integer-tick grid makes raw frequency steps coarse — see Precise-frequency mode for how that is worked around.)

Operating modes

The OFFTIME_MOD encoder cycles modes: every click (any direction) advances 1 ↔ 2. Mode params persist across switches, so you can tune in mode 1, flip to mode 2 to ramp them per-pulse, flip back, etc.

  • Mode 1 — standard burst. N identical pulses (WIDTH high, SPACING low) followed by GATE low, looped in RMT hardware. Optionally combined with the precise-frequency dither (f<Hz> over serial) — sigma-delta period dither across the burst gives an exact mean frequency well below the raw 12.5 ns tick grid. The OFFSET encoder is the fine-frequency vernier.
  • Mode 2 — elongation. The on-time and off-time of each successive pulse in the burst are scaled by per-pulse multipliers T1 and T2. Pulse k has width WIDTH × T1^k and spacing SPACING × T2^k, then GATE. WIDTH and SPACING encoders now adjust T1 and T2 in 1 % steps (clamped 0.5×–2.0×); base widths from mode 1 are preserved. Use make reset-elong to set T1=T2=1.0.
  • Mode 3 — randomized gaps. Currently disabled in this build (the randomized timing was glitchy in initial testing). The builder code is retained for a future revision.

Hardware

  • Board: ESP32 (Arduino core 3.x), FQBN esp32:esp32:esp32
  • Output: GPIO2 = Channel 1 (the only active channel in this build). GPIO5 (CH2) is wired but disabled — a second RMT stream introduced cross-channel jitter, so it is parked until that is solved cleanly.
  • Inputs: 7 quadrature encoder modules (KY-040-class, with on-board pull-ups). A hardware PCNT glitch filter and a software debounce reject contact bounce / EMI so resting noise can't disturb the output.
  • No WiFi, no web server — serial-only control.

Encoder map (pins unchanged from the v2/v3 hardware)

Encoder (A / B GPIO) Function Step / click
PULSE (14 / 13) Cycles per burst — also sets precise-mode dither resolution +1
WIDTH (35 / 34) Mode 1: pulse HIGH time. Mode 2: T1 elongation (1 %) 1 µs / 1 %
SPACING (19 / 18) Mode 1: pulse LOW time. Mode 2: T2 elongation (1 %) 1 µs / 1 %
OFFTIME (23 / 22) Gate / inter-burst off-time 50 µs
WIDTH_MOD (27 / 26) Width fine-tune (100 ns/click) — or freq vernier in f<Hz> mode 100 ns
SPACING_MOD (15 / 32) Spacing fine-tune (100 ns/click) 100 ns
OFFTIME_MOD (33 / 4) MODE cycle — any click advances 1 ↔ 2

1 RMT tick = 12.5 ns. WIDTH/SPACING = 8 ticks/click, GATE = 80 ticks/click. GPIO34/35 are input-only (no internal pull-up) — the WIDTH encoder relies on its module's on-board pull-ups.

How it works

The pulse engine runs on core 0; encoder polling/CLI on core 1 (separated so neither starves the other). The engine builds the burst once, loads it into the RMT 64-slot memory block, enables hardware loop mode, and sleeps. It only stops/rebuilds/restarts when a parameter actually changes (debounced), so a steady setting produces an exact, hardware-repeated waveform with no CPU seam to smear.

Two modes

Knob mode (default). A burst of PULSE pulses, each WIDTH × widthMult high then SPACING low, followed by GATE × gateMult low — looped.

Precise-frequency mode (f<Hz>). Generates a ~50 % square wave at an exact target frequency. Because one RMT period is an integer number of 12.5 ns ticks, a single period can only land on f = 1/(N·12.5 ns); the gap to the next value grows as (≈125 Hz at 100 kHz, ≈12.5 kHz at 1 MHz). To beat that, the engine uses a sigma-delta period dither: across the cycles in the burst it spreads N and N+1-tick periods so the average frequency is exact. The mean is precise; only the instantaneous edge moves ≤12.5 ns — which a resonant load simply averages out (it does not shift the centre frequency). Resonances never sit on a whole Hz anyway; what matters is fine step resolution, which this provides.

Resolution scales with cycles per burst: step ≈ f² × 12.5 ns / cycles. Turn the PULSE knob up for finer steps; use the WIDTH_MOD (fine-trim) encoder to walk across in the smallest possible increment — ideal for creeping onto a resonance peak. Gating is preserved the whole time (GATE / OFFTIME_MOD still set the inter-burst gap).

Serial CLI (115200 8N1)

Cmd Action
p Print state (mode, params, achieved frequency, fine step)
m Advance mode (or m1 / m2 to jump to a specific mode)
r Reset elongation factors T1=T2=1.0 (mode 2 only)
f<Hz> Mode 1: set precise target frequency, e.g. f47325.6. f0 = off
g1 Toggle channel 1 enable
i1 Toggle channel 1 invert
e Dump raw encoder counts
d Toggle the encoder-debug stream
? Help

Build / flash

Requires arduino-cli, the esp32 core, and libraries ArduinoJson, ESP32Encoder. A Makefile wraps the workflow:

make flash         # compile + upload
make build         # compile only
make upload        # upload last build
make monitor       # open the serial console
make log           # background serial logger -> cwg.log (no repeated resets)
make state         # ask the running firmware to print its state
make mode1         # switch to mode 1 (standard burst)
make mode2         # switch to mode 2 (per-pulse elongation)
make reset-elong   # T1 = T2 = 1.0
make clean

Override the port/board if needed: make flash PORT=/dev/cu.usbserial-XXXX.

Notes & limitations

  • Single channel (GPIO2) in this build; CH2 is parked. The active channel claims all 8 RMT memory blocks (512 items), so long gates fit cleanly in the hardware loop buffer.
  • Boot defaults are sparse and safe for a live DUT: 5 pulses × 8 µs at 1 ms gate → ~925 Hz train rate, ~7 % average duty. Crank the OFFTIME knob up for sparser, down for denser.
  • Clean/precise to ~20 kHz; the RMT 12.5 ns grid + dither still works far higher but raw resolution coarsens with .
  • Connecting USB serial resets the board (CH340 auto-reset) — encoder values re-seed to defaults on every boot (normal for incremental encoders).
  • Extreme knob settings are clamped, not allowed to overflow/crash.
  • OFFSET encoder is the fine-frequency vernier — only active in precise mode (f<Hz>). In knob mode it intentionally does nothing.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages