RisalDash RisalDash

Project · Instrumentation · ADC

A pocket scope from one ADC pin

You don't always need a $300 instrument. For “is this signal alive, how big is it, what frequency?” an ESP32's own 12-bit ADC answers all three: burst-sample the pin, do three loops of maths, and read Vpp, Vrms and frequency off a live dashboard. Not a Rigol — but a real measurement tool that fits in a matchbox.

ProjectInstrumentationADCESP32
ESP32 oscilloscope project: Vpp, Vrms and frequency measured on one ADC pin

First: don't cook the pin

The ADC tolerates 0–3.3 V, nothing else. Real-world signals swing negative and spike — so the front-end matters more than the code: a coupling capacitor into a two-resistor mid-rail bias (so AC swings around 1.65 V), and a pair of clamping diodes to 3V3 and GND for everything unexpected. Also: use an ADC1 pin (GPIO32–39) — ADC2 stops working the moment Wi-Fi is on.

The maths — three loops, no FFT needed

For “what is this signal”, time-domain maths beats reaching for an FFT: min/max gives Vpp, the mean-removed RMS gives Vrms, and counting rising zero-crossings of the mean-removed signal gives frequency — robust even on a noisy trace:

// --- the scope maths: mean, Vpp, Vrms, zero-cross frequency ---
float mean = 0, mn = 99, mx = -99;
for (int i = 0; i < N; i++) { mean += buf[i]; if (buf[i] < mn) mn = buf[i]; if (buf[i] > mx) mx = buf[i]; }
mean /= N;
float sq = 0; int crossings = 0;
for (int i = 1; i < N; i++) {
  float a = buf[i - 1] - mean, b = buf[i] - mean;
  sq += b * b;
  if (a < 0 && b >= 0) crossings++;              // rising zero-crossings
}
vpp  = mx - mn;
vrms = sqrtf(sq / (N - 1));
freq = crossings * SPS / N;                      // crossings per second

Sample-rate honesty: plain analogRead() reaches roughly 10 kSps — by Nyquist that's clean up to a few kHz, perfect for mains hum, audio and sensor waveforms. Need more? The ESP32's I2S/DMA ADC path streams 50 kSps+ without the CPU touching each sample — same maths, bigger buffer.

The dashboard

dash.layout("Scope", RICON_SIGNAL);
dash.stat("Frequency", &freq, "Hz");
dash.stat("Vpp", &vpp, "V");
dash.stat("Vrms", &vrms, "V");
dash.chart("Signal level", &level, "V");   // the live "screen"
dash.beginAP("RisalDash-Scope", "12345678");

// acquire a burst — real board:
// for (int i = 0; i < N; i++) buf[i] = analogRead(PIN) * 3.3f / 4095.0f;

The shipped example (examples/04_Templates/Oscilloscope) runs on a synthetic 45–55 Hz sine + noise, so you can watch the frequency counter track the drift before any wire is connected — then swap the demo loop for analogRead(). Add the battery-monitor divider from the battery guide and the same pin measures DC rails too.

What it's honestly good for

TaskVerdict
Is this sensor/oscillator alive?✅ perfect
Mains hum, audio-range signals✅ clean measurements
PWM duty & frequency up to ~5 kHz✅ works
MHz digital buses, µs edges❌ get a logic analyser / real scope

Watch the maths track a drifting signal before you wire anything.