esp32 beginner 25 min

ESP32: oscilloscope basics, what those waveforms actually mean

Read a scope like you read a multimeter. Volts per div, time per div, AC vs DC coupling, what bandwidth actually buys you, and how to measure PWM duty cycle.

Code available for: ESP32 ArduinoArduino C
Published Aug 26, 2026

The first time I borrowed an oscilloscope, I turned it on, looked at a blinking LED’s GPIO line, saw a square wave, and thought “great, that matches what I expected.” Then I tried to measure a PWM signal, the trace looked weird, and I spent an hour adjusting knobs before I understood what “volts per division” and “time per division” actually meant. This tutorial is the 30-minute version of what I learned that day.

You do not need an oscilloscope for most ESP32 projects. When you do need one, it is for power supply noise, PWM signal verification, analog sensor outputs, and signal integrity on long wires. This tutorial covers what a scope measures, the controls you need to know, and the workflows I use weekly.

What an oscilloscope measures

An oscilloscope plots voltage over time. One or more channels probe a point in your circuit; the scope samples the voltage at high rate and renders it on the screen. You see the actual waveform, not just “high” or “low.”

Two numbers define what a scope can do:

  • Bandwidth: the highest frequency signal the scope can measure accurately. A 100 MHz scope can show a 100 MHz sine wave at about 70% of its true amplitude (the -3 dB point). To measure a 100 MHz signal accurately, you want a scope with 3-5x the bandwidth.
  • Sample rate: how many voltage measurements the scope takes per second. A 1 GSa/s scope takes a billion samples per second. Rule of thumb: sample rate should be at least 4-5x the highest frequency in your signal so the waveform looks smooth.

For ESP32 work, signals are slow (PWM is at most a few MHz, I2C is typically 100-400 kHz, SPI is usually under 10 MHz). A 100 MHz scope with 1 GSa/s sample rate is plenty.

The cheap options

  • Rigol DS1054Z ($350 used, $400 new): the standard hobbyist scope. 4 channels, 50 MHz bandwidth (officially upgradeable to 100 MHz via a paid firmware key from Rigol for about $99), 1 GSa/s. The unofficial hack that unlocks 100 MHz without paying is well- documented online, but it technically violates the EULA, so do not do it; pay for the official upgrade. Most lab benches I know have one of these.
  • Siglent SDS1104 ($300): similar capability to the Rigol, 4 channels, 100 MHz, 1 GSa/s. Siglent is the more conservative Rigol alternative.
  • FNIRSI 1C15 ($100): a cheap handheld scope that is genuinely useful for low-frequency work. 1 channel, 100 MHz bandwidth, battery-powered. Good for field debugging where dragging a bench scope is not practical.
  • Hantek DSO5102P ($200): older 2-channel 100 MHz scope. Decent, but the UI is dated and the build quality is mediocre.
  • PicoScope 2204A ($150): USB scope, 2 channels, 10 MHz bandwidth. The bandwidth is low but the software is genuinely good. Good for laptop-based debugging.

For your first scope, the Rigol DS1054Z is the right pick. You will not outgrow it for a long time.

Voltage vs time (the axes)

Two knobs control what you see on the screen:

  • Volts per division (vertical axis): how many volts each square on the screen represents. Set this so the waveform fills most of the screen vertically. If your signal is 3.3V and volts per division is 1V, the waveform takes up about 3 squares of the screen. If volts per division is 5V, the waveform is two- thirds of one square and hard to read.
  • Time per division (horizontal axis): how many seconds each square on the screen represents. Set this so you see enough cycles of the waveform to understand its shape. If your signal is a 1 kHz square wave and time per division is 1 ms, you see one cycle per square. If time per division is 100 us, you see ten cycles per square.

The relationship: volts per division is the “vertical zoom,” time per division is the “horizontal zoom.” Adjust them independently until the waveform is clear.

The key controls (the ones you actually use)

There are 20+ knobs on a typical scope. Most people use four:

  1. Volts per division (per channel): sets the vertical scale.
  2. Time per division: sets the horizontal scale.
  3. Trigger level: sets the voltage at which the scope starts drawing the trace. Without a trigger, the waveform scrolls across the screen because the scope does not know where “the start” of a cycle is. With a trigger, the scope waits for the signal to cross the trigger voltage (e.g. the rising edge of a 3.3V signal crossing 1.65V), then draws the waveform anchored at that point. Set the trigger to the middle of your signal’s voltage range, on the channel you are probing, on the edge you want to align to.
  4. Run / Stop: starts or stops the capture. “Single” captures one trace and stops, useful for one-shot events.

That is it for 90% of debugging. Every other control is fine to ignore until you hit a specific problem.

AC vs DC coupling

The coupling selector on each channel is one of the most misunderstood controls. It has three positions:

  • DC coupling (default): the scope shows the actual voltage at the probe, including any DC offset. A 3.3V supply rail shows as a flat line at 3.3V. A PWM signal swings between 0V and 3.3V.
  • AC coupling: the scope blocks the DC component and only shows the AC (changing) part. A 3.3V supply rail shows as a flat line at 0V. A PWM signal with a 1.65V average shows the ripple around 0V. Use AC coupling to see small AC signals riding on a large DC offset (e.g. power supply ripple on a 3.3V rail).
  • GND: the scope disconnects the input and grounds it, so you see exactly where 0V is on the screen. Use this to verify the zero line before making measurements.

For most ESP32 work, DC coupling is correct. AC coupling is for measuring ripple on a power rail.

What a clean waveform looks like

A clean 1 kHz square wave from an ESP32 GPIO looks like:

  • Flat at 0V most of the time, then jumps to 3.3V, then falls back to 0V.
  • The rising edge is a vertical line (or close to it; in reality it has a small slope because the GPIO has finite drive strength).
  • The falling edge is the same in reverse.

A noisy or distorted square wave shows:

  • Ringing on the edges (a damped sine wave on the rising or falling transition, usually caused by inductance in the wire
    • capacitance in the load).
  • Overshoot (the signal goes above 3.3V on the rising edge before settling).
  • Undershoot (the signal goes below 0V on the falling edge).
  • Glitches (unexpected transitions in the middle of a “flat” region).

These are all symptoms of impedance mismatch, long wires, or missing decoupling. The scope shows them; Serial.print does not.

Measuring PWM duty cycle

PWM duty cycle is the percentage of time the signal is high in one cycle. To measure it:

  1. Probe the GPIO that outputs the PWM signal.
  2. Set time per division so you see 2-3 cycles of the waveform.
  3. Most scopes have a “Measure” menu with built-in duty cycle measurement. Pick the channel, pick “Duty Cycle,” read the percentage off the screen.

If your scope does not have auto-measure, use the cursors:

  • Cursor 1 on the rising edge of one cycle.
  • Cursor 2 on the falling edge of the same cycle.
  • Cursor 3 on the next rising edge (defines the period).
  • The ratio of (cursor2-cursor1) to (cursor3-cursor1) is the duty cycle.

The expected value: duty / 255 * 100 for 8-bit PWM, duty / 1023 * 100 for 10-bit PWM. If you see a value off by more than a few percent, the LEDC configuration is wrong.

Measuring rise time

Rise time is the time the signal takes to go from 10% to 90% of its final value. It is a measure of how fast the signal can change, and it tells you whether your GPIO drive strength is adequate for the load.

To measure rise time:

  1. Probe the signal.
  2. Trigger on the rising edge.
  3. Zoom in horizontally (time per division of 10 ns or 100 ns depending on the signal) so you can see the rising edge in detail.
  4. Use the cursors: one at the 10% point, one at the 90% point.
  5. Read the delta.

A clean ESP32 GPIO output has a rise time of a few nanoseconds. If you see 50 ns rise time, the load is heavier than the GPIO can drive quickly (e.g. long cable, big capacitor, weak pull-up). The fix is usually a series resistor, a smaller load, or a buffer.

What bandwidth means and why it matters

Bandwidth is the single most important spec on a scope. It determines what frequencies the scope can measure accurately.

If you probe a 100 MHz signal with a 100 MHz scope, the scope shows the signal at about 70% of its true amplitude. The waveform looks “softer” than it really is. Rise times appear longer than they really are.

Rule of thumb: scope bandwidth should be 3-5x the highest frequency in your signal. For a 1 MHz PWM signal, a 5 MHz scope is enough (and almost any scope is). For a 100 MHz SPI bus, a 300-500 MHz scope is the right pick.

For ESP32 work specifically, signals are slow. A 100 MHz scope handles anything the ESP32 can produce. The 50 MHz DS1054Z is fine for GPIO and I2C; you only need more bandwidth for analog signals or fast external buses.

Scope probes (1x vs 10x)

Most scope probes have a switch on them: 1x or 10x. This is the attenuation factor.

  • 1x probe: the signal goes straight to the scope. Easy, but the probe adds capacitance to your circuit (~100 pF), which can affect high-impedance signals.
  • 10x probe: the signal is attenuated 10x before reaching the scope. The scope’s volts per division effectively multiplies by 10 (most scopes do this automatically when you tell them you are using a 10x probe). The probe capacitance is much lower (~10 pF), so the loading on your circuit is much smaller.

For ESP32 GPIO work, 10x is the right pick. The signal is 3.3V which both probes can handle, but the 10x probe’s lower capacitance means you do not distort the signal you are trying to measure.

To configure the scope for a 10x probe, go to the channel menu and set “Probe Attenuation” to 10x. Most probes have a small calibration loop on them: connect the probe tip to the cal terminal on the scope front panel, set the scope to a known square wave, and adjust the trimmer capacitor on the probe until the waveform is a clean square (no overshoot, no rolloff). Do this once when you first get the probe.

When something breaks

  • “The scope shows nothing.” Check the probe ground clip. Check that the right channel is on. Check that the trigger level is reachable by the signal (if trigger is 5V and the signal is 3.3V, the scope never triggers and shows a flat line).
  • “The waveform scrolls across the screen.” Trigger is wrong. Set the trigger source to the channel you are probing, the trigger level to the middle of the signal, and the edge to rising or falling.
  • “The waveform looks fuzzy.” Either the probe is set wrong (1x vs 10x), the trigger is jittery (use “normal” trigger mode instead of “auto”), or the signal genuinely has noise.
  • “The voltage reading is wrong by 10x.” Probe attenuation mismatch. Set the scope to match the probe (1x or 10x).

What to build next

  • A test circuit: an ESP32 GPIO driving an LED through a 220-ohm resistor. Probe the GPIO. See the PWM waveform. Measure the duty cycle.
  • The esp32-logic-analyzer tutorial if you do not have a logic analyzer. Scope and logic analyzer are the two bench tools you need; the rest is optional.
  • A simple RC low-pass filter on a PWM output to generate a DAC. Probe before and after the filter; see the PWM rectangle become a smooth DC level. This is how ESP32 DAC-less PWM DAC works.