ESP32 S3 Camera - Save Battery with Deep Sleep

All the other camera projects need a USB cable all the time. This tutorial is different. The ESP32 S3 sleeps and uses almost no power. When the motion sensor sees somebody, the board wakes up, takes a photo, saves it to the microSD card, and goes back to sleep. Because the board is awake for only about two seconds at a time, it uses very little power, so you can run it from a battery and put the camera at a gate, in a garden, or on a parcel box.

ESP32 S3 Camera - Save Battery with Deep Sleep

What you'll build:

  1. A camera that sleeps until something happens
  2. A photo saved to the microSD card on every wake up
  3. A counter that survives deep sleep, so the photo numbers never start again at 1
  4. A camera that works from a battery or a power bank, with no cable

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×HC-SR501 Motion Sensor
1×Alternatively, AM312 Mini Motion Sensor
1×microSD Card
1×Jumper Wires
1×Optionally, DC Power Jack
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.

How It Works

All the other camera projects run in a circle: the board is awake all the time, and it asks again and again whether something happened. That is easy, but it uses power all day for nothing.

This project turns the idea around. The board sleeps, and the sensor wakes it up.

Here is the whole life of the camera:

StepWhat happensHow longPower
1The board sleeps. Only one small part inside it watches one pin.Hours, or daysAlmost none
2Somebody walks past. The PIR sensor pulls GPIO21 HIGH.At onceNone, the sensor is not the board
3That HIGH wakes the board. It starts again from the beginning, like after a RESET.About 1 secondFull
4The camera and the card start. The board takes a photo and writes it to the card.About 1 secondFull
5The board switches the camera and the card off, and goes back to sleep.At onceBack to almost none
How the ESP32 S3 Deep Sleep Camera Works

So the board is awake for only about two seconds for every visitor. All the rest of the time it is asleep. That is the whole trick, and it is why this camera can work with no cable.

※ NOTE THAT:

The PIR sensor never sleeps. It watches the room all the time, and it uses only a very small current to do it. The sensor is the part that decides when the board is needed.

Three Things Make It Work

WhatWhy it is needed
esp_sleep_enable_ext0_wakeup()Tells the board which pin may wake it, and at which level
RTC_DATA_ATTRKeeps the photo counter alive during sleep. Normal variables lose their value.
esp_camera_deinit() and SD_MMC.end()Switch the camera and the card off, so they use nothing during sleep

Each of these is explained below.

Overview of Deep Sleep

Deep sleep switches off almost everything inside the ESP32 S3: the two processors, the WiFi, and the memory. Only one very small part stays awake, and its only job is to watch one pin.

StateWhat is runningCurrent it uses
Normal, WiFi onEverythingabout 150 mA
Normal, WiFi offTwo processorsabout 40 mA
Deep sleepAlmost nothingless than 1 mA

※ NOTE THAT:

These numbers are a guide, not a promise. Your board, your sensor and your power bank all change the result. Many power banks also switch themselves off when the current is very small, which is the most common problem in battery projects.

The Board Starts Again Every Time

This is the part that surprises everybody: after deep sleep, the board does not continue where it stopped. It starts again from the very beginning, exactly like after a RESET. setup() runs again, and loop() never runs at all.

So the whole program lives inside setup():

void setup() { // wake up, take the photo, save it goToSleep(); // this line never returns } void loop() { // Empty. This board never reaches loop(). }

How to Remember Something During Sleep

Normal variables lose their value during deep sleep. Variables marked RTC_DATA_ATTR do not, because they live in the small part that stays awake:

RTC_DATA_ATTR int photoNumber = 1; RTC_DATA_ATTR int wakeCount = 0;

※ NOTE THAT:

RTC_DATA_ATTR keeps the value through deep sleep, but not through a power cut or a RESET. The code also checks the card for free file names, so no photo is ever written over.

Waking Up on a Pin

ext0 wakes the board when one pin reaches a level you choose. The number 1 means "wake when the pin is HIGH":

esp_sleep_enable_ext0_wakeup(GPIO_NUM_21, 1); esp_deep_sleep_start();

※ NOTE THAT:

Only the pins GPIO0 to GPIO21 can wake the ESP32 S3 from deep sleep. They are called RTC pins. GPIO21 is one of them, so it works. A pin like GPIO40 would never wake the board.

Overview of PIR Motion Sensor

A PIR motion sensor sees the heat of a human body. Its OUT pin goes HIGH when something warm moves in front of it, and that HIGH is what wakes the ESP32 S3.

PIR Motion Sensor HC-SR501

The sensor has two small orange screws. Turn the time delay screw all the way to the left, so the output stays HIGH for only about 3 seconds. For more detail, see the ESP32 S3 - Motion Sensor tutorial.

※ NOTE THAT:

The PIR sensor itself never sleeps. It uses about 50 µA, which is very little, and it must stay awake, because it is the part that wakes the board.

Wiring Diagram between PIR Motion Sensor and ESP32 S3

The wiring diagram between ESP32 S3 Deep Sleep Camera

This image is created using Fritzing. Click to enlarge image

PIR Sensor PinESP32 S3 Pin
VCC5V
GNDGND
OUTGPIO21

Then put the microSD card into the slot on the board.

Safety Notes

The PIR sensor needs 5V on its VCC pin, not 3.3V. Its OUT pin gives 3.3V, which is correct for the ESP32 S3.

The OUT pin must go to GPIO21, because only GPIO0 to GPIO21 can wake the board from deep sleep.

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

How To Program the Sleeping Camera

  • Find out why the board woke up. This is useful, because the first start is not a wake up.
esp_sleep_wakeup_cause_t reason = esp_sleep_get_wakeup_cause(); if (reason == ESP_SLEEP_WAKEUP_EXT0) Serial.println("The PIR sensor saw a movement"); else Serial.println("The board was switched on");
  • Give the camera a moment. The first pictures after a start are always too dark, because the camera has not set the light yet.
delay(400); for (int i = 0; i < 2; i++) { camera_fb_t *old = esp_camera_fb_get(); if (old) esp_camera_fb_return(old); }

※ NOTE THAT:

This is the most common mistake in deep sleep cameras: the photo is black. The camera needs about half a second of light before the first good picture.

  • Switch the camera and the card off before sleeping, so they use no power.
esp_camera_deinit(); SD_MMC.end();
  • Hold the pin down during sleep, so a floating pin cannot wake the board by itself.
rtc_gpio_pulldown_en(GPIO_NUM_21); rtc_gpio_pullup_dis(GPIO_NUM_21);

※ NOTE THAT:

Without this, an unconnected pin picks up electrical noise, the board wakes up hundreds of times, and the battery is empty in one night.

  • Go to sleep. Nothing after this line ever runs.
esp_deep_sleep_start();

ESP32 S3 Code

The photos are named wake_00001.jpg, wake_00002.jpg, and so on. The counter lives in RTC_DATA_ATTR, so it keeps counting through every sleep.

/* * 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-battery-with-deep-sleep */ #include "esp_camera.h" #include "FS.h" #include "SD_MMC.h" #include "driver/rtc_io.h" #define PIR_PIN GPIO_NUM_21 // the PIR motion sensor is connected to GPIO21 #define WARMUP_TIME 400 // give the camera 400 ms to set the light // 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 // RTC_DATA_ATTR keeps the value during deep sleep. // Normal variables lose their value, these do not. RTC_DATA_ATTR int photoNumber = 1; RTC_DATA_ATTR int wakeCount = 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_SVGA; // 800x600 - smaller photo, shorter awake time 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; } return true; } // Makes a file name like /wake_00001.jpg String makeFileName(int number) { char name[32]; sprintf(name, "/wake_%05d.jpg", number); return String(name); } void takeAndSavePhoto() { // The first pictures after a start are too dark, so we throw two away for (int i = 0; i < 2; i++) { camera_fb_t *old = esp_camera_fb_get(); if (old) esp_camera_fb_return(old); } camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Photo capture failed"); return; } // The counter may be behind after a card change, so find a free name while (SD_MMC.exists(makeFileName(photoNumber))) photoNumber++; 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); } // Turns everything off and goes to sleep until the PIR sensor goes HIGH void goToSleep() { Serial.println("Going to sleep. Bye."); Serial.flush(); esp_camera_deinit(); SD_MMC.end(); // Wake up when the PIR pin becomes HIGH (the number 1 means HIGH) esp_sleep_enable_ext0_wakeup(PIR_PIN, 1); // Hold the pull-down during sleep, so the pin does not float rtc_gpio_pulldown_en(PIR_PIN); rtc_gpio_pullup_dis(PIR_PIN); esp_deep_sleep_start(); // Nothing after this line ever runs. The board starts again at setup(). } void setup() { Serial.begin(115200); delay(500); wakeCount++; esp_sleep_wakeup_cause_t reason = esp_sleep_get_wakeup_cause(); if (reason == ESP_SLEEP_WAKEUP_EXT0) Serial.printf("Wake up number %d: the PIR sensor saw a movement\n", wakeCount); else Serial.printf("Wake up number %d: the board was switched on\n", wakeCount); if (!initCamera()) { Serial.println("The camera failed. Sleeping anyway."); goToSleep(); } if (!initSDCard()) { Serial.println("The card failed. Sleeping anyway."); goToSleep(); } delay(WARMUP_TIME); takeAndSavePhoto(); goToSleep(); } void loop() { // Empty. This board never reaches loop(), because setup() ends in deep sleep. }

Detailed Instructions

  • New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  • Format your microSD card as FAT32 on your computer.
  • Unplug the USB cable. Put the card into the slot, and wire the PIR sensor as in the table above.
  • Copy the above code and paste it into the Arduino IDE.
  • Change the settings in the Tools menu. See the table above.
  • Compile and upload the code to the ESP32 S3 board by clicking the Upload button in Arduino IDE.
Arduino IDE Upload Code
  • Open the Serial Monitor in Arduino IDE.
How to open serial monitor on Arduino IDE
  • Press the RESET button one time. The board takes one photo and sleeps.
  • Wave your hand in front of the sensor. The board wakes up, you see a new line, and it sleeps again.
  • When you are happy, unplug the USB cable and use a power bank instead.
  • Pro Tip: Keep the Serial Monitor open while you test. It is the only way to see that the board really wakes up.

Serial Monitor

Every wake up prints two lines. 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-28 11:00:01] Wake up number 1: the board was switched on [2026-09-28 11:00:02] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-28 11:00:03] Saved /wake_00001.jpg (51353 bytes) [2026-09-28 11:00:03] Going to sleep. Bye. [2026-09-28 11:04:41] Wake up number 2: the PIR sensor saw a movement [2026-09-28 11:04:43] Saved /wake_00002.jpg (52180 bytes) [2026-09-28 11:04:43] Going to sleep. Bye. [2026-09-28 11:19:07] Wake up number 3: the PIR sensor saw a movement [2026-09-28 11:19:09] Saved /wake_00003.jpg (50992 bytes) [2026-09-28 11:19:09] Going to sleep. Bye.
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

※ NOTE THAT:

The board is awake for only about 2 seconds each time. All the rest of the time it sleeps and uses almost nothing. That is the whole idea of this project.

How To Save Even More Power

The battery is used in two ways: a very small amount all the time while the board sleeps, and a much larger amount during the two seconds of each photo. So there are only two ways to use less:

What to doHowWhy it helps
Stay awake for less timeUse a smaller FRAME_SIZE, for example FRAMESIZE_VGAA smaller photo is taken and written faster
Wake up fewer timesTurn the sensitivity screw of the sensor down, and point it away from the streetEvery wake up costs much more than an hour of sleeping

Watch the Serial Monitor to see how long your board is really awake. The time between Wake up number ... and Going to sleep. Bye. is the part that costs power. Everything between two wake ups is almost free.

※ NOTE THAT:

We do not say here how many days or weeks a battery lasts, because it depends on too many things: which battery, how old it is, how cold the night is, how often somebody walks past, and how much your own power bank uses by itself. Measure it in your own place, with your own battery.

※ NOTE THAT:

Many power banks switch themselves off when a device takes almost no current, which is exactly what deep sleep does. If your camera stops during the night, this is usually the reason. Use a power bank that supports "low current mode", or use a battery with a proper holder.

Troubleshooting

The board never wakes up.

  • The OUT pin of the sensor must be on GPIO21. A pin above GPIO21 cannot wake the board.
  • Check that the sensor has 5V, not 3.3V.
  • Wait one minute after the first start. The PIR sensor is still warming up.

The board wakes up again and again with nothing in front of it.

  • The sensor is too sensitive. Turn the sensitivity screw a little to the left.
  • Point the sensor away from windows, heaters and air conditioners.

Every photo is black or very dark.

The camera did not have time to set the light. Increase WARMUP_TIME from 400 to 800, and change the loop from 2 to 3 pictures.

The photos stop after some hours on a power bank.

The power bank switched itself off, because deep sleep takes too little current for it. Use a power bank with low current mode, or a battery.

The Serial Monitor loses the connection after every sleep.

This is normal on the native USB port, because the board disappears when it sleeps. Use the UART port for testing, and set USB CDC On Boot to Disabled.

The numbers start again at 1 after a power cut.

RTC_DATA_ATTR does not survive a power cut. This is not a problem: the code looks for the first free name on the card, so no photo is ever written over.

Applications

A camera that needs no cable can go where the other projects cannot. Here are practical projects you can build with this code:

  1. Trail and wildlife camera: Put it in a forest or a field with no power at all.
  2. Parcel box camera: A small battery inside the box, and no cable through the wall.
  3. Garden gate watcher: Watch a gate far away from the house, with no cable to lay.
  4. Bicycle and garage camera: Keep a photo of everybody who comes near, with no wiring work.
  5. Holiday home camera: Leave it in an empty house while you are away.
  6. Farm and barn camera: Watch animals or a machine far from any socket.
  7. Building site camera: Move it every day, because there is nothing to plug in.
  8. Beehive watcher: Watch the hive without a cable across the garden.
  9. Car camera: Take a photo when somebody comes near your parked car.
  10. Temporary security camera: Put it anywhere for a while, then take it away again.

Video Tutorial

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

Challenges

  1. Beginner: Change the photo size from FRAMESIZE_SVGA to FRAMESIZE_UXGA and see how much longer the board stays awake.
  2. Intermediate: Add a timer wake up as well, so the board also takes one photo every hour, even without movement. Use esp_sleep_enable_timer_wakeup().
  3. Advanced: Connect WiFi after the wake up and send the photo to Telegram, then sleep. Watch the Serial Monitor and see how much longer the board must stay awake.

※ 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!