Module 6: Sensors Deep Dive
Level: 🟡 Intermediate
Board: Arduino Uno
Prerequisites: Modules 1–5
Estimated time: 70–90 minutes
Goal: Work with a variety of sensors and learn to evaluate new ones independently.
What You'll Learn
Welcome to Intermediate. From this module forward, you won't always get complete wiring diagrams. You'll read datasheets and figure out the connections yourself. That's not cruelty, it's the single most valuable skill in electronics: the ability to work with any component you buy, not just the ones in a tutorial.
In this module, you'll work with four sensors (temperature/humidity, ultrasonic distance, motion, and soil moisture), learn to install and evaluate libraries, and implement noise filtering techniques that make your readings reliable.
6.1 Temperature & Humidity: DHT11 and DHT22
The Sensors
| Spec | DHT11 | DHT22 |
|---|---|---|
| Temperature range | 0–50°C | −40–80°C |
| Temperature accuracy | ±2°C | ±0.5°C |
| Humidity range | 20–80% | 0–100% |
| Humidity accuracy | ±5% | ±2–5% |
| Sample rate | 1 reading/second | 1 reading/2 seconds |
| Price | ~$1 | ~$3 |
| Operating voltage | 3.3V–5.5V | 3.3V–5.5V |
| Current draw | 2.5 mA | 2.5 mA |
Both use a single-wire proprietary protocol (not I2C or SPI). A pull-up resistor (10kΩ) is needed on the data line, though many breakout boards include one.
Wiring (from the datasheet)
Task: Look up the DHT22 datasheet. You'll find these pins:
| Pin | Function |
|---|---|
| 1 | VCC (3.3V–5.5V) |
| 2 | DATA |
| 3 | Not connected |
| 4 | GND |
Connect VCC to 5V, GND to GND, DATA to a digital pin with a 10kΩ pull-up resistor to VCC. If using a breakout board, the pull-up is likely already on the board; check the schematic on the board's product page.
Installing the Library
- In the Arduino IDE: Sketch → Include Library → Manage Libraries
- Search for "DHT sensor library" by Adafruit
- Install it (it will also ask to install the "Adafruit Unified Sensor" dependency; install that too)
Reading Temperature and Humidity
/*
* Module 6: DHT22 Temperature & Humidity
*
* Circuit (from datasheet):
* - DHT22 pin 1 (VCC) → 5V
* - DHT22 pin 2 (DATA) → digital pin 2 + 10kΩ pull-up to 5V
* - DHT22 pin 4 (GND) → GND
*
* Library: Adafruit DHT Sensor Library
* Board: Arduino Uno
*/
#include <DHT.h>
const int DHT_PIN = 2;
const int DHT_TYPE = DHT22; // Change to DHT11 if using that sensor
DHT dht(DHT_PIN, DHT_TYPE);
void setup() {
Serial.begin(9600);
dht.begin();
Serial.println("DHT22 sensor ready");
}
void loop() {
// DHT22 needs 2 seconds between readings
delay(2000);
float humidity = dht.readHumidity();
float tempC = dht.readTemperature();
float tempF = dht.readTemperature(true); // Fahrenheit
// Check for read failures
if (isnan(humidity) || isnan(tempC)) {
Serial.println("ERROR: Failed to read from DHT sensor");
Serial.println("Check: wiring, pull-up resistor, timing (2s min between reads)");
return;
}
// Heat index (feels-like temperature)
float heatIndex = dht.computeHeatIndex(tempC, humidity, false);
Serial.print("Temp: ");
Serial.print(tempC, 1);
Serial.print("°C (");
Serial.print(tempF, 1);
Serial.print("°F) Humidity: ");
Serial.print(humidity, 1);
Serial.print("% Heat Index: ");
Serial.print(heatIndex, 1);
Serial.println("°C");
}
Evaluating the Library
When installing any library, check:
- Last updated: Is it actively maintained?
- Stars / downloads: Is it widely used?
- Dependencies: What else does it install?
- Examples: Does it include working example sketches?
- Platform compatibility: Does it support your board?
The Adafruit DHT library is well-maintained, widely used, and includes examples. Some alternatives exist (SimpleDHT, DHT_nonblocking); choose based on your needs.
6.2 Distance Measurement: HC-SR04 Ultrasonic Sensor
How It Works
The HC-SR04 sends an ultrasonic pulse (40 kHz) and measures how long it takes for the echo to return. Distance is calculated from the round-trip time.
Distance = (echo_duration × speed_of_sound) / 2
Speed of sound ≈ 343 m/s at 20°C = 0.0343 cm/µs
Datasheet Specs
| Spec | Value |
|---|---|
| Operating voltage | 5V DC |
| Operating current | 15 mA |
| Range | 2 cm – 400 cm |
| Accuracy | ±3 mm |
| Measuring angle | 15° cone |
| Trigger pulse | 10 µs HIGH |
Wiring (4 pins)
| HC-SR04 Pin | Arduino Pin |
|---|---|
| VCC | 5V |
| Trig | Any digital pin (output) |
| Echo | Any digital pin (input) |
| GND | GND |
Add a 0.1µF decoupling capacitor between VCC and GND, close to the sensor.
Code with Proper Error Handling
/*
* Module 6: HC-SR04 Distance Measurement
*
* Circuit:
* - VCC → 5V, GND → GND
* - Trig → pin 10, Echo → pin 11
* - 0.1µF cap across VCC/GND near sensor
*
* Board: Arduino Uno
*/
const int TRIG_PIN = 10;
const int ECHO_PIN = 11;
// Speed of sound varies with temperature
// At 20°C: 343.2 m/s = 0.03432 cm/µs
const float SPEED_OF_SOUND_CM_PER_US = 0.0343;
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
Serial.println("HC-SR04 ready");
}
float measureDistanceCM() {
// Ensure trigger is LOW
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
// Send 10µs trigger pulse
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Measure echo duration (timeout: 30ms ≈ 500cm round trip)
unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) {
return -1.0; // No echo — object out of range or error
}
// Calculate distance
float distance = (duration * SPEED_OF_SOUND_CM_PER_US) / 2.0;
// Reject readings outside sensor's rated range
if (distance < 2.0 || distance > 400.0) {
return -1.0;
}
return distance;
}
void loop() {
float distance = measureDistanceCM();
if (distance < 0) {
Serial.println("Out of range or no echo");
} else {
Serial.print("Distance: ");
Serial.print(distance, 1);
Serial.println(" cm");
}
delay(100); // Minimum 60ms between measurements recommended
}
Temperature-Compensated Distance
For higher accuracy, adjust the speed of sound based on temperature:
// Speed of sound (m/s) ≈ 331.3 + (0.606 × temperature_celsius)
float speedOfSound = 331.3 + (0.606 * temperatureC);
float speedCmPerUs = speedOfSound / 10000.0;
float distance = (duration * speedCmPerUs) / 2.0;
If you have a DHT22, you already have temperature data; combine them for more accurate distance readings.
6.3 Motion Detection: PIR Sensor
How It Works
A PIR (Passive Infrared) sensor detects changes in infrared radiation, the kind emitted by warm bodies (humans, animals). It doesn't measure distance or direction; it just says "something warm moved."
Datasheet Specs (typical HC-SR501)
| Spec | Value |
|---|---|
| Operating voltage | 4.5V–20V |
| Output | Digital HIGH (3.3V) when motion detected |
| Detection range | 3–7 meters (adjustable via potentiometer) |
| Detection angle | ~120° cone |
| Delay time | 0.3s–5 minutes (adjustable via potentiometer) |
| Warm-up time | 30–60 seconds after power-on |
Important PIR Behavior
- The sensor outputs HIGH for a configurable duration after detecting motion (adjustable potentiometer on the back)
- There's a "blind time" of ~2.5 seconds after each trigger where it won't re-trigger
- The sensor needs 30–60 seconds to stabilize after power-on, so ignore readings during this period
- Potentiometer 1: adjusts sensitivity (detection range)
- Potentiometer 2: adjusts hold time (how long output stays HIGH)
Wiring
| PIR Pin | Arduino Pin |
|---|---|
| VCC | 5V |
| OUT | Any digital pin (input) |
| GND | GND |
Code
/*
* Module 6: PIR Motion Detection
*
* Circuit:
* - PIR VCC → 5V
* - PIR OUT → pin 3
* - PIR GND → GND
*
* Note: Allow 60 seconds warm-up after power-on.
*
* Board: Arduino Uno
*/
const int PIR_PIN = 3;
const int LED_PIN = 13; // Built-in LED as indicator
const unsigned long WARMUP_TIME = 60000; // 60 seconds
bool warmedUp = false;
void setup() {
Serial.begin(9600);
pinMode(PIR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
Serial.println("PIR sensor warming up (60 seconds)...");
Serial.println("Avoid moving near the sensor during warm-up.");
}
void loop() {
unsigned long now = millis();
// Wait for warm-up
if (!warmedUp) {
if (now < WARMUP_TIME) {
// Blink LED during warm-up
digitalWrite(LED_PIN, (now / 500) % 2);
return;
}
warmedUp = true;
Serial.println("PIR ready — monitoring for motion");
digitalWrite(LED_PIN, LOW);
}
// Read PIR output
int motionDetected = digitalRead(PIR_PIN);
if (motionDetected == HIGH) {
digitalWrite(LED_PIN, HIGH);
Serial.print("[");
Serial.print(now / 1000);
Serial.println("s] Motion detected!");
} else {
digitalWrite(LED_PIN, LOW);
}
delay(100);
}
6.4 Soil Moisture Sensor
How It Works
A soil moisture sensor uses two probes that measure the electrical resistance (or capacitance, in capacitive models) of the soil between them. Wet soil conducts better than dry soil, so:
- Resistive sensor: Lower resistance → higher analog reading → wetter soil
- Capacitive sensor: Change in capacitance → different analog reading
Recommendation: Capacitive sensors are preferred because they don't corrode over time. Resistive probes degrade because the DC current causes electrolysis. If you use resistive probes, only power them briefly when taking a reading to extend their life.
Wiring (capacitive v1.2)
| Sensor Pin | Arduino Pin |
|---|---|
| VCC | 3.3V or 5V (check your model) |
| GND | GND |
| AOUT | Any analog pin (A0–A5) |
Code with Calibration
/*
* Module 6: Soil Moisture Sensor
*
* Circuit:
* - Sensor VCC → 3.3V (check your sensor's rated voltage)
* - Sensor GND → GND
* - Sensor AOUT → A1
*
* Calibration required: measure dry air and water values to set range.
*
* Board: Arduino Uno
*/
const int MOISTURE_PIN = A1;
// --- CALIBRATION VALUES ---
// Step 1: Hold sensor in air → record the reading (DRY value)
// Step 2: Submerge sensor in water → record the reading (WET value)
// Replace these with YOUR measured values:
const int DRY_VALUE = 620; // Reading in dry air
const int WET_VALUE = 310; // Reading submerged in water
void setup() {
Serial.begin(9600);
Serial.println("Soil Moisture Sensor Ready");
Serial.println("Calibration mode: raw values and percentage shown");
}
void loop() {
int rawValue = analogRead(MOISTURE_PIN);
// Map raw reading to 0–100% moisture
int moisturePercent = map(rawValue, DRY_VALUE, WET_VALUE, 0, 100);
moisturePercent = constrain(moisturePercent, 0, 100);
Serial.print("Raw: ");
Serial.print(rawValue);
Serial.print(" Moisture: ");
Serial.print(moisturePercent);
Serial.print("% — ");
// Interpret the reading
if (moisturePercent < 20) {
Serial.println("VERY DRY — water immediately");
} else if (moisturePercent < 40) {
Serial.println("DRY — water soon");
} else if (moisturePercent < 70) {
Serial.println("MOIST — good level");
} else {
Serial.println("WET — no watering needed");
}
delay(2000);
}
6.5 Calibration: Why Raw Readings Aren't Enough
Sensor readings are numbers, not units. A photoresistor reading of 450 doesn't mean "450 lumens." It means "450 out of 1023 on this particular voltage divider with this particular resistor in this particular lighting condition."
Calibration Process
1. Identify known reference points
- Temperature sensor: ice water (0°C) and boiling water (100°C)
- Soil moisture: dry air (0%) and standing water (100%)
- Distance sensor: measured ruler distance
2. Record raw readings at each reference point
Reference: 0°C → Raw reading: 213
Reference: 100°C → Raw reading: 847
3. Map raw readings to real units
float tempC = map(rawReading, 213, 847, 0, 100);
4. Verify intermediate values Measure at a known middle point (e.g., body temperature 37°C) and confirm the mapped value matches.
When Calibration Drifts
Sensors change over time (especially cheap ones). Temperature affects resistance. Humidity affects soil sensors. Plan to recalibrate periodically, and consider storing calibration values in EEPROM so they survive power cycles:
#include <EEPROM.h>
// Save calibration
EEPROM.put(0, dryValue);
EEPROM.put(4, wetValue);
// Load calibration
EEPROM.get(0, dryValue);
EEPROM.get(4, wetValue);
6.6 Noise Filtering: Making Readings Reliable
Raw sensor readings are noisy. Electrical interference, supply voltage fluctuations, and the ADC's own limitations cause readings to jump around even when the physical quantity is steady.
Moving Average Filter
The simplest approach: average the last N readings.
const int NUM_SAMPLES = 10;
int readings[NUM_SAMPLES];
int readIndex = 0;
long total = 0;
void setup() {
// Initialize array
for (int i = 0; i < NUM_SAMPLES; i++) {
readings[i] = 0;
}
}
int smoothedRead(int pin) {
// Subtract the oldest reading
total -= readings[readIndex];
// Read the new value
readings[readIndex] = analogRead(pin);
// Add the new reading
total += readings[readIndex];
// Advance to next position
readIndex = (readIndex + 1) % NUM_SAMPLES;
// Return the average
return total / NUM_SAMPLES;
}
Trade-off: More samples = smoother readings, but slower response to actual changes. 10 samples is a good starting point.
Median Filter
Takes the middle value of the last N readings. Better than averaging for rejecting sudden spikes (outliers).
int medianOfThree(int pin) {
int a = analogRead(pin);
delay(10);
int b = analogRead(pin);
delay(10);
int c = analogRead(pin);
// Sort and return middle value
if (a > b) { int t = a; a = b; b = t; } // a <= b
if (b > c) { int t = b; b = c; c = t; } // b <= c
if (a > b) { int t = a; a = b; b = t; } // a <= b
return b; // Middle value
}
When to Use Which
| Filter | Best for | Weakness |
|---|---|---|
| Moving average | Steady signals with small noise | Lags behind rapid changes, affected by outliers |
| Median | Signals with occasional spike noise | Slower (needs multiple readings per call) |
| Exponential (EWMA) | Real-time response with smoothing | Requires tuning the weight parameter |
Exponential Weighted Moving Average (EWMA)
Responds faster to recent values, less memory than full moving average:
float smoothed = 0;
const float ALPHA = 0.1; // 0.0–1.0: lower = smoother, higher = more responsive
void loop() {
int raw = analogRead(A0);
smoothed = (ALPHA * raw) + ((1.0 - ALPHA) * smoothed);
Serial.println(smoothed);
delay(50);
}
Module Project: Environment Monitor
Objective
Build a multi-sensor environment monitor that reads temperature, humidity, and distance, displays calibrated values on the Serial Monitor, and triggers threshold alerts. You must source the wiring from the datasheets; the pin assignments below are hints, not instructions.
Components Needed
| Component | Quantity | Notes |
|---|---|---|
| Arduino Uno | 1 | |
| USB-B cable | 1 | |
| Breadboard | 1 | Full-size |
| DHT22 sensor | 1 | |
| HC-SR04 ultrasonic | 1 | |
| 10kΩ Resistor | 1 | Pull-up for DHT22 data line |
| 0.1µF Ceramic Capacitor | 1 | Decoupling for HC-SR04 |
| Red LED + 220Ω | 1 each | High-temp alert |
| Green LED + 220Ω | 1 each | Normal status |
| Jumper wires | 10+ |
Pin Assignments (verify against datasheets)
| Function | Pin |
|---|---|
| DHT22 data | D2 |
| HC-SR04 Trig | D10 |
| HC-SR04 Echo | D11 |
| Red LED (alert) | D8 |
| Green LED (OK) | D9 |
The Code
/*
* Module 6 Project: Environment Monitor
*
* Reads temperature, humidity (DHT22) and distance (HC-SR04).
* Displays calibrated values with noise filtering.
* Alerts when thresholds are exceeded.
*
* WIRING: Source from datasheets. Suggested pins above for reference.
*
* Libraries: Adafruit DHT Sensor Library
* Board: Arduino Uno
*/
#include <DHT.h>
// --- Pin definitions ---
const int DHT_PIN = 2;
const int TRIG_PIN = 10;
const int ECHO_PIN = 11;
const int ALERT_LED = 8;
const int OK_LED = 9;
// --- Sensor setup ---
DHT dht(DHT_PIN, DHT22);
// --- Thresholds ---
const float TEMP_HIGH_THRESHOLD = 30.0; // °C
const float TEMP_LOW_THRESHOLD = 10.0; // °C
const float HUMIDITY_HIGH_THRESHOLD = 70.0;
const float DISTANCE_CLOSE_THRESHOLD = 20.0; // cm
// --- Smoothing (EWMA) ---
float smoothedTemp = 0;
float smoothedHumidity = 0;
float smoothedDistance = 0;
const float ALPHA = 0.2;
bool firstReading = true;
// --- Timing ---
unsigned long lastDHTRead = 0;
const unsigned long DHT_INTERVAL = 2000;
unsigned long lastDistRead = 0;
const unsigned long DIST_INTERVAL = 200;
unsigned long lastPrint = 0;
const unsigned long PRINT_INTERVAL = 1000;
void setup() {
Serial.begin(9600);
dht.begin();
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(ALERT_LED, OUTPUT);
pinMode(OK_LED, OUTPUT);
Serial.println("Environment Monitor — Module 6");
Serial.println("Warming up sensors...");
Serial.println("-----");
delay(2000); // Initial DHT stabilization
}
float measureDistanceCM() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long duration = pulseIn(ECHO_PIN, HIGH, 30000);
if (duration == 0) return -1.0;
// Temperature-compensated speed of sound
float speedCmUs = (331.3 + (0.606 * smoothedTemp)) / 10000.0;
float dist = (duration * speedCmUs) / 2.0;
if (dist < 2.0 || dist > 400.0) return -1.0;
return dist;
}
void loop() {
unsigned long now = millis();
// --- Read DHT22 (every 2s) ---
if (now - lastDHTRead >= DHT_INTERVAL) {
lastDHTRead = now;
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
if (firstReading) {
smoothedTemp = t;
smoothedHumidity = h;
} else {
smoothedTemp = (ALPHA * t) + ((1.0 - ALPHA) * smoothedTemp);
smoothedHumidity = (ALPHA * h) + ((1.0 - ALPHA) * smoothedHumidity);
}
}
}
// --- Read distance (every 200ms) ---
if (now - lastDistRead >= DIST_INTERVAL) {
lastDistRead = now;
float d = measureDistanceCM();
if (d > 0) {
if (firstReading) {
smoothedDistance = d;
firstReading = false;
} else {
smoothedDistance = (ALPHA * d) + ((1.0 - ALPHA) * smoothedDistance);
}
}
}
// --- Check thresholds ---
bool alert = false;
String alertReasons = "";
if (smoothedTemp > TEMP_HIGH_THRESHOLD) {
alert = true;
alertReasons += "HIGH TEMP ";
}
if (smoothedTemp < TEMP_LOW_THRESHOLD && smoothedTemp > 0) {
alert = true;
alertReasons += "LOW TEMP ";
}
if (smoothedHumidity > HUMIDITY_HIGH_THRESHOLD) {
alert = true;
alertReasons += "HIGH HUMIDITY ";
}
if (smoothedDistance > 0 && smoothedDistance < DISTANCE_CLOSE_THRESHOLD) {
alert = true;
alertReasons += "PROXIMITY ";
}
// --- Update LEDs ---
digitalWrite(ALERT_LED, alert ? HIGH : LOW);
digitalWrite(OK_LED, alert ? LOW : HIGH);
// --- Print status (every 1s) ---
if (now - lastPrint >= PRINT_INTERVAL) {
lastPrint = now;
Serial.print("Temp: ");
Serial.print(smoothedTemp, 1);
Serial.print("°C Humidity: ");
Serial.print(smoothedHumidity, 1);
Serial.print("% Distance: ");
if (smoothedDistance > 0) {
Serial.print(smoothedDistance, 1);
Serial.print("cm");
} else {
Serial.print("---");
}
if (alert) {
Serial.print(" ⚠ ALERT: ");
Serial.print(alertReasons);
} else {
Serial.print(" ✓ OK");
}
Serial.println();
}
}
Circuit Diagram
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 850 480" font-family="monospace" font-size="11">
<!-- Arduino -->
<rect x="30" y="80" width="140" height="320" fill="#1a7a8a" stroke="#333" stroke-width="2" rx="8"/>
<text x="100" y="110" text-anchor="middle" fill="white" font-size="14" font-weight="bold">Arduino Uno</text>
<!-- Pins -->
<rect x="170" y="130" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="140" text-anchor="middle" fill="white" font-size="8">5V</text>
<rect x="170" y="150" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="160" text-anchor="middle" fill="white" font-size="8">GND</text>
<rect x="170" y="190" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="200" text-anchor="middle" fill="white" font-size="8">D2</text>
<rect x="170" y="240" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="250" text-anchor="middle" fill="white" font-size="8">D8</text>
<rect x="170" y="270" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="280" text-anchor="middle" fill="white" font-size="8">D9</text>
<rect x="170" y="310" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="320" text-anchor="middle" fill="white" font-size="8">D10</text>
<rect x="170" y="340" width="22" height="14" fill="#2a8a9a" stroke="white" stroke-width="1" rx="2"/>
<text x="181" y="350" text-anchor="middle" fill="white" font-size="8">D11</text>
<!-- === DHT22 === -->
<rect x="350" y="100" width="110" height="60" fill="#4488aa" stroke="#333" stroke-width="2" rx="6"/>
<text x="405" y="125" text-anchor="middle" fill="white" font-size="12" font-weight="bold">DHT22</text>
<text x="405" y="145" text-anchor="middle" fill="white" font-size="9">Temp + Humidity</text>
<!-- DHT connections -->
<line x1="192" y1="137" x2="350" y2="115" stroke="red" stroke-width="1.5"/>
<text x="270" y="118" font-size="8" fill="red">VCC (5V)</text>
<line x1="192" y1="197" x2="350" y2="130" stroke="green" stroke-width="1.5"/>
<text x="270" y="168" font-size="8" fill="green">DATA (D2)</text>
<!-- Pull-up resistor notation -->
<rect x="280" y="108" width="40" height="12" fill="none" stroke="#333" stroke-width="1" rx="2"/>
<text x="300" y="117" text-anchor="middle" font-size="7">10kΩ↑</text>
<line x1="192" y1="157" x2="350" y2="150" stroke="blue" stroke-width="1.5"/>
<!-- === HC-SR04 === -->
<rect x="350" y="220" width="110" height="60" fill="#6699cc" stroke="#333" stroke-width="2" rx="6"/>
<text x="405" y="245" text-anchor="middle" fill="white" font-size="12" font-weight="bold">HC-SR04</text>
<text x="405" y="265" text-anchor="middle" fill="white" font-size="9">Ultrasonic</text>
<!-- HC-SR04 connections -->
<line x1="260" y1="137" x2="260" y2="230" stroke="red" stroke-width="1.5"/>
<line x1="260" y1="230" x2="350" y2="230" stroke="red" stroke-width="1.5"/>
<text x="310" y="225" font-size="8" fill="red">VCC</text>
<line x1="192" y1="317" x2="350" y2="250" stroke="orange" stroke-width="1.5"/>
<text x="265" y="285" font-size="8" fill="orange">Trig (D10)</text>
<line x1="192" y1="347" x2="350" y2="265" stroke="purple" stroke-width="1.5"/>
<text x="265" y="315" font-size="8" fill="purple">Echo (D11)</text>
<line x1="260" y1="157" x2="260" y2="270" stroke="blue" stroke-width="1"/>
<line x1="260" y1="270" x2="350" y2="270" stroke="blue" stroke-width="1.5"/>
<!-- 0.1uF cap -->
<rect x="470" y="232" width="35" height="12" fill="none" stroke="#333" stroke-width="1" rx="2"/>
<text x="487" y="241" text-anchor="middle" font-size="7">0.1µF</text>
<!-- === ALERT LEDs === -->
<rect x="350" y="350" width="110" height="55" fill="#f0f0f0" stroke="#999" stroke-width="1.5" rx="6"/>
<text x="405" y="370" text-anchor="middle" font-size="10" font-weight="bold">Status LEDs</text>
<line x1="192" y1="247" x2="350" y2="365" stroke="#cc0000" stroke-width="1.5"/>
<text x="265" y="362" font-size="8" fill="#cc0000">Alert (D8)</text>
<line x1="192" y1="277" x2="350" y2="385" stroke="#009900" stroke-width="1.5"/>
<text x="265" y="385" font-size="8" fill="#009900">OK (D9)</text>
<!-- Legend box -->
<rect x="560" y="100" width="240" height="175" fill="#f8f8f8" stroke="#ccc" stroke-width="1" rx="6"/>
<text x="680" y="120" text-anchor="middle" font-size="11" font-weight="bold">Threshold Alerts</text>
<text x="575" y="140" font-size="9">Temp > 30°C → Alert LED</text>
<text x="575" y="158" font-size="9">Temp < 10°C → Alert LED</text>
<text x="575" y="176" font-size="9">Humidity > 70% → Alert LED</text>
<text x="575" y="194" font-size="9">Distance < 20cm → Alert LED</text>
<text x="575" y="218" font-size="9">All OK → Green LED</text>
<text x="575" y="245" font-size="9" fill="#666">EWMA filter (α=0.2) on all readings</text>
<text x="575" y="263" font-size="9" fill="#666">Temp-compensated distance calc</text>
</svg>
Circuit Schema (JSON)
{
"module": 6,
"project": "Environment Monitor",
"board": "Arduino Uno",
"schematic": {
"components": [
{
"id": "U1",
"type": "arduino_uno",
"pins_used": {
"5V": "net_vcc", "GND": "net_gnd",
"D2": "net_dht_data", "D8": "net_alert_led",
"D9": "net_ok_led", "D10": "net_trig", "D11": "net_echo"
}
},
{
"id": "DHT1",
"type": "dht22",
"operating_voltage": "3.3-5.5V",
"current_draw": "2.5mA",
"pins": { "vcc": "net_vcc", "data": "net_dht_data", "gnd": "net_gnd" }
},
{
"id": "R_PULLUP",
"type": "resistor",
"value": "10000", "unit": "ohm",
"purpose": "Pull-up for DHT22 data line",
"pins": { "pin1": "net_vcc", "pin2": "net_dht_data" }
},
{
"id": "US1",
"type": "ultrasonic_sensor",
"model": "HC-SR04",
"current_draw": "15mA",
"pins": { "vcc": "net_vcc", "trig": "net_trig", "echo": "net_echo", "gnd": "net_gnd" }
},
{
"id": "C1",
"type": "capacitor_ceramic",
"value": "100", "unit": "nF",
"purpose": "Decoupling for HC-SR04",
"pins": { "pin1": "net_vcc", "pin2": "net_gnd" }
},
{
"id": "R_ALERT",
"type": "resistor", "value": "220", "unit": "ohm",
"pins": { "pin1": "net_alert_led", "pin2": "net_alert_led_anode" }
},
{
"id": "LED_ALERT",
"type": "led", "color": "red",
"pins": { "anode": "net_alert_led_anode", "cathode": "net_gnd" }
},
{
"id": "R_OK",
"type": "resistor", "value": "220", "unit": "ohm",
"pins": { "pin1": "net_ok_led", "pin2": "net_ok_led_anode" }
},
{
"id": "LED_OK",
"type": "led", "color": "green",
"pins": { "anode": "net_ok_led_anode", "cathode": "net_gnd" }
}
],
"nets": [
{ "name": "net_vcc", "nodes": ["U1.5V", "DHT1.vcc", "R_PULLUP.pin1", "US1.vcc", "C1.pin1"] },
{ "name": "net_gnd", "nodes": ["U1.GND", "DHT1.gnd", "US1.gnd", "C1.pin2", "LED_ALERT.cathode", "LED_OK.cathode"] },
{ "name": "net_dht_data", "nodes": ["U1.D2", "DHT1.data", "R_PULLUP.pin2"] },
{ "name": "net_trig", "nodes": ["U1.D10", "US1.trig"] },
{ "name": "net_echo", "nodes": ["U1.D11", "US1.echo"] },
{ "name": "net_alert_led", "nodes": ["U1.D8", "R_ALERT.pin1"] },
{ "name": "net_alert_led_anode", "nodes": ["R_ALERT.pin2", "LED_ALERT.anode"] },
{ "name": "net_ok_led", "nodes": ["U1.D9", "R_OK.pin1"] },
{ "name": "net_ok_led_anode", "nodes": ["R_OK.pin2", "LED_OK.anode"] }
],
"power": {
"source": "USB",
"total_current_ma": 60,
"note": "Well within USB 500mA limit"
}
},
"code": { "filename": "module06_environment_monitor.ino", "language": "cpp" },
"validation": {
"expected_behavior": "Serial shows temp, humidity, distance with alerts. LEDs indicate status. EWMA smooths readings.",
"datasheet_tasks": [
"Verify DHT22 pin numbering matches your specific module (breakout boards may differ)",
"Confirm HC-SR04 trigger pulse timing from its datasheet",
"Check if your DHT22 breakout already includes the pull-up resistor"
],
"common_mistakes": [
"DHT reads NaN: missing pull-up resistor, wrong pin, reading too fast (<2s for DHT22)",
"Distance always -1: trigger pulse too short, echo pin not set as INPUT",
"Temperature reading seems wrong: check if you're reading Celsius vs. Fahrenheit",
"Alert LED always on: thresholds set wrong for your environment"
]
}
}
Experiments to Try
Experiment 1: Data Logger
Print readings as CSV format and use the Serial Monitor's copy function to paste into a spreadsheet:
Serial.print(millis() / 1000);
Serial.print(",");
Serial.print(smoothedTemp, 1);
Serial.print(",");
Serial.print(smoothedHumidity, 1);
Serial.print(",");
Serial.println(smoothedDistance, 1);
Experiment 2: Compare Filters
Run the moving average, median, and EWMA filters in parallel on the same sensor. Print all three to the Serial Plotter to see how they differ.
Experiment 3: Add the Soil Moisture Sensor
Extend the environment monitor with a soil moisture sensor on A1. Add a "DRY SOIL" alert and test with actual plant soil.
Self-Check: Module 6
Before moving to Module 7, make sure you can:
- Wire a DHT22 sensor from its datasheet (including pull-up resistor)
- Install and use a sensor library from the Library Manager
- Read and filter HC-SR04 distance measurements with error handling
- Explain how a PIR sensor works and its warm-up requirement
- Calibrate a soil moisture sensor with real reference points
- Implement EWMA and moving average noise filtering
- Combine multiple sensors in a single sketch with non-blocking timing
- Read a sensor datasheet to find operating voltage, pinout, and timing requirements
Key Terms Glossary
| Term | Definition |
|---|---|
| DHT22 | Digital temperature and humidity sensor with single-wire protocol |
| HC-SR04 | Ultrasonic distance sensor (2–400 cm range) |
| PIR sensor | Passive Infrared motion detector. Senses warm bodies moving |
| Capacitive soil sensor | Measures soil moisture via capacitance change (no corrosion) |
| Calibration | Mapping raw sensor values to real-world units using known reference points |
| Moving average | Smoothing filter that averages the last N readings |
| Median filter | Takes the middle value of N readings, rejecting outliers |
| EWMA | Exponential Weighted Moving Average. Responds faster to recent changes |
| Pull-up resistor | Keeps a data line at a known HIGH state when not actively driven |
| Decoupling capacitor | Small capacitor near a sensor's power pins to absorb voltage noise |
| pulseIn() | Arduino function that measures the duration of a pulse on a pin |
| isnan() | Checks if a value is Not-A-Number, used to detect failed sensor reads |
| EEPROM | Non-volatile memory on the Arduino. Survives power cycles |
| Library Manager | Arduino IDE tool for finding and installing code libraries |
Previous: ← Module 5: Power Management Next: Module 7: Communication Protocols: How Components Talk →