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.
Hardware Needed
Components
4 itemsNeed 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.
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.
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.
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.
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
| Symptom | Likely Cause | Fix |
|---|---|---|
| "SD Mount Failed" on every boot | Card not formatted FAT32, or seated poorly | Reformat as FAT32; reseat the card firmly |
| Card mounts but every file write fails | Card full, or write-protected | Check free space with SD_MMC.totalBytes()/usedBytes(); check the card's physical lock switch if present |
| Photos save but are 0 bytes or corrupted | Camera capture failed but wasn't checked before writing | Verify esp_camera_fb_get() didn't return null before writing to the card |
| File list loads but downloads fail with 404 | URI path mismatch between listing and download handler | Confirm 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 SD | Power supply insufficient for camera + SD write simultaneously | Use 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.
