Sensors and Actuators
Sensors are the eyes and ears of an IoT system — they convert physical phenomena (temperature, light, motion) into electrical signals your microcontroller can read. Actuators are the hands — they convert electrical commands back into physical action.
Sensor Categories
| Category | What It Measures | Common Sensors |
|---|---|---|
| Environmental | Temperature, humidity, pressure, air quality | DHT22, BME280, MQ-135 |
| Motion | Presence, acceleration, orientation | PIR (HC-SR501), MPU-6050 |
| Distance / Proximity | Range, object presence | HC-SR04, VL53L0X |
| Light | Ambient light, UV, IR | LDR, BH1750, VEML6070 |
| Touch / Force | Pressure, capacitance | FSR, MPR121 |
| Flow | Water/gas flow rate | YF-S201 |
| GPS | Location, speed, altitude | NEO-6M, NEO-8M |
| Sound | Decibels, clap detection | KY-038, INMP441 |
Temperature & Humidity — DHT22
The DHT22 is one of the most widely used sensors, measuring temperature (−40 to +80 °C) and humidity (0–100 % RH) with ±0.5 °C / ±2 % accuracy.
# MicroPython on ESP32 with DHT22
import dht
from machine import Pin
import time
# DHT22 on GPIO 4
sensor = dht.DHT22(Pin(4))
while True:
try:
sensor.measure()
temp = sensor.temperature() # Celsius
hum = sensor.humidity() # Percent
print(f"Temperature: {temp:.1f} °C")
print(f"Humidity: {hum:.1f} %")
print(f"Heat Index: {calc_heat_index(temp, hum):.1f} °C")
except OSError as e:
print(f"Sensor error: {e}")
time.sleep(2) # DHT22 needs 2 s between readings
def calc_heat_index(T, H):
"""Steadman's heat index approximation (Celsius)."""
hi = -8.78469475556 + 1.61139411 * T + 2.33854883889 * H \
- 0.14611605 * T * H - 0.012308094 * T**2 \
- 0.016424828 * H**2 + 0.002211732 * T**2 * H \
+ 0.00072546 * T * H**2 - 0.000003582 * T**2 * H**2
return hiDHT11 vs DHT22
| Feature | DHT11 | DHT22 |
|---|---|---|
| Temp range | 0–50 °C | −40–80 °C |
| Temp accuracy | ±2 °C | ±0.5 °C |
| Humidity range | 20–90 % | 0–100 % |
| Humidity accuracy | ±5 % | ±2 % |
| Sampling rate | Once per second | Once per 2 seconds |
| Price | $1–2 | $3–5 |
| Use for | Learning, rough readings | Production monitoring |
Pressure & Altitude — BME280
The BME280 measures temperature, humidity, AND barometric pressure over I2C or SPI. It is highly accurate and used in weather stations.
# MicroPython with BME280 over I2C
from machine import I2C, Pin
import bme280 # Install from: micropython-bme280
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400_000)
# BME280 default address is 0x76 (some boards use 0x77)
sensor = bme280.BME280(i2c=i2c)
temperature, pressure, humidity = sensor.read_compensated_data()
# Convert to human-readable units
temp_c = temperature / 100 # Celsius
pressure_hpa = pressure / 25600 # hPa
humidity_pct = humidity / 1024 # %
# Calculate approximate altitude from pressure
SEA_LEVEL_HPA = 1013.25
altitude = 44330 * (1 - (pressure_hpa / SEA_LEVEL_HPA) ** (1 / 5.255))
print(f"Temperature: {temp_c:.2f} °C")
print(f"Pressure: {pressure_hpa:.2f} hPa")
print(f"Humidity: {humidity_pct:.2f} %")
print(f"Altitude: {altitude:.1f} m")Motion Detection — PIR Sensor (HC-SR501)
A Passive Infrared (PIR) sensor detects movement of warm bodies within its detection cone.
# MicroPython — PIR motion detection with interrupt
from machine import Pin
import time
motion_pin = Pin(5, Pin.IN, Pin.PULL_DOWN)
led = Pin(2, Pin.OUT)
motion_detected = False
def on_motion(pin):
global motion_detected
motion_detected = True
print(f"Motion at {time.ticks_ms()} ms!")
led.on()
# Use interrupt — don't poll in a tight loop
motion_pin.irq(trigger=Pin.IRQ_RISING, handler=on_motion)
print("PIR armed. Watching for motion...")
while True:
if motion_detected:
time.sleep(5) # Keep LED on for 5 seconds
led.off()
motion_detected = False
time.sleep_ms(100)PIR Sensitivity Adjustment
HC-SR501 has two potentiometers:
│
├── Sensitivity pot (Sx): distance range 3–7 m
│ Turn CW to increase range
│
└── Time pot (Tx): output high duration 5 s – 5 min
Turn CW to increase hold time
Jumper (J):
Single trigger (H) — re-trigger only after output goes LOW
Repeating trigger (L) — re-trigger while motion continuesDistance — HC-SR04 Ultrasonic
The HC-SR04 measures distance by sending an ultrasonic pulse and timing the echo.
# MicroPython — HC-SR04 distance measurement
from machine import Pin, time_pulse_us
import time
TRIG = Pin(12, Pin.OUT)
ECHO = Pin(14, Pin.IN)
def measure_distance_cm():
# Send 10 µs trigger pulse
TRIG.off()
time.sleep_us(2)
TRIG.on()
time.sleep_us(10)
TRIG.off()
# Measure echo pulse duration
pulse_duration = time_pulse_us(ECHO, 1, 30000) # max 30 ms
if pulse_duration < 0:
return None # No echo received (out of range)
# Speed of sound: 343 m/s = 0.0343 cm/µs
# Divide by 2 because sound travels to object AND back
distance_cm = pulse_duration * 0.0343 / 2
return round(distance_cm, 1)
while True:
d = measure_distance_cm()
if d is not None:
print(f"Distance: {d} cm")
if d < 10:
print("WARNING: Object too close!")
else:
print("Out of range (> 4 m)")
time.sleep(0.5)Air Quality — MQ-135
MQ-type sensors detect various gases. The MQ-135 detects CO2, NH3, benzene, and NOx.
# ESP32 MicroPython — reading MQ-135 via ADC
from machine import ADC, Pin
import time
# MQ-135 analog output → ESP32 ADC pin 34
adc = ADC(Pin(34))
adc.atten(ADC.ATTN_11DB) # Full 0–3.3 V range
adc.width(ADC.WIDTH_12BIT) # 12-bit resolution (0–4095)
def get_ppm(raw_value, R0=76.63):
"""
Rough conversion from ADC raw value to ppm.
R0 must be calibrated in clean air.
"""
voltage = raw_value / 4095 * 3.3
Rs = (3.3 - voltage) / voltage * 10_000 # Load resistor = 10 kΩ
ratio = Rs / R0
# Simplified curve for CO2-equivalent (varies by gas)
ppm = 116.6020682 * (ratio ** -2.769034857)
return round(ppm, 1)
# Warm-up period (MQ sensors need 20–30 seconds to stabilize)
print("Warming up sensor...")
time.sleep(30)
while True:
raw = adc.read()
ppm = get_ppm(raw)
print(f"Raw ADC: {raw} | Air Quality: {ppm} ppm")
time.sleep(2)Warning: MQ sensors require a 20–30 second warm-up period. Values before warm-up are unreliable. The ESP32's ADC is also non-linear; for precision measurements, use an external ADC like the ADS1115.
Common Actuators
LEDs and Relays
# Controlling a relay to switch AC mains devices (e.g. a lamp)
from machine import Pin
import time
# Most relay modules are ACTIVE LOW (LOW = ON)
relay = Pin(26, Pin.OUT, value=1) # Start with relay OFF
def relay_on():
relay.value(0) # Active LOW
print("Relay ON — device powered")
def relay_off():
relay.value(1) # Active LOW
print("Relay OFF — device unpowered")
# Simple timer-based automation
for i in range(5):
relay_on()
time.sleep(3)
relay_off()
time.sleep(2)Servo Motor
# Servo motor control via PWM (MicroPython)
from machine import Pin, PWM
import time
servo = PWM(Pin(15), freq=50) # Servo frequency is 50 Hz
def set_angle(degrees):
"""Set servo position: 0° to 180°."""
# Duty cycle: 500 µs (0°) to 2500 µs (180°)
# At 50 Hz, period = 20000 µs
min_duty = int(500 / 20000 * 1023) # ~25
max_duty = int(2500 / 20000 * 1023) # ~128
duty = int(min_duty + (max_duty - min_duty) * degrees / 180)
servo.duty(duty)
# Sweep 0° to 180° and back
for angle in range(0, 181, 10):
set_angle(angle)
time.sleep_ms(50)
for angle in range(180, -1, -10):
set_angle(angle)
time.sleep_ms(50)Sensor Wiring Quick Reference
| Sensor | Power | Signal Pins | Protocol |
|---|---|---|---|
| DHT22 | 3.3–5 V | 1 data pin | Single-wire |
| BME280 | 3.3 V | SDA, SCL | I2C (0x76) |
| HC-SR04 | 5 V | TRIG, ECHO | GPIO |
| HC-SR501 (PIR) | 5 V | 1 digital out | GPIO |
| MQ-135 | 5 V | Analog out | ADC |
| SG90 Servo | 5 V | 1 PWM | PWM |
| Relay Module | 5 V | 1 digital in | GPIO |
Summary
- Sensors convert physical phenomena into electrical signals readable by microcontrollers
- DHT22 is the standard for temperature and humidity; BME280 adds pressure
- PIR sensors detect motion efficiently using hardware interrupts rather than polling
- HC-SR04 measures distance using ultrasonic echo timing
- MQ-series sensors detect gases but require warm-up and calibration
- Actuators include relays (switch AC devices), servos (position control), and motors
- Always check voltage levels — most sensors need 3.3 V or 5 V and the ESP32 is 3.3 V only
Next, we'll learn how to interface sensors using GPIO pins, I2C, SPI, and UART protocols.