70  Signal Processing Labs and Assessments

70.1 Learning Objectives

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

  • Configure ESP32 ADC: Set up analog-to-digital conversion with appropriate resolution and sampling rate
  • Implement Digital Filters: Apply moving average, median, and low-pass filters in firmware
  • Perform FFT Analysis: Use frequency analysis to identify signal components and noise
  • Design Filter Pipelines: Combine anti-aliasing, digital filters, and downsampling for optimal performance
  • Validate Understanding: Demonstrate mastery through hands-on exercises and knowledge checks

Prerequisites: - Signal Processing Overview - Sampling and Nyquist theorem - Aliasing and ADC - Quantization and filtering - Voice Compression - Audio signal processing - Sensor Dynamics - Temporal response

Practical: - Hardware Prototyping - ESP32 setup - Sensor Labs - Additional sensor exercises

70.2 Prerequisites

  • All previous signal processing chapters: This is the capstone practical exercise
  • ESP32 familiarity: Basic understanding of microcontroller programming
  • C programming: Variables, arrays, loops, functions

70.3 Signal Processing Lab: ESP32 Wokwi Simulation

70.3.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)

70.3.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


70.4 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);
    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

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


70.5 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
float movingAverage(float newValue) {
    static float buffer[5] = {0};
    static int index = 0;

    buffer[index] = newValue;
    index = (index + 1) % 5;

    float sum = 0;
    for (int i = 0; i < 5; i++) sum += buffer[i];
    return sum / 5.0;
}

// 5-point median filter
float medianFilter(float newValue) {
    static float buffer[5] = {0};
    static int index = 0;

    buffer[index] = newValue;
    index = (index + 1) % 5;

    // Sort (bubble sort for simplicity)
    float sorted[5];
    memcpy(sorted, buffer, sizeof(buffer));
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4-i; j++) {
            if (sorted[j] > sorted[j+1]) {
                float temp = sorted[j];
                sorted[j] = sorted[j+1];
                sorted[j+1] = temp;
            }
        }
    }
    return sorted[2];  // Middle value
}

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

void loop() {
    // Simulated sensor with noise and occasional spike
    float trueValue = 23.5;
    float noise = random(-10, 10) / 100.0;  // Β±0.1Β°C
    float spike = (random(0, 100) < 5) ? 50.0 : 0;  // 5% chance of spike
    float reading = trueValue + noise + spike;

    float avg = movingAverage(reading);
    float med = medianFilter(reading);

    Serial.print("Raw: ");
    Serial.print(reading);
    Serial.print(" | Avg: ");
    Serial.print(avg);
    Serial.print(" | Median: ");
    Serial.println(med);

    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


70.6 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 SAMPLING_FREQ 100  // 100 Hz

double vReal[SAMPLES];
double vImag[SAMPLES];

ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, SAMPLES, SAMPLING_FREQ);

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

void loop() {
    // Generate composite signal
    for (int i = 0; i < SAMPLES; i++) {
        float t = i / (float)SAMPLING_FREQ;
        vReal[i] = sin(2.0 * PI * 1.0 * t)   // 1 Hz component
                 + 0.5 * sin(2.0 * PI * 10.0 * t)  // 10 Hz component
                 + (random(-10, 10) / 100.0);  // Noise
        vImag[i] = 0;
    }

    // Perform FFT
    FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.compute(FFT_FORWARD);
    FFT.complexToMagnitude();

    // Find peak frequency
    double peak = FFT.majorPeak();

    Serial.println("Frequency Spectrum:");
    for (int i = 0; i < SAMPLES/2; i++) {
        double freq = (i * SAMPLING_FREQ) / (double)SAMPLES;
        Serial.print(freq, 2);
        Serial.print(" Hz: ");
        Serial.println(vReal[i], 2);
    }

    Serial.print("Major Peak at: ");
    Serial.print(peak, 2);
    Serial.println(" Hz\n");

    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)


70.7 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 low-pass filter (anti-aliasing)
    float filtered = lowPassFilter(rawValue, 0.2);  // ~80 Hz cutoff

    // 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


70.8 Learning Outcomes Checklist

After completing the labs, you should be able to:


70.9 Key Takeaways from the Lab

  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
  5. Practical Constraints: Balance precision, speed, and power consumption

70.10 What’s Next

Continue Learning: - Sensor Labs: Apply signal processing to real sensors - Multi-Sensor Fusion: Combine signals from multiple sources - Edge Computing: Process signals locally to reduce cloud bandwidth

Challenge Projects: 1. Build a vibration monitor using accelerometer + FFT 2. Create a smart thermostat with filtering and deadband control 3. Design a water quality sensor with multi-parameter fusion

Advanced Topics: - Kalman filtering for sensor fusion - Adaptive filters for time-varying noise - Machine learning on sensor data (feature extraction from FFT)