esp32.diy

ESP32 ST7789 Example: Drive the Display with ESP-IDF esp_lcd

Oct 22, 2026 · 7 min read · Intermediate

ESP32 ST7789 Example: Drive the Display with ESP-IDF esp_lcd
TL;DR The cleanest ESP32 ST7789 example is the ESP-IDF esp_lcd flow: initialize an SPI bus with DMA, attach the LCD as an SPI IO device with your DC and CS pins, then call esp_lcd_new_panel_st7789() with a 16-bit-per-pixel panel config. The same final call also works over an Intel 8080 parallel bus, and FabGL offers an Arduino-side alternative for ST7789 240x240 panels.
FrameworkESP-IDF esp_lcd component (C)
Panel driver callesp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)
Interfaces coveredSPI (plus Quad/Octal SPI) and Intel 8080 parallel (8- or 16-bit)
Pixel format16 bits per pixel (RGB565 assumed for buffer sizing)
SPI mode in reference example0, with trans_queue_depth = 10
Arduino alternativeFabGL, requires ESP32 core 2.0.17 or earlier

What do I need before starting an ESP32 ST7789 example?

This guide follows the official ESP-IDF esp_lcd approach, which is the most direct way to bring up an ST7789 panel on an ESP32 without third-party libraries. Here is what you should have ready:

Item Why it matters
An ESP32 development board Any target supported by ESP-IDF; note the default LCD clock source can differ between ESP targets
An ST7789-based LCD module Most hobby modules expose SPI (SCLK, MOSI, DC/RS, CS, RST); some expose an 8080 parallel bus instead
A working ESP-IDF toolchain The code below uses spi_bus_initialize, esp_lcd_new_panel_io_spi and esp_lcd_new_panel_st7789 from ESP-IDF
Your module's datasheet Command/parameter bit widths, maximum pixel clock and RGB/BGR order are chip- and module-specific

One thing to be clear about: the ESP-IDF docs use placeholder macros such as EXAMPLE_PIN_NUM_SCLK and EXAMPLE_PIN_NUM_LCD_DC. There is no universal pin map for an ST7789 on ESP32. You define those macros yourself based on how you wired the module, so decide your wiring first and write the GPIO numbers down.

The flow has three layers: a bus (SPI or I80), an LCD IO device on that bus, and the panel driver on top. Once you understand that stack, swapping interfaces becomes a small change rather than a rewrite.

A typical ESP32 development board, the kind used to drive an ST7789 LCD over SPI.\2

A typical ESP32 development board, the kind used to drive an ST7789 LCD over SPI. Image: Edwiyanto, CC BY-SA 4.0, via Wikimedia Commons

Step 1: How do I set up the SPI bus for the ST7789?

The ST7789 is attached through an SPI master bus. The ESP-IDF driver supports plain SPI, Quad SPI and Octal SPI (the latter simulates Intel 8080 timing).

  1. Define your pin macros for SCLK, MOSI and (optionally) MISO.
  2. Fill in spi_bus_config_t. The quad pins are set to -1 because a standard ST7789 module does not use them.
  3. Set max_transfer_sz large enough for the biggest transfer you plan to send. The reference example budgets 80 lines of RGB565 pixels per transaction.
  4. Call spi_bus_initialize with SPI_DMA_CH_AUTO so DMA is enabled.
spi_bus_config_t buscfg = {
    .sclk_io_num = EXAMPLE_PIN_NUM_SCLK,
    .mosi_io_num = EXAMPLE_PIN_NUM_MOSI,
    .miso_io_num = EXAMPLE_PIN_NUM_MISO,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t),
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));

LCD_HOST is the SPI host you choose for the display. EXAMPLE_LCD_H_RES is the horizontal resolution of your panel; the docs do not fix a value, so take it from your module's spec. Because the size calculation uses sizeof(uint16_t), it assumes 16-bit RGB565 pixels, which lines up with the bits_per_pixel = 16 setting used later.

Step 2: How do I attach the ST7789 as an SPI IO device?

Next, allocate an LCD IO handle on the bus. This layer knows how to toggle the DC line between command and data and how to clock bytes out.

  1. Set dc_gpio_num to the GPIO wired to the module's DC pin (some datasheets call it RS).
  2. Set cs_gpio_num to the chip-select GPIO. If the ST7789 is the only device on this SPI bus, you may use -1 to hold the bus exclusively.
  3. Set pclk_hz to a pixel clock that stays inside the range your LCD spec recommends.
  4. Set lcd_cmd_bits and lcd_param_bits to the widths your controller expects; these are chip-specific values you should take from the ST7789 documentation, not guess.
  5. Keep spi_mode = 0 as in the reference example and trans_queue_depth = 10.
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
    .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,
    .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
    .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
    .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
    .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
    .spi_mode = 0,
    .trans_queue_depth = 10,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));

Two optional fields are worth knowing about: cs_ena_pretrans and cs_ena_posttrans set how many SPI bit-cycles CS is asserted before and after a transfer (0-16 each). You normally leave them at default, but they are the knobs to reach for if a particular module needs extra CS setup or hold time.

A larger trans_queue_depth lets more transactions wait in line, at the cost of memory. Ten is the value the docs use and is a sensible starting point.

Step 3: How do I install the ST7789 panel driver?

With the IO handle in place, install the ST7789 controller driver. This is the part that actually understands ST7789 commands and parameters.

  1. Set reset_gpio_num to the GPIO wired to the module's RST pin, or -1 if your module has no reset line.
  2. Set rgb_ele_order. The SPI reference uses LCD_RGB_ELEMENT_ORDER_BGR; the I80 reference uses LCD_RGB_ELEMENT_ORDER_RGB. Pick the one that matches your module.
  3. Set bits_per_pixel = 16. The driver uses this to compute how many bytes to send per pixel.
  4. Call esp_lcd_new_panel_st7789 and keep the returned panel_handle.
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
    .reset_gpio_num = EXAMPLE_PIN_NUM_RST,
    .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
    .bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

There is also a data_endian field in esp_lcd_panel_dev_config_t. It specifies the byte order sent to the screen for multi-byte colour formats; for single-byte formats like RGB232 it is unnecessary, and drivers that do not support it ignore the field.

At this point the stack is complete: SPI bus, IO device, ST7789 panel. Every subsequent drawing operation goes through panel_handle.

How do I verify the ST7789 example is working?

The example wraps every call in ESP_ERROR_CHECK, which is your first verification tool. If any step fails, the firmware aborts and prints the failing expression and error code on the serial console, so a clean boot with no abort means the bus, IO device and panel driver were all created successfully.

A practical checklist:

Check What success looks like
Build Compiles with no missing-symbol errors for esp_lcd_new_panel_st7789
Boot log No ESP_ERROR_CHECK abort after spi_bus_initialize, esp_lcd_new_panel_io_spi or esp_lcd_new_panel_st7789
Colours Red and blue appear where you expect; if they are swapped, flip rgb_ele_order between RGB and BGR
Stability No flicker or tearing; if present, reduce pclk_hz toward what the LCD spec allows

If the init passes but the screen stays blank, re-check the DC and CS wiring against the macros you defined, and confirm reset_gpio_num matches your RST pin (or is -1 if there is none). Wrong lcd_cmd_bits/lcd_param_bits also lead to a panel that initializes without errors on the ESP32 side yet shows nothing, because the controller misreads every command.

What are the most common mistakes with ESP32 and ST7789?

Guessing pin numbers. The docs deliberately use EXAMPLE_PIN_NUM_* placeholders. Copying them without defining your own values will not compile, and inventing values without checking your wiring is the top cause of a dead screen.

Pixel clock too high. pclk_hz must stay within the range your LCD spec recommends. On the parallel I80 bus there are additional guidelines: below 80 MHz for an 8-bit bus and below 40 MHz for a 16-bit bus. Pushing past this causes flicker or corrupt images.

Wrong colour order. Some ST7789 modules want BGR, others RGB. The two ESP-IDF references even differ from each other on this. Swapped red/blue is a config issue, not a wiring fault.

Undersized transfer buffer. max_transfer_sz caps how much you can send per SPI transaction. The reference budgets 80 lines of RGB565; if you later push bigger blocks, size this accordingly.

Forgetting DMA. Pass SPI_DMA_CH_AUTO to spi_bus_initialize. Without DMA you lose the throughput the driver is designed around.

Mismatched command and parameter widths. lcd_cmd_bits and lcd_param_bits are chip-specific. Take them from the ST7789 spec rather than from a different panel's example.

Unnecessary CS pin. If the ST7789 is alone on the bus, cs_gpio_num = -1 is valid and frees a GPIO. Conversely, if you share the bus with another device, you must wire and configure CS.

Can I use ST7789 over a parallel bus or with Arduino instead?

Intel 8080 parallel (I80). Some ST7789 modules expose an 8- or 16-bit parallel interface. ESP-IDF supports this through esp_lcd_new_i80_bus, and the final step is identical: esp_lcd_new_panel_st7789 on an I80 IO handle. The bus config needs a wr_gpio_num (pixel clock, WR), dc_gpio_num (RS), a data_gpio_nums array whose length equals bus_width, and max_transfer_bytes. The I80 reference budgets 100 lines of RGB565 per transaction.

esp_lcd_i80_bus_config_t bus_config = {
    .clk_src = LCD_CLK_SRC_DEFAULT,
    .dc_gpio_num = EXAMPLE_PIN_NUM_DC,
    .wr_gpio_num = EXAMPLE_PIN_NUM_PCLK,
    .data_gpio_nums = { EXAMPLE_PIN_NUM_DATA0, /* ... */ EXAMPLE_PIN_NUM_DATA7 },
    .bus_width = 8,
    .max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t),
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));

The I80 IO config adds dc_levels (idle, command, dummy and data levels for the DC line) alongside the usual cs_gpio_num, pclk_hz, lcd_cmd_bits and lcd_param_bits.

FabGL (Arduino). If you prefer the Arduino environment, FabGL is an ESP32 graphics library that includes SPI display drivers and has a demo running a TFT ST7789 240x240 alongside an ILI9341 and an SSD1306 OLED. It bundles fonts, sprites, a GUI with windows and widgets, and a sound engine. Two caveats from its README: it requires ESP32 revision 1 or later, and the latest Espressif ESP32 Arduino core it runs on is 2.0.17 or earlier, because newer cores leave too little free memory for a library of that size. It is GPL v3, with commercial licensing available on request.

Approach Best for
ESP-IDF esp_lcd over SPI Standard ST7789 modules, full control, modern IDF
ESP-IDF esp_lcd over I80 Parallel-interface ST7789 modules needing higher bandwidth
FabGL Arduino users wanting GUI, fonts and demos out of the box, on an older core

Frequently asked questions

Which function creates the ST7789 panel in ESP-IDF?

esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle). It works with either an SPI or an I80 IO handle.

Do I need a CS pin for the ST7789 on ESP32?

Only if other devices share the SPI bus. If the display is alone, set cs_gpio_num to -1 to occupy the bus exclusively.

Why are red and blue swapped on my ST7789?

The rgb_ele_order setting does not match your module. Switch between LCD_RGB_ELEMENT_ORDER_RGB and LCD_RGB_ELEMENT_ORDER_BGR.

What pixel clock should I use?

The docs do not give a fixed number for SPI; stay within your LCD spec. For I80, keep below 80 MHz on an 8-bit bus and below 40 MHz on a 16-bit bus.

Can I run an ST7789 with FabGL on Arduino?

Yes, FabGL supports SPI displays including a 240x240 ST7789 demo, but it needs ESP32 Arduino core 2.0.17 or earlier and ESP32 revision 1 or later.

Bottom line

For most makers, the ESP-IDF esp_lcd three-layer flow (SPI bus, IO device, esp_lcd_new_panel_st7789) is the most reliable ESP32 ST7789 example to build on. Define your own pin macros, take command widths and colour order from your module's spec, and let ESP_ERROR_CHECK tell you when something is off. Reach for I80 only if your panel has a parallel interface, and for FabGL only if you are committed to an older Arduino core.

Sources

github.comI80 Interfaced LCD (esp-idf docs)github.comSPI Interfaced LCD (esp-idf docs)github.comfdivitto/FabGL (GitHub README)

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