ESP32-CAM: save pictures to SD card

ESP32-CAM: save pictures to SD card

After you learnt how to capture a frame with the ESP32-CAM (see related posts at the bottom), you may want to save it somewhere to later access the file on your PC.

The ESP32 camera ships with an internal storage (up to 16MB on some boards) that you can in part fill with files. Most of the times, though, you will insert an external SD card for both larger storage space and easier accessibility.

This article will show you how to easily interact with the SD card of your ESP32 camera to save images on it in 3 different ways:

  1. manually setting the filename
  2. using an always incrementing counter that survives reboots
  3. using NTP (Network Time Protocol) to use the current timestamp as filename

Hardware requirements

An ESP32-CAM board with a microSD slot.

SD configuration

SD MMC configuration

Some boards use the SD MMC driver. Most of the time, the pins are configured for you out of the box. But in some cases (e.g. Freenove S3 board) you have to configure them on your own.

#include <eloquent_esp32cam3.h>
#include <eloquent_esp32cam3/modules/sdmmc.h>

using eloquent::camera::fs::MMC;
MMC sd;

void setup() {
    sd.pins.clk(1);
    sd.pins.cmd(2);
    sd.pins.d0(3);

    sd.begin();
    sd.raise();
    Serial.println("SD ready!");
}

Save frame with manual filename

If you're manually interacting with the ESP32 camera or you want to use your own naming scheme, you can save a frame with the following syntax.

auto image = camera.grab();

sd.write("/path/to/file.jpg", image);

That's it. This is the most straightforward way of all: just set an absolute filename and you're good to go.

If you want to compose the filename by joining a folder name and a filename, instead of hardcoding it as in the example above, you can leverage an helper function from the library.

auto image = camera.grab();
// create an absolute path by joining
// the folder, the filename and the extension
auto dest = nested("folder name", "filename", ".jpg");

sd.write(dest, image);

Here's a full sketch which asks you to enter the folder and the filename in the Serial Monitor. It leverages the nested helper to construct the actual filename.

/**
 * Save image to SD with manual filename.
 *
 * Open the Serial Monitor and enter the folder
 * & filename to store the photo.
 */
#include <eloquent_esp32cam3.h>
#include <eloquent_esp32cam3/modules/sdmmc.h>

using eloquent::camera::Camera;
using eloquent::camera::fs::MMC;
using eloquent::camera::fs::nested;

Camera camera;
MMC sd;


void setup() {
    Serial.begin(115200);
    Serial.println("Save files to SD using MMC");

    // see "GetStarted.ino" for more comments
    camera.hardware.brownout.disable();
    camera.hardware.clock.fast();
    camera.hardware.pinout.ask();
    camera.frame.pixformat.jpeg();
    camera.frame.resolution.vga();
    camera.frame.quality.high();

    // init camera and discard first frames
    camera.begin();
    camera.raise();
    camera.discard(2);

    // init SD
    sd.begin();
    sd.raise();
    Serial.println("SD ready!");
}


void loop() {
    // get folder name
    // (may be left empty)
    Serial.print("Folder: ");
    while (!Serial.available()) delay(1);

    String folder = Serial.readStringUntil('\n');
    Serial.println(folder);

    // get file name
    // (cannot be left empty)
    // (you may leave out the .jpg extension)
    Serial.print("Filename: ");
    while (!Serial.available()) delay(1);

    String filename = Serial.readStringUntil('\n');
    Serial.println(filename);

    if (!filename)
        return;

    // save frame to SD
    auto image = camera.grab();
    // construct filename like {folder}/{filename}.jpg
    auto dest = nested(folder, filename, ".jpg");

    sd.write(dest, image);

    // report status
    if (sd.failed()) {
        Serial.print("Error: ");
        Serial.println(sd.error);
    } else {
        Serial.print("Photo saved to ");
        Serial.println(dest->toString());
    }
}

Save frame with incremental filename

Many times you will use the ESP32 camera in a autonomous deployment (e.g. a timelapse setup). In this case you may not want to bother with manually choosing a filename for each frame, while preserving an ascending order so you can sort the stored files.

By incremental naming I mean that your pictures will be saved as 0000001.jpg, 0000002.jpg and so on.

The writing logic is exactly the same as above, so I will only show you how to get such an incremental name.

auto image = camera.grab();
auto dest = named("frames", sd.nextid(), ".jpg");

sd.write(dest, image);

The nextid() function will return a new number each time you call it and it survives reboots! By default, the generated id is 8 chars long, but you can change the length manually (up to 16).

// generate a next id of 12 chars
auto dest = named("frames", sd.nextid(12), ".jpg");

Save frame with timestamps

In the most demanding scenarios, you may want to keep track of the exact date and time the picture was taken. If you have an external RTC connected, you can use it to generate a filename.

If you have access to WiFi, you can leverage NTP (Network Time Protocol) to keep track of time. The EloquentEsp32Cam3 library ships an helper class to deal with NTP easily.

#include <eloquent_esp32cam3/modules/ntp.h>

using eloquent::camera::helpers::NTP;
NTP ntp;

void setup() {
    // choose city from list
    ntp.city("?");
    // or set directly
    ntp.city("Rome");

    // connect to Wi-Fi to sync time
    ntp.wifiConnect("SSID", "PASSWORD");
    ntp.begin();
    ntp.raise();
}

void loop() {
    auto image = camera.grab();
    // save photo to <date>/<datetime>.jpg
    // datetime() format is "YYYYmmddHHMMSS"
    // you can separate the date from the time
    // by using e.g. ntp.datetime("_")
    auto dest = nested(npt.date(), ntp.datetime(), ".jpg");

    sd.write(dest, image);
    sd.raise();
}<br>

top
Select a NTP timezone
 [ 1] london
 [ 2] madrid
 [ 3] paris
 [ 4] rome
 [ 5] berlin
 [ 6] cairo
 [ 7] athens
 [ 8] johannesburg
 [ 9] moscow
 [10] riyadh
 [11] tehran
 [12] dubai
 [13] kabul
 [14] karachi
 [15] delhi
 [16] dhaka
 [17] yangon
 [18] bangkok
 [19] singapore
 [20] hong kong
 [21] beijing
 [22] taipei
 [23] seoul
 [24] tokyo
 [25] adelaide
 [26] sydney
 [27] noumea
 [28] auckland
 [29] suva
 [30] midway
 [31] honolulu
 [32] anchorage
 [33] vancouver
 [34] los angeles
 [35] denver
 [36] mexico city
 [37] chicago
 [38] new york
 [39] montreal
 [40] santiago
 [41] rio de janeiro
 [42] fernando de n.
 [43] ponta delgada
Enter choice number: 4
You selected rome
Photo saved to 20241203/20241203_104038.jpg
Photo saved to 20241203/20241203_104043.jpg
Photo saved to 20241203/20241203_104048.jpg
top

Conclusion

In this page you learned 3 ways to save the current frame of your ESP32 camera to an external SD card:

  1. manually setting the filename
  2. using an always incrementing counter that survives reboots
  3. using NTP (Network Time Protocol) to use the current timestamp as filename

After configuring the SD and (optionally) the NTP server, it only takes 1 line of code to actually save the frame. 

This gives you complete freedom over file naming scheme and folder structuring. One thing less to worry about in your next project!

Related posts