Skip to content

Display Drivers — Intel 8080 (i80) Parallel Bus

Display Drivers — Intel 8080 (i80) Parallel Bus

Section titled “Display Drivers — Intel 8080 (i80) Parallel Bus”

The display_drivers_i80 module provides ESP-IDF–compatible LCD panel drivers for the Intel 8080 (i80) parallel bus, also known as the “8080-compatible MCU interface” or “8-bit/16-bit parallel” interface. Unlike SPI (serial) or RGB streaming interfaces, the i80 bus transfers pixel data over a wide parallel data bus (8 or 16 data lines), giving it significantly higher bandwidth than SPI while retaining MCU-controlled timing — a key advantage for mid-to-large displays on ESP32-S3.

This module sits in the Hardware Abstraction Layer (HAL) as a peer of display_drivers_spi.md and display_drivers_rgb.md. It is consumed exclusively by board-specific BSP code; the application layer (LVGL, UI Manager) never calls into it directly.

Two controller families are supported:

Sub-driverControllerBusTypical Board
disp_rm68120Raydium RM68120i80 16-bitesp32s3_hmi43v3
disp_st7796_i80Sitronix ST7796 (i80 mode)i80 8/16-bitesp32s3_zx3d50ce02s_usrc_4832

Each sub-driver is composed of three files forming two distinct layers:

┌─────────────────────────────────────────────────┐
│ BSP (board_init.c) │ ← board-specific pin config + lifecycle
├─────────────────────────────────────────────────┤
│ WRAPPER LAYER disp_<ic>.c / disp_<ic>.h │ ← bus/IO init, flush-ready plumbing
├─────────────────────────────────────────────────┤
│ PANEL LAYER <ic>_panel.c / <ic>_panel.h │ ← esp_lcd_panel_t vtable implementation
├─────────────────────────────────────────────────┤
│ ESP-IDF esp_lcd (esp_lcd_panel_io / ops) │ ← IDF i80 bus + DMA engine
└─────────────────────────────────────────────────┘
LayerFilesResponsibility
BSPboards/<board>/components/bsp/board_init.cPin mapping, config structs, calls disp_<ic>_configure(), wires LVGL flush chain
Wrapperdisp_<ic>.c, disp_<ic>.hCreates i80 bus + panel IO handles, stores static handles, injects on_color_trans_done ISR hook, exposes get_panel_handle() / get_io_handle()
Panel<ic>_panel.c, <ic>_panel.hImplements full esp_lcd_panel_t vtable: init, reset, draw_bitmap, mirror, swap_xy, invert_color, set_gap, disp_on_off, del

LVGL

ESP-IDF esp_lcd

Panel - st7796_i80_panel

Wrapper - disp_st7796_i80

Panel - rm68120_panel

Wrapper - disp_rm68120

BSP - board_init.c

esp_lcd_panel_draw_bitmap()

esp_lcd_panel_draw_bitmap()

on_color_trans_done

on_color_trans_done

board_init

disp_rm68120_configure

or disp_st7796_i80_configure

init_lvgl

lvgl_flush_cb

i80_flush_ready_cb

disp_rm68120_configure

disp_rm68120_get_io_handle

disp_rm68120_get_panel_handle

disp_rm68120_notify_flush_ready

★ ISR context

esp_lcd_new_panel_rm68120

rm68120_panel_t vtable

disp_st7796_i80_configure

disp_st7796_i80_get_io_handle

disp_st7796_i80_get_panel_handle

disp_st7796_i80_notify_flush_ready

★ ISR context

esp_lcd_new_panel_st7796_i80

st7796_i80_panel_t vtable

esp_lcd_i80_bus

esp_lcd_panel_io_i80

DMA Engine / ISR

lv_display_t

lv_display_flush_ready


hardware/drivers_video_i80/
├── disp_rm68120/
│ ├── disp_rm68120.h # Public API — configure, get_panel/io_handle
│ ├── disp_rm68120.c # Wrapper: bus/IO creation, ISR routing
│ ├── disp_rm68120_config.h # disp_rm68120_config_t, orientation enum, callback typedef
│ ├── rm68120_panel.h # esp_lcd_new_panel_rm68120() declaration
│ └── rm68120_panel.c # Panel vtable implementation + init register sequence
└── disp_st7796_i80/
├── disp_st7796_i80.h # Public API — configure, get_panel/io_handle
├── disp_st7796_i80.c # Wrapper: bus/IO creation, ISR routing
├── disp_st7796_i80_config.h# disp_st7796_i80_config_t, orientation enum, callback typedef
├── st7796_i80_panel.h # esp_lcd_new_panel_st7796_i80() declaration
└── st7796_i80_panel.c # Panel vtable implementation + init register sequence

The RM68120 is a Raydium TFT LCD controller supporting up to 480×800 resolution on an i80 16-bit parallel bus. It is used on the ESP32-S3 HMI 4.3″ board (esp32s3_hmi43v3).

The RM68120 initialization sequence is extensive: it programs three full per-channel gamma curves (Red D100h–D134h, Green D200h–D234h, Blue D300h–D334h, plus D400h–D634h for the second set of channels), AVDD/AVEE/VCL/VGLX voltage regulators (B000h–BA02h), display timing controls (C80xh), and tearing-effect output before issuing SLEEP OUT, MADCTL, COLMOD, and DISPON.

// hardware/drivers_video_i80/disp_rm68120/disp_rm68120_config.h
typedef struct {
esp_lcd_i80_bus_config_t bus_config; // Data bus width, clock speed, GPIO data pins
esp_lcd_panel_io_i80_config_t io_config; // CS, DC/RS, pclk_hz, lcd_cmd_bits (16-bit)
esp_lcd_panel_dev_config_t panel_config; // RST GPIO, bits_per_pixel, RGB element order
disp_rm68120_orientation_t orientation; // Portrait / Landscape / inverted variants
uint16_t hor_res; // Native horizontal resolution (pixels)
uint16_t ver_res; // Native vertical resolution (pixels)
} disp_rm68120_config_t;
// Invoked from the DMA ISR when a color transfer completes — must be minimal and ISR-safe
typedef void (*disp_rm68120_flush_ready_cb_t)(void);
ValueDegreesMADCTL byteWidth/Height swap
DISP_RM68120_ORIENTATION_PORTRAIT0°0x00No
DISP_RM68120_ORIENTATION_LANDSCAPE90°0x60Yes
DISP_RM68120_ORIENTATION_PORTRAIT_INVERTED180°0xC0No
DISP_RM68120_ORIENTATION_LANDSCAPE_INVERTED270°0xA0Yes

The panel struct also stores a scr_dir_t direction value (SCR_DIR_LRTB, SCR_DIR_TBLR, etc.) derived from the orientation that encodes the physical frame-buffer scan direction.

// Initialize the i80 bus, panel IO, and RM68120 panel.
// flush_ready_cb is called from the DMA ISR when a pixel transfer completes.
esp_err_t disp_rm68120_configure(const disp_rm68120_config_t *config,
disp_rm68120_flush_ready_cb_t flush_ready_cb);
// Returns the panel handle for use with esp_lcd_panel_* ops and LVGL user data.
esp_lcd_panel_handle_t disp_rm68120_get_panel_handle(void);
// Returns the panel IO handle.
esp_lcd_panel_io_handle_t disp_rm68120_get_io_handle(void);
typedef struct {
esp_lcd_panel_t base; // vtable — must be first member
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
bool reset_level;
uint16_t width; // effective after orientation swap
uint16_t height;
uint8_t dir; // scr_dir_t scan direction
int x_gap;
int y_gap;
unsigned int bits_per_pixel;
uint8_t madctl_val; // current MADCTL register value (3600h)
uint8_t colmod_cal; // current COLMOD register value (3A00h)
} rm68120_panel_t;
FunctionDescription
panel_rm68120_resetHardware GPIO reset (20 ms assert + 20 ms deassert) or software SWRESET
panel_rm68120_initRuns rm68120_reg_config(), then SLEEP OUT → MADCTL → COLMOD → DISPON
panel_rm68120_draw_bitmapSets column window via 4 separate 8-bit register writes to CASET, row window via 4 writes to RASET, then RAMWR + DMA color burst
panel_rm68120_mirrorUpdates MX / MY bits of live MADCTL register
panel_rm68120_swap_xyUpdates MV bit of live MADCTL register
panel_rm68120_invert_colorSends INVON or INVOFF
panel_rm68120_set_gapStores x/y pixel offsets applied at every draw_bitmap
panel_rm68120_disp_offSends DISPOFF or DISPON
panel_rm68120_delResets RST GPIO and frees the panel struct

RM68120 register protocol note: Unlike standard MIPI DCS where a command is followed by a data payload in a single transaction, the RM68120 encodes each command byte and its sequential byte-index into a 16-bit word on the i80 bus. For example, the first byte of CASET is sent as (LCD_CMD_CASET << 8) + 0, the second as (LCD_CMD_CASET << 8) + 1, etc. This is why draw_bitmap issues eight separate tx_param calls (four for CASET, four for RASET) instead of two 4-byte payloads.


The ST7796 is a Sitronix TFT LCD controller with both SPI and parallel interface modes. This sub-driver covers the i80 (parallel) variant used on the ZX3D50CE02S board (esp32s3_zx3d50ce02s_usrc_4832). The SPI variant of the same controller is covered in display_drivers_spi.md.

Unlike the RM68120, the ST7796 i80 panel uses standard MIPI DCS command framing: CASET/RASET are sent as single 4-byte payload transactions. Color inversion (INVON) is applied by default in disp_st7796_i80_configure() immediately after panel.init(). If the orientation is landscape, swap_xy is also applied automatically at configure time.

// hardware/drivers_video_i80/disp_st7796_i80/disp_st7796_i80_config.h
typedef struct {
esp_lcd_i80_bus_config_t bus_config; // Data bus width, clock speed, GPIO data pins
esp_lcd_panel_io_i80_config_t io_config; // CS, DC, pclk_hz, lcd_cmd_bits
esp_lcd_panel_dev_config_t panel_config; // RST GPIO, bits_per_pixel, RGB element order
disp_st7796_i80_orientation_t orientation; // Portrait / Landscape / inverted
uint16_t hor_res; // Horizontal resolution (pixels)
uint16_t ver_res; // Vertical resolution (pixels)
} disp_st7796_i80_config_t;
// Invoked from the DMA ISR when a color transfer completes — must be minimal and ISR-safe
typedef void (*disp_st7796_i80_flush_ready_cb_t)(void);
ValueDegrees
DISP_ST7796_I80_ORIENTATION_PORTRAIT0°
DISP_ST7796_I80_ORIENTATION_LANDSCAPE90°
DISP_ST7796_I80_ORIENTATION_PORTRAIT_INVERTED180°
DISP_ST7796_I80_ORIENTATION_LANDSCAPE_INVERTED270°
// Initialize the i80 bus, panel IO, and ST7796 panel.
// Also applies invert_color and swap_xy post-init when landscape orientation is selected.
esp_err_t disp_st7796_i80_configure(const disp_st7796_i80_config_t *config,
disp_st7796_i80_flush_ready_cb_t flush_ready_cb);
// Returns the panel handle for LVGL / esp_lcd_panel_* calls.
esp_lcd_panel_handle_t disp_st7796_i80_get_panel_handle(void);
// Returns the panel IO handle.
esp_lcd_panel_io_handle_t disp_st7796_i80_get_io_handle(void);

Internal Panel Struct (st7796_i80_panel_t)

Section titled “Internal Panel Struct (st7796_i80_panel_t)”
typedef struct {
esp_lcd_panel_t base; // vtable — must be first member
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
bool reset_level;
int x_gap;
int y_gap;
unsigned int bits_per_pixel;
uint8_t madctl_val; // current MADCTL register value
uint8_t colmod_cal; // current COLMOD register value
} st7796_i80_panel_t;
FunctionDescription
panel_st7796_i80_resetHardware GPIO reset (10 ms pulse) or software SWRESET (20 ms delay)
panel_st7796_i80_initSLEEP OUT (100 ms) → MADCTL → COLMOD → DISPON
panel_st7796_i80_draw_bitmapCASET (4-byte payload), RASET (4-byte payload), RAMWR + DMA color burst
panel_st7796_i80_mirrorUpdates MX / MY bits of live MADCTL register
panel_st7796_i80_swap_xyUpdates MV bit of live MADCTL register
panel_st7796_i80_invert_colorSends INVON or INVOFF
panel_st7796_i80_set_gapStores x/y pixel offsets
panel_st7796_i80_disp_offSends DISPOFF or DISPON
panel_st7796_i80_delResets RST GPIO and frees the panel struct

The i80 flush path is fully asynchronous and DMA-driven. The LVGL task submits a pixel buffer and returns immediately; LVGL can render the next frame only after the ISR signals completion.

i80_flush_ready_cb (BSP)notify_flush_ready (ISR ctx)DMA Complete ISResp_lcd i80 DMApanel draw_bitmaplvgl_flush_cb (BSP)LVGL Task (Core 1)i80_flush_ready_cb (BSP)notify_flush_ready (ISR ctx)DMA Complete ISResp_lcd i80 DMApanel draw_bitmaplvgl_flush_cb (BSP)LVGL Task (Core 1)DMA transfer in progress (non-blocking)lvgl_flush_cb(disp, area, px_map)optional color swap (lv_draw_sw_rgb565_swap)esp_lcd_panel_draw_bitmap(handle, x1, y1, x2+1, y2+1, px_map)tx_param(CASET) - set column windowtx_param(RASET) - set row windowtx_color(RAMWR, px_map, len) - starts DMA burston_color_trans_done callback firesdisp_<ic>_notify_flush_ready(panel_io, edata, user_ctx)flush_ready_cb() [= i80_flush_ready_cb in BSP]lv_display_flush_ready(lvgl_display)render next dirty area (if any)
  • notify_flush_ready runs in ISR context. It invokes the user callback through the user_ctx pointer stored at configure time — zero heap allocation, no blocking, no FreeRTOS API calls.
  • i80_flush_ready_cb must be one line. In all i80 board BSPs it contains exactly: lv_display_flush_ready(lvgl_display);.
  • lvgl_flush_cb must not block after submitting the bitmap. It calls esp_lcd_panel_draw_bitmap() and returns; LVGL resumes only after the ISR fires.
  • The on_color_trans_done field is always injected by the wrapper — the BSP never sets it on io_config. The wrapper overwrites that field before calling esp_lcd_new_panel_io_i80, piping the callback through user_ctx.

yes

no

yes

no

ST7796: invert_color + landscape swap_xy

RM68120: orientation handled in init

yes

no

yes

no

board_init

activity_manager_init

Backlight

Control?

backlight_configure()

backlight_set(0)

IO Expander

Needed?

init_io_expander

e.g. CH422G on hmi43v3

disp_rm68120_configure()

or disp_st7796_i80_configure()

esp_lcd_new_i80_bus

Allocate i80 bus handle

esp_lcd_new_panel_io_i80

Inject on_color_trans_done → notify_flush_ready

esp_lcd_new_panel_()

Alloc panel struct, populate vtable

panel.reset()

Hardware or software reset

panel.init()

Write registers, gamma tables, timing

Post-init

adjustments?

esp_lcd_panel_invert_color()

esp_lcd_panel_swap_xy()

Touch Feature

Enabled?

init_touch_controller

init_lvgl

lv_init

lv_display_create(DISPLAY_WIDTH_PX, DISPLAY_HEIGHT_PX)

heap_caps_malloc(MALLOC_CAP_DMA)

Single or double draw buffer

lv_display_set_flush_cb → lvgl_flush_cb

lv_display_set_user_data → panel_handle

esp_timer_start_periodic → increase_lvgl_tick

Touch indev

registered?

lv_indev_create(LV_INDEV_TYPE_POINTER)

lv_indev_set_read_cb → touch_read_cb

Board ready


Both i80 boards follow the same three-part integration pattern. Here is the canonical form:

// ── 1. Static board config (pin numbers are board-specific) ──────────────
static const disp_rm68120_config_t disp_rm68120_default_config = {
.bus_config = {
.dc_gpio_num = PIN_DC,
.wr_gpio_num = PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = { PIN_D0, PIN_D1, ... PIN_D15 },
.bus_width = 16,
.max_transfer_bytes = DISPLAY_WIDTH_PX * DISPLAY_HEIGHT_PX * 2 + 8,
},
.io_config = {
.cs_gpio_num = PIN_CS,
.pclk_hz = 10 * 1000 * 1000,
.lcd_cmd_bits = 16,
.lcd_param_bits = 16,
/* on_color_trans_done is left 0 here — wrapper injects it */
},
.panel_config = {
.reset_gpio_num = PIN_RST,
.bits_per_pixel = 16,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
},
.orientation = DISP_RM68120_ORIENTATION_LANDSCAPE,
.hor_res = 480,
.ver_res = 800,
};
// ── 2. ISR-context flush-ready callback — must stay minimal ─────────────
static void i80_flush_ready_cb(void) {
lv_display_flush_ready(lvgl_display); // single safe call, no blocking
}
// ── 3. LVGL flush callback — runs on LVGL task (Core 1) ─────────────────
static void lvgl_flush_cb(lv_display_t *disp,
const lv_area_t *area,
uint8_t *px_map) {
esp_lcd_panel_handle_t panel = lv_display_get_user_data(disp);
if (DISPLAY_SWAP_COLOR_FLAG) {
lv_draw_sw_rgb565_swap(
px_map,
(area->x2 + 1 - area->x1) * (area->y2 + 1 - area->y1));
}
// Submit to DMA — returns immediately, ISR fires lv_display_flush_ready later
esp_lcd_panel_draw_bitmap(panel,
area->x1, area->y1,
area->x2 + 1, area->y2 + 1,
px_map);
}
// ── 4. board_init wires everything together ──────────────────────────────
ret = disp_rm68120_configure(&disp_rm68120_default_config, i80_flush_ready_cb);
// init_lvgl() retrieves the panel handle via disp_rm68120_get_panel_handle()

BoardDriverControllerBits/pxOrientationIO ExpanderTouch
esp32s3_hmi43v3disp_rm68120RM6812016LandscapeCH422GFT5x06
esp32s3_zx3d50ce02s_usrc_4832disp_st7796_i80ST779616Landscape—FT6336U

Both boards also ship factory test applications that include a lean standalone flush-ready callback:

  • boards/esp32s3_hmi43v3/Factory/main/rm68120.c — rm68120_flush_ready_cb()
  • boards/esp32s3_zx3d50ce02s_usrc_4832/Factory/main/st7796_i80.c — st7796_i80_flush_ready_cb()

The esp32s3_hmi43v3 board requires the CH422G I/O expander to drive the RM68120 reset line and control backlight GPIO. See bsp.md for IO expander driver details.


AspectRM68120ST7796 i80
Register protocolExtended: (cmd << 8) + byte_index per 16-bit wordStandard MIPI DCS: cmd + contiguous data payload
CASET / RASET8 separate tx_param calls (one byte each)2 tx_param calls (4-byte payload each)
Gamma tablesFully programmed in rm68120_reg_config() at startupDefault silicon gamma; no custom gamma at startup
Page unlock sequenceRequired: F000h–F004h sequence before register groupsNot required
Invert defaultNot inverted (driver applies orientation MADCTL only)Inverted by default — INVON always applied in configure()
Orientation handlingFully resolved inside esp_lcd_new_panel_rm68120() using scr_dir_tswap_xy applied by the wrapper in disp_st7796_i80_configure() for landscape
Color orderConfigurable via rgb_ele_order (LCD_RGB_ELEMENT_ORDER_RGB/BGR)Configurable via rgb_ele_order
Pixel color format16/18/24 bpp via COLMOD (0x55/0x66/0x77)16/18/24 bpp via COLMOD (0x55/0x66/0x77)

RGB - display_drivers_rgb.md

RGB565 streaming · 16 lines

VSYNC / HSYNC / DE timing

VSync event triggers flush

EK9716 · ILI9485 · ST7262

SPI - display_drivers_spi.md

1 serial MOSI line

CS / DC GPIO

SPI ISR flush-ready

ILI9341 · ST7796-SPI

i80 - This Module

8 or 16 parallel data lines

MCU-controlled WR / CS / DC

DMA burst → ISR flush-ready

RM68120 · ST7796-i80

Propertyi80 (this module)SPIRGB
Data lines8 or 161 (MOSI)16 (RGB565)
Timing controlMCU (WR pulse)MCU (CLK edge)Panel (VSYNC/HSYNC)
Flush triggeron_color_trans_done DMA ISRDMA ISR / notify_flush_readydisp_on_vsync_event
Frame bufferPartial — in MCU DMA-capable RAMPartial — in MCU RAMFull frame — typically PSRAM
Typical panel size3.5–5″≤ 3.5″4.3–7″
GPIO countHigh (8–16 data + control)Low (≤ 4)Very high (16 data + sync)

The i80 drivers themselves allocate only one small struct per panel (calloc(1, sizeof(<panel>_t))). All substantial memory is owned by the BSP:

  • Draw buffer(s): Allocated in init_lvgl() via heap_caps_malloc(..., MALLOC_CAP_DMA). DMA-capable internal DRAM is required — the ESP32-S3 i80 DMA engine cannot access PSRAM without special configuration.
  • Single vs double buffer: Controlled per board by the DISPLAY_USE_DOUBLE_BUFFER_FLAG compile-time flag. Double buffering eliminates tearing at the cost of doubling the DMA buffer allocation.
  • Flush path is allocation-free: The notify_flush_ready ISR handler performs no heap operations.
  • Snapshot feature: When ESP3D_SNAPSHOT_FEATURE is enabled, lvgl_flush_cb writes pixel chunks to a file during the flush. Chunk size is fixed at 120 bytes to avoid stack pressure.

See esp32_memory_constraints.md for heap budget analysis across display configurations.


DocumentTopic
display_drivers.mdTop-level architecture: SPI vs i80 vs RGB, physical/logical resolution, orientation math
display_drivers_spi.mdSPI bus drivers: ILI9341, ST7796 SPI variant
display_drivers_rgb.mdRGB parallel streaming drivers: EK9716, ILI9485, ST7262
bsp.mdBoard Support Package: board_init, touch controllers, IO expanders, control events
esp32_memory_constraints.mdHeap budgets, DMA buffer sizing, fragmentation guidance