Skip to content
beginnerESP32esp32-cammicrosdphoto-capture

ESP32-CAM Photo Capture & microSD Storage

Capture JPEG photos with the ESP32-CAM and save them directly to a microSD card, with sequential file naming so nothing gets overwritten.

10/17/2026 9 min read 0 views

Every ESP32-CAM project that saves anything -- a motion trigger, a doorbell press, a timelapse frame -- eventually needs the same two things: a place to write photos that survives a power cycle, and a way to actually get those photos back off the board. This guide covers both, using the AI-Thinker board's onboard microSD slot and SD_MMC, the same foundation the PIR motion camera, smart doorbell, and fingerprint security projects in this series all build on.

ESP32-CAM AI-Thinker board with a microSD card inserted in the onboard slot

Hardware Needed

Components

4 items
NameQtyLink
ESP32-CAM (AI-Thinker) board×1—
microSD card, 32GB or smaller, Class 10, formatted FAT32×1—
FTDI USB-to-serial adapter (3.3V) for flashing×1—
5V power supply, at least 500mA×1—

Need these parts? Get this build sheet by email

Parts list with buy links, plus new builds as they're published. By subscribing you agree to our Privacy Policy. Unsubscribe anytime.

Formatting a microSD card as FAT32 before inserting it into the ESP32-CAM

Why SD_MMC, Not SD (SPI Mode)

The AI-Thinker board wires its microSD slot to run in 1-bit SD_MMC mode, not the more commonly-referenced SPI SD library. This isn't optional or a config choice -- the physical wiring on the board only exposes the pins SD_MMC needs, so the SPI-based SD library, which expects a different pin arrangement, simply won't find the card.

SD_MMC in 1-bit mode uses four GPIOs: 2, 14, 15, and 12 (clock, command, and data lines), plus GPIO 13 in some slower fallback configurations. This is the exact pin range referenced as "shared with SD" in the fingerprint security and PIR motion camera guides in this series -- if a project needs both onboard SD storage and a peripheral on GPIO 12-15, one of them has to move to a different pin or interface.

⚠️ Warning

Format the card as FAT32 before inserting it, not exFAT. SD_MMC on the ESP32 reliably supports FAT32 and works best on cards 32GB or smaller -- larger cards often ship pre-formatted as exFAT and need reformatting first.

Mounting the Card and Taking a Single Photo

Mounting the card and capturing one photo is the smallest useful version of this -- everything else in this guide builds on top of it.

C++
Serial Monitor confirming the photo was captured and written to /photo.jpg

Auto-Incrementing Filenames

Writing every photo to the same /photo.jpg overwrites the previous one -- fine for a one-off test, useless for anything that captures more than once. A counter stored in RTC_DATA_ATTR survives deep sleep (though not a full power-off), which is exactly the pattern the PIR motion camera and timelapse recorder projects in this series rely on for unique filenames across many capture cycles.

C++

If the board loses power entirely rather than just sleeping, photoCount resets to zero on the next boot -- which means it can start overwriting /img_0.jpg onward again. For a project that needs filenames to stay unique across a full power cycle, reading the highest existing img_*.jpg number off the card at boot and continuing from there is more reliable than trusting a counter that lives only in RTC memory.

Browsing and Downloading Photos Over WiFi

Pulling the card and reading it on a computer works, but it means physically accessing the board every time. A small web server running on the ESP32-CAM itself can list and serve saved files over the local network instead.

C++
Browser view of the file-listing page, showing saved photos as clickable links

This same list-and-serve pattern is what the fingerprint security system's access log and the smart doorbell's photo alerts build on top of, just filtered to specific filename patterns instead of showing every file on the card.

Managing Card Capacity

A 32GB card at roughly 100-150KB per JPEG holds well over 100,000 photos in theory, but a project capturing frequently (a busy driveway, a timelapse running for weeks) can still fill it faster than expected. Two practical approaches:

  • Check free space before writing, and stop or overwrite the oldest file once free space drops below a safe threshold, rather than letting a write silently fail on a full card
  • Delete the oldest N files on a rolling basis once a maximum file count is reached, keeping only the most recent captures

Silently failing writes are the more common problem in practice -- a full card doesn't throw an obvious error in every code path, so a capture routine that doesn't check the return value of file.open() or file.write() can appear to work for weeks and then quietly stop saving anything.

Common Mistakes

Assuming the SD (SPI) library will work. The AI-Thinker board's wiring only supports SD_MMC in 1-bit mode -- the SPI-based SD library won't find the card regardless of how it's initialized in code.

Using an exFAT-formatted card. Reformat as FAT32 before inserting; exFAT is a common default on larger cards straight from the store and won't mount reliably.

Writing every photo to the same filename. This silently overwrites the previous capture -- use an incrementing counter or timestamp-based name for anything that captures more than once.

Not checking file.open() and file.write() return values. A full card or a loose connection can cause a write to fail silently if the code doesn't check for it, and the project appears to work right up until someone tries to retrieve a photo that was never actually saved.

Forgetting that GPIO 12-15 are claimed by the SD interface. Any other peripheral needing those specific pins -- a fingerprint sensor's UART, for instance -- has to move to a different pin or interface instead.

Troubleshooting

SymptomLikely CauseFix
"SD Mount Failed" on every bootCard not formatted FAT32, or seated poorlyReformat as FAT32; reseat the card firmly
Card mounts but every file write failsCard full, or write-protectedCheck free space with SD_MMC.totalBytes()/usedBytes(); check the card's physical lock switch if present
Photos save but are 0 bytes or corruptedCamera capture failed but wasn't checked before writingVerify esp_camera_fb_get() didn't return null before writing to the card
File list loads but downloads fail with 404URI path mismatch between listing and download handlerConfirm the href in the file list exactly matches the path passed to SD_MMC.open() in the download handler
Board resets or hangs when writing to SDPower supply insufficient for camera + SD write simultaneouslyUse a 5V supply rated for at least 500mA, not a marginal USB port

Frequently Asked Questions

Can I use a card larger than 32GB? Sometimes, if reformatted to FAT32, but SD_MMC on the ESP32 is most reliable at 32GB and under -- larger cards work inconsistently across board revisions.

Do I need an SD card at all, or can I just send photos over WiFi? No -- WiFi-only projects (like the Telegram Camera Bot in this series) skip local storage entirely and stream captures directly. SD storage matters specifically when a project needs to keep photos even if WiFi is down, or needs a persistent local log.

Why does the same code work on some ESP32-CAM boards but not others? Non-AI-Thinker ESP32-CAM variants sometimes wire the SD slot differently or omit it entirely -- confirm your specific board is the AI-Thinker variant before assuming a wiring issue.

How do I know how much space is left on the card? SD_MMC.totalBytes() and SD_MMC.usedBytes() return the total and used space in bytes -- checking the difference before a write is the basis for the capacity management approach above.

Where to Go Next

This storage pattern is the foundation for several other projects in this series: the ESP32-CAM PIR Motion Detection Camera and Timelapse Recorder both use the incrementing-filename pattern above for unrelated triggers (motion vs. a timer), while the Smart Doorbell and Fingerprint Security System extend the file-listing web server into a filtered access log.

Q&A is currently disabled site-wide.

Related projects