esp32.diy

ESP32 TFT Display Example: Step-by-Step Setup Guide

Oct 14, 2026 · 8 min read · Intermediate

ESP32 TFT Display Example: Step-by-Step Setup Guide
TL;DR The quickest path to an ESP32 TFT display example is to wire a SPI panel — ILI9341, ILI9486, ILI9488, or ST7796 — and use the Adafruit TFT library, which has full ESP32 SPI support. If you need higher throughput, an 8-bit parallel interface is possible but requires writing your own GPIO layer using gpio_config and gpio_set_level, since the Adafruit library does not cover parallel mode on ESP32.
Tested SPI controllersILI9341, ILI9486, ILI9488, ST7796
Display resolutions320×240 px, 480×320 px, 800×480 px, 1024×600 px
SPI libraryAdafruit TFT driver (fully supported on ESP32)
High-speed alternative8-bit parallel — manual GPIO code required
Recommended IDEPlatformIO
Boards supportedESP32, ESP32-S3, ESP32-P4

What do you need before starting?

Gather everything before touching a wire — mismatched parts are the leading cause of a blank screen on first boot.

Hardware checklist

Software checklist

One decision to make now: SPI or 8-bit parallel? SPI needs fewer wires and has library support ready to go. Parallel is faster but has no ready-made ESP32 library — you write the data bus yourself. If you are running your first ESP32 TFT display example, choose SPI.

Display size comparison from the MiniWebRadio project showing SPI TFT modules supported by ESP32-S3.\2

Display size comparison from the MiniWebRadio project showing SPI TFT modules supported by ESP32-S3.

SPI or 8-bit parallel: which interface should you pick?

Both interfaces appear on common TFT modules. The right choice depends on how much display bandwidth you actually need.

Feature SPI 8-bit parallel
Data pins needed ~4–5 8 data + control lines
ESP32 library support Full (Adafruit) None — manual GPIO code required
Display throughput Moderate Higher
Wiring complexity Low Higher
Good for first project Yes No

SPI is the right choice for nearly every starting project. The Adafruit library abstracts away all the bus signalling, so your sketch focuses on drawing text and shapes rather than toggling pins.

8-bit parallel is worth the effort when you need to push a full 480×320 framebuffer at high speed — for example, a live video overlay or a rapidly animating UI. The trade-off is that you must implement the parallel write and read functions yourself, since the Adafruit TFT driver for ESP32 does not include parallel mode. The approach for doing that is covered in the GPIO section below.

Tested TFT display modules confirmed working with the ESP32-S3 MiniWebRadio firmware over SPI.\2

Tested TFT display modules confirmed working with the ESP32-S3 MiniWebRadio firmware over SPI.

How do you wire a TFT display to an ESP32?

SPI TFT modules expose a consistent set of signals regardless of which controller chip is inside. Wire them to the ESP32's SPI peripheral.

A few things to confirm before powering on:

Separate wiring schematics exist for the ESP32-S3 with SPI displays, the ESP32-P4 with SPI displays, and both chips with RGB and DSI panels — the pin assignments differ between chip families, so use the schematic for your specific board.

SPI TFT wiring schematic for the ESP32-S3 — the display controller, DAC, and SD card all share the SPI bus with separate chip-select lines.\2

SPI TFT wiring schematic for the ESP32-S3 — the display controller, DAC, and SD card all share the SPI bus with separate chip-select lines.

How do you configure GPIO pins for the 8-bit parallel interface in code?

The ESP32 does not have Arduino-style port registers (DDRB, PORTB, PINB). Instead, use the gpio_config struct for direction and gpio_set_level / gpio_get_level for state. Here is the complete pattern from the source.

Step 1 — Define a software register variable and map pins to readable names:

unsigned char disp_register;
#define REGISTER_BIT7_ON_PIN        9
#define REGISTER_BIT6_ON_PIN        10
#define REGISTER_BIT5_ON_PIN        5
// continue with all the pins you need

Step 2 — Configure all data pins as outputs at startup:

io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pin_bit_mask =  ((1ULL<< REGISTER_BIT7_ON_PIN) | (1ULL<< REGISTER_BIT6_ON_PIN) | (1ULL<< REGISTER_BIT5_ON_PIN)); // of course, do like this all the pins
gpio_config(&io_conf);

gpio_set_level(REGISTER_BIT7_ON_PIN, 0); // do like this all the pins you need to set the boot-up value, pin-by-pin

Step 3 — Write a byte to the display using a copy function:

void copy_register_to_GPIO_pins(unsigned char disp_register)
{
    gpio_set_level(REGISTER_BIT7_ON_PIN, (disp_register & 0x80) >> 7);
    gpio_set_level(REGISTER_BIT6_ON_PIN, (disp_register & 0x40) >> 6);
    gpio_set_level(REGISTER_BIT5_ON_PIN, (disp_register & 0x20) >> 5);
    gpio_set_level(REGISTER_BIT4_ON_PIN, (disp_register & 0x10) >> 4);
    gpio_set_level(REGISTER_BIT3_ON_PIN, (disp_register & 0x08) >> 3);
    gpio_set_level(REGISTER_BIT2_ON_PIN, (disp_register & 0x04) >> 2);
    gpio_set_level(REGISTER_BIT1_ON_PIN, (disp_register & 0x02) >> 1);
    gpio_set_level(REGISTER_BIT0_ON_PIN, (disp_register & 0x01));
}

To send a value to the display:

disp_register = 0x2A; // example value you want to send to display
copy_register_to_GPIO_pins(disp_register);

// or, output byte WITHOUT using any register:
copy_register_to_GPIO_pins(0x2A);

Step 4 — Read a byte back from the display. Switch the pins to input mode first. The source provides this read function (the source listing ends before the closing brace, so add your own return and closing brace):

unsigned char copy_GPIO_pins_to_register(void)
{
    unsigned char retval = 0;

    retval |= gpio_get_level(REGISTER_BIT7_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT6_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT5_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT4_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT3_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT2_ON_PIN);
    retval = retval << 1;
    retval |= gpio_get_level(REGISTER_BIT1_ON_PIN);
    // source listing ends here
}
A 13.3-inch TFT LCD module with LED backlight; smaller panel variants using the same TFT technology (ILI9341, ILI9486) are the type connected to ESP32 over SPI.\2

A 13.3-inch TFT LCD module with LED backlight; smaller panel variants using the same TFT technology (ILI9341, ILI9486) are the type connected to ESP32 over SPI. Image: Raimond Spekking, CC BY-SA 4.0, via Wikimedia Commons

How do you verify the display is working?

Work through these checks in order before assuming a hardware fault.

  1. Backlight on? If the backlight does not illuminate at all, the problem is power or ground, not the SPI signals. Fix power first.
  2. Blank white screen? The display has power but received no valid initialisation sequence. Recheck MOSI, SCLK, CS, and DC connections, and confirm the chip-select pin in your library settings matches the physical wiring.
  3. Scrambled or partial image? The controller driver in the library does not match the actual panel. The ILI9341, ILI9486, ILI9488, and ST7796 all use different initialisation sequences and are not interchangeable. Identify your panel's controller chip and select the matching driver.
  4. Image correct but upside-down or mirrored? Most libraries expose a rotation method accepting values 0 through 3. Adjust until the orientation matches your physical mounting.
  5. Image correct but slow to update? Over SPI, large framebuffers take longer to transfer. If throughput is the bottleneck, the 8-bit parallel interface (with manual GPIO code as described above) is the upgrade path.

What are the most common mistakes with ESP32 TFT display code?

These errors account for the majority of time lost during bring-up.

Using the wrong library for the interface. The Adafruit TFT library supports SPI on ESP32 but not 8-bit parallel. Attempting to use it in parallel mode will not work — you must write the GPIO data bus layer yourself.

Mixing up controller drivers. The ILI9341 (320×240) and ILI9486 (480×320) look physically similar as modules but require completely different initialisation sequences. Loading an ILI9341 sketch onto an ILI9486 panel produces a blank or incorrectly coloured screen every time.

Applying Arduino register syntax on ESP32. On ATmega boards you write DDRB, PORTB, PINB. The ESP32 does not expose GPIO that way. Use gpio_config for pin direction and gpio_set_level / gpio_get_level for state — or access the ESP32 GPIO registers directly if performance demands it.

Insufficient RAM for the framebuffer. A 480×320 display at 16-bit colour needs roughly 300 KB for a full framebuffer. Standard ESP32 internal SRAM is not enough for that. For any project using 480×320 or larger panels, use an ESP32-S3 board with at least 4 MB of PSRAM.

SD card SPI conflicts. When an SD card and a TFT panel share a SPI bus (common on combo modules), both devices must have separate chip-select lines and the SPI frequency must be tuned carefully. SD cards can operate at up to 40 MHz in tested designs, but a frequency mismatch between the card and the display driver causes intermittent read errors.

What ESP32 TFT display projects can you build after the basics?

A practical reference for what is achievable is the MiniWebRadio project, which combines an ESP32-S3 with a SPI TFT touchscreen to produce a fully self-contained internet radio with no physical buttons beyond the touchscreen itself. Its feature set illustrates the realistic scope of an ESP32 with TFT display:

The project uses PlatformIO as the build environment. Compatible display controllers are ILI9341, ILI9486, ILI9488, and ST7796 over SPI, as well as RGB (800×480) and DSI (1024×600) panels on appropriate hardware. No rotary encoders, switches, or passive components beyond the core SPI wiring are required.

Frequently asked questions

Which TFT controllers are confirmed to work with ESP32?

The ILI9341 (320×240 px), ILI9486 (480×320 px), ILI9488 (480×320 px), and ST7796 (480×320 px) are confirmed working over SPI. RGB panels at 800×480 and DSI panels at 1024×600 are supported on ESP32-S3 and ESP32-P4 with the appropriate peripherals.

Does the Adafruit TFT library support 8-bit parallel mode on ESP32?

No. The Adafruit library covers SPI on ESP32 but not 8-bit parallel. For parallel mode you must implement the GPIO data bus yourself using gpio_config and gpio_set_level.

How much PSRAM does an ESP32 TFT display project need?

For demanding projects with 480×320 or larger displays — especially those combining display, audio, and networking — at least 4 MB of PSRAM is recommended. Simpler SPI display sketches can run on standard ESP32 boards without PSRAM.

Why can't I use DDRB and PORTB on ESP32 like on Arduino?

The ESP32 GPIO architecture does not expose Arduino-style port registers. Use gpio_config to set pin direction and gpio_set_level / gpio_get_level to control state, or write directly to the ESP32 GPIO hardware registers.

Which IDE is recommended for ESP32 TFT display projects?

PlatformIO is recommended, particularly for complex projects that combine a TFT display with audio decoding, SD card access, and network features.

Bottom line

Getting an ESP32 TFT display example running over SPI with the Adafruit library and an ILI9341, ILI9486, ILI9488, or ST7796 panel is a solid beginner project that scales into serious builds. For higher refresh rates the 8-bit parallel interface unlocks more throughput, but you must implement the GPIO data bus yourself since there is no ready-made parallel library for ESP32. For any project beyond a simple sketch — especially those mixing display, audio, and networking — the ESP32-S3 with at least 4 MB of PSRAM and PlatformIO as the IDE is the recommended foundation.

Sources

stackoverflow.comESP32 direct port manipulationgithub.comschreibfaul1/ESP32-MiniWebRadio (GitHub README)

This guide summarises the public sources listed above in our own words. Check the original documentation before relying on exact figures.