Time-lapses are great for capturing slow-moving events, such as the movement of clouds, the growth of plants, or construction progress. Sure, you can use your smartphone and a dedicated app to create time-lapses, but who wants to leave their expensive device unattended for hours?
What if you could achieve the same results using a much more affordable device? Enter the ESP32-CAM—a powerful little microcontroller board equipped with a camera and Wi-Fi capabilities—that costs only about $10.
This guide will take you through the steps to set up your ESP32-CAM to capture images at regular intervals, store them on an SD card, and compile them into a time-lapse video.
It’s simpler than you might think! So, let’s get started!
Things You Will Need
For this project, you will need the following items:
- ESP32-CAM module: This will serve as the brains of your project.
- Micro USB cable: For powering and programming the ESP32-CAM.
- FTDI Adapter or ESP32-CAM-MB Adapter (optional): If your ESP32-CAM doesn’t have a built-in USB-to-Serial converter, you’ll need this to upload code.
- Computer with Arduino IDE: You’ll use this to write and upload the code to your ESP32.
- Micro SD Card: This will store your captured images.
When choosing your ESP32-CAM board for this project, it’s important to pick the right type. You’ll want one that can be easily programmed from your computer and powered up afterwards.
The ESP32-CAM with the ESP32-CAM-MB daughterboard or the newer ESP32-CAM-CH340 are good choices, as they both come with a USB port for programming and power. It’s best to avoid the bare ESP32-CAM board, as it needs a USB-to-Serial converter that you might not have on hand.
Connecting the ESP32-CAM to the Computer
The bare ESP32-CAM module, though powerful, lacks a built-in USB interface for programming and communication with your computer. This means you’ll need a little help to get it up and running. There are three main options you can choose from:
Option 1: Bare ESP32-CAM + FTDI Adapter
If you have a bare ESP32-CAM module, you can use a USB-to-serial adapter (an FTDI adapter) to connect it to your computer like this:

Many FTDI adapters have a jumper that lets you choose between 3.3V and 5V. As we are powering the ESP32-CAM with 5V, make sure the jumper is set to 5V.
Please note that the GPIO 0 pin is connected to Ground. This connection is only necessary while programming the ESP32-CAM. Once you have finished programming the module, you must disconnect this connection.
Remember! You’ll have to make this connection every time you want to upload a new sketch.
Option 2: Bare ESP32-CAM + ESP32-CAM-MB Adapter
Using the FTDI Adapter to program the ESP32-CAM is a bit of a hassle. This is why many vendors now sell the ESP32-CAM board along with a small add-on daughterboard called the ESP32-CAM-MB.
You stack the ESP32-CAM on the daughterboard, attach a micro USB cable, and click the Upload button to program your board. It’s that simple.

Option 3: ESP32-CAM-CH340
If you have an ESP32-CAM-CH340 module, you’re in luck! This variant comes with a CH340 USB-to-serial chip onboard, eliminating the need for additional hardware. Simply connect it to your computer via a micro USB cable and you’re ready to go.

Keep in mind that you’ll still need to connect the GPIO0 pin to GND during programming, just like with the FTDI adapter.
Setting Up the Arduino IDE
No matter which option you choose, the setup within the Arduino IDE is the same.
Installing the ESP32 Board
To use the ESP32-CAM, or any ESP32, with the Arduino IDE, you must first install the ESP32 board (also known as the ESP32 Arduino Core) via the Arduino Board Manager.
If you haven’t already, follow this tutorial to install the ESP32 board:
Selecting the Board and Port
After installing the ESP32 Arduino Core, restart your Arduino IDE and click on “Select other board and port…” on the top drop-down menu.

A new window will pop up. Search for the specific type of ESP32 board you are using (in our case, it’s the AI Thinker ESP32-CAM).
Next, select the port that corresponds to your ESP32-CAM board. It’s usually labeled something like “/dev/ttyUSB0” (on Linux or macOS) or “COM6” (on Windows).

That’s it; the Arduino IDE is now set up for the ESP32-CAM!
Uploading the Code
Now let’s get on to making the time-lapse! Below is the code that will make it happen.
By default, this code is set to snap a picture every 30 minutes. If you want to change this, edit the number on the line that says #define MINUTES_BETWEEN_PHOTOS 30.
#include "esp_camera.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#define MINUTES_BETWEEN_PHOTOS 30
// Pin definition for CAMERA_MODEL_AI_THINKER
#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
// Flash LED pin
#define FLASH_LED_PIN 4
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout detector
// Initialize serial
Serial.begin(115200);
while (!Serial) delay(100);
// Initialize SD Card BEFORE camera to reduce memory pressure
startMicroSD();
// Initialize camera
startCamera();
delay(2000); // Delay 2 seconds before first photo
}
void loop() {
// Keep a count of the number of photos we have taken
static int number = 0;
number++;
// Construct a filename that looks like "/photo_0001.jpg"
String filename = "/photo_";
if (number < 1000) filename += "0";
if (number < 100) filename += "0";
if (number < 10) filename += "0";
filename += number;
filename += ".jpg";
takePhoto(filename);
// Delay until the next photo
delay(MINUTES_BETWEEN_PHOTOS * 60 * 1000);
}
bool startMicroSD() {
Serial.print("Starting microSD... ");
// Pin 13 needs to be pulled-up
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sd_pullup_requirements.html#pull-up-conflicts-on-gpio13
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
if (SD_MMC.begin("/sdcard", true)) {
Serial.println("OKAY");
return true;
} else {
Serial.println("FAILED");
return false;
}
}
void startCamera() {
// Turn off the flash
pinMode(FLASH_LED_PIN, OUTPUT);
digitalWrite(FLASH_LED_PIN, LOW);
// Camera configuration - REDUCED settings to prevent stack overflow
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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;
// CRITICAL: Reduced frame size and quality to prevent stack overflow
if (psramFound()) {
Serial.println("PSRAM found");
config.frame_size = FRAMESIZE_XGA; // Reduced from UXGA to XGA
config.jpeg_quality = 12; // Increased from 10 to 12 (lower quality, smaller file)
config.fb_count = 1; // Reduced from 2 to 1 to save memory
} else {
Serial.println("PSRAM not found");
config.frame_size = FRAMESIZE_VGA; // Reduced from SVGA to VGA
config.jpeg_quality = 15; // Lower quality for boards without PSRAM
config.fb_count = 1;
}
// Additional memory-saving configurations
config.grab_mode = CAMERA_GRAB_LATEST; // Changed from WHEN_EMPTY to LATEST
// Initialize Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x\n", err);
return;
}
Serial.println("Camera initialized successfully");
// Additional sensor settings to optimize for memory usage
sensor_t *s = esp_camera_sensor_get();
if (s != NULL) {
// Lower the resolution if needed
// s->set_framesize(s, FRAMESIZE_VGA);
// Optimize other settings
s->set_brightness(s, 0); // -2 to 2
s->set_contrast(s, 0); // -2 to 2
s->set_saturation(s, 0); // -2 to 2
s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect)
s->set_whitebal(s, 1); // 0 = disable , 1 = enable
s->set_awb_gain(s, 1); // 0 = disable , 1 = enable
s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled
s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable
s->set_aec2(s, 0); // 0 = disable , 1 = enable
s->set_ae_level(s, 0); // -2 to 2
s->set_aec_value(s, 300); // 0 to 1200
s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable
s->set_agc_gain(s, 0); // 0 to 30
s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6
s->set_bpc(s, 0); // 0 = disable , 1 = enable
s->set_wpc(s, 1); // 0 = disable , 1 = enable
s->set_raw_gma(s, 1); // 0 = disable , 1 = enable
s->set_lenc(s, 1); // 0 = disable , 1 = enable
s->set_hmirror(s, 0); // 0 = disable , 1 = enable
s->set_vflip(s, 0); // 0 = disable , 1 = enable
s->set_dcw(s, 1); // 0 = disable , 1 = enable
s->set_colorbar(s, 0); // 0 = disable , 1 = enable
}
}
void takePhoto(String filename) {
digitalWrite(FLASH_LED_PIN, HIGH);
// Take Picture with Camera
Serial.println("Taking picture...");
camera_fb_t *fb = NULL;
// Try to take picture with retries
for (int retry = 0; retry < 3; retry++) {
fb = esp_camera_fb_get();
if (fb) break;
Serial.printf("Camera capture failed, retry %d\n", retry + 1);
delay(500);
}
digitalWrite(FLASH_LED_PIN, LOW);
if (!fb) {
Serial.println("Camera capture failed after retries");
return;
}
Serial.printf("Picture taken! Size: %zu bytes\n", fb->len);
// Save picture to SD card
fs::FS &fs = SD_MMC;
Serial.printf("Picture file name: %s\n", filename.c_str());
File file = fs.open(filename.c_str(), FILE_WRITE);
if (!file) {
Serial.println("Failed to open file in writing mode");
esp_camera_fb_return(fb);
return;
}
// Write file in chunks to prevent memory issues
size_t totalBytes = fb->len;
size_t bytesWritten = 0;
const size_t chunkSize = 1024; // Write in 1KB chunks
while (bytesWritten < totalBytes) {
size_t toWrite = min(chunkSize, totalBytes - bytesWritten);
size_t written = file.write(fb->buf + bytesWritten, toWrite);
if (written != toWrite) {
Serial.println("Write error occurred");
break;
}
bytesWritten += written;
}
file.close();
if (bytesWritten == totalBytes) {
Serial.printf("Saved file to path: %s (%zu bytes)\n", filename.c_str(), bytesWritten);
Serial.println("Picture saved successfully!");
} else {
Serial.printf("File write incomplete: %zu/%zu bytes\n", bytesWritten, totalBytes);
}
// Return the frame buffer immediately after use
esp_camera_fb_return(fb);
}Code Explanation
The stretch starts by including several libraries.
- The
esp_camera.hlibrary lets us control the ESP32-CAM’s camera. It includes all the tools we need to take photos, adjust camera settings, and work with image data. - The
Arduino.his a standard Arduino library that includes basic functions like delay(), Serial.print(), and other things we use in almost every Arduino program. - The
FS.hlibrary gives us commands to read from and write to files on the SD card. - The
SD_MMC.hlibrary helps us work with SD cards using the SD_MMC interface, which is how the ESP32-CAM talks to its microSD card slot. - The
soc.handrtc_cntl_reg.hlibraries allow us to disable the brownout detector, which can cause unwanted resets when the board’s voltage drops a bit. - The
rtc_io.hlibrary helps us control RTC (Real Time Clock) pins, which is useful when we want to hold GPIOs during deep sleep—like keeping the flash LED off.
#include "esp_camera.h"
#include "Arduino.h"
#include "FS.h" // SD Card ESP32
#include "SD_MMC.h" // SD Card ESP32
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"Some constants are then defined. The MINUTES_BETWEEN_PHOTOS determines the time interval (in minutes) between each photo capture and is set to 30 minutes.
#define MINUTES_BETWEEN_PHOTOS 30Then, we define all the pins the ESP32-CAM board uses to talk to the camera and control other features. These pin numbers are specific to the AI-Thinker model, so we make sure to match them properly. We also define the pin for the flash LED, which helps take photos in low light.
// Pin definition for CAMERA_MODEL_AI_THINKER
#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
// Flash LED pin
#define FLASH_LED_PIN 4Next, the startMicroSD() function is defined. This function starts the microSD card communication. First, pin 13 is set to HIGH to enable the SD card. Then, the SD card is initialized. If successful, it prints “OKAY” and returns true, otherwise it prints “FAILED” and returns false.
bool startMicroSD() {
Serial.print("Starting microSD... ");
// Pin 13 needs to be pulled-up
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/sd_pullup_requirements.html#pull-up-conflicts-on-gpio13
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
if (SD_MMC.begin("/sdcard", true)) {
Serial.println("OKAY");
return true;
} else {
Serial.println("FAILED");
return false;
}
}The startCamera() function is then defined. This function sets up the camera module. First, the camera flash is turned off. A configuration structure camera_config_t is then created and filled with various camera settings (resolution, pin assignments, pixel format, etc.). The resolution is set based on the availability of extra memory (PSRAM). If PSRAM is found, a higher resolution is used. If not, we lower the settings to make sure the board doesn’t crash from using too much memory. Finally, the camera is initialized. If successful, it prints “OKAY” and returns true, otherwise it prints “FAILED” and returns false.
void startCamera() {
// Turn off the flash
pinMode(FLASH_LED_PIN, OUTPUT);
digitalWrite(FLASH_LED_PIN, LOW);
// Camera configuration - REDUCED settings to prevent stack overflow
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_sccb_sda = SIOD_GPIO_NUM;
config.pin_sccb_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;
// CRITICAL: Reduced frame size and quality to prevent stack overflow
if (psramFound()) {
Serial.println("PSRAM found");
config.frame_size = FRAMESIZE_XGA; // Reduced from UXGA to XGA
config.jpeg_quality = 12; // Increased from 10 to 12 (lower quality, smaller file)
config.fb_count = 1; // Reduced from 2 to 1 to save memory
} else {
Serial.println("PSRAM not found");
config.frame_size = FRAMESIZE_VGA; // Reduced from SVGA to VGA
config.jpeg_quality = 15; // Lower quality for boards without PSRAM
config.fb_count = 1;
}
// Additional memory-saving configurations
config.grab_mode = CAMERA_GRAB_LATEST; // Changed from WHEN_EMPTY to LATEST
// Initialize Camera
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x\n", err);
return;
}
Serial.println("Camera initialized successfully");
// Additional sensor settings to optimize for memory usage
sensor_t *s = esp_camera_sensor_get();
if (s != NULL) {
// Lower the resolution if needed
// s->set_framesize(s, FRAMESIZE_VGA);
// Optimize other settings
s->set_brightness(s, 0); // -2 to 2
s->set_contrast(s, 0); // -2 to 2
s->set_saturation(s, 0); // -2 to 2
s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect)
s->set_whitebal(s, 1); // 0 = disable , 1 = enable
s->set_awb_gain(s, 1); // 0 = disable , 1 = enable
s->set_wb_mode(s, 0); // 0 to 4 - if awb_gain enabled
s->set_exposure_ctrl(s, 1); // 0 = disable , 1 = enable
s->set_aec2(s, 0); // 0 = disable , 1 = enable
s->set_ae_level(s, 0); // -2 to 2
s->set_aec_value(s, 300); // 0 to 1200
s->set_gain_ctrl(s, 1); // 0 = disable , 1 = enable
s->set_agc_gain(s, 0); // 0 to 30
s->set_gainceiling(s, (gainceiling_t)0); // 0 to 6
s->set_bpc(s, 0); // 0 = disable , 1 = enable
s->set_wpc(s, 1); // 0 = disable , 1 = enable
s->set_raw_gma(s, 1); // 0 = disable , 1 = enable
s->set_lenc(s, 1); // 0 = disable , 1 = enable
s->set_hmirror(s, 0); // 0 = disable , 1 = enable
s->set_vflip(s, 0); // 0 = disable , 1 = enable
s->set_dcw(s, 1); // 0 = disable , 1 = enable
s->set_colorbar(s, 0); // 0 = disable , 1 = enable
}
}Next, the takePhoto(String filename) function is defined. This function captures and saves a photo. First, it captures a photo using esp_camera_fb_get(). It checks if the captured image is in JPEG format. If the format is correct, it opens a file on the SD card with the given filename in write mode. The image data is written to the file. The flash is blinked to indicate the photo was taken. The file is closed, and the camera buffer is returned to free up memory.
void takePhoto(String filename) {
digitalWrite(FLASH_LED_PIN, HIGH);
// Take Picture with Camera
Serial.println("Taking picture...");
camera_fb_t *fb = NULL;
// Try to take picture with retries
for (int retry = 0; retry < 3; retry++) {
fb = esp_camera_fb_get();
if (fb) break;
Serial.printf("Camera capture failed, retry %d\n", retry + 1);
delay(500);
}
digitalWrite(FLASH_LED_PIN, LOW);
if (!fb) {
Serial.println("Camera capture failed after retries");
return;
}
Serial.printf("Picture taken! Size: %zu bytes\n", fb->len);
// Save picture to SD card
fs::FS &fs = SD_MMC;
Serial.printf("Picture file name: %s\n", filename.c_str());
File file = fs.open(filename.c_str(), FILE_WRITE);
if (!file) {
Serial.println("Failed to open file in writing mode");
esp_camera_fb_return(fb);
return;
}
// Write file in chunks to prevent memory issues
size_t totalBytes = fb->len;
size_t bytesWritten = 0;
const size_t chunkSize = 1024; // Write in 1KB chunks
while (bytesWritten < totalBytes) {
size_t toWrite = min(chunkSize, totalBytes - bytesWritten);
size_t written = file.write(fb->buf + bytesWritten, toWrite);
if (written != toWrite) {
Serial.println("Write error occurred");
break;
}
bytesWritten += written;
}
file.close();
if (bytesWritten == totalBytes) {
Serial.printf("Saved file to path: %s (%zu bytes)\n", filename.c_str(), bytesWritten);
Serial.println("Picture saved successfully!");
} else {
Serial.printf("File write incomplete: %zu/%zu bytes\n", bytesWritten, totalBytes);
}
// Return the frame buffer immediately after use
esp_camera_fb_return(fb);
}In the setup() function, we start by disabling the brownout detector, which is a feature that resets the board if the voltage drops too low. This can sometimes cause problems during normal use, so we turn it off for smoother operation. Next, the microSD card is initialized, and the camera module is started and configured. Finally, a 2-second delay is implemented to ensure that all components are fully initialized before the first photo is captured.
void setup() {
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // Disable brownout detector
// Initialize serial
Serial.begin(115200);
while (!Serial) delay(100);
// Initialize SD Card BEFORE camera to reduce memory pressure
startMicroSD();
// Initialize camera
startCamera();
delay(2000); // Delay 2 seconds before first photo
}The loop() function handles the continuous photo-taking process. It first increases a counter to track the total number of photos taken. Then, it constructs a unique filename for the new photo, such as “/photo_0001.jpg.” After that, it calls the takePhoto(filename) function to capture and save the image to the SD card. Finally, it pauses execution for the duration specified by the MINUTES_BETWEEN_PHOTOS variable, creating the interval between captures.
void loop() {
// Keep a count of the number of photos we have taken
static int number = 0;
number++;
// Construct a filename that looks like "/photo_0001.jpg"
String filename = "/photo_";
if (number < 1000) filename += "0";
if (number < 100) filename += "0";
if (number < 10) filename += "0";
filename += number;
filename += ".jpg";
takePhoto(filename);
// Delay until the next photo
delay(MINUTES_BETWEEN_PHOTOS * 60 * 1000);
}Recording the Time-Lapse
After you’ve uploaded the code to your ESP32-CAM, unplug the module, insert a micro SD card, and plug it back in. Wait two seconds, and if all is well, you’ll see the white LED flash once to signal that a photo has been saved to the card. Unless you changed the timing, the next photo will be taken in half an hour.
Before you start recording your time-lapse for real, let’s do a quick check. Take the SD card out of the camera and put it in your computer. Make sure you can see a photo file named “photo_0001.jpg” on the card. If it’s there, you’re ready to go!

Now, set up your ESP32-CAM in your desired location and power it with a USB charger. Wait for the LED to flash once, confirming the first image was captured. You can now leave it to record as long as you’d like!
Remember, whenever you remove and reinsert the SD card, you’ll need to cut power to the ESP32-CAM to start a new recording. Just unplug and replug the cable or charger, and watch for the LED flash to know it’s working.
Video Creation
When you’ve collected enough images to create your time-lapse, remove the SD card from the ESP32-CAM and transfer the files to your computer.
You can then use video editing software to assemble the images into a final video. There are several options available, from more advanced tools like FFmpeg and OpenShot to simpler online video editors. Choose the one that suits your needs and skill level best, and let the magic of time-lapse unfold!
Going Further
While this project shows a simple way to create a time-lapse using the ESP32-CAM, there are several ways you can make it even better.
For example, you could modify the code to put the ESP32-CAM into deep sleep mode between captures, only waking up when it’s time to take a photo. This would significantly reduce power consumption, making your time-lapse setup more portable and battery-friendly.
Another idea would be to leverage the wireless capabilities of the ESP32-CAM to add a web interface. This interface could let you download pictures remotely or even use your smartphone as a viewfinder. This way, you can easily frame your subject during setup without needing to remove the SD card and start over each time.

