Introduction: ESP32-CAM Take Photo and Save to MicroSD Card With Date and Time. How to Take Timelapse With ESP32-CAM

In this tutorial, you’ll learn to take a picture with the ESP32-CAM and save it to the microSD card. We’ll include the current date and time on the filename. Including the date and time on the filename is useful because each picture will have a unique filename, which means no issues with overwriting existing files; you’ll know when each photo was taken; and it will be easier to organize or retrieve your files later on.

Supplies

For this project, we’ll be using an ESP32-CAM AI-Thinker board. (Amazon)(AliExpress)

  • MicroSD card



You can also use a different ESP32 Camera board, you just need to make sure it supports a microSD card and that you adjust the right pinout on the code. This is only compatible with OV2640 cameras.

Step 1: Installing ESP32 Add-On

We’ll program the ESP32 board using Arduino IDE. So you need the Arduino IDE installed as well as the ESP32 add-on.

Step 2: Project Overview

The diagram above shows a high-level overview of the example we’ll build.

In this tutorial, we’ll show you a simple example that does the following:

  1. The ESP32-CAM connects to your router via Wi-Fi (this is needed so that we can get time from an NTP server);
  2. The ESP32 connects to an NTP server to initialize the date and time with the correct timezone;
  3. It initializes the camera and the microSD card;
  4. The camera takes a new picture;
  5. It gets the current date and time from the NTP server;
  6. The photo is saved to the microSD card – its filename contains the date and time
  7. it was taken (it’s a unique filename);
  8. Repeat steps 4 to 6 every 10 seconds. For demonstration purposes, the ESP32-CAM will do this task over and over again on the loop(). The idea is to apply the concepts you’ll learn with this example to your own projects.

Using date and time on the picture filename has several advantages:

  • all pictures will have a different filename—you won’t have problems with overwriting previous files when the ESP32 resets;
  • you’ll know when each picture was taken;
  • it will be easier to organize your pictures later on.

Step 3: Code

Copy the following code to your Arduino IDE. Before uploading the code to your board, you need to insert your SSID and password, and timezone string to set the correct timezone for your location


#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"
#include <WiFi.h>
#include "time.h"


// REPLACE WITH YOUR NETWORK CREDENTIALS
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";


// REPLACE WITH YOUR TIMEZONE STRING: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
String myTimezone ="CST6CDT,M3.2.0,M11.1.0";


// Pin definition for CAMERA_MODEL_AI_THINKER
// Change pin definition if you're using another ESP32 camera module
#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


// Stores the camera configuration parameters
camera_config_t config;


// Initializes the camera
void configInitCamera(){
  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; //YUV422,GRAYSCALE,RGB565,JPEG
  config.grab_mode = CAMERA_GRAB_LATEST;


  // Select lower framesize if the camera doesn't support PSRAM
  if(psramFound()){
    config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
    config.jpeg_quality = 10; //0-63 lower number means higher quality
    config.fb_count = 1;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // Initialize the Camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
}


// Connect to wifi
void  initWiFi(){
  WiFi.begin(ssid, password);
  Serial.println("Connecting Wifi");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
}


// Function to set timezone
void setTimezone(String timezone){
  Serial.printf("  Setting Timezone to %s\n",timezone.c_str());
  setenv("TZ",timezone.c_str(),1);  //  Now adjust the TZ.  Clock settings are adjusted to show the new local time
  tzset();
}


// Connect to NTP server and adjust timezone
void initTime(String timezone){
  struct tm timeinfo;
  Serial.println("Setting up time");
  configTime(0, 0, "pool.ntp.org");    // First connect to NTP server, with 0 TZ offset
  if(!getLocalTime(&timeinfo)){
    Serial.println(" Failed to obtain time");
    return;
  }
  Serial.println("Got the time from NTP");
  // Now we can set the real timezone
  setTimezone(timezone);
}


// Get the picture filename based on the current ime
String getPictureFilename(){
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return "";
  }
  char timeString[20];
  strftime(timeString, sizeof(timeString), "%Y-%m-%d_%H-%M-%S", &timeinfo);
  Serial.println(timeString);
  String filename = "/picture_" + String(timeString) +".jpg";
  return filename;
}


// Initialize the micro SD card
void initMicroSDCard(){
  // Start Micro SD card
  Serial.println("Starting SD Card");
  if(!SD_MMC.begin()){
    Serial.println("SD Card Mount Failed");
    return;
  }
  uint8_t cardType = SD_MMC.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD Card attached");
    return;
  }
}


// Take photo and save to microSD card
void takeSavePhoto(){
  // Take Picture with Camera
  camera_fb_t * fb = esp_camera_fb_get();

  //Uncomment the following lines if you're getting old pictures
  //esp_camera_fb_return(fb); // dispose the buffered image
  //fb = NULL; // reset to capture errors
  //fb = esp_camera_fb_get();

  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
  }


  // Path where new picture will be saved in SD Card
  String path = getPictureFilename();
  Serial.printf("Picture file name: %s\n", path.c_str());

  // Save picture to microSD card
  fs::FS &fs = SD_MMC;
  File file = fs.open(path.c_str(),FILE_WRITE);
  if(!file){
    Serial.printf("Failed to open file in writing mode");
  }
  else {
    file.write(fb->buf, fb->len); // payload (image), payload length
    Serial.printf("Saved: %s\n", path.c_str());
  }
  file.close();
  esp_camera_fb_return(fb);
}


void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable brownout detector


  Serial.begin(115200);
  delay(2000);


  // Initialize Wi-Fi
  initWiFi();
  // Initialize time with timezone
  initTime(myTimezone);    
  // Initialize the camera  
  Serial.print("Initializing the camera module...");
  configInitCamera();
  Serial.println("Ok!");
  // Initialize MicroSD
  Serial.print("Initializing the MicroSD card module... ");
  initMicroSDCard();
}


void loop() {    
  // Take and Save Photo
  takeSavePhoto();
  delay(10000);
}

Step 4: How Code Works

Include Libraries

First, include the required libraries to handle the camera, microSD card, and time.

#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"
#include <WiFi.h>
#include "time.h"


Network Credentials

Insert your network credentials on the following variables.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";


Timezone String

Insert your timezone string on the myTimezone variable. You can check a list of timezone string variables here. For example, I live in the midwest. The timezone is America/Chicago. From the list of timezone string variables, I see that the timezone string variable for my location is CST6CDT,M3.2.0,M11.1.0

//REPLACE WITH YOUR TIMEZONE STRING: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
String myTimezone ="CST6CDT,M3.2.0,M11.1.0";


Camera Pinout and Configurations

Then, include the pin definition for the camera model we’re using. For the ESP32‑CAM AI-Thinker module, this is the pin definition (change it if you’re using a different ESP32 camera model):

// Pin definition for CAMERA_MODEL_AI_THINKER
// Change pin definition if you're using another ESP32 camera module
#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


Configure and Initialize the Camera

The configInitCamera() function, starts by assigning the GPIOs you’ve defined earlier as well as the picture format.

// Initializes the camera
void configInitCamera(){
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; //YUV422,GRAYSCALE,RGB565,JPEG
config.grab_mode = CAMERA_GRAB_LATEST;


The picture format is defined in the following line

config.pixel_format = PIXFORMAT_JPEG; //YUV422,GRAYSCALE,RGB565,JPEG


It can be set to one of the following formats:

  • PIXFORMAT_JPEG
  • PIXFORMAT_YUV422
  • PIXFORMAT_GRAYSCALE
  • PIXFORMAT_RGB565
  • PIXFORMAT_JPEG (format that we’re using)


Define the frame size, image quality, and the number of pictures saved in the frame buffer:

  // Select lower framesize if the camera doesn't support PSRAM
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA; // FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10; //0-63 lower number means higher quality
config.fb_count = 1;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}


In case the camera model supports PSRAM, we set the frame size to UXGA (1600×1200) and the image quality to 1.

You can set the frame size to one of these options:

  • FRAMESIZE_UXGA (1600 x 1200)
  • FRAMESIZE_QVGA (320 x 240)
  • FRAMESIZE_CIF (352 x 288)
  • FRAMESIZE_VGA (640 x 480)
  • FRAMESIZE_SVGA (800 x 600)
  • FRAMESIZE_XGA (1024 x 768)
  • FRAMESIZE_SXGA (1280 x 1024)

The image quality can be a number between 0 and 63. A lower number means higher quality.

Important: very low numbers for image quality, especially at higher resolution, can make the ESP32-CAM crash, or it may not be able to take the photos correctly. So, if you notice that the images taken with the ESP32-CAM are cut in half or with strange colors, that’s probably a sign that you need to lower the quality (select a higher number like 10).

Step 5: Uploading the Code

After inserting your network credentials and timezone string, upload the code to your board.

If you have an ESP32-CAM AI-Thinker, you should select AI Thinker ESP32-CAM in the ToolsBoard menu.


After that, select the COM port in Tools Port.

Finally, upload the code.

Step 6: Serial Monitor

After successfully uploading the code to the board, open the Serial Monitor at a baud rate of 115200. Press the RST button so that it restarts the board and it starts running the code.

Don’t forget to insert a microSD card on the ESP32-CAM microSD card slot.

Every 10 seconds, the flash will go on and it will save a picture to the microSD card. Let the code run for a while so that you get a considerable amount of pictures.

In the Serial Monitor, you should get something as shown above.

Step 7: Viewing the Photos

Then, insert the microSD card into your computer to see the captured photos.

The photos should have the date and time in its name. For example:

picture_2023-01-17_17-31-40.jpg

This means that this picture was taken on:

  • Year: 2023
  • Month: 01 (January)
  • Day: 17th
  • Time: 17:31:40 (5:31:40 PM)

Step 8: Help

If You Need Any Help Or Have Any Questions Post A Comment Below Or Join My Discord Server https://discord.gg/Cq7tSEfaC2