← SHEET 02 · ASSEMBLIES EL-007

ESP32 Web Oscilloscope

ELEMBEDDED
LIVE DRAWING — HOVER OR DRAG TO CRANK · BUILT FROM THE REAL PLANT PARAMETERS
PART NOEL-007
MATL / SYSTEMESP32 · 12-BIT ADC · WIFI SOFTAP
TOOLSArduino IDE · Wokwi · C++ · JS

A browser-based scope that lives entirely on one ESP32. A hardware timer fires an ISR at a fixed rate, pulls a 12-bit ADC sample into a ring buffer, and checks it against a rising-edge trigger; once armed and fired, a pre-trigger + post-trigger window is streamed as a binary WebSocket frame to whatever laptop or phone joined the chip’s own SoftAP, where a <canvas> draws the trace. No PC software, no drivers, no USB — connect to the WiFi network and open a page. It’s built for hobby-level signals (0–3.3 V, DC-coupled) and stated honestly: this is not a real scope, bandwidth tops out around a few kHz, and the SAR ADC has known nonlinearity.

OVERVIEW & MOTIVATION

Most “I need to see a waveform” situations in hobby electronics don’t need a real scope’s bandwidth or accuracy — they need to know whether a signal is toggling, roughly what shape it has, and where it crosses a level. An ESP32 already has a 12-bit ADC, a CPU fast enough to run a timed sampling loop, and a radio to serve a web page — so the whole front end (sampling, triggering, transport, and display) fits on one chip with almost no external hardware. The design goal was a minimal, self-contained “good enough” scope: timer-driven sampling for a known, steady rate; a ring buffer so a trigger event can keep samples from before it fired; and a WebSocket stream into a canvas UI so there’s nothing to install on the viewing device.

COMPONENTS & BOM

The point of the design is that it’s nearly component-free — the ESP32 does essentially everything; the only external parts are a probe-protection network.

REFCOMPONENTSPECROLE
U1ESP32 DevKit (WROOM-32)ESP32-D0WDQ6, 12-bit SAR ADC, WiFisampling, triggering, SoftAP + WebSocket server
R1series resistor~1 kΩcurrent-limit into the clamp network, crude anti-alias with stray C
D1clamp diode1N4148 (or similar), to 3V3clips excursions above the 3.3 V rail
D2clamp diode1N4148, to GNDclips excursions below 0 V

WIRING

PROBE R1 1k GPIO34 D1 3V3 D2 GND ESP32 DEVKIT-C ADC1_CH6 · TIMER ISR RING BUF + TRIGGER HTTP :80 / WS :81 SOFTAP: ESP32-SCOPE WIFI SOFTAP WIFI CLIENT BROWSER: CANVAS UI ws://192.168.4.1:81 R1 + D1/D2 CLAMP PROBE TO 0-3.3V
ESP32 PINNETROLE
GPIO34PROBE_ADCADC1_CHANNEL_6, input-only — sample source for the ring buffer
GPIO2STATUS_LEDon-board LED (most DevKit boards), toggles per captured frame
3V3PROTO_3V3upper clamp rail for the probe protection network (D1)
GNDPROTO_GNDlower clamp rail (D2) + common return

ADC1 (not ADC2) is used deliberately: ADC2 shares hardware with the WiFi radio and reads unreliably once the SoftAP is running.

FIRMWARE

Sampling runs entirely inside a hardware-timer ISR so the rate is independent of whatever the WiFi/HTTP/WebSocket stack is doing in loop(). The ISR reads the ADC through adc1_get_raw() (a direct register read, fast enough for a 10 kHz ISR), writes into a power-of-two ring buffer, and does the trigger comparison inline:

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  uint16_t sample = (uint16_t)adc1_get_raw(PROBE_ADC_CH);
  ring[ringHead] = sample;
  if (armed) {
    if (prevSample < triggerLevel && sample >= triggerLevel) {
      armed = false;
      triggerHead = ringHead;
      postCount = FRAME_SAMPLES - PRETRIG_SAMPLES;
    }
  } else if (postCount > 0 && --postCount == 0) {
    frameReady = true;
  }
  prevSample = sample;
  ringHead = (ringHead + 1) & RING_MASK;
  portEXIT_CRITICAL_ISR(&timerMux);
}

A capture frame is 1024 samples: 256 pre-trigger (already sitting in the ring buffer when the edge fires) and 768 post-trigger, so the trace shows the signal approaching the trigger point, not just after it. loop() only handles the parts that can tolerate jitter — serving the HTTP page (WebServer, built into the ESP32 core) and pumping the WebSocket (WebSocketsServer by Markus Sattler / Links2004, arduinoWebSockets in the Library Manager) — and copies the windowed slice out of the ring buffer once frameReady is set:

if (frameReady) {
  uint32_t startIdx = (triggerHead - PRETRIG_SAMPLES) & RING_MASK;
  frameReady = false;
  for (int i = 0; i < FRAME_SAMPLES; i++) frame[i] = ring[(startIdx + i) & RING_MASK];
  wsServer.broadcastBIN((uint8_t*)frame, sizeof(frame));
}

Frames go out as raw binary (Uint16Array on the JS side) — no per-sample text parsing. On connect, the firmware sends one text/JSON message ({"fs":10000,"bits":12,"n":1024,"pretrig":256}) so the browser knows the sample rate, resolution, frame length, and where the trigger sits in the frame without hardcoding it client-side. The trigger-level slider sends a small text command back ("L2048") that the firmware parses and applies inside a critical section. The UI itself is a single HTML/JS/CSS page stored as a PROGMEM string and served at / — the canvas draws a grid, a red pre-trigger marker, and the trace scaled from raw 12-bit counts to 0–3.3 V.

SIMULATION

Wokwi’s ESP32 model runs real WiFi (including SoftAP) inside the simulator, so the transport half of this project — HTTP page, WebSocket handshake, binary frames, trigger-level commands — sim-checks end to end. What it can’t do is generate a fast analog waveform: diagram.json substitutes a potentiometer on GPIO34 (wiper → D34, ends → 3V3/GND) as a hand-turned, slow signal source. Steps: open the project on wokwi.com, load diagram.json, paste in sketch.ino, add the WebSockets (Links2004) library through the Library Manager, and run — the Serial Monitor prints the SoftAP IP once it’s up. Since a host browser can’t join a simulated WiFi network directly, Wokwi forwards the sim’s HTTP/WebSocket traffic through its own network-gateway URL for the running project; open that forwarded address to reach the same / page and :81 socket a real WiFi client would see. Turning the potentiometer slowly across the trigger-level slider’s value produces a capture frame and redraws the canvas — confirming the pipeline, not the 10 kS/s / kHz-bandwidth targets, which need real hardware and a real signal generator to check.

STATUS

Design and simulation only — no bench build. The sketch is written against a concrete timer configuration (100 µs alarm period → 10 kS/s target, 12-bit ADC width), but that number is a design target read off the timer setup, not a measured sample rate; nothing here has been scoped against a reference signal or compared to a real oscilloscope. Bandwidth (“a few kHz”) is likewise an estimate from the R1/stray-capacitance rough low-pass and the ADC’s known settling behavior, not a bench-measured -3 dB point. The Wokwi sim exercises the WiFi/WebSocket/UI pipeline at slow potentiometer-turn rates; it does not validate timing at the design sample rate. diagram.json parses as valid JSON and mirrors the pinout used in the firmware and the wiring diagram above. Not yet built: physical assembly, R1/D1/D2 protection network on a real board, and any bench verification of sample rate, trigger accuracy, or ADC linearity.

USE CASES & APPLICATIONS

Field debugging where a real scope isn’t on hand — checking whether a GPIO is toggling, whether a sensor’s analog output looks sane, or roughly where a signal crosses a threshold, using only a phone or laptop already carried around. Classroom or workshop signal demos: cheap enough to hand a student a board and a browser tab instead of scope time. Slow-changing analog logging (battery voltage sag, a sensor drifting over minutes) fits the same trigger-and-stream pipeline even better than fast signals do, since the ADC nonlinearity and kHz-class bandwidth limits matter less at DC-ish rates.

FILES

  • sketch.ino — full firmware: timer ISR sampling, ring buffer, rising-edge trigger with pre-trigger capture, SoftAP, WebServer-served HTML/JS UI, and WebSocketsServer-streamed binary frames. Flash via Arduino IDE with the ESP32 board package and the WebSockets (Links2004) library installed.
  • diagram.json — Wokwi wiring: ESP32 DevKit-C v4 + a potentiometer standing in for the probe signal on GPIO34.
  • README.md — Wokwi run steps (including the simulator’s network-gateway URL for reaching the served page) and real-probe protection notes (series R
    • clamp diodes, DC-coupled 0–3.3 V only).

← BACK TO ASSEMBLIES

NAME ODILBEK MARIMOV
DWG NO. PF-2026
SHEET 01 / 07
DISCIPLINE ROBOTICS / MECHATRONICS
SCALE 1:1
REV A
THIRD-ANGLE PROJECTION
DATE 2026-07-11
UNITS mm