The ESP32-CAM is a compact, low-cost Wi-Fi camera module with an OV2640 sensor that can capture images to a microSD card. Building a time-lapse camera with ESP32-CAM and SD card is an excellent beginner-to-intermediate IoT project that runs autonomously on battery power. At under Rs 800 for the complete setup, it’s one of India’s most cost-effective time-lapse solutions. This tutorial covers programming the ESP32-CAM in Arduino IDE, configuring deep sleep for battery efficiency, and assembling a weatherproof outdoor time-lapse unit.
Table of Contents
- ESP32-CAM Overview
- Hardware and Wiring
- Arduino IDE Setup
- Time-Lapse Code with Deep Sleep
- SD Card File Management
- Battery Life Calculation
- Creating the Time-Lapse Video
- FAQ
ESP32-CAM Overview
The ESP32-CAM module combines an ESP32-S chip with an OV2640 2MP camera, microSD card slot, and on-board LED flash. Key specs: Wi-Fi 802.11 b/g/n, Bluetooth 4.2, 4MB PSRAM, supports image resolutions from QQVGA (160×120) to UXGA (1600×1200). Current draw: 250mA during photo capture, 10mA idle, 10-20uA in deep sleep. Available in India from Robu.in, Zbotic, and local electronics markets (Lamington Road, SP Road Bangalore) for Rs 350-500 depending on vendor.
Hardware and Wiring
ESP32-CAM lacks a built-in USB port. You need a FTDI USB-to-Serial adapter (CP2102 or CH340) for programming:
- ESP32-CAM GND → FTDI GND
- ESP32-CAM 5V → FTDI VCC (5V)
- ESP32-CAM U0TXD (GPIO1) → FTDI RX
- ESP32-CAM U0RXD (GPIO3) → FTDI TX
- ESP32-CAM GPIO0 → GND (for programming mode)
Remove the GPIO0-GND jumper after programming. For the time-lapse unit, power from a 18650 LiPo cell via TP4056 charging module + step-up converter to 5V. Total battery solution cost: under Rs 200.
Arducam OV2640 Wide-Angle Camera Module
Standalone OV2640 camera module for custom ESP32/Arduino builds. If you prefer to build your own time-lapse camera with a separate microcontroller, this OV2640 module connects via DVP interface. Available in wide-angle and standard FOV variants.
Arduino IDE Setup
// Arduino IDE setup for ESP32-CAM
// 1. File > Preferences > Additional Board Manager URLs:
// https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
// 2. Tools > Board Manager: search 'esp32', install 'ESP32 by Espressif'
// 3. Tools > Board: 'AI Thinker ESP32-CAM'
// 4. Tools > Upload Speed: 115200
// 5. Tools > Port: select your FTDI COM port
// Hold GPIO0 LOW during upload, release after upload completes
Time-Lapse Code with Deep Sleep
#include "esp_camera.h"
#include "SD_MMC.h"
#include "driver/rtc_io.h"
#define CAMERA_MODEL_AI_THINKER
// Camera pin definitions for AI Thinker ESP32-CAM
#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
// Time-lapse interval in seconds
#define INTERVAL_SECONDS 30
// Photo counter stored in RTC memory (survives deep sleep)
RTC_DATA_ATTR int photoCount = 0;
void setup() {
Serial.begin(115200);
// Camera configuration
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;
config.frame_size = FRAMESIZE_UXGA; // 1600x1200
config.jpeg_quality = 10; // 0-63, lower = higher quality
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed: 0x%xn", err);
return;
}
// Mount SD card
if (!SD_MMC.begin()) {
Serial.println("SD Card mount failed");
return;
}
// Capture and save photo
camera_fb_t *fb = esp_camera_fb_get();
if (fb) {
char filename[32];
snprintf(filename, sizeof(filename), "/photo_%05d.jpg", photoCount++);
File file = SD_MMC.open(filename, FILE_WRITE);
if (file) {
file.write(fb->buf, fb->len);
file.close();
Serial.printf("Saved: %s (%d bytes)n", filename, fb->len);
}
esp_camera_fb_return(fb);
}
// Enter deep sleep until next interval
esp_sleep_enable_timer_wakeup((uint64_t)INTERVAL_SECONDS * 1000000ULL);
Serial.println("Going to deep sleep...");
esp_deep_sleep_start();
}
void loop() { /* Never runs - deep sleep restarts setup() */ }
SD Card File Management
Use a Class 10 microSD card, maximum 32GB (FAT32 format required). Format with SD Card Formatter tool before use. File naming: /photo_00001.jpg through /photo_99999.jpg allows 100,000 photos. At 1 photo per 30 seconds, that’s over 833 hours of time-lapse capacity.
To check remaining space, add to setup() before sleeping:
uint64_t total = SD_MMC.totalBytes() / (1024 * 1024);
uint64_t used = SD_MMC.usedBytes() / (1024 * 1024);
Serial.printf("SD: %llu MB used of %llu MBn", used, total);
Battery Life Calculation
With a 3000mAh 18650 cell:
- Active during photo + save: ~500ms at 250mA = 0.035mAh per cycle
- Deep sleep: 29.5 seconds at 20uA = 0.00016mAh per cycle
- Total per 30-second cycle: ~0.035mAh
- Battery life: 3000 / 0.035 = ~85,000 cycles = ~2550 hours = ~106 days
In practice, battery self-discharge and voltage regulator losses reduce this to 60-80 days. Still excellent for unattended outdoor deployment in India – through a full monsoon season.
Pi Camera CSI FPC Ribbon Cable 15cm
Short FPC ribbon cable for compact builds. Useful when building custom enclosures where the standard 15cm cable needs to be routed through tight spaces. Compatible with standard CSI connectors.
Creating the Time-Lapse Video
Once you have your JPEG sequence, create the video using FFmpeg on a PC or Raspberry Pi:
# Install FFmpeg
sudo apt install -y ffmpeg # Linux/Raspberry Pi
# or download from ffmpeg.org for Windows
# Create 30 FPS time-lapse video from JPEG sequence
ffmpeg -framerate 30 -i photo_%05d.jpg -c:v libx264 -pix_fmt yuv420p timelapse.mp4
# With crossfade/dissolve between frames (smoother)
ffmpeg -framerate 30 -i photo_%05d.jpg -vf minterpolate=fps=60:mi_mode=blend -c:v libx264 timelapse_smooth.mp4
# Create 4K upscaled version for YouTube
ffmpeg -framerate 30 -i photo_%05d.jpg -vf scale=3840:2160:flags=lanczos -c:v libx264 -crf 18 timelapse_4k.mp4
FAQ
Can ESP32-CAM work without SD card (send to server via Wi-Fi)?
Yes. Remove SD card code and add Wi-Fi connection + HTTP POST to upload each JPEG to a server. Battery life drops significantly (Wi-Fi association takes 1-3 seconds at ~200mA). SD card storage is more battery-efficient.
How weatherproof is the ESP32-CAM for Indian monsoon?
Not at all – it has no IP rating. Mount inside a transparent IP65 project box from an electrical supply store (available at Lamington Road, Mumbai for Rs 80-150). Use silica gel sachets to prevent condensation inside the box.
What JPEG quality setting should I use?
Quality 10-12 gives good results with reasonable file size (~200-400KB at UXGA). Quality 4-6 for maximum sharpness but larger files. Avoid quality 0-3 as it causes compression artifacts visible in time-lapse videos.
Can I change the interval without reprogramming?
Add code to read interval from a file on the SD card at startup. This lets you change timing by editing a config.txt file on the SD card without reflashing.
Add comment