ESP32 S3 Camera - Show Live Video on a TFT Display

All the other camera projects need WiFi and a web browser. This one needs neither. The ESP32 S3 sends the live picture straight to a TFT display that sits next to it. You get a small camera with its own screen, and it works with no network at all.

ESP32 S3 Camera - Show Live Video on a TFT Display

What you'll build:

  1. An ESP32 S3 camera with a TFT display
  2. A live picture on the screen, with no WiFi and no router
  3. A frame counter in the Serial Monitor, so you can see how fast it runs
  4. A small camera you can carry anywhere, with only a power bank

Hardware Preparation

1×ESP32 S3 N16R8 OV5640 Camera
1×Alternatively, ESP32 S3 N16R8 OV2640 Camera
1×TFT SPI Display
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×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

A camera picture and a display picture are almost the same thing, if you choose the right settings.

SettingValueWhy
Picture formatRGB565The display wants exactly this. Two bytes are one colour.
Picture sizeQVGA, 320 x 240The display in landscape is also 320 x 240
Rotation1 (landscape)Turns the panel from standing up to lying down

So the code is short. Ask the camera for a picture, then give it to the display:

camera_fb_t *fb = esp_camera_fb_get(); TFT_display.drawRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height); esp_camera_fb_return(fb);

※ NOTE THAT:

Do not use PIXFORMAT_JPEG for this project. A JPEG is packed, and the display cannot read it. PIXFORMAT_RGB565 gives the raw colours, which is what the screen needs.

Why the Colours May Look Wrong

One colour is two bytes, and the camera and the display do not agree about which byte comes first. If you send the picture without thinking, the sky becomes orange and faces become blue.

The code turns the two bytes around before it draws:

uint8_t *p = fb->buf; for (size_t i = 0; i < fb->len; i += 2) { uint8_t keep = p[i]; p[i] = p[i + 1]; p[i + 1] = keep; }

※ NOTE THAT:

If your picture looks strange, change the line #define SWAP_BYTES true to false. One of the two values is right for your display, and it costs nothing to try both.

Which Pins Are Left?

The camera uses 14 pins, so you must choose the display pins with care:

Used byPins
CameraGPIO4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18
microSD card slotGPIO38, 39, 40
USBGPIO19, 20
Serial MonitorGPIO43, 44
Boot (never use)GPIO0, 3, 45, 46
Free for the displayGPIO1, 2, 14, 21, 41, 42, 47 (we use 6 of them, GPIO14 stays free)

There are enough free pins for the display, so everything fits.

Different Pins Than the Normal Display Tutorial

If you have already followed the ESP32 S3 - TFT LCD Touch Display SPI tutorial, two wires move in this project:

Display PinNormal display tutorialThis camera projectWhy
CSGPIO1GPIO1Same
DC / RSGPIO42GPIO42Same
RSTGPIO2GPIO2Same
SDI / MOSIGPIO11GPIO21GPIO11 is a camera data pin
SCKGPIO12GPIO47GPIO12 is a camera data pin
SDO / MISOGPIO13not connectedGPIO13 is the camera pixel clock

※ NOTE THAT:

GPIO11, GPIO12 and GPIO13 are the normal SPI pins of the ESP32 S3, and that is why the other tutorial uses them. On the camera board those three pins belong to the camera, so we must choose other ones. This is the only difference. The three control wires, CS, DC and RST, stay exactly the same.

Wiring Diagram between TFT Display and ESP32 S3

The wiring diagram between ESP32 S3 TFT Display

This image is created using Fritzing. Click to enlarge image

TFT Display PinESP32 S3 PinMeaning
VCC3.3VPower
GNDGNDGround
CSGPIO1Chip select
DC / RSGPIO42Data or command
RESETGPIO2Reset
SDI / MOSIGPIO21Data to the display
SCKGPIO47Clock
LED / BLGPIO41Backlight
SDO / MISOnot connectedOnly needed to read from the display

Safety Notes

Use 3.3V for VCC, not 5V. The ESP32 S3 works at 3.3V, and a 5V display can damage the pins of the board.

The LED or BL pin is the backlight. This board has only one 3.3V pin, and the VCC wire already uses it. So we put the backlight on GPIO41 instead, and the code switches it on:

TFT_display.initBacklight(TFT_LED_PIN); // the backlight goes on

This is easier to wire, and you get two free extras: TFT_display.backlightOff() switches the screen off to save power, and TFT_display.setBacklight(128) makes it half bright.

You do not need the SDO / MISO wire. This project only writes to the display, it never reads from it.

※ NOTE THAT:

Are you also using the microSD card slot? Then keep GPIO38, 39 and 40 free. The display pins above do not touch them, so the card and the display work together.

Install DIYables TFT SPI Library

  1. Open the Arduino IDE.
  2. Click the Libraries icon on the left bar.
  3. Type DIYables TFT SPI into the search box.
  4. Find the library by DIYables.io and click INSTALL.
  5. When the IDE asks about other libraries, click INSTALL ALL, because this library uses Adafruit GFX.
  • Search for DIYables TFT SPI created by DIYables.io and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Library Manager
Type:
All
Topic:
All
DIYables TFT SPI by DIYables.io
Works with both touch and non-touch versions of the same SPI TFT modules. Supports ILI9341 (240x320, 16-bit RGB565), ILI9488 (320x480, 18-bit RGB666), and ST7789 (240x320, 16-bit RGB565) displays over SPI. Includes built-in driver for XPT2046 / HR2046 / ADS7843 SPI touch controllers and 4-wire resistive touch panels - no separate touch library required. Use the display-only API for non-touch panels, or add initTouchSPI() to enable touch on modules that include a touch controller. Extends Adafruit GFX for full graphics support. Works with any Arduino-compatible board that has SPI. More info
1.0.3
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32S3 Dev Module on COM15
1

The library works with three kinds of display. The code uses the ST7789. Use the line that matches your own module:

Your displayLine in the codeSize
ST7789 (used in this tutorial)DIYables_ST7789_SPI TFT_display(...)240 x 320
ILI9341DIYables_ILI9341_SPI TFT_display(...)240 x 320
ILI9488DIYables_ILI9488_SPI TFT_display(...)320 x 480

For another display, put // in front of the ST7789 line and remove the // from the line of your own display. Nothing else changes.

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 is very important here. One RGB565 picture of 320 x 240 takes 153600 bytes, which does not fit in the normal memory of the chip. Without PSRAM the code falls back to a very small picture.

How To Program the Camera Display

  • Include the library and choose your display.
#include <DIYables_TFT_SPI.h> SPIClass tftBus(FSPI); DIYables_ST7789_SPI TFT_display(240, 320, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, &tftBus);

※ NOTE THAT:

The ESP32 S3 has more than one SPI bus. We use FSPI, so the display never fights with anything else on the board.

  • Tell the SPI bus which pins to use, then start the display. The two -1 mean "we do not read from the display".
tftBus.begin(TFT_SCK_PIN, -1, TFT_MOSI_PIN, -1); TFT_display.begin(); TFT_display.setRotation(1); // 1 means landscape TFT_display.fillScreen(BLACK);
  • Ask the camera for the picture in the format the display wants.
config.pixel_format = PIXFORMAT_RGB565; config.frame_size = FRAMESIZE_QVGA; // 320x240
  • Draw the picture. This one line puts the whole camera image on the screen.
TFT_display.drawRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height);
  • Give the memory back, or the camera stops after a few pictures.
esp_camera_fb_return(fb);
  • You can also write text on the screen, because the library uses Adafruit GFX.
TFT_display.setTextColor(WHITE); TFT_display.setTextSize(2); TFT_display.setCursor(20, 110); TFT_display.print("Starting camera...");

ESP32 S3 Code

/* * 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-show-live-video-on-a-tft-display */ #include <DIYables_TFT_SPI.h> // DIYables TFT SPI library #include "esp_camera.h" // Pins of the TFT display. // CS, DC and RST are the same as in the ESP32 S3 TFT LCD Touch Display SPI tutorial. // SCK and MOSI had to be moved, because the camera uses GPIO11, 12 and 13. #define TFT_SCK_PIN 47 #define TFT_MOSI_PIN 21 #define TFT_CS_PIN 1 #define TFT_DC_PIN 42 #define TFT_RST_PIN 2 #define TFT_LED_PIN 41 // the backlight. The board has only one 3.3V pin, so we use a GPIO. // The size of the panel when it stands up (portrait) #define TFT_WIDTH 240 #define TFT_HEIGHT 320 // The camera and the display do not agree about the order of the two bytes // of one pixel. Change this to false if the colours look wrong. #define SWAP_BYTES true // 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 // The ESP32 S3 has more than one SPI bus. We use FSPI for the display. SPIClass tftBus(FSPI); // Use the line that matches your display module DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, &tftBus); // DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, &tftBus); // DIYables_ILI9488_SPI TFT_display(320, 480, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, &tftBus); #define BLACK DIYables_TFT_SPI::colorRGB(0, 0, 0) #define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255) unsigned long frameCount = 0; unsigned long lastReport = 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.jpeg_quality = 10; config.fb_count = 1; config.fb_location = psramFound() ? CAMERA_FB_IN_PSRAM : CAMERA_FB_IN_DRAM; config.grab_mode = CAMERA_GRAB_WHEN_EMPTY; // RGB565 gives the picture in the same form that the display wants. // QVGA is 320x240, which is exactly the size of the display in landscape. config.pixel_format = PIXFORMAT_RGB565; config.frame_size = FRAMESIZE_QVGA; if (!psramFound()) { Serial.println("WARNING: PSRAM not found. Please set PSRAM to OPI PSRAM in the Tools menu."); config.frame_size = FRAMESIZE_QQVGA; // 160x120, the only size that fits without PSRAM } 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; } void setup() { Serial.begin(115200); delay(1000); // Tell the SPI bus which pins to use. -1 means we do not read from the display. tftBus.begin(TFT_SCK_PIN, -1, TFT_MOSI_PIN, -1); TFT_display.begin(); TFT_display.initBacklight(TFT_LED_PIN); // switches the backlight on TFT_display.setRotation(1); // 1 means landscape, so the screen is 320 x 240 TFT_display.fillScreen(BLACK); TFT_display.setTextColor(WHITE); TFT_display.setTextSize(2); TFT_display.setCursor(20, 110); TFT_display.print("Starting camera..."); Serial.println("Display is ready"); if (!initCamera()) { TFT_display.fillScreen(BLACK); TFT_display.setCursor(20, 110); TFT_display.print("Camera failed"); while (true) delay(1000); } TFT_display.fillScreen(BLACK); Serial.println("Showing the live picture on the display"); lastReport = millis(); } void loop() { camera_fb_t *fb = esp_camera_fb_get(); if (!fb) { Serial.println("Frame capture failed"); delay(100); return; } // Turn the two bytes of every pixel around, if needed if (SWAP_BYTES) { uint8_t *p = fb->buf; for (size_t i = 0; i < fb->len; i += 2) { uint8_t keep = p[i]; p[i] = p[i + 1]; p[i + 1] = keep; } } TFT_display.drawRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height); esp_camera_fb_return(fb); // give the memory back to the camera frameCount++; // Every 5 seconds, tell the Serial Monitor how fast we are if (millis() - lastReport >= 5000) { Serial.printf("%lu pictures in 5 seconds (%.1f per second)\n", frameCount, frameCount / 5.0); frameCount = 0; lastReport = millis(); } }

Detailed Instructions

  • New to ESP32 S3? Complete our Getting Started with ESP32 S3 guide first.
  • Install the DIYables TFT SPI library, and click INSTALL ALL when the IDE asks.
  • Unplug the USB cable, then wire the display as in the table above.
  • Copy the above code and paste it into the Arduino IDE.
  • If your display is not an ST7789, change the display line. See the table above.
  • 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 screen says Starting camera..., then the live picture appears.
  • Pro Tip: Do the colours look wrong? Change #define SWAP_BYTES true to false and upload again.

Serial Monitor

The Serial Monitor tells you how many pictures the board draws. 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 14:00:01] Display is ready [2026-09-28 14:00:02] Camera sensor: OV5640 (5 MP, max QSXGA 2592x1944) [2026-09-28 14:00:02] Showing the live picture on the display [2026-09-28 14:00:07] 61 pictures in 5 seconds (12.2 per second) [2026-09-28 14:00:12] 63 pictures in 5 seconds (12.6 per second) [2026-09-28 14:00:17] 62 pictures in 5 seconds (12.4 per second)
Ln 11, Col 1
ESP32S3 Dev Module on COM15
2

※ NOTE THAT:

About 12 pictures per second is normal. The slow part is the SPI wire to the display, not the camera. A smaller picture is faster, and a bigger picture is slower.

Troubleshooting

The screen is dark, but I can see a faint picture when I shine a light on it.

The backlight is off. Check the LED / BL wire on GPIO41, and check that initBacklight() is in your code.

The backlight is weak, or the board restarts when the screen lights up.

Your display module takes more current than one ESP32 S3 pin can give. Connect LED / BL to 3.3V together with VCC instead, and delete the initBacklight() line. You then lose the on and off control, but the backlight is always full.

The screen stays white or stays black.

  • Check the CS, DC and RESET wires. These three are the ones people swap most often.
  • Check the LED / BL wire on GPIO41. Without it the backlight stays off and you see nothing, even when the picture is really there.
  • Check that VCC is on 3.3V.

The colours are wrong. The sky is orange, faces are blue.

Change #define SWAP_BYTES true to false and upload again.

The picture is upside down or on its side.

Change TFT_display.setRotation(1) to 0, 2 or 3 until it looks right.

The picture is there but very small in one corner.

Your display is bigger than the picture, for example an ILI9488 of 320 x 480. This is normal. You can move the picture to the middle by changing the first two numbers of drawRGBBitmap(0, 0, ...).

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.

The video is very slow, only 2 or 3 pictures per second.

  • Make sure PSRAM is OPI PSRAM, not Disabled.
  • Use short wires. Long jumper wires make SPI slow and unreliable.
  • Try FRAMESIZE_QQVGA (160 x 120) to see if the wiring is the problem.

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.

Applications

A camera with its own screen needs no WiFi, no router and no phone. Here are practical projects you can build with this code:

  1. Portable camera: A camera and a screen with a power bank, and you can carry it anywhere.
  2. Digital magnifier: Point it at small text or a circuit board and read it on the screen.
  3. Inspection camera: Look inside a machine, a pipe or a dark box, and watch on the screen.
  4. Door viewer: A screen inside the house shows who is at the door outside.
  5. Reversing camera for a model car: Show the picture of the back on a small screen.
  6. Microscope helper: Put the camera on a lens and show the picture big.
  7. Photo booth: Let people see themselves before the photo is taken.
  8. Robot with a face: Give a robot a screen that shows what it sees.
  9. Teaching tool: Show students how a camera picture becomes pixels on a screen.
  10. Camera in a place with no network: A factory, a cellar, or a field where WiFi does not reach.

Video Tutorial

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

Challenges

  1. Beginner: Write the text "LIVE" in the corner of the screen, on top of the picture.
  2. Intermediate: Add a button. When you press it, the picture freezes for three seconds, like a photo.
  3. Advanced: Add the microSD card slot. When you press the button, save the picture as a JPEG file, and show "Saved!" on the screen. Remember to use esp_camera_reconfigure() to change between RGB565 and JPEG.

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