25  Hands-On Sensor Labs

In 60 Seconds

These hands-on labs cover the four most common sensor types: DHT22 (temperature/humidity with 2-second read intervals), LDR with voltage divider (analog light level triggering an LED), HC-SR04 ultrasonic distance (using time-of-flight with speed of sound), and a complete ESP32 monitoring system with data validation and moving average filtering. Always validate readings for NaN and range errors before using them.

Key Concepts
  • Lab Safety Protocol: Rules for safe electronics lab work: never power a circuit before verifying wiring, disconnect power before modifying circuits, use current-limiting resistors, and never apply voltages exceeding component ratings
  • Component Identification: Reading component markings — resistor color bands, capacitor SMD codes, IC package part numbers — to verify the correct part is installed before powering the circuit
  • Serial Debug Output: Using Serial.println() in Arduino/ESP32 firmware to stream real-time sensor values to the IDE’s Serial Monitor; the primary tool for verifying sensor behavior without an oscilloscope
  • Wokwi Online Simulator: Browser-based electronics simulator supporting ESP32, Arduino Uno, and Raspberry Pi Pico with real-time simulation and sensor library support, eliminating hardware requirements for introductory labs
  • Sensor Library Initialization: The begin() or init() call required to initialize an I2C or SPI sensor before reading values; if this returns false, the sensor is not communicating — check wiring and I2C address before debugging firmware
  • Lab Notebook Practice: Documenting circuit diagrams, firmware code, raw readings, and observations during each session; enables reproducibility, systematic troubleshooting, and evidence of progress
  • Polling vs. Interrupt-Driven Sensing: Polling continuously reads a sensor in the main loop; interrupt-driven sensing triggers a read only when the sensor signals new data, reducing CPU load and enabling lower power consumption
  • LED Status Indicators: Using LEDs to signal sensor states (data ready, error, calibration mode) during debugging; a blinking LED confirms the MCU is running even when serial output is unavailable

Learning Objectives

After completing this chapter, you will be able to:

  • Configure and acquire readings from common IoT sensors (DHT22, LDR, HC-SR04)
  • Wire sensors to ESP32 microcontrollers with correct voltage levels and pull-up resistors
  • Implement data acquisition routines with validation and moving average filtering
  • Diagnose and resolve common sensor connection failures using systematic troubleshooting

These labs let you connect real sensors (like a temperature and humidity sensor or a distance sensor) to a microcontroller and see live data on your screen. You will use an online simulator, so you can experiment without buying any hardware. Think of it as a virtual electronics workbench where mistakes cost nothing and you can try things over and over until they work.

25.1 Prerequisites

All four labs below use the Wokwi simulator for safe experimentation without physical hardware.

25.2 Lab 1: Read Environmental Data (DHT22)

Objective: Read temperature and humidity from a DHT22 sensor.

Hardware Setup:

  • ESP32 DevKit
  • DHT22 sensor
  • 10 kΩ pull-up resistor (data line to VCC)

Wiring: | DHT22 Pin | ESP32 Pin | |———–|———–| | VCC (1) | 3.3V | | Data (2) | GPIO4 | | NC (3) | - | | GND (4) | GND |

Code:

#include "DHT.h"

#define DHTPIN 4
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(115200);
  dht.begin();
  Serial.println("DHT22 Sensor Test");
}

void loop() {
  delay(2000);  // DHT22 needs 2 seconds between readings

  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();

  if (isnan(humidity) || isnan(temperature)) {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  Serial.print("Temperature: ");
  Serial.print(temperature);
  Serial.print(" C, Humidity: ");
  Serial.print(humidity);
  Serial.println(" %");
}

Try It: Modify the code to calculate heat index using the dht.computeHeatIndex() function.

25.3 Lab 2: Light-Activated System (LDR)

Objective: Build a light sensor that triggers an LED when it gets dark.

Hardware Setup:

  • ESP32 DevKit
  • LDR (photoresistor)
  • 10 kΩ resistor (voltage divider)
  • LED + 220 Ω resistor

Wiring:

3.3V ---[LDR]---+---[10k]--- GND
                |
              GPIO34 (Analog input)

GPIO2 ---[220R]---[LED]--- GND

Code:

#define LDR_PIN 34
#define LED_PIN 2
// ESP32 ADC is 12-bit: values range from 0 (dark) to 4095 (bright)
#define DARK_THRESHOLD 1000  // Adjust based on your environment

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  int lightLevel = analogRead(LDR_PIN);

  Serial.print("Light level: ");
  Serial.println(lightLevel);

  if (lightLevel < DARK_THRESHOLD) {
    digitalWrite(LED_PIN, HIGH);  // Turn on LED when dark
    Serial.println("Dark - LED ON");
  } else {
    digitalWrite(LED_PIN, LOW);   // Turn off LED when light
    Serial.println("Light - LED OFF");
  }

  delay(500);
}

25.4 Lab 3: Ultrasonic Distance Measurement (HC-SR04)

Objective: Measure distance using ultrasonic sensor.

Hardware Setup:

  • ESP32 DevKit
  • HC-SR04 ultrasonic sensor
  • Voltage divider for ECHO pin (5V to 3.3V)

Wiring: | HC-SR04 Pin | ESP32 Pin | Notes | |————-|———–|——-| | VCC | 5V | Needs 5V power | | TRIG | GPIO5 | Trigger pulse | | ECHO | GPIO18 | Via voltage divider! | | GND | GND | |

Voltage Divider for ECHO:

ECHO ---[10k]---+---[20k]--- GND
                |
              GPIO18

Code:

#define TRIG_PIN 5
#define ECHO_PIN 18

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
}

float measureDistance() {
  // Send trigger pulse
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // Measure echo duration
  long duration = pulseIn(ECHO_PIN, HIGH, 30000);  // 30ms timeout

  // Calculate distance (speed of sound = 343 m/s)
  // distance = (duration * 0.0343) / 2
  float distance = (duration * 0.0343) / 2;

  return distance;
}

void loop() {
  float distance = measureDistance();

  if (distance > 0) {
    Serial.print("Distance: ");
    Serial.print(distance);
    Serial.println(" cm");
  } else {
    Serial.println("Out of range or no echo");
  }

  delay(100);
}

25.4.1 Ultrasonic Distance Calculator

Use this calculator to convert echo duration to distance, or explore how temperature affects measurements.

25.5 Lab 4: ESP32 Temperature Monitoring with DHT22

Lab 1 showed you basic DHT22 reads. This lab builds on that foundation by adding data validation and moving average filtering – two techniques essential for any production sensor system.

Objective: Create a complete temperature monitoring system with data validation.

Hardware Setup: Same wiring as Lab 1 (DHT22 on GPIO4 with 10 kΩ pull-up resistor).

Features:

  • Read temperature and humidity
  • Validate readings (check for NaN, range errors)
  • Apply moving average filter
  • Display on Serial and (optionally) OLED

Code:

#include "DHT.h"

#define DHTPIN 4
#define DHTTYPE DHT22
#define FILTER_SIZE 5

DHT dht(DHTPIN, DHTTYPE);

float tempReadings[FILTER_SIZE];
float humReadings[FILTER_SIZE];
int readIndex = 0;

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

  // Initialize filter arrays (first FILTER_SIZE readings will
  // average with zeros until the buffer fills completely)
  for (int i = 0; i < FILTER_SIZE; i++) {
    tempReadings[i] = 0;
    humReadings[i] = 0;
  }

  Serial.println("Temperature Monitor Started");
  delay(2000);  // DHT22 warm-up
}

bool validateReading(float temp, float hum) {
  // Check for NaN
  if (isnan(temp) || isnan(hum)) return false;

  // Check for reasonable range
  if (temp < -40 || temp > 80) return false;
  if (hum < 0 || hum > 100) return false;

  return true;
}

float getFilteredValue(float readings[], int size) {
  float sum = 0;
  for (int i = 0; i < size; i++) {
    sum += readings[i];
  }
  return sum / size;
}

void loop() {
  delay(2000);  // DHT22 requires 2s between readings

  float temp = dht.readTemperature();
  float hum = dht.readHumidity();

  if (!validateReading(temp, hum)) {
    Serial.println("Invalid reading - skipping");
    return;
  }

  // Add to filter buffer
  tempReadings[readIndex] = temp;
  humReadings[readIndex] = hum;
  readIndex = (readIndex + 1) % FILTER_SIZE;

  // Get filtered values
  float filteredTemp = getFilteredValue(tempReadings, FILTER_SIZE);
  float filteredHum = getFilteredValue(humReadings, FILTER_SIZE);

  // Output
  Serial.print("Raw: ");
  Serial.print(temp);
  Serial.print("C, ");
  Serial.print(hum);
  Serial.print("% | Filtered: ");
  Serial.print(filteredTemp);
  Serial.print("C, ");
  Serial.print(filteredHum);
  Serial.println("%");
}

25.6 Troubleshooting Common Issues

Problem Likely Cause Solution
NaN readings Wiring issue, no pull-up Check connections, add pull-up resistor
Constant zero Wrong pin number Verify pin definitions match wiring
Random spikes No filtering, noise Add moving average or median filter
I2C not detected Wrong address, no pull-ups Scan with I2C scanner, add 4.7k pull-ups
Slow response Reading too fast Respect minimum sampling interval
5V sensor on 3.3V MCU Voltage mismatch Add level shifter or voltage divider
Key Takeaway

Hands-on sensor work follows a consistent pattern: check wiring and voltage levels first, add required pull-up resistors, initialize the sensor in code, read data with proper timing, validate readings (NaN and range checks), and apply filtering for stable output. Most sensor problems are hardware issues (loose wires, missing pull-ups, voltage mismatch), not software bugs.

It is LAB TIME! The Sensor Squad is rolling up their sleeves to build real projects!

Lab 1: Sammy the Sensor (DHT22) measures temperature and humidity. “Wire me to GPIO pin 4 with a 10k pull-up resistor, and I will tell you the weather in your room!” Remember: Sammy needs 2 seconds between readings – he is thorough, not fast!

Lab 2: Lila the LED and her friend the LDR (light-dependent resistor) work together. “When it gets dark, the LDR’s resistance goes UP, the voltage drops, and I turn ON to light up the room! It is like an automatic night-light!”

Lab 3: Ulti the Ultrasonic Sensor measures distance. “I send a tiny BEEP (too high for you to hear) and time how long the echo takes to come back. If it takes 580 microseconds, the object is about 10 centimeters away!”

Max the Microcontroller reminded everyone of the golden rules: 1. “Check your wires FIRST – most problems are bad connections!” 2. “ALWAYS validate readings – if I get NaN (Not a Number), I skip it and try again!” 3. “Use a moving average filter – it smooths out the bumps like a rolling pin on dough!”

Bella the Battery cheered: “Now go build something awesome!”

Common Mistake: Connecting 5V Sensors Directly to 3.3V Microcontrollers

The Problem: You connect an HC-SR04 ultrasonic sensor (5V logic) directly to an ESP32 (3.3V GPIO), and after a few seconds, the ESP32 stops responding or behaves erratically. Sometimes it works initially, then fails after minutes or hours.

Why It Happens: The HC-SR04 ECHO pin outputs 5V when HIGH. ESP32 GPIO pins have an absolute maximum rating of 3.6V. Applying 5V to a 3.3V GPIO causes: 1. Immediate damage to the input protection circuitry (overvoltage stress) 2. Latch-up — a condition where excess current flows through the chip, causing it to lock up or overheat 3. Gradual degradation — even if it “works” initially, the GPIO slowly fails over time

Real-World Example: A student builds a parking sensor using HC-SR04 + ESP32. The prototype works perfectly for 2 hours during testing, then the ESP32 stops booting. Why? The 5V ECHO pin gradually damaged the ESP32’s GPIO input circuitry until it failed completely.

The Fix: Voltage Divider

Use a resistor voltage divider to reduce 5V to 3.3V:

HC-SR04 ECHO (5V) ---[10kΩ]---+---[20kΩ]--- GND
                              |
                          ESP32 GPIO18 (3.3V max)

Calculation:

Vout = Vin × R2 / (R1 + R2)
Vout = 5V × 20kΩ / (10kΩ + 20kΩ)
Vout = 5V × 20/30 = 3.33V ✅ Safe for ESP32!

A voltage divider reduces sensor output voltage to safe levels for microcontroller GPIO pins using two resistors.

$ V_{} = V_{} $

Worked example: The HC-SR04 ECHO pin outputs 5V, but ESP32 GPIO max is 3.6V. Design a voltage divider using 10kΩ and 20kΩ:

$ V_{} = 5V = 5V = 3.33V $

This is safely below 3.6V (92% of max). Common resistor pairs: (10k,20k), (1k,2k), (4.7k,10k) all give ~3.3V output. Avoid very high resistances (>100kΩ) – weak signals pick up noise from nearby wires.

Try your own resistor values:

Complete Safe Wiring for HC-SR04 + ESP32:

#define TRIG_PIN 5   // ESP32 can output 3.3V, HC-SR04 accepts it as HIGH
#define ECHO_PIN 18  // MUST use voltage divider!

// Voltage divider circuit:
// HC-SR04 ECHO (5V) ---[10kΩ]---+---[20kΩ]--- GND
//                                |
//                            GPIO18

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);  // Protected by voltage divider
}

Alternative Fix: Logic Level Converter

For multi-pin sensors or I2C devices, use a bidirectional logic level converter (BSS138-based):

ESP32 (3.3V side) ←→ Level Shifter ←→ Sensor (5V side)

Cost: $1-2 for a 4-channel converter vs. $0.20 for two resistors

Prevention Checklist:

Key Insight: The HC-SR04 is a classic trap for beginners because: 1. The TRIG pin accepts 3.3V input (works fine) 2. The ECHO pin outputs 5V (damages ESP32) 3. It often “works” initially, hiding the problem until failure hours later

What about 5V-tolerant pins? Some microcontrollers (STM32, some AVR) have 5V-tolerant GPIO pins. ALWAYS check the datasheet — never assume!

25.7 Concept Relationships

Concept Related To Connection Type
NaN Validation Sensor Communication NaN indicates wiring or protocol failure; always check before using data
Moving Average Noise Reduction Averages last N samples to smooth random noise
Voltage Divider Level Shifting Reduces 5V to 3.3V for ESP32 compatibility
Pull-up Resistors 1-Wire / I2C Required for open-drain bus protocols (DHT22 uses 1-Wire-like protocol)
Sampling Interval Datasheet Specs DHT22 requires minimum 2-second intervals

25.8 Summary

Key hands-on takeaways:

  1. Always check wiring first - Most issues are connection problems
  2. Use pull-up resistors - Required for I2C and 1-Wire
  3. Validate readings - Check for NaN and out-of-range values
  4. Apply filtering - Reduce noise for stable readings
  5. Respect timing - Don’t read faster than sensor allows

25.9 See Also

25.10 What’s Next

Now that you have hands-on experience with sensor wiring, data acquisition, and validation:

Next Step Chapter What You Will Learn
Choose sensors systematically Selection Guide Decision frameworks and trade-off analysis for picking the right sensor
Avoid common pitfalls Common Mistakes Top 10 sensor integration errors and how to prevent them
Filter and process signals Signal Processing Advanced filtering, FFT, and noise reduction techniques
Explore advanced topics Advanced Topics Sensor fusion, calibration, and power optimization
Review sensor specifications Common IoT Sensors Datasheets and specs for DHT22, HC-SR04, and more