Sensors and Peripherals

This guide covers common sensors and peripherals used with ESP32: temperature, humidity, motion, displays, motors, audio, and cameras.

Temperature and Humidity Sensors

DHT22 / AM2302

Popular, affordable digital sensor. Accuracy: ±0.5°C, ±2-5% RH.

Wiring:

DHT22        ESP32
------       -----
VCC    ───── 3.3V
DATA   ───── GPIO 4 (with 10kΩ pull-up to 3.3V)
NC     ───── (not connected)
GND    ───── GND

Code:

#include "DHT.h"

#define DHTPIN 4
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);

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

void loop() {
    float humidity = dht.readHumidity();
    float temperature = dht.readTemperature();  // Celsius
    float temperatureF = dht.readTemperature(true);  // Fahrenheit

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

    float heatIndex = dht.computeHeatIndex(temperature, humidity, false);

    Serial.printf("Temperature: %.1f°C (%.1f°F)\n", temperature, temperatureF);
    Serial.printf("Humidity: %.1f%%\n", humidity);
    Serial.printf("Heat Index: %.1f°C\n", heatIndex);

    delay(2000);  // DHT22 minimum sampling period
}

BME280 (Temperature, Humidity, Pressure)

More accurate than DHT22, I2C interface. Accuracy: ±0.5°C, ±3% RH.

Wiring (I2C):

BME280       ESP32
------       -----
VIN    ───── 3.3V
GND    ───── GND
SCL    ───── GPIO 22
SDA    ───── GPIO 21

Code:

#include <Wire.h>
#include <Adafruit_BME280.h>

Adafruit_BME280 bme;

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

    if (!bme.begin(0x76)) {  // or 0x77
        Serial.println("BME280 not found!");
        while (1);
    }

    // Weather monitoring settings
    bme.setSampling(Adafruit_BME280::MODE_FORCED,
                    Adafruit_BME280::SAMPLING_X1,   // temperature
                    Adafruit_BME280::SAMPLING_X1,   // pressure
                    Adafruit_BME280::SAMPLING_X1,   // humidity
                    Adafruit_BME280::FILTER_OFF);
}

void loop() {
    bme.takeForcedMeasurement();

    Serial.printf("Temperature: %.2f °C\n", bme.readTemperature());
    Serial.printf("Pressure: %.2f hPa\n", bme.readPressure() / 100.0F);
    Serial.printf("Humidity: %.2f %%\n", bme.readHumidity());
    Serial.printf("Altitude: %.2f m\n", bme.readAltitude(1013.25));  // Sea level pressure

    delay(2000);
}

DS18B20 (Waterproof Temperature)

Waterproof, accurate, multiple sensors on one wire.

Wiring:

DS18B20      ESP32
-------      -----
VCC (Red)    ───── 3.3V
DATA (Yellow)───── GPIO 4 (with 4.7kΩ pull-up)
GND (Black)  ───── GND

Code:

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 4

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

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

    Serial.printf("Found %d sensors\n", sensors.getDeviceCount());
}

void loop() {
    sensors.requestTemperatures();

    float temperatureC = sensors.getTempCByIndex(0);

    if (temperatureC != DEVICE_DISCONNECTED_C) {
        Serial.printf("Temperature: %.2f°C\n", temperatureC);
    } else {
        Serial.println("Sensor disconnected!");
    }

    delay(1000);
}

Motion and Presence Sensors

PIR Motion Sensor (HC-SR501)

Detects infrared radiation from humans/animals.

Wiring:

HC-SR501      ESP32
--------      -----
VCC      ───── 5V (or 3.3V for some models)
OUT      ───── GPIO 13
GND      ───── GND

Code:

#define PIR_PIN 13

volatile bool motionDetected = false;

void IRAM_ATTR detectMotion() {
    motionDetected = true;
}

void setup() {
    Serial.begin(115200);
    pinMode(PIR_PIN, INPUT);

    attachInterrupt(digitalPinToInterrupt(PIR_PIN), detectMotion, RISING);

    Serial.println("PIR sensor warming up...");
    delay(30000);  // PIR needs 30-60 seconds to stabilize
    Serial.println("Ready!");
}

void loop() {
    if (motionDetected) {
        Serial.println("Motion detected!");
        motionDetected = false;

        // Debounce
        delay(5000);
    }
}

Ultrasonic Distance Sensor (HC-SR04)

Measures distance using sound waves. Range: 2-400cm.

Wiring:

HC-SR04       ESP32
-------       -----
VCC      ───── 5V
TRIG     ───── GPIO 5
ECHO     ───── GPIO 18 (with voltage divider for 3.3V!)
GND      ───── GND

ECHO Voltage Divider:
HC-SR04 ECHO ── 1kΩ ──┬── GPIO 18
                      │
                     2kΩ
                      │
                     GND

Code:

#define TRIG_PIN 5
#define ECHO_PIN 18

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

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

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

    return distance;
}

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

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

    if (distance > 0 && distance < 400) {
        Serial.printf("Distance: %.1f cm\n", distance);
    } else {
        Serial.println("Out of range");
    }

    delay(100);
}

MPU6050 Accelerometer/Gyroscope

6-axis motion sensor. Great for orientation, gesture detection.

Wiring (I2C):

MPU6050       ESP32
-------       -----
VCC      ───── 3.3V
GND      ───── GND
SCL      ───── GPIO 22
SDA      ───── GPIO 21

Code:

#include <Wire.h>
#include <MPU6050.h>

MPU6050 mpu;

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

    mpu.initialize();

    if (!mpu.testConnection()) {
        Serial.println("MPU6050 connection failed!");
        while (1);
    }

    Serial.println("MPU6050 connected");

    // Calibrate (keep sensor still)
    mpu.CalibrateAccel(6);
    mpu.CalibrateGyro(6);
}

void loop() {
    int16_t ax, ay, az, gx, gy, gz;
    mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);

    // Convert to real units
    float accelX = ax / 16384.0;  // g
    float accelY = ay / 16384.0;
    float accelZ = az / 16384.0;
    float gyroX = gx / 131.0;     // degrees/sec
    float gyroY = gy / 131.0;
    float gyroZ = gz / 131.0;

    Serial.printf("Accel: X=%.2f Y=%.2f Z=%.2f g\n", accelX, accelY, accelZ);
    Serial.printf("Gyro:  X=%.1f Y=%.1f Z=%.1f °/s\n", gyroX, gyroY, gyroZ);

    delay(100);
}

Displays

OLED Display (SSD1306, 128x64)

Small, low-power, I2C display.

Wiring:

SSD1306       ESP32
-------       -----
VCC      ───── 3.3V
GND      ───── GND
SCL      ───── GPIO 22
SDA      ───── GPIO 21

Code:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

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

    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
        Serial.println("SSD1306 allocation failed");
        while (1);
    }

    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println("ESP32 OLED Demo");
    display.display();
}

void loop() {
    static int counter = 0;

    display.clearDisplay();

    // Title
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println("ESP32 Sensor Hub");

    // Draw line
    display.drawLine(0, 10, 127, 10, SSD1306_WHITE);

    // Data
    display.setCursor(0, 16);
    display.printf("Temp:  %.1f C\n", 25.5);
    display.printf("Humid: %.1f %%\n", 60.0);
    display.printf("Count: %d\n", counter++);

    // Progress bar
    int progress = (counter % 100);
    display.drawRect(0, 54, 128, 10, SSD1306_WHITE);
    display.fillRect(2, 56, progress * 1.24, 6, SSD1306_WHITE);

    display.display();
    delay(100);
}

TFT Display (ILI9341, 320x240)

Color display with SPI interface.

Wiring (SPI):

ILI9341       ESP32
-------       -----
VCC      ───── 3.3V
GND      ───── GND
CS       ───── GPIO 5
RST      ───── GPIO 4
DC       ───── GPIO 2
MOSI     ───── GPIO 23
SCK      ───── GPIO 18
LED      ───── 3.3V (or GPIO for brightness control)

Code:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>

#define TFT_CS   5
#define TFT_DC   2
#define TFT_RST  4

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

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

    tft.begin();
    tft.setRotation(1);  // Landscape

    tft.fillScreen(ILI9341_BLACK);

    tft.setTextColor(ILI9341_WHITE);
    tft.setTextSize(2);
    tft.setCursor(50, 100);
    tft.println("ESP32 TFT Demo");
}

void loop() {
    // Draw random rectangles
    int x = random(0, 320);
    int y = random(0, 240);
    int w = random(10, 100);
    int h = random(10, 100);
    uint16_t color = random(0xFFFF);

    tft.fillRect(x, y, w, h, color);
    delay(50);
}

E-Paper Display (Waveshare)

Ultra-low power, paper-like appearance.

#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>

// 2.9" display
GxEPD2_BW<GxEPD2_290, GxEPD2_290::HEIGHT> display(
    GxEPD2_290(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4));

void setup() {
    display.init();
    display.setRotation(1);
    display.setFont(&FreeMonoBold9pt7b);
    display.setTextColor(GxEPD_BLACK);

    display.setFullWindow();
    display.firstPage();
    do {
        display.fillScreen(GxEPD_WHITE);
        display.setCursor(10, 30);
        display.println("E-Paper Display");
        display.println("Low Power Mode");
    } while (display.nextPage());

    display.hibernate();
}

void loop() {
    // E-paper retains image without power
}

Motors and Actuators

DC Motor with L298N

H-bridge motor driver for DC motors.

Wiring:

L298N         ESP32
-----         -----
IN1      ───── GPIO 27
IN2      ───── GPIO 26
ENA      ───── GPIO 14 (PWM for speed)
12V      ───── External 12V
GND      ───── GND (shared with ESP32)
5V       ───── Can power ESP32 (regulated)

Code:

#define IN1 27
#define IN2 26
#define ENA 14

#define PWM_CHANNEL 0
#define PWM_FREQ 1000
#define PWM_RESOLUTION 8

void setup() {
    pinMode(IN1, OUTPUT);
    pinMode(IN2, OUTPUT);

    ledcSetup(PWM_CHANNEL, PWM_FREQ, PWM_RESOLUTION);
    ledcAttachPin(ENA, PWM_CHANNEL);
}

void motorForward(int speed) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
    ledcWrite(PWM_CHANNEL, speed);  // 0-255
}

void motorBackward(int speed) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
    ledcWrite(PWM_CHANNEL, speed);
}

void motorStop() {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    ledcWrite(PWM_CHANNEL, 0);
}

void loop() {
    motorForward(200);
    delay(2000);

    motorStop();
    delay(500);

    motorBackward(150);
    delay(2000);

    motorStop();
    delay(500);
}

Servo Motor

#include <ESP32Servo.h>

Servo myServo;

#define SERVO_PIN 18

void setup() {
    myServo.attach(SERVO_PIN, 500, 2500);  // min/max pulse width
}

void loop() {
    // Sweep from 0 to 180 degrees
    for (int pos = 0; pos <= 180; pos++) {
        myServo.write(pos);
        delay(15);
    }

    for (int pos = 180; pos >= 0; pos--) {
        myServo.write(pos);
        delay(15);
    }
}

Stepper Motor (A4988 Driver)

#define DIR_PIN 26
#define STEP_PIN 27
#define ENABLE_PIN 25

#define STEPS_PER_REV 200

void setup() {
    pinMode(DIR_PIN, OUTPUT);
    pinMode(STEP_PIN, OUTPUT);
    pinMode(ENABLE_PIN, OUTPUT);

    digitalWrite(ENABLE_PIN, LOW);  // Enable driver
}

void stepMotor(int steps, int delayUs, bool clockwise) {
    digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);

    for (int i = 0; i < steps; i++) {
        digitalWrite(STEP_PIN, HIGH);
        delayMicroseconds(delayUs);
        digitalWrite(STEP_PIN, LOW);
        delayMicroseconds(delayUs);
    }
}

void loop() {
    // One revolution clockwise
    stepMotor(STEPS_PER_REV, 1000, true);
    delay(500);

    // One revolution counter-clockwise
    stepMotor(STEPS_PER_REV, 1000, false);
    delay(500);
}

Audio

Passive Buzzer (PWM)

#define BUZZER_PIN 25
#define PWM_CHANNEL 0

void playTone(int frequency, int duration) {
    ledcSetup(PWM_CHANNEL, frequency, 8);
    ledcAttachPin(BUZZER_PIN, PWM_CHANNEL);
    ledcWrite(PWM_CHANNEL, 127);  // 50% duty cycle

    delay(duration);

    ledcWrite(PWM_CHANNEL, 0);
    ledcDetachPin(BUZZER_PIN);
}

void setup() {
    // Play a melody
    int melody[] = {262, 294, 330, 349, 392, 440, 494, 523};  // C major scale
    int durations[] = {250, 250, 250, 250, 250, 250, 250, 500};

    for (int i = 0; i < 8; i++) {
        playTone(melody[i], durations[i]);
        delay(50);
    }
}

void loop() {}

I2S Audio Output (MAX98357A)

#include "driver/i2s.h"

#define I2S_NUM         I2S_NUM_0
#define I2S_BCK_PIN     26
#define I2S_WS_PIN      25
#define I2S_DATA_PIN    22
#define SAMPLE_RATE     44100

void setupI2S() {
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
        .communication_format = I2S_COMM_FORMAT_STAND_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 8,
        .dma_buf_len = 64,
        .use_apll = false,
    };

    i2s_pin_config_t pin_config = {
        .bck_io_num = I2S_BCK_PIN,
        .ws_io_num = I2S_WS_PIN,
        .data_out_num = I2S_DATA_PIN,
        .data_in_num = I2S_PIN_NO_CHANGE,
    };

    i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
    i2s_set_pin(I2S_NUM, &pin_config);
}

void playSineWave(int frequency, int durationMs) {
    int samples = (SAMPLE_RATE * durationMs) / 1000;
    int16_t buffer[64];
    size_t bytes_written;

    for (int i = 0; i < samples; i += 32) {
        for (int j = 0; j < 32; j++) {
            float t = (float)(i + j) / SAMPLE_RATE;
            int16_t sample = (int16_t)(sin(2 * PI * frequency * t) * 32767 * 0.5);
            buffer[j * 2] = sample;      // Left
            buffer[j * 2 + 1] = sample;  // Right
        }
        i2s_write(I2S_NUM, buffer, sizeof(buffer), &bytes_written, portMAX_DELAY);
    }
}

void setup() {
    setupI2S();
    playSineWave(440, 1000);  // 440Hz for 1 second
}

void loop() {}

Camera Module (ESP32-CAM)

ESP32-CAM includes OV2640 camera.

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_http_server.h"

// AI-Thinker ESP32-CAM pins
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

const char* ssid = "YourSSID";
const char* password = "YourPassword";

void startCameraServer();

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

    camera_config_t config;
    config.ledc_channel = LEDC_CHANNEL_0;
    config.ledc_timer = LEDC_TIMER_0;
    config.pin_d0 = Y2_GPIO_NUM;
    config.pin_d1 = Y3_GPIO_NUM;
    config.pin_d2 = Y4_GPIO_NUM;
    config.pin_d3 = Y5_GPIO_NUM;
    config.pin_d4 = Y6_GPIO_NUM;
    config.pin_d5 = Y7_GPIO_NUM;
    config.pin_d6 = Y8_GPIO_NUM;
    config.pin_d7 = Y9_GPIO_NUM;
    config.pin_xclk = XCLK_GPIO_NUM;
    config.pin_pclk = PCLK_GPIO_NUM;
    config.pin_vsync = VSYNC_GPIO_NUM;
    config.pin_href = HREF_GPIO_NUM;
    config.pin_sscb_sda = SIOD_GPIO_NUM;
    config.pin_sscb_scl = SIOC_GPIO_NUM;
    config.pin_pwdn = PWDN_GPIO_NUM;
    config.pin_reset = RESET_GPIO_NUM;
    config.xclk_freq_hz = 20000000;
    config.pixel_format = PIXFORMAT_JPEG;

    // PSRAM available
    if (psramFound()) {
        config.frame_size = FRAMESIZE_UXGA;  // 1600x1200
        config.jpeg_quality = 10;
        config.fb_count = 2;
    } else {
        config.frame_size = FRAMESIZE_SVGA;  // 800x600
        config.jpeg_quality = 12;
        config.fb_count = 1;
    }

    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
        Serial.printf("Camera init failed: 0x%x\n", err);
        return;
    }

    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.printf("\nCamera Ready! Visit: http://%s\n", WiFi.localIP().toString().c_str());

    startCameraServer();
}

void loop() {
    delay(10000);
}

Sensor Selection Guide

ApplicationRecommended SensorInterfaceAccuracy
Indoor temp/humidityBME280I2C±0.5°C, ±3%
Outdoor weatherBME280 + UV sensorI2CGood
Water temperatureDS18B201-Wire±0.5°C
Motion detectionPIR (HC-SR501)DigitalN/A
Distance (<4m)Ultrasonic HC-SR04GPIO±3mm
Distance (>4m)VL53L0X (ToF)I2C±3%
Gesture/OrientationMPU6050/BNO055I2CGood
Light levelBH1750I2C1 lux
Air qualityBME680 / CCS811I2CVariable
Current sensingINA219I2C1%
Weight/ForceHX711 + Load cellGPIO0.1%

Next: Hardware Design Basics →