Capturing Images with an ESP32-CAM
| Difficulty | Advanced |
|---|---|
| Category | Communication |
| Supported boards | ESP32-CAM, programmed through a USB-to-serial adapter |
| Time needed | 80 minutes |
| Prerequisites | Serving a Page from an ESP32 |
| Tested board | Not yet verified |
| Tested on | Not yet verified |
| Arduino IDE version | Not yet verified |
| Library | Not yet verified |
What it does
An ESP32 with a camera and a micro SD slot, for about the price of a sensor. It serves a live video stream to a browser, takes still images to a card, and does face detection on the board itself.
It is remarkable value and it is the most awkward board in this library to get started with, for one reason: it has no USB socket. Everything difficult about it follows from that, so this page deals with it first.
Read this first: there is no USB
The board has a micro SD slot, a camera connector, a flash LED and two rows of pins. It has no USB connector and no USB-to-serial chip.
So to program it you need:
- A USB-to-serial adapter, often called an FTDI or CP2102 adapter, set to 5 V.
- A wire from
IO0toGNDwhile uploading, which is what puts the board into flash mode. - To remove that wire and press reset afterwards, or the sketch will not run.
Boards sold with a small “MB” programmer baseboard have the adapter and a USB socket built in, and avoid all of this. If yours is the bare board, as the Siratek photographs show, the adapter is a required part rather than an optional one.
It needs a solid 5 volts
The camera and the radio together draw bursts of current that a weak supply cannot follow, and the result is a board that resets during the first upload or the first stream, repeatedly.
- Power it through the
5Vpin, from the adapter’s 5 V output or, better, from a separate supply. - Not through
3V3, which bypasses the onboard regulator and starves it. - Some USB-to-serial adapters cannot supply enough on their own. A board that resets as soon as the camera starts is telling you about the supply, not the code.
“Brownout detector was triggered” in the serial output is this, and it is the most common message people see from this board.
Before you begin
Required parts and tools
- One ESP32-CAM. This is Siratek product 6742, supplied with the OV2640 camera module.
- A USB-to-serial adapter with a 5 V output.
- Five jumper wires, and one more for the
IO0link. - A micro SD card, 32 GB or smaller, FAT32, if you want to save images.
Fitting the camera
The camera arrives as a separate module on a short ribbon. The connector on the board has a small dark latch along its top edge.
- Lift the latch gently upward. It hinges; it does not come off.
- Slide the ribbon in, contacts facing the board.
- Press the latch down until it clicks.
A ribbon not fully seated gives “camera probe failed” or a picture full of stripes. It is worth reseating before suspecting anything else.
The pins, and how few are left
The camera and the SD card together use most of the chip’s GPIO. What is actually free depends on whether you use the card:
| Pin | Free? |
|---|---|
IO0 |
Needed for flashing; usable afterwards with care |
IO2, IO4, IO12 to IO15 |
Used by the SD card in 4-bit mode |
IO16 |
Free |
IO1, IO3 |
The serial port, U0T and U0R |
IO4 is the flash LED, and it is extremely bright. It is also an SD card data line, which is why the LED sometimes flickers while the card is being written.
If you use the SD card in 1-bit mode, which the library supports, you get IO4, IO12 and IO13 back. For a camera project that needs a couple of sensors, that is the difference between possible and not.
Pin reference
For programming:
| Adapter | ESP32-CAM |
|---|---|
| 5 V | 5V |
| GND | GND |
| TX | U0R |
| RX | U0T |
IO0 to GND while uploading only |
Wiring steps
- Adapter
GNDto the board’sGND. - Adapter 5 V to the board’s
5V. Check the adapter’s jumper is on 5 V, not 3.3 V. - Adapter
TXtoU0R, adapterRXtoU0T. Crossed, as always. - A wire from
IO0toGND. - Plug in the adapter.
Software and libraries
The ESP32 board package, already installed from the earlier tutorial, includes everything. In the IDE:
- Tools, Board: AI Thinker ESP32-CAM
- Tools, Partition Scheme: Huge APP, if offered. The camera examples do not fit otherwise.
- Tools, Port: whichever the adapter appeared on.
Uploading, which has a ritual
Every single time:
IO0connected toGND.- Press and release the board’s reset button.
- Click Upload.
- Wait for “Connecting……” and then the upload.
- Remove the
IO0wire. - Press reset again.
Miss step 5 and the board boots back into flash mode and your sketch never runs, which looks exactly like a failed upload.
If it hangs at “Connecting……”, press reset again while that message is on screen.
Minimal working example
The IDE ships a complete example, and it is genuinely the right starting point: File, Examples, ESP32, Camera, CameraWebServer.
Three edits before uploading:
// 1. Select the board model. Comment out the others.
#define CAMERA_MODEL_AI_THINKER
// 2. Your network.
const char* ssid = "your network name";
const char* password = "your password";
Upload, remove the IO0 wire, press reset, and open the serial monitor at 115200. It prints an address. Open that in a browser on the same network and you have a camera with a live view and a page of settings.
Taking a still to the card
// Siratek: capture one image to the SD card, then sleep.
// Set the SD card to 1-bit mode to keep IO4, IO12 and IO13 free.
#include "esp_camera.h"
#include "FS.h"
#include "SD_MMC.h"
void setup() {
Serial.begin(115200);
// ... camera config as in the CameraWebServer example ...
if (!SD_MMC.begin("/sdcard", true)) { // true: 1-bit mode
Serial.println("Siratek: no SD card.");
return;
}
camera_fb_t* frame = esp_camera_fb_get();
if (!frame) {
Serial.println("capture failed: reseat the camera ribbon");
return;
}
File file = SD_MMC.open("/siratek-001.jpg", FILE_WRITE);
if (file) {
file.write(frame->buf, frame->len);
file.close();
Serial.printf("saved %u bytes\n", frame->len);
} else {
Serial.println("could not open the file");
}
esp_camera_fb_return(frame); // MUST be returned
}
void loop() {
}
esp_camera_fb_return() is not optional. The camera has a small number of frame buffers, and a frame you do not return is never reused. Forget it and capture works a few times and then stops with no error, which is among the more confusing failures on this board.
The true in SD_MMC.begin() selects 1-bit mode, giving back IO4, IO12 and IO13. It is slower and almost always worth it.
Why the stream is slower than you expect
At 1600 by 1200 the board manages a frame every second or two. At 640 by 480 it is smooth enough to be useful. The limits are the Wi-Fi throughput and the JPEG compression, not the camera.
Set FRAMESIZE_VGA or smaller for anything that needs to look like video, and keep the higher resolutions for stills.
Boards with PSRAM handle the larger sizes; boards without cannot allocate the buffers at all and fail at startup with a frame-size error. The AI Thinker board the Siratek part matches has PSRAM, but it is worth knowing the symptom.
What it is, and is not, for
Worth saying plainly, because this board invites a particular project.
It is excellent for: a nest box camera, a workshop monitor you glance at, a time-lapse to a card, a doorbell that sends a picture.
It is poor for: anything needing reliable continuous recording, high frame rates, or good low-light images. The sensor is small, the flash LED is harsh and close to the lens, and the board runs warm enough to matter in an enclosure.
And as with the web server tutorial: the stream has no password and no encryption. It is fine on your own network. Putting a camera onto the internet by forwarding a port means anyone who finds it can watch. If images must leave the building, send them outward to a service rather than accepting connections.
Expected result
An IP address on the serial monitor at 115200, and a live view in a browser on the same network.
The flash LED is very bright. It is on IO4, and the example page has a control for it.
Troubleshooting
| Symptom | Likely cause | Check or fix |
|---|---|---|
| “Brownout detector was triggered” | Supply cannot follow the bursts | Better 5 V supply, through the 5V pin |
| Hangs at “Connecting……” | Not in flash mode | IO0 to GND, then press reset |
| Uploads, but nothing runs | IO0 link still fitted |
Remove it and press reset |
| “Camera probe failed” | Ribbon not seated | Lift the latch, reseat, press it down |
| Stripes or noise in the image | Same cause | Reseat the ribbon |
| Sketch will not fit | Partition scheme | Choose Huge APP |
| Capture works a few times then stops | Frame buffer not returned | Call esp_camera_fb_return() |
| Fails at startup with a frame-size error | No PSRAM for that size | Use a smaller frame size |
| SD card will not mount | Card too large or wrong format | 32 GB or smaller, FAT32 |
| Flash LED flickers while writing | IO4 is shared with the card |
Use 1-bit mode |
| Stream is slow | Resolution and Wi-Fi | FRAMESIZE_VGA or smaller |
| Board hot in an enclosure | It runs warm | Allow airflow |
Next steps
- Easier first: Serving a Page from an ESP32, which this board’s example extends.
- Related: Sending Sensor Data with MQTT, for pushing images or events outward instead.
- Next project: a time-lapse camera writing a numbered image to the card every ten minutes.
Sources and attribution
All text, wiring guidance and code in this tutorial are original Siratek work.
- Espressif ESP32 camera documentation for the frame buffer handling, the SD_MMC 1-bit mode and the partition scheme requirement.
- The absence of a USB connector, the micro SD slot, the camera ribbon connector, the flash LED and the supplied OV2640 module were read from the Siratek product photographs for item 6742.
Frame rates depend on resolution, lighting and network conditions and are given as typical figures. The caution about exposing a camera stream to the internet is a deliberate limitation of scope. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

