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
| Application | Recommended Sensor | Interface | Accuracy |
|---|---|---|---|
| Indoor temp/humidity | BME280 | I2C | ±0.5°C, ±3% |
| Outdoor weather | BME280 + UV sensor | I2C | Good |
| Water temperature | DS18B20 | 1-Wire | ±0.5°C |
| Motion detection | PIR (HC-SR501) | Digital | N/A |
| Distance (<4m) | Ultrasonic HC-SR04 | GPIO | ±3mm |
| Distance (>4m) | VL53L0X (ToF) | I2C | ±3% |
| Gesture/Orientation | MPU6050/BNO055 | I2C | Good |
| Light level | BH1750 | I2C | 1 lux |
| Air quality | BME680 / CCS811 | I2C | Variable |
| Current sensing | INA219 | I2C | 1% |
| Weight/Force | HX711 + Load cell | GPIO | 0.1% |
Next: Hardware Design Basics →