Hardware Fundamentals

Sensors and Actuators

Learn about the most common IoT sensors and actuators — temperature, humidity, motion, distance, gas — and how to interface them with microcontrollers.

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

CategoryWhat It MeasuresCommon Sensors
EnvironmentalTemperature, humidity, pressure, air qualityDHT22, BME280, MQ-135
MotionPresence, acceleration, orientationPIR (HC-SR501), MPU-6050
Distance / ProximityRange, object presenceHC-SR04, VL53L0X
LightAmbient light, UV, IRLDR, BH1750, VEML6070
Touch / ForcePressure, capacitanceFSR, MPR121
FlowWater/gas flow rateYF-S201
GPSLocation, speed, altitudeNEO-6M, NEO-8M
SoundDecibels, clap detectionKY-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.

python
# 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 hi

DHT11 vs DHT22

FeatureDHT11DHT22
Temp range0–50 °C−40–80 °C
Temp accuracy±2 °C±0.5 °C
Humidity range20–90 %0–100 %
Humidity accuracy±5 %±2 %
Sampling rateOnce per secondOnce per 2 seconds
Price$1–2$3–5
Use forLearning, rough readingsProduction 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.

python
# 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.

python
# 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 continues

Distance — HC-SR04 Ultrasonic

The HC-SR04 measures distance by sending an ultrasonic pulse and timing the echo.

python
# 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.

python
# 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

python
# 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

python
# 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

SensorPowerSignal PinsProtocol
DHT223.3–5 V1 data pinSingle-wire
BME2803.3 VSDA, SCLI2C (0x76)
HC-SR045 VTRIG, ECHOGPIO
HC-SR501 (PIR)5 V1 digital outGPIO
MQ-1355 VAnalog outADC
SG90 Servo5 V1 PWMPWM
Relay Module5 V1 digital inGPIO

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.