Nothing on this page will run on a bare S3. The page below is unchanged — read it if you are planning ahead or using a different board.
MIPI displays
SPI runs out of road around 320 by 240. Above that a screen needs a wider wire, and on the P4 that means MIPI DSI - a few differential pairs carrying gigabits, which is what makes a proper tablet-sized panel possible at all.
Bandwidth decides the bus
Three ways to drive a screen
SPI — four or five pins, any chip, comfortable to about 320×240 at a usable frame rate. This is where nearly every project should start.
RGB parallel — around twenty pins on the S3, driving 800×480. Fast, and it takes most of your GPIO with it.
MIPI DSI — six wires, gigabits, 1024×600 and beyond. P4 only, ESP-IDF today, and it needs PSRAM with real bandwidth behind it.
Redraw less
Whatever the bus, the biggest single win is not sending pixels that did not change. LVGL does this by default with a small partial buffer, and it is why a well-written interface on plain SPI can feel smoother than a badly written one on a much faster bus.
The code
MIPI DSI is ESP-IDF territory today. The Arduino layer on the P4 is young, and the working examples are IDF ones - so this is the shape rather than a sketch you can paste.
// esp_lcd_mipi_dsi, in outline. See the ESP-IDF example for your panel.
//
// 1. esp_lcd_new_dsi_bus() - lanes, and the bit rate per lane
// 2. esp_lcd_new_panel_io_dbi() - the command channel
// 3. esp_lcd_new_panel_<vendor>() - the panel's own init sequence
// 4. esp_lcd_dpi_panel_... - the video stream itself
//
// The frame buffer lives in PSRAM and the DMA engine reads it directly,
// so the CPU only touches the pixels it actually changes.
//
// A 1024x600 RGB565 frame is 1.2 MB. Two of them is 2.4 MB, which is
// why the P4 pairs MIPI with a much wider memory interface than the
// other chips have.Nothing here is a substitute for the panel's own initialisation sequence. Every DSI panel needs a vendor-specific list of commands before it will show anything, and it comes from the panel datasheet.
There is no MIPI support in MicroPython. For a screen from Python, the practical route is SPI - which is genuinely fine up to about 320 by 240 with a good driver.
from machine import SPI, Pin
import st7789 # or ili9341, or an LVGL binding
spi = SPI(2, baudrate=40_000_000, sck=Pin(18), mosi=Pin(23))
tft = st7789.ST7789(spi, 240, 320, cs=Pin(5), dc=Pin(2), rst=Pin(4))
tft.init()
tft.fill(0)
tft.text('hello', 10, 10)LVGL bindings for MicroPython give you widgets and partial refresh over SPI, which gets a lot more out of a small panel than redrawing whole frames does.
When it does not work
The panel's initialisation sequence is wrong or missing. Every controller needs its own list of commands from the datasheet, and a generic driver rarely has it.
Not enough bandwidth for the frame rate you asked for. Lower the frame rate, redraw only what changed, or use two buffers so the DMA engine never reads a half-drawn frame.
The frame buffer needs PSRAM at anything above about 320 by 240. Check it is enabled, and count the buffers - two is smooth and twice the memory.
Among the chips here, only the P4 has DSI. The others use SPI or, on the S3, a 16-bit RGB parallel interface that costs about twenty pins.
That is all forty-four. The chapters are on the contents page, and every one of them stands on its own if you want to jump back in somewhere.
Back to the contents →Edit this page — content/esp32/mipi-displays.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.