31  Signal Processing Labs

In 60 Seconds

These hands-on labs teach signal processing on real ESP32 hardware: configure ADC resolution (8/10/12-bit), implement digital filters (median for spikes, moving average for smoothing, EMA low-pass for gradual noise), and perform FFT frequency analysis to identify signal components. The key practical insight: combine filters in a pipeline (anti-alias analog filter, then median to remove outliers, then low-pass to smooth) and downsample to reduce data rate while preserving signal integrity.

31.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Configure ESP32 ADC resolution: Select and program 8-bit, 10-bit, or 12-bit quantization to match sensor accuracy requirements
  • Implement digital filters in firmware: Code moving average, median, and exponential moving average (EMA) low-pass filters on an ESP32
  • Analyse frequency spectra with FFT: Execute Fast Fourier Transform on sampled signals to isolate frequency components from noise
  • Construct multi-stage filter pipelines: Sequence anti-aliasing, median spike removal, low-pass smoothing, and downsampling into an end-to-end processing chain
  • Evaluate filter trade-offs: Compare median vs moving average performance on impulsive noise and justify filter selection for a given noise profile
  • Calculate ADC precision metrics: Derive LSB step size, usable quantization levels, and voltage precision from resolution and reference voltage parameters

Key Concepts

  • ADC resolution must match the sensor span: A 12-bit ADC only helps if the sensor uses a meaningful portion of the reference range.
  • Median filters remove spikes better than averages: Use median first when noise includes impulsive outliers, because averaging spreads spike damage into neighboring samples.
  • FFT choices are design choices, not defaults: Sampling rate, FFT length, and anti-aliasing determine whether the reported peaks are physically real.
  • Downsample only after filtering: If you reduce the sample rate before the signal is band-limited, high-frequency content aliases into the preserved band.
  • Pipelines beat single filters: In real embedded systems, the best outcome usually comes from a sequence such as anti-aliasing, median filtering, smoothing, and then decimation.
  • Lab success is measured on the target platform: CPU cost, RAM use, serial throughput, and timing jitter matter just as much as the math.

These hands-on labs let you experiment with signal processing on real hardware (the ESP32 microcontroller). You will read sensor values, apply filters to clean up noisy data, and analyze frequency patterns – all skills that are essential for building reliable IoT devices. Think of it as cooking class after learning the theory: you follow step-by-step recipes to see the concepts in action.

Prerequisites:

Practical:

31.2 Signal Processing Lab: ESP32 Wokwi Simulation

31.2.1 Interactive Lab Overview

This lab uses Wokwi ESP32 simulator - no physical hardware required! You’ll explore:

  1. ADC Resolution Effects: Compare 8-bit, 10-bit, and 12-bit quantization
  2. Filter Comparison: Test moving average, median, and low-pass filters
  3. FFT Frequency Analysis: Identify signal components and noise frequencies
  4. Multi-Stage Pipeline: Design complete signal processing chain

Time Required: 60-90 minutes (all exercises)

31.2.2 Getting Started with Wokwi

Access the Lab:

  1. Open Wokwi ESP32 Simulator
  2. Create new ESP32 project
  3. Copy lab code from exercises below
  4. Click “Run” to start simulation

Simulator Tips:

  • Use Serial Monitor to view output
  • Pause simulation to inspect values
  • Adjust potentiometer to change analog input
  • Reset to restart from beginning

31.3 Exercise 1: Understanding ADC Resolution

Objective: Compare 8-bit vs 12-bit ADC precision

Setup:

  • Connect potentiometer to GPIO34 (ADC1_CH6)
  • Read analog values at different resolutions
  • Calculate voltage from ADC reading

Code Template:

#include <Arduino.h>

void setup() {
    Serial.begin(115200);
    analogReadResolution(12);  // Change to 8, 10, or 12
    pinMode(34, INPUT);
}

void loop() {
    int adc_value = analogRead(34);
    // Adjust divisor when changing resolution: 255 (8-bit), 1023 (10-bit), 4095 (12-bit)
    float voltage = (adc_value / 4095.0) * 3.3;  // For 12-bit

    Serial.print("ADC: ");
    Serial.print(adc_value);
    Serial.print(" | Voltage: ");
    Serial.print(voltage, 4);  // 4 decimal places
    Serial.println(" V");

    delay(500);
}

Tasks:

  1. Run with 8-bit resolution (0-255 range)
  2. Run with 12-bit resolution (0-4095 range)
  3. Calculate step size for each: 3.3V / (2^bits - 1)
  4. Observe precision difference in Serial Monitor

Expected Results:

  • 8-bit: Step size = 12.9 mV, ±0.4% precision
  • 12-bit: Step size = 0.8 mV, ±0.025% precision

ADC resolution is governed by least-significant-bit (LSB) size.

\[ \text{LSB}=\frac{V_{ref}}{2^n-1} \]

Worked example: With \(V_{ref}=3.3\) V:

\[ \begin{aligned} \text{LSB}_{8bit}&=\frac{3.3}{255}=12.94\text{ mV}\\ \text{LSB}_{12bit}&=\frac{3.3}{4095}=0.806\text{ mV} \end{aligned} \]

For a 100 mV sensor signal span:

\[ \begin{aligned} \text{usable levels}_{8bit}&=\frac{100}{12.94}\approx 7.7\\ \text{usable levels}_{12bit}&=\frac{100}{0.806}\approx 124 \end{aligned} \]

12-bit ADC gives about \(124/7.7 \approx 16\times\) finer measurement granularity on the same signal.

Interactive ADC Resolution Calculator:

Question: If your sensor accuracy is ±1%, which ADC resolution is sufficient?


31.4 Exercise 2: Comparing Filter Types

Objective: Test median vs moving average filters on noisy data

Setup:

  • Simulated noisy temperature sensor
  • Inject random spikes to test outlier rejection
  • Compare filter outputs

Code Template:

#include <Arduino.h>

// 5-point moving average filter
float movingAverage(float newVal) {
    static float buf[5] = {0};
    static int idx = 0;
    buf[idx] = newVal;
    idx = (idx + 1) % 5;
    float sum = 0;
    for (int i = 0; i < 5; i++) sum += buf[i];
    return sum / 5.0;
}

// 5-point median filter (rejects spikes)
float medianFilter(float newVal) {
    static float buf[5] = {0};
    static int idx = 0;
    buf[idx] = newVal;
    idx = (idx + 1) % 5;
    float s[5];
    memcpy(s, buf, sizeof(buf));
    for (int i = 0; i < 4; i++)         // bubble sort
        for (int j = 0; j < 4-i; j++)
            if (s[j] > s[j+1]) { float t=s[j]; s[j]=s[j+1]; s[j+1]=t; }
    return s[2];  // middle value
}

void setup() { Serial.begin(115200); }

void loop() {
    float base = 23.5 + random(-10,10)/100.0;  // true + noise
    float spike = (random(0,100) < 5) ? 50.0 : 0; // 5% spike
    float raw = base + spike;
    Serial.printf("Raw: %.1f | Avg: %.1f | Med: %.1f\n",
                  raw, movingAverage(raw), medianFilter(raw));
    delay(200);
}

Tasks:

  1. Run simulation and observe outputs
  2. Note how moving average reacts to spikes
  3. Note how median filter handles spikes
  4. Which filter is better for spike rejection?

Expected Results:

  • Moving Average: Spikes corrupt output for 5 samples
  • Median Filter: Spikes have no effect if <50% of buffer
Check Your Understanding: Median vs Moving Average


31.5 Exercise 3: FFT Frequency Analysis

Objective: Use Fast Fourier Transform (FFT) to identify signal frequencies

Setup:

  • Generate composite signal (1 Hz + 10 Hz + noise)
  • Apply FFT to find frequency components
  • Visualize frequency spectrum

Code Template:

#include <Arduino.h>
#include "arduinoFFT.h"

#define SAMPLES 128
#define FS 100  // 100 Hz sampling rate

double vReal[SAMPLES], vImag[SAMPLES];
ArduinoFFT<double> FFT(vReal, vImag, SAMPLES, FS);

void setup() { Serial.begin(115200); }

void loop() {
    // Generate composite signal: 1 Hz + 10 Hz + noise
    for (int i = 0; i < SAMPLES; i++) {
        float t = i / (float)FS;
        vReal[i] = sin(2*PI*1.0*t) + 0.5*sin(2*PI*10.0*t)
                 + random(-10,10)/100.0;
        vImag[i] = 0;
    }
    // FFT: window -> transform -> magnitude
    FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.compute(FFT_FORWARD);
    FFT.complexToMagnitude();

    // Print spectrum and dominant frequency
    for (int i = 0; i < SAMPLES/2; i++)
        Serial.printf("%.1f Hz: %.2f\n", i*FS/(double)SAMPLES, vReal[i]);
    Serial.printf("Peak: %.2f Hz\n\n", FFT.majorPeak());
    delay(5000);
}

Tasks:

  1. Run FFT and observe frequency spectrum
  2. Identify peaks at 1 Hz and 10 Hz
  3. Change signal frequencies and re-run
  4. What happens if sampling rate is too low (aliasing)?

Expected Results:

  • Peaks at 1 Hz and 10 Hz visible
  • Noise spread across all frequencies
  • If fs < 20 Hz, aliasing occurs (10 Hz appears as lower frequency)

31.6 Exercise 4: Designing a Multi-Stage Filter Pipeline

Objective: Combine anti-aliasing, digital filtering, and downsampling

Setup:

  • Sample at 1000 Hz
  • Apply low-pass filter (cutoff 100 Hz)
  • Downsample to 250 Hz for transmission

Code Template:

#include <Arduino.h>

// Simple low-pass IIR filter (exponential moving average)
float lowPassFilter(float newValue, float alpha = 0.1) {
    static float filtered = 0;
    filtered = alpha * newValue + (1 - alpha) * filtered;
    return filtered;
}

int downsampleCounter = 0;

void setup() {
    Serial.begin(115200);
}

void loop() {
    // Sample at 1000 Hz (1 ms period)
    float rawValue = analogRead(34) * (3.3 / 4095.0);

    // Apply digital low-pass filter (smoothing before downsampling)
    float filtered = lowPassFilter(rawValue, 0.2);  // alpha=0.2 smoothing

    // Downsample 4:1 (1000 Hz → 250 Hz)
    downsampleCounter++;
    if (downsampleCounter >= 4) {
        downsampleCounter = 0;

        Serial.print("Filtered & Downsampled: ");
        Serial.println(filtered, 4);
    }

    delayMicroseconds(1000);  // 1 kHz sampling
}

Tasks:

  1. Implement pipeline and observe output
  2. Try removing low-pass filter - what happens?
  3. Try different downsample ratios (2:1, 8:1)
  4. Calculate data rate reduction (1000 Hz → 250 Hz = 75% savings)

Expected Results:

  • Smooth signal at 250 Hz output rate
  • High-frequency noise removed
  • 4× reduction in data transmission

31.7 Learning Outcomes Checklist

After completing the labs, you should be able to:


31.8 Key Takeaways from the Lab

The most effective signal processing pipeline combines multiple filter stages: median filter first (removes spikes and outliers), then low-pass or moving average (smooths remaining noise), then downsample (reduces data rate). Specific insights:

  1. ADC Resolution: 12-bit is sufficient for most IoT sensors (±0.025% precision)
  2. Filter Selection: Use median for spikes, low-pass for gradual noise
  3. FFT Analysis: Reveals hidden frequency components and noise sources
  4. Pipeline Design: Combine filters and downsampling to reduce data rate (up to 80% noise reduction)
  5. Practical Constraints: Balance precision, speed, and power consumption

The Sensor Squad went to the lab for hands-on experiments!

Sammy the Sensor was reading a noisy temperature signal. “Help! My readings are jumping between 24, 25, 99, 24, 26 degrees! That 99 is clearly wrong!”

Max the Microcontroller pulled out his filter toolkit. “First, let’s use a median filter – it sorts your last 5 readings and picks the middle one. The crazy 99 gets thrown out!” The display now showed: 24, 25, 25, 24, 25.

“Still a bit bumpy,” noted Lila the LED. Max applied a moving average: “Now I average the last 3 readings together. Smooth as butter!” The display showed: 24.7, 24.7, 24.7.

Bella the Battery was impressed: “And by cleaning the data HERE instead of sending all the noisy readings to the cloud, we save energy! Smart processing at the edge means fewer transmissions!”

Max held up his FFT analyzer: “And THIS tool shows us the hidden frequencies in the signal – like X-ray vision for data! It tells us WHERE the noise comes from so we can fix the source.”


31.9 Knowledge Check

Q1: When building a multi-stage filter pipeline, what is the recommended order?

  1. Low-pass filter, then median filter, then anti-aliasing
  2. Anti-aliasing (analog), then median filter (remove spikes), then low-pass (smooth) correct
  3. FFT analysis, then all filters at once
  4. No specific order matters

Answer: B) Anti-aliasing, median, then low-pass. The analog anti-aliasing filter must come first (before ADC) to prevent aliasing. Then median filter removes spike outliers that would corrupt subsequent averaging. Finally, low-pass smoothing removes remaining gradual noise. Order matters because each stage prepares the signal for the next.

Q2: An ESP32 ADC reads a stable 3.3V reference but values fluctuate between 4050-4095. What does this indicate?

  1. The ADC is broken
  2. Normal ADC noise characteristic of the ESP32 (approximately ±20 LSB is typical for the ESP32’s SAR ADC) correct
  3. The reference voltage is unstable
  4. The sample rate is too low

Answer: B) Normal ESP32 ADC noise. The ESP32 uses a successive-approximation (SAR) ADC that is known for relatively high noise levels – fluctuations of ±10-30 LSB are typical and documented by Espressif. The readings 4050-4095 represent approximately 3.26-3.30V, which is within the expected noise envelope for a 3.3V input. Averaging or applying a digital low-pass filter across multiple samples significantly reduces this noise.

Match each filter type to its primary strength:

Order the steps to build a complete signal processing pipeline (from raw sensor input to clean, downsampled output):


31.10 Decision Framework: Choosing the Right Filter Pipeline

In the labs, you implemented several filter types. In real projects, you’ll often combine multiple filters in a pipeline. This framework helps you choose the right sequence.

Filter Pipeline Design Table:

  • Impulsive spikes + Gaussian noise: median filter (5-point) -> moving average (8-point) -> decimate 4:1. Use for temperature sensors with EMI spikes.
  • High-frequency noise only: skip outlier removal -> low-pass IIR (alpha = 0.2) -> decimate 2:1. Use for accelerometers with vibration noise.
  • 50/60 Hz power-line interference: skip outlier removal -> notch filter at 50 Hz -> keep full rate. Use for ECG or audio signals near mains wiring.
  • Slow drift + spikes: median filter (3-point) -> high-pass filter for DC drift removal -> keep full rate. Use for load cells and slowly drifting analog sensors.

Example: Smart Thermostat (Temperature + Humidity)

Noise profile:

  • Occasional spikes from HVAC cycling (±5°C for 1 sample)
  • Slow Gaussian noise (±0.2°C standard deviation)
  • Very slow real temperature changes (0.1°C/minute)

Optimal pipeline:

1. Median filter (5-point window)
   → Removes spikes without affecting true signal

2. Exponential moving average (alpha = 0.1)
   → Smooths Gaussian noise (70% reduction)
   → 0.1 coefficient gives ~10-sample effective window

3. Downsample 10:1 (1 Hz → 0.1 Hz)
   → Temperature changes slower than 0.1 Hz
   → Saves 90% transmission bandwidth

Why NOT other combinations:

  • Moving average alone: Spikes corrupt 5 consecutive samples
  • EMA alone: Spikes cause exponential decay artifacts
  • No downsampling: Wastes energy transmitting 10 nearly-identical readings

Quick Decision Tree:

  • If impulsive spikes are present, start with a median filter of 3 to 5 samples.
  • If the remaining noise is broadband, use a moving average or low-pass IIR.
  • If the noise is a single interfering frequency, use a notch filter.
  • If the dominant problem is slow drift, add a high-pass stage.
  • Downsample only when the preserved signal bandwidth is comfortably below the new Nyquist limit.

Key Takeaway: No single filter solves all problems. Real systems combine 2-4 filter stages tailored to specific noise characteristics. From Lab Exercise 4, you learned that a pipeline (median → low-pass → downsample) achieved ~80% noise reduction — far better than any single technique alone.


Concept Relationships: Signal Processing Labs
  • Median filter -> Spike removal: Eliminates one-sample outliers without smearing them into neighboring readings.
  • EMA -> Noise smoothing: Trades some responsiveness for cleaner trends using exponentially decaying weight on older samples.
  • FFT -> Frequency analysis: Exposes periodic content that is difficult to diagnose from raw time-domain samples alone.
  • Filter pipeline -> Multi-stage processing: Combining median, smoothing, and downsampling can remove noise more effectively than any one stage by itself.

Cross-module connection: Multi-Sensor Fusion shows how to combine filtered signals from multiple sensors.

31.11 See Also

Common Pitfalls

A lab sketch that looks correct in Wokwi can still miss deadlines on real hardware if the filter, FFT, or serial output takes too long. Measure loop timing, memory use, and actual sample cadence before assuming the design scales.

Digital filters cannot recover a signal that was sampled too slowly or without analog anti-alias protection. If unexpected peaks appear in the FFT, check the analog front end and sample rate before piling on more software filtering.

Moving averages are good for broadband noise, but they are poor at rejecting spikes. Median filters reject spikes, but they do not remove narrowband interference. Match the filter to the noise pattern you actually observe.

31.12 What’s Next