ESP32 S3 Camera - Save Photo to SD Card When Door Opens

This tutorial instructs you how to build a door camera with the ESP32 S3. A magnetic door sensor watches your door. The moment somebody opens it, the ESP32 S3 takes a photo and saves it to a microSD card. The board works alone. It does not need WiFi, and it does not need a computer.

ESP32 S3 Camera - Save Photo to SD Card When Door Opens

What you'll build:

  1. An ESP32 S3 camera with a magnetic door sensor
  2. A photo saved to the microSD card every time the door is opened
  3. Clean detection, so one door movement saves exactly one photo
  4. A door camera that works with no internet at all

Hardware Preparation

1×ESP32 S3 N16R8 OV5640 Camera
1×Alternatively, ESP32 S3 N16R8 OV2640 Camera
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×Door Sensor
1×microSD Card
1×Jumper Wires
1×Optionally, DC Power Jack
1×Optionally, ESP32 S3 44-Pin Screw Terminal Block
1×Optionally, ESP32 S3 44-Pin Breakout Board
1×Recommended: Screw Terminal Expansion Board for ESP32 S3
1×Recommended: Breakout Expansion Board for ESP32 S3
1×Recommended: Power Splitter for ESP32 S3

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

※ NOTE THAT:

The ESP32 S3 camera board has 40 pins, but the screw terminal block and the breakout board have 44 pins. They still work together. Put the camera board in the center of the 44-pin board, so 2 pins stay free on the left side and 2 pins stay free on the right side.

The screw terminal block lets you connect wires with a screwdriver, with no soldering. The breakout board gives you easy pin headers for a breadboard.

Overview of Door Sensor

A door sensor has two parts:

  1. A small plastic box with two wires. This part holds a reed switch. You put it on the door frame.
  2. A second plastic box with no wires. This part holds a magnet. You put it on the door itself.

When the door is closed, the magnet sits next to the reed switch and closes it. When the door opens, the magnet moves away, and the switch opens again.

Door Sensor

For more detail about the door sensor, see the ESP32 S3 - Door Sensor tutorial.

Key Specifications

ItemValue
TypeMagnetic reed switch (normally open)
Wires2, and they have no direction
Working gapabout 15 mm to 25 mm
PowerNone. It is only a switch.

※ NOTE THAT:

The two wires have no plus and no minus. You can connect them either way round.

How The ESP32 S3 Reads The Door

The door sensor is only a switch, so the ESP32 S3 must give it a voltage itself. We use the internal pull-up resistor:

pinMode(DOOR_PIN, INPUT_PULLUP);

With INPUT_PULLUP, the pin is HIGH when nothing pulls it down. The table shows what happens:

DoorMagnetReed switchPin reads
ClosedNext to the switchClosed (connected to GND)LOW
OpenAway from the switchOpen (nothing connected)HIGH

So in the code:

#define DOOR_CLOSED LOW #define DOOR_OPEN HIGH

Why The Code Waits 50 Milliseconds

A metal switch never changes cleanly. In the first milliseconds it jumps between open and closed many times. This is called bouncing. Without protection, one door movement would save five photos.

The code waits until the value stays the same for 50 milliseconds:

if (reading != lastReadState) { lastReadState = reading; lastChangeTime = millis(); // the value changed, so start counting again } if (millis() - lastChangeTime >= DEBOUNCE_TIME && reading != doorState) { doorState = reading; // the value is stable now, so we believe it }

Motion Sensor or Door Sensor?

Both build a small security camera, but they are not the same:

Door SensorMotion Sensor (PIR)
SeesOnly the door openingAny warm body that moves
False alarmsAlmost neverSometimes (heaters, windows, animals)
Warm-up timeNone30 to 60 seconds
Needs powerNoYes, 5V
Works through glassYesNo

A door sensor is the better choice when you want to know about one exact event. See also the ESP32 S3 Camera - Save Photo to SD Card When Motion Detected tutorial.

Overview of microSD Card on ESP32 S3

The ESP32 S3 camera board has a microSD card slot. The card is a very good place for photos, because:

  • A photo is too big for the memory of the ESP32 S3. The card can hold thousands of photos.
  • The card keeps the photos when the power goes off.
  • You can take the card out and read it on your computer.

Card Requirements

ItemValue
Card typemicroSD or microSDHC
SizeUp to 32 GB works best
File systemFAT32
Speed classClass 10 or faster

※ NOTE THAT:

A card bigger than 32 GB usually comes with the exFAT file system. The ESP32 S3 cannot read exFAT. Format the card as FAT32 before you use it.

The card slot is already connected inside the board. You do not need to wire it. But the code must know the pins:

Card PinESP32 S3 PinMeaning
CLKGPIO39Clock
CMDGPIO38Commands
D0GPIO40Data

※ NOTE THAT:

The camera uses the other card pins, so the code must open the card in 1-bit mode. That is the word true in SD_MMC.begin("/sdcard", true). Learn more in the ESP32 S3 Camera - Save Photo to SD Card Every Few Seconds tutorial.

Wiring Diagram between Door Sensor and ESP32 S3

Connect the two wires of the door sensor to your ESP32 S3.

The wiring diagram between ESP32 S3 Door Sensor SD Card

This image is created using Fritzing. Click to enlarge image

Door Sensor WireESP32 S3 Pin
First wireGPIO2
Second wireGND

Then put the microSD card into the slot, and put the two parts of the sensor on your door:

  1. Put the part with the wires on the door frame.
  2. Put the part without wires on the door itself.
  3. Move the door until the two parts are next to each other. They must almost touch, with a gap smaller than 15 mm.
  4. Point the camera at the door.

Safety Notes

The door sensor needs no power. Do not connect it to 5V or to 3.3V. One wire goes to GPIO2, the other goes to GND. Nothing can break, because it is only a switch.

Never take the microSD card out while the board is writing a photo. Always unplug the USB cable first, wait two seconds, then take the card out.

Do not use a camera pin for the sensor. The camera already uses GPIO4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17 and 18. The card slot uses GPIO38, 39 and 40. GPIO2 is free, so we use it.

Arduino IDE Settings for ESP32 S3 Camera

The camera does not work with the default settings of the Arduino IDE. You must change some items in the Tools menu before you upload the code.

Tools Menu ItemSelect This
BoardESP32S3 Dev Module
Flash Size16MB (128Mb)
PSRAMOPI PSRAM
Partition SchemeHuge APP (3MB No OTA/1MB SPIFFS)
CPU Frequency240MHz (WiFi)
USB ModeHardware CDC and JTAG
Upload ModeUART0 / Hardware CDC
Upload Speed921600
USB CDC On BootEnabled

※ NOTE THAT:

PSRAM must be OPI PSRAM, and Partition Scheme must be Huge APP. Change Flash Size first, because the list of Partition Scheme options changes after that.

How To Program the Door Camera

You do not need to install any library. SD_MMC and esp_camera are already inside the ESP32 board package.

  • Set the sensor pin as an input with a pull-up resistor.
#define DOOR_PIN 2 pinMode(DOOR_PIN, INPUT_PULLUP);
  • Read the door one time in setup(), so the code knows the state at the start.
doorState = digitalRead(DOOR_PIN); lastReadState = doorState;
  • In loop(), wait until the value is stable, then look for the change to open.
int reading = digitalRead(DOOR_PIN); if (reading != lastReadState) { lastReadState = reading; lastChangeTime = millis(); } if (millis() - lastChangeTime >= DEBOUNCE_TIME && reading != doorState) { doorState = reading; if (doorState == DOOR_OPEN) takeAndSavePhoto(); }

※ NOTE THAT:

The code saves a photo only when the door opens. Nothing happens when the door closes again. Do you want a photo for both? Then call takeAndSavePhoto() in the else part too.

  • Start the microSD card in 1-bit mode.
SD_MMC.setPins(39, 38, 40); // CLK, CMD, D0 if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found"); }
  • Give every photo a different name, so a new photo never writes over an old one.
String makeFileName(int number) { char name[32]; sprintf(name, "/door_%05d.jpg", number); return String(name); }
  • When the board restarts, the counter starts at 1 again. To keep the old photos, look for the first free name:
while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++;
  • Open a file, write the photo, and close the file.
camera_fb_t *fb = esp_camera_fb_get(); File file = SD_MMC.open("/door_00001.jpg", FILE_WRITE); file.write(fb->buf, fb->len); file.close(); esp_camera_fb_return(fb);

※ NOTE THAT:

You must call file.close(). If you forget it, the photo stays in the memory and never reaches the card.

ESP32 S3 Code

The following code saves a photo named door_00001.jpg, door_00002.jpg, and so on, every time the door is opened. The quiet time is 3 seconds. To change it, change the line #define QUIET_TIME 3000.

/* * This ESP32 S3 code was developed by newbiely.com * * This ESP32 S3 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3/esp32-s3-camera-save-photo-to-sd-card-when-door-opens */ #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" #define DOOR_PIN 2 // the door sensor is connected to GPIO2 #define DEBOUNCE_TIME 50 // wait 50 milliseconds, because the switch bounces #define QUIET_TIME 3000 // wait 3 seconds before the next photo (3000 milliseconds) // Camera pins of the ESP32-S3-N16R8 camera board. // The same pins work for the OV2640, OV3660 and OV5640 versions. #define PWDN_GPIO_NUM -1 #define RESET_GPIO_NUM -1 #define XCLK_GPIO_NUM 15 #define SIOD_GPIO_NUM 4 #define SIOC_GPIO_NUM 5 #define Y9_GPIO_NUM 16 #define Y8_GPIO_NUM 17 #define Y7_GPIO_NUM 18 #define Y6_GPIO_NUM 12 #define Y5_GPIO_NUM 10 #define Y4_GPIO_NUM 8 #define Y3_GPIO_NUM 9 #define Y2_GPIO_NUM 11 #define VSYNC_GPIO_NUM 6 #define HREF_GPIO_NUM 7 #define PCLK_GPIO_NUM 13 // Pins of the microSD card slot on the board #define SD_CLK_GPIO_NUM 39 #define SD_CMD_GPIO_NUM 38 #define SD_D0_GPIO_NUM 40 int photoNumber = 1; // The door sensor is a switch: // LOW = the switch is closed = the magnet is near = the door is CLOSED // HIGH = the switch is open = the magnet is away = the door is OPEN #define DOOR_CLOSED LOW #define DOOR_OPEN HIGH int doorState = DOOR_CLOSED; int lastReadState = DOOR_CLOSED; unsigned long lastChangeTime = 0; unsigned long lastPhotoTime = 0; // Returns the name of the camera sensor that is installed on the board const char *getSensorName(int pid) { switch (pid) { case OV2640_PID: return "OV2640 (2 MP, max UXGA 1600x1200)"; case OV3660_PID: return "OV3660 (3 MP, max QXGA 2048x1536)"; case OV5640_PID: return "OV5640 (5 MP, max QSXGA 2592x1944)"; default: return "Unknown sensor"; } } bool initCamera() { 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; config.frame_size = FRAMESIZE_UXGA; // 1600x1200 - a big, sharp photo config.jpeg_quality = 12; // 0 to 63, a lower number means a better image config.fb_count = 1; config.fb_location = CAMERA_FB_IN_DRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; if (psramFound()) { config.fb_location = CAMERA_FB_IN_PSRAM; config.jpeg_quality = 10; config.fb_count = 2; config.grab_mode = CAMERA_GRAB_LATEST; } else { config.frame_size = FRAMESIZE_QVGA; Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); } esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x\n", err); return false; } sensor_t *s = esp_camera_sensor_get(); Serial.printf("Camera sensor: %s\n", getSensorName(s->id.PID)); // Each sensor needs slightly different settings switch (s->id.PID) { case OV2640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; case OV3660_PID: s->set_vflip(s, 1); // the OV3660 image is upside down by default s->set_brightness(s, 1); // make the image a little brighter s->set_saturation(s, -2); // make the colors a little softer break; case OV5640_PID: s->set_vflip(s, 0); s->set_hmirror(s, 0); break; default: break; } return true; } bool initSDCard() { SD_MMC.setPins(SD_CLK_GPIO_NUM, SD_CMD_GPIO_NUM, SD_D0_GPIO_NUM); // true means 1-bit mode. The camera uses the other pins, so we cannot use 4-bit mode. if (!SD_MMC.begin("/sdcard", true)) { Serial.println("SD card not found. Please check the card and push it in again."); return false; } if (SD_MMC.cardType() == CARD_NONE) { Serial.println("No SD card inside the slot"); return false; } Serial.printf("SD card size: %llu MB\n", SD_MMC.cardSize() / (1024 * 1024)); return true; } // Makes a file name like /door_00001.jpg String makeFileName(int number) { char name[32]; sprintf(name, "/door_%05d.jpg", number); return String(name); } // Looks for the first free file name, so old photos are never lost void findFirstFreeNumber() { while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; Serial.printf("The next photo will be door_%05d.jpg\n", photoNumber); } void takeAndSavePhoto() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Photo capture failed"); return; } String fileName = makeFileName(photoNumber); File file = SD_MMC.open(fileName.c_str(), FILE_WRITE); if (file) { file.write(fb->buf, fb->len); file.close(); Serial.printf("Saved %s (%u bytes)\n", fileName.c_str(), fb->len); photoNumber++; } else { Serial.printf("Cannot write the file %s\n", fileName.c_str()); } esp_camera_fb_return(fb); // give the memory back to the camera } void setup() { Serial.begin(115200); delay(1000); // INPUT_PULLUP: the pin is HIGH when the switch is open pinMode(DOOR_PIN, INPUT_PULLUP); if (!initCamera()) { Serial.println("Stopped"); while (true) delay(1000); } if (!initSDCard()) { Serial.println("Stopped"); while (true) delay(1000); } findFirstFreeNumber(); doorState = digitalRead(DOOR_PIN); lastReadState = doorState; if (doorState == DOOR_OPEN) Serial.println("Ready. The door is open now."); else Serial.println("Ready. The door is closed now."); } void loop() { int reading = digitalRead(DOOR_PIN); // The switch bounces, so we wait until the value is stable if (reading != lastReadState) { lastReadState = reading; lastChangeTime = millis(); } if (millis() - lastChangeTime >= DEBOUNCE_TIME && reading != doorState) { doorState = reading; if (doorState == DOOR_OPEN) { Serial.println("The door is open!"); if (millis() - lastPhotoTime >= QUIET_TIME) { takeAndSavePhoto(); lastPhotoTime = millis(); } else { Serial.println("Too soon after the last photo. Nothing is saved."); } } else { Serial.println("The door is closed."); } } }

Detailed Instructions

  1. New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  2. Format your microSD card as FAT32 on your computer.
  3. Unplug the USB cable. Put the card into the slot, and connect the two wires of the door sensor as in the table above.
  4. Copy the above code and paste it into the Arduino IDE.
  5. Change the settings in the Tools menu. See the table above.
  6. Compile and upload the code to the ESP32 S3 board by clicking the Upload button in Arduino IDE.
Arduino IDE Upload Code
  1. Open the Serial Monitor in Arduino IDE.
How to open serial monitor on Arduino IDE
  1. Press the RESET button one time.
  2. Put the two parts of the sensor next to each other. The Serial Monitor says the door is closed.
  3. Move the two parts away from each other, like an opening door.
  4. Look at the Serial Monitor. You see The door is open! and the name of the photo.
  5. Unplug the USB cable, take the card out, and read the photos on your computer.

Serial Monitor

The following readings were captured in 2026:

Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-09-23 15:00:01] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-23 15:00:01] SD card size: 15193 MB [2026-09-23 15:00:01] The next photo will be door_00001.jpg [2026-09-23 15:00:01] Ready. The door is closed now. [2026-09-23 15:01:12] The door is open! [2026-09-23 15:01:13] Saved /door_00001.jpg (184320 bytes) [2026-09-23 15:01:31] The door is closed. [2026-09-23 15:02:05] The door is open! [2026-09-23 15:02:06] Saved /door_00002.jpg (183104 bytes)
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

When the door is opened again too quickly, the code says so and saves nothing:

Newbiely | Arduino IDE 2.3.8
──
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32S3 Dev Module' on 'COM15')
New Line
9600 baud
[2026-09-23 15:02:08] The door is open! [2026-09-23 15:02:08] Too soon after the last photo. Nothing is saved.
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

Troubleshooting

The Serial Monitor always says the door is open, even when it is closed.

  • The two parts are too far apart. They must almost touch, with a gap smaller than 15 mm.
  • Check the wire on GPIO2 and the wire on GND.
  • Some door sensors are "normally closed" instead of "normally open". Then the two lines #define DOOR_CLOSED LOW and #define DOOR_OPEN HIGH must swap their values.

The Serial Monitor always says the door is closed.

The two wires are probably touching each other, or one wire is on the wrong pin. One wire goes to GPIO2, the other to GND.

One door movement saves two or three photos.

Increase DEBOUNCE_TIME from 50 to 150.

The Serial Monitor shows SD card not found.

Format the card as FAT32, use a card of 32 GB or smaller, and push the card in again. You can also check the three pin numbers at the top of the code against the drawing of your board.

The Serial Monitor shows No SD card inside the slot.

The board found the slot but no card. Push the card in again, or try another card.

The Serial Monitor shows Cannot write the file.

The card is full, or the card is locked, or the card is broken. Check the free space on your computer.

The photo is dark.

The camera cannot see in the dark. Add a light near the door, or a lamp that switches on with the door.

The Serial Monitor shows nothing after the boot messages.

If your USB cable is in the port named UART, set USB CDC On Boot to Disabled. If it is in the port named USB, set it to Enabled.

The Serial Monitor shows Camera init failed with error 0x105.

Set PSRAM to OPI PSRAM in the Tools menu, and check the flat cable of the camera.

Applications

A door sensor gives you one clear event with almost no false alarms, and the microSD card means no WiFi, no internet and no monthly payment. Here are practical projects you can build with this code:

  1. Offline front door camera: See who opened your front door while you were away, with no internet connection at all.
  2. Parcel box camera: Keep a photo of the person who opens your parcel box or your mailbox.
  3. Tool box, safe or locker record: Get a picture every time somebody opens it, even in a place with no WiFi.
  4. Garage and shed camera: Know who opened your garage door at night.
  5. Medicine cabinet watcher: Keep a photo when a child opens a cabinet that must stay closed.
  6. Server rack and control cabinet: Keep a photo record of everybody who opens a cabinet at work.
  7. Holiday home door camera: Leave it in an empty house for weeks, and read the card when you come back.
  8. Window opening record: Put the sensor on a window instead of a door.
  9. Farm gate or barn camera: Watch a door far away from any router.
  10. Shop door counter with photos: Keep a picture of every customer who opens the door.

Video Tutorial

Watch the step-by-step video walkthrough for this ESP32 S3 project below.

Challenges

  1. Beginner: Also save a photo when the door is closed again, so you have a picture of both moments.
  2. Intermediate: Write a text file on the card with one line per event, for example the photo name and the time since the board started.
  3. Advanced: Add a real time clock (RTC) and put the real date and time into the file name, for example /2026-09-23_14-05-31.jpg.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!