
early ESP32-CAM projects and tutorials :
Watch the Video Demonstration
Watch the follow video demonstration to see what you ’ re going to build throughout this tutorial.
Parts Required
To follow this project, you need the pursue parts :
You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price !
Project Overview
The comply image shows the web server we ’ ll build in this tutorial .
When you access the vane server, you ’ ll see three buttons :
- ROTATE: depending on your ESP32-CAM orientation, you might need to rotate the photo;
- CAPTURE PHOTO: when you click this button, the ESP32-CAM takes a new photo and saves it in the ESP32 SPIFFS. Please wait at least 5 seconds before refreshing the web page to ensure the ESP32-CAM takes and stores the photo;
- REFRESH PAGE: when you click this button, the web page refreshes and it’s updated with the latest photo.
Note: as mentioned previously the latest photograph captured is stored in the ESP32 SPIFFS, so even if you restart your board, you can constantly access the concluding save photograph .
Installing the ESP32 add-on
We ’ ll broadcast the ESP32 control panel using Arduino IDE. thus, you need the Arduino IDE installed equally well as the ESP32 addition :
Installing Libraries
To build the web waiter, we ’ ll use the ESPAsyncWebServer library. This library besides requires the AsyncTCP Library to work by rights. Follow the next steps to install those libraries .
Installing the ESPAsyncWebServer library
Follow the following steps to install the ESPAsyncWebServer library :
- Click here to download the ESPAsyncWebServer library. You should have a .zip folder in your Downloads folder
- Unzip the .zip folder and you should get ESPAsyncWebServer-master folder
- Rename your folder from ESPAsyncWebServer-master to ESPAsyncWebServer
- Move the ESPAsyncWebServer folder to your Arduino IDE installation libraries folder
alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you ’ ve just downloaded .
Installing the Async TCP Library for ESP32
The ESPAsyncWebServer library requires the AsyncTCP library to work. Follow the next steps to install that library :
- Click here to download the AsyncTCP library. You should have a .zip folder in your Downloads folder
- Unzip the .zip folder and you should get AsyncTCP-master folder
- Rename your folder from AsyncTCP-master to AsyncTCP
- Move the AsyncTCP folder to your Arduino IDE installation libraries folder
- Finally, re-open your Arduino IDE
alternatively, after downloading the library, you can go to Sketch > Include Library > Add .ZIP library… and select the library you ’ ve fair downloaded .
ESP32-CAM Take and Display Photo Web Server Sketch
Copy the follow code to your Arduino IDE. This code builds a web server that allows you to take a photograph with your ESP32-CAM and display the last photograph taken. Depending on the orientation of your ESP32-CAM, you may want to rotate the movie, so we besides included that feature .
/*********
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-cam-take-photo-display-web-server/
IMPORTANT!!!
- Select Board "AI Thinker ESP32-CAM"
- GPIO 0 must be connected to GND to upload a sketch
- After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*********/
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownour problems
#include "soc/rtc_cntl_reg.h" // Disable brownour problems
#include "driver/rtc_io.h"
#include
#include
#include
#include
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
boolean takeNewPhoto = false;
// Photo File Name to save in SPIFFS
#define FILE_PHOTO "/photo.jpg"
// OV2640 camera module pins (CAMERA_MODEL_AI_THINKER)
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
const char index_html[] PROGMEM = R"rawliteral(
ESP32-CAM Last Photo
It might take more than 5 seconds to capture a photograph .
)rawliteral";
void setup() {
// Serial port for debugging purposes
Serial.begin(115200);
// Connect to Wi-Fi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
if (!SPIFFS.begin(true)) {
Serial.println("An Error has occurred while mounting SPIFFS");
ESP.restart();
}
else {
delay(500);
Serial.println("SPIFFS mounted successfully");
}
// Print ESP32 Local IP Address
Serial.print("IP Address: http://");
Serial.println(WiFi.localIP());
// Turn-off the 'brownout detector'
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
// OV2640 camera module
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_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_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;
if (psramFound()) {
config.frame_size = FRAMESIZE_UXGA;
config.jpeg_quality = 10;
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
// Camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
ESP.restart();
}
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request->send_P(200, "text/plain", "Taking Photo");
});
server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
});
// Start server
server.begin();
}
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = false;
}
delay(1);
}
// Check if photo capture was successful
bool checkPhoto( fs::FS &fs ) {
File f_pic = fs.open( FILE_PHOTO );
unsigned int pic_sz = f_pic.size();
return ( pic_sz > 100 );
}
// Capture Photo and Save it to SPIFFS
void capturePhotoSaveSpiffs( void ) {
camera_fb_t * fb = NULL; // pointer
bool ok = 0; // Boolean indicating if the picture has been taken correctly
do {
// Take a photo with the camera
Serial.println("Taking a photo...");
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// Photo file name
Serial.printf("Picture file name: %s\n", FILE_PHOTO);
File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE);
// Insert the data in the photo file
if (!file) {
Serial.println("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.print("The picture has been saved in ");
Serial.print(FILE_PHOTO);
Serial.print(" - Size: ");
Serial.print(file.size());
Serial.println(" bytes");
}
// Close the file
file.close();
esp_camera_fb_return(fb);
// check if file has been correctly saved in SPIFFS
ok = checkPhoto(SPIFFS);
} while ( !ok );
}
View crude code
How the Code Works
first base, include the needed libraries to work with the camera, to build the world wide web waiter and to use SPIFFS .
#include "WiFi.h"
#include "esp_camera.h"
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "soc/soc.h" // Disable brownout problems
#include "soc/rtc_cntl_reg.h" // Disable brownout problems
#include "driver/rtc_io.h"
#include
#include
#include
#include
following, write your network credentials in the following variables, so that the ESP32-CAM can connect to your local anesthetic network .
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Create an AsyncWebServer object on port 80 .
AsyncWebServer server(80);
The takeNewPhoto boolean variable indicates when it ’ second time to take a newfangled photograph .
boolean takeNewPhoto = false;
then, define the path and name of the photograph to be saved in SPIFFS .
#define FILE_PHOTO "/photo.jpg"
adjacent, define the television camera pins for the ESP32-CAM AI THINKER module .
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
Building the Web Page
adjacent, we have the HTML to build the web page :
const char index_html[] PROGMEM = R"rawliteral(
ESP32-CAM Last Photo
It might take more than 5 seconds to capture a photograph .
)rawliteral";
We won ’ triiodothyronine go into much detail on how this HTML works. We ’ ll precisely take a promptly overview .
basically, create three buttons : ROTATE ; CAPTURE PHOTO and REFRESH PAGE. Each photograph calls a different JavaScript routine : rotatePhoto ( ), capturePhoto ( ) and reload ( ) .
The capturePhoto ( ) function sends a request on the /capture URL to the ESP32, so it takes a new photograph.
Read more : 12 Best Stock Photo Sites in 2022 (Updated)
function capturePhoto() {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/capture", true);
xhr.send();
}
The rotatePhoto ( ) function rotates the photograph .
function rotatePhoto() {
var img = document.getElementById("photo");
deg += 90;
if(isOdd(deg/90)){ document.getElementById("container").className = "vert"; }
else{ document.getElementById("container").className = "hori"; }
img.style.transform = "rotate(" + deg + "deg)";
}
function isOdd(n) { return Math.abs(n % 2) == 1; }
We ’ rhenium not certain what ’ s the “ best ” way to rotate a photograph with JavaScript. This method acting works perfectly, but there may be better ways to do this. If you have any hypnotism please share with us .
last, the postdate incision displays the photograph .

When, you click the REFRESH button, it will load the latest image .
setup()
In the setup ( ), initialize a serial communication :
Serial.begin(115200);
Connect the ESP32-CAM to your local net :
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
initialize spiff :
if (!SPIFFS.begin(true)) {
Serial.println("An Error has occurred while mounting SPIFFS");
ESP.restart();
}
else {
delay(500);
Serial.println("SPIFFS mounted successfully");
}
Print the ESP32-CAM local IP savoir-faire :
Serial.print("IP Address: http://");
Serial.println(WiFi.localIP());
The lines that follow, configure and initialize the television camera with the right settings .
Handle the Web Server
next, we need to handle what happens when the ESP32-CAM receives a request on a URL .
When the ESP32-CAM receives a request on the etymon / URL, we send the HTML text to build the web page .
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send_P(200, "text/html", index_html);
});
When we press the “ CAPTURE ” button on the web server, we send a request to the ESP32 /capture URL. When that happens, we set the takeNewPhoto variable to true, then that we know it is time to take a modern photograph .
server.on("/capture", HTTP_GET, [](AsyncWebServerRequest * request) {
takeNewPhoto = true;
request->send_P(200, "text/plain", "Taking Photo");
});
In case there ’ s a request on the /saved-photo URL, send the photograph saved in SPIFFS to a connected customer :
server.on("/saved-photo", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, FILE_PHOTO, "image/jpg", false);
});
last, start the world wide web server .
server.begin();
loop()
In the closed circuit ( ), if the takeNewPhoto variable is genuine, we call the capturePhotoSaveSpiffs ( ) to take a new photograph and save it to SPIFFS. then, set the takeNewPhoto varying to false .
void loop() {
if (takeNewPhoto) {
capturePhotoSaveSpiffs();
takeNewPhoto = false;
}
delay(1);
}
Take a Photo
There are two other functions in the sketch : checkPhoto ( ) and capturePhotoSaveSpiffs ( ) .
The checkPhoto ( ) affair checks if the photograph was successfully saved to SPIFFS .
bool checkPhoto( fs::FS &fs ) {
File f_pic = fs.open( FILE_PHOTO );
unsigned int pic_sz = f_pic.size();
return ( pic_sz > 100 );
}
The capturePhotoSaveSpiffs ( ) officiate takes a photograph and saves it to SPIFFS .
void capturePhotoSaveSpiffs( void ) {
camera_fb_t * fb = NULL; // pointer
bool ok = 0; // Boolean indicating if the picture has been taken correctly
do {
// Take a photo with the camera
Serial.println("Taking a photo...");
fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// Photo file name
Serial.printf("Picture file name: %s\n", FILE_PHOTO);
File file = SPIFFS.open(FILE_PHOTO, FILE_WRITE);
// Insert the data in the photo file
if (!file) {
Serial.println("Failed to open file in writing mode");
}
else {
file.write(fb->buf, fb->len); // payload (image), payload length
Serial.print("The picture has been saved in ");
Serial.print(FILE_PHOTO);
Serial.print(" - Size: ");
Serial.print(file.size());
Serial.println(" bytes");
}
// Close the file
file.close();
esp_camera_fb_return(fb);
// check if file has been correctly saved in SPIFFS
ok = checkPhoto(SPIFFS);
} while ( !ok );
}
This function was based on this sketch by dualvim .
ESP32-CAM Upload Code
To upload code to the ESP32-CAM board, connect it to your computer using an FTDI programmer. Follow the adjacent conventional diagram :
Important: GPIO 0 needs to be connected to GND so that you ’ re able to upload code .
many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make indisputable the sweater is in the good locate to select 5V .
ESP32-CAM | FTDI Programmer |
GND | GND |
5V | VCC (5V) |
U0R | TX |
U0T | RX |
GPIO 0 | GND |
To upload the code, follow the following steps :
1 ) Go to Tools > Board and blue-ribbon AI-Thinker ESP32-CAM .
2 ) Go to Tools > Port and select the COM larboard the ESP32 is connected to .
3 ) then, click the upload clitoris to upload the code .
4 ) When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button .
After a few seconds, the code should be successfully upload to your board .
Demonstration
Open your browser and type the ESP32-CAM IP Address. then, click the “ CAPTURE PHOTO ” to take a new photograph and wait a few seconds for the photograph to be saved in SPIFFS .
then, if you press the “ REFRESH PAGE ” button, the page will update with the latest save photograph. If you need to adjust the image orientation, you can constantly use the “ ROTATE ” button to do it indeed .
In your Arduino IDE Serial Monitor window, you should see alike messages :
Troublehsooting
If you ’ re getting any of the be errors, read our ESP32-CAM Troubleshooting Guide: Most Common Problems Fixed
- Failed to connect to ESP32: Timed out waiting for packet header
- Camera init failed with error 0x20001 or similar
- Brownout detector or Guru meditation error
- Sketch too big error – Wrong partition scheme selected
- Board at COMX is not available – COM Port Not Selected
- Psram error: GPIO isr service is not installed
- Weak Wi-Fi Signal
- No IP Address in Arduino IDE Serial Monitor
- Can’t open web server
- The image lags/shows lots of latency
Wrapping Up
We hope you ’ ve found this example useful. We ’ ve tried to keep it angstrom bare as possible so it is easy for you to modify and include it in your own projects. You can combine this example with the ESP32-CAM PIR Motion Detector with Photo Capture to capture and display a new photograph when apparent motion is detected.
Read more : Photo Editor
For more ESP32-CAM projects you can subscribe to our newsletter. If you don ’ thymine have an ESP32-CAM however, you can get one for approximately $ 6 .
If you like this project, you may besides like other projects with the ESP32-CAM :
Thank you for reading .