← How-Tos
electronics Aug 10, 2026 ◑ 9 views ◯ 7 min read

Driving TFT and OLED Displays with Arduino and ESP32: SSD1306, ST7789, ILI9341, and Choosing a Graphics Library

tft displayoledarduinoesp32ssd1306st7789ili9341graphics librarydisplayshowto

Almost every ESP32 or Arduino project eventually needs to show something more informative than a blinking LED, and the display market for makers has consolidated around a small handful of controller chips: the SSD1306 for small monochrome OLEDs, the ST7789 and ILI9341 for color TFTs, and a long tail of similar parts that all speak a similar language over SPI or I2C. The hardware is cheap and well documented, but the software side trips up a lot of people the first time — there are at least four popular graphics libraries, they don't all support the same displays, and picking the wrong one for your project means either fighting slow refresh rates or drowning in flash usage on a small microcontroller. This guide walks through the common display types, how to wire them, and which library actually fits your project.

The Display Landscape

Nearly all hobbyist displays fall into one of these families:

The practical difference that matters for wiring: I2C displays (SSD1306, most character LCDs) need only SDA and SCL plus power, so they're trivial to add to a project that already has other I2C sensors on the bus. SPI displays (everything color) need MOSI, SCK, CS, DC (data/command), and usually a RST line, which eats more GPIO pins but refreshes dramatically faster — SPI clock speeds of 40–80MHz are common, versus I2C's practical ceiling around 400kHz–1MHz, which matters a lot once you're pushing full-color frames.

Wiring Reference

DisplayInterfaceTypical PinsNotes SSD1306 OLEDI2CVCC, GND, SDA, SCLAddress usually 0x3C or 0x3D; check with an I2C scanner sketch if unsure SSD1306 OLEDSPI variantVCC, GND, SCK, MOSI, CS, DC, RSTFaster refresh than I2C, rare on hobby boards ST7789 TFTSPIVCC, GND, SCK, MOSI, CS, DC, RST, BLK (backlight)No MISO needed unless reading back framebuffer; tie BLK to a PWM pin to dim ILI9341 TFTSPIVCC, GND, SCK, MOSI, MISO, CS, DC, RST, LEDTouch controller (XPT2046) usually shares the SPI bus with its own CS/IRQ pins GC9A01 round TFTSPISame as ST7789Software init sequence differs slightly from ST7789; use a GC9A01-specific driver

On the ESP32, you have multiple hardware SPI peripherals (HSPI/VSPI on the original ESP32, more flexible GPIO matrix routing on the S2/S3/C3), so you can put a display on its own bus separate from an SD card or other SPI peripheral if you need the throughput. On classic AVR Arduinos (Uno, Nano, Mega), SPI pins are fixed, so plan your wiring around them rather than the other way around.

Choosing a Graphics Library

This is where most confusion happens, because the four most common libraries overlap in what they support but aren't interchangeable:

A reasonable rule of thumb: SSD1306/monochrome → U8g2. Color TFT with simple text/graphics → TFT_eSPI on ESP32, Adafruit_GFX stack on AVR where TFT_eSPI's ESP-focused DMA path doesn't apply. Full touchscreen UI with multiple views → LVGL on top of TFT_eSPI.

A Minimal ST7789 Example (TFT_eSPI)

After configuring User_Setup.h for your specific board and pin mapping, a basic sketch looks like this:

#include <TFT_eSPI.h> TFT_eSPI tft = TFT_eSPI(); void setup() { tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_GREEN, TFT_BLACK); tft.setTextSize(2); tft.setCursor(10, 10); tft.println("Hello, ESP32!"); } void loop() {}

The equivalent U8g2 sketch for an SSD1306 over I2C is similarly compact, but note U8g2's two rendering modes: full-buffer mode holds the whole frame in RAM and is simplest to work with, while page-buffer mode redraws in horizontal strips to save RAM at the cost of needing to wrap your drawing code in a `u8g2.firstPage() / nextPage()` loop. On an ESP32 with plenty of RAM, full-buffer mode is fine; on an ATmega328-based Arduino, page mode is often mandatory.

Common Problems

SymptomLikely Cause Screen stays white or shows garbage on first bootWrong init sequence for your specific panel variant — many ST7789 modules from different vendors need slightly different offsets/inversion settings Colors look swapped (blue looks red)RGB vs BGR panel order; most libraries have a color-order flag or `invertDisplay()` call to fix this Image is mirrored or rotated wrongSet the rotation constant appropriately; also check the display isn't wired backwards relative to its ribbon orientation SSD1306 not detected on I2C scanWrong address (0x3C vs 0x3D) or missing pull-up resistors on SDA/SCL if using a bare breakout without them built in TFT_eSPI compiles but nothing drawsUser_Setup.h pin definitions don't match your actual wiring — this is the single most common TFT_eSPI support request Flickering or tearing during animationUsing Adafruit_GFX direct-to-display draws instead of an off-screen sprite/buffer; switch to double-buffered drawing where the library supports it

Power and Backlight Notes

Color TFTs draw considerably more current than OLEDs, especially with the backlight at full brightness — budget 80–120mA for a typical 2.4″–2.8″ panel versus 10–20mA for a small OLED showing a mostly-dark UI. If you're running from battery, wire the backlight (BLK/LED pin) to a PWM-capable GPIO instead of straight to 3.3V so you can dim it or shut it off entirely during sleep, which is often the single biggest power draw in a battery-powered display project.

Between the three-letter alphabet soup of controller chips and four competing libraries, the display side of a project can eat more debugging time than the rest of the firmware combined. Match the library to the job — U8g2 for small monochrome status screens, TFT_eSPI for anything color on an ESP32, and LVGL once you're building a real interface — and most of that friction disappears.