Skip to content

ESP32-S3 HMI43V3 Board Support Package (BSP)

ESP32-S3 HMI43V3 Board Support Package (BSP)

Section titled “ESP32-S3 HMI43V3 Board Support Package (BSP)”

The esp32s3_hmi43v3_bsp module is the Board Support Package for the ESP32-S3 HMI43V3 — a 4.3-inch capacitive-touch display board built around an ESP32-S3 SoC. It initialises every hardware subsystem (display, touch, IO expander, LVGL) and exposes a uniform BSP interface consumed by the main firmware. Because all hardware access is encapsulated here, the rest of the application firmware is board-agnostic.


  1. Module Overview
  2. Hardware Topology
  3. Software Architecture
  4. Initialization Sequence
  5. Component Reference
  6. LVGL Integration
  7. Display Pipeline
  8. Touch Pipeline
  9. Control Event System
  10. Snapshot Feature
  11. Build System
  12. Factory Application
  13. Dependencies
  14. Configuration Constants (Quick Reference)

PropertyValue
Board identifieresp32s3_hmi43v3
SoCESP32-S3
Display controllerRM68120 (4.3 ″, Intel 8080 / I80 parallel bus)
Touch controllerFT5x06 capacitive (I²C)
IO expanderTCA9554 (I²C, shared bus with touch)
Shared I²C busBacklight enable + touch reset routed through TCA9554
Color formatRGB 565
LVGL render modePartial, single or double DMA buffer
Touch indev polling10 ms
LVGL tick periodLVGL_TICK_PERIOD_MS (from tasks_def.h)
esp32s3_hmi43v3_bsp ← this BSP (current module)
boards/esp32s3_hmi43v3/
components/bsp/
board_init.c ← hardware initialisation & LVGL wiring
control_event.h ← control_event_t struct
control_types.c ← custom LVGL event registration
build_scripts/ → esp32s3_hmi43v3_build
Factory/ → esp32s3_hmi43v3_factory_app

IO Expander

Touch Subsystem

Display Subsystem

ESP32-S3 SoC

16-bit parallel data

+ control signals

pixel data

command/status

touch coordinates

GPIO: BL_EN

GPIO: RST

lv_tick_inc()

lv_task_handler()

Intel 8080 (I80)

Parallel Bus

I²C Bus

(shared)

DMA Engine

esp_timer

(LVGL tick)

CPU Cores

RM68120

Display Controller

4.3 ″ TFT Panel

FT5x06

Capacitive Touch

Controller

TCA9554

8-bit GPIO Expander

(Backlight + Touch Reset)

Key constraint: the TCA9554 IO expander must be initialised before both the RM68120 display and the FT5x06 touch controller, because backlight enable and touch reset signals are routed through it.


Cross-cutting

LVGL Stack

Hardware Driver Layer

BSP Layer - esp32s3_hmi43v3_bsp

Application Layer

optional

main/main.cpp

(app_main)

esp3d_lvgl.cpp

(LVGL task handler)

UIManager

(esp3d_ui.cpp)

board_init()

board_init.c

init_io_expander()

disp_rm68120_configure()

init_touch_controller()

init_lvgl()

control_events_init()

control_types.c

lvgl_flush_cb()

i80_flush_ready_cb() [ISR]

touch_read_cb()

increase_lvgl_tick()

control_event_t

control_event.h

disp_rm68120

(display_drivers_i80)

touch_ft5x06

(touch_drivers)

io_tca9554

(io_expander_drivers)

bus_i2c

(communication_bus_drivers)

lv_display_t

lv_indev_t

lv_tick_inc()

lv_event_register_id()

activity_manager

(activity_manager)

esp3d_log

(logging_system)

esp3d_snapshot

(optional)


board_init() enforces a strict ordering. Each step is a prerequisite for the next:

LVGL stackinit_lvgl()touch_ft5x06init_touch_controller()disp_rm68120_configure()io_tca9554bus_i2cinit_io_expander()activity_managerboard_init()app_mainLVGL stackinit_lvgl()touch_ft5x06init_touch_controller()disp_rm68120_configure()io_tca9554bus_i2cinit_io_expander()activity_managerboard_init()app_mainSets BL_EN output HIGH,touch RST output HIGHCreates I80 bus,panel IO, RM68120 panel,sends init sequenceDetects I²C address,applies swap/invert configRegisters custom LVGL event IDsboard_init()activity_manager_init()ESP_OKinit_io_expander()bus_i2c_init(port, SDA, SCL, freq)ESP_OKio_tca9554_configure(&default_config)ESP_OKESP_OKdisp_rm68120_configure(&config, i80_flush_ready_cb)ESP_OK (panel_handle stored internally)init_touch_controller()touch_ft5x06_configure(&default_config)ESP_OKESP_OKinit_lvgl()lv_init()lv_display_create(W, H)heap_caps_malloc(DMA) → buf1 [, buf2]lv_display_set_buffers()lv_display_set_flush_cb(lvgl_flush_cb)esp_timer_create / start_periodic → increase_lvgl_ticklv_indev_create() + set_read_cb(touch_read_cb)OKESP_OKcontrol_events_init()ESP_OK

Primary file: boards/esp32s3_hmi43v3/components/bsp/board_init.c

SymbolSignatureDescription
board_initesp_err_t board_init(void)Top-level board initialisation. Returns ESP_OK on success; any sub-step failure is propagated immediately (fail-fast).
board_get_nameconst char *board_get_name(void)Returns BOARD_NAME_STR (compile-time constant from board_config.h).
board_get_versionconst char *board_get_version(void)Returns BOARD_VERSION_STR.
get_lvgl_displaylv_display_t *get_lvgl_display(void)Accessor for the LVGL display handle (used by ESP3DXUi). Returns NULL when ESP3D_DISPLAY_FEATURE is disabled.
get_lvgl_lock_lock_t *get_lvgl_lock(void)Accessor for the LVGL API mutex (used by ESP3DXUi). Returns NULL when display is disabled.
get_touch_indevlv_indev_t *get_touch_indev(void)Accessor for the touch input device handle (guarded by ESP3D_TOUCH_FEATURE).
SymbolDescription
i80_flush_ready_cbRegistered as the DMA-complete ISR callback with the RM68120 driver. Calls lv_display_flush_ready() to unblock LVGL rendering. Executes in ISR context.
lvgl_flush_cbLVGL flush callback. Optionally writes pixels to the snapshot file (ESP3D_SNAPSHOT_FEATURE), optionally byte-swaps the RGB565 buffer (DISPLAY_SWAP_COLOR_FLAG), then calls esp_lcd_panel_draw_bitmap().
touch_read_cbLVGL input device read callback (10 ms period). Reads touch_ft5x06_data_t, implements wake-up suppression (first touch wakes screen but is not forwarded to LVGL), and calls activity_process_event().
increase_lvgl_tickesp_timer periodic callback. Calls lv_tick_inc(LVGL_TICK_PERIOD_MS) to advance the LVGL internal clock.
init_io_expanderInitialises the shared I²C bus via bus_i2c_init() then configures the TCA9554. Must be the first display-related call.
init_touch_controllerInitialises the FT5x06 over the shared I²C bus (already brought up by init_io_expander).
init_lvglAllocates DMA pixel buffers, creates the LVGL display and touch indev, starts the periodic tick timer.
VariableTypePurpose
lvgl_api_lock_lock_tMutual exclusion for all lv_* API calls originating outside the LVGL task
lvgl_displaylv_display_t *LVGL display object wrapping the RM68120 panel
lvgl_buf1 / lvgl_buf2lv_color16_t *DMA-capable pixel buffers (allocated from MALLOC_CAP_DMA)
lvgl_tick_timeresp_timer_handle_tPeriodic high-resolution timer driving lv_tick_inc()
touch_indevlv_indev_t *LVGL pointer input device backed by the FT5x06

File: boards/esp32s3_hmi43v3/components/bsp/control_event.h

Defines the unified event payload carried by every control input (touch, encoder, switch, potentiometer) when dispatched as an LVGL custom event.

typedef struct {
lv_indev_t *indev; // originating LVGL input device
uint32_t btn_id; // button / control identifier
lv_indev_type_t type; // LVGL indev type (pointer, encoder, …)
control_family_t family_id; // logical grouping (switch, encoder, …)
int32_t steps; // signed encoder step count
uint32_t press_duration; // hold duration in milliseconds
} control_event_t;

This struct is passed as event_data in lv_event_send() calls throughout the UI layer. UI screens retrieve it with:

control_event_t *evt = (control_event_t *)lv_event_get_param(e);

See pibot_pendant_v1_0_bsp.md for the full control family design — the pendant BSP uses the same control_event_t pattern with additional physical input types (encoder, potentiometer, switches).


File: boards/esp32s3_hmi43v3/components/bsp/control_types.c

Registers three custom LVGL event IDs after lv_init() has run:

Global variableRegistered event
LV_EVENT_SWITCH_PRESSEDA physical switch was pressed
LV_EVENT_SWITCH_RELEASEDA physical switch was released
LV_EVENT_POTENTIOMETER_CHANGEDPotentiometer value changed
void control_events_init(void); // called by board_init() after init_lvgl()

All three variables are initialised to LV_EVENT_ALL at compile time and are assigned unique IDs by lv_event_register_id() at runtime.

LVGL threading rule: control_events_init() must be called from the LVGL task (Core 1) strictly after lv_init() and before any UI screen subscribes to these events.


Hardware

ISR / Timer context

LVGL Task (Core 1)

DMA done ISR

lv_display_flush_ready()

lv_tick_inc()

lv_task_handler()

Render engine

(lv_draw_sw)

lvgl_flush_cb()

touch_read_cb()

(10 ms timer)

increase_lvgl_tick()

(esp_timer periodic)

i80_flush_ready_cb()

(I80 DMA complete ISR)

DMA Buffers

buf1 [+ buf2]

RM68120

panel_draw_bitmap()

FT5x06

touch_ft5x06_read()

Double-buffer mode (DISPLAY_USE_DOUBLE_BUFFER_FLAG): when enabled, a second DMA buffer is allocated so LVGL can prepare the next frame while the previous one is being transferred over the I80 bus, eliminating the flush stall.

Color byte-swap (DISPLAY_SWAP_COLOR_FLAG): some RM68120 panel variants expect RGB565 bytes in swapped order. When the flag is set, lv_draw_sw_rgb565_swap() is called inside lvgl_flush_cb() before esp_lcd_panel_draw_bitmap(). This is applied in software and has a small CPU cost proportional to the dirty rectangle size.

Tick accuracy: increase_lvgl_tick fires via esp_timer (hardware timer, not FreeRTOS tick), giving sub-millisecond accuracy for LVGL animation timings regardless of FreeRTOS tick rate.


yes (ESP3D_SNAPSHOT_FEATURE)

no

yes (DISPLAY_SWAP_COLOR_FLAG)

no

I80 parallel

16-bit bus

DMA complete ISR

lv_display_flush_ready()

LVGL renders

pixels → buf1

Snapshot

ongoing?

Write tiles to

g_snapshot.file

in 120-byte chunks

Swap color?

lv_draw_sw_rgb565_swap()

esp_lcd_panel_draw_bitmap()

RM68120

Display Controller

i80_flush_ready_cb()

LVGL unblocks

next frame

The RM68120 driver (display_drivers_i80 → disp_rm68120) handles:

  • I80 bus creation with esp_lcd_new_i80_bus()
  • Panel IO handle with esp_lcd_new_panel_io_i80()
  • Panel initialisation sequence: SLEEP OUT → MADCTL → COLMOD → DISPLAY ON
  • The internal disp_rm68120_notify_flush_ready ISR that invokes the BSP-supplied i80_flush_ready_cb

See display_drivers_i80.md for the full driver reference.


I²C read

yes

yes → wake up

no → already awake

continuation press

yes

no

yes + wake-up cycle

yes + normal

FT5x06 HW

(capacitive sensor)

touch_ft5x06_read()

→ touch_ft5x06_data_t

touch_read_cb()

(LVGL indev, 10 ms)

First press

this session?

activity_process_event()

Screen was

asleep?

Set touch_consumed_for_wakeup = true

Forward: LV_INDEV_STATE_RELEASED

Set touch_consumed_for_wakeup = false

Forward: LV_INDEV_STATE_PRESSED (x,y)

Wake-up

cycle active?

Forward: LV_INDEV_STATE_RELEASED

(suppress)

Forward: LV_INDEV_STATE_PRESSED (x,y)

Release

detected?

Clear flag

Suppress release event

activity_process_event()

Forward: LV_INDEV_STATE_RELEASED

Wake-up suppression is implemented with two static booleans:

  • last_pressed_state — tracks the previous touch state to detect edges
  • touch_consumed_for_wakeup — when true, suppresses both the press and the matching release from reaching LVGL

This ensures no accidental UI tap fires on screen wake-up.

The FT5x06 driver configuration (touch_ft5x06_config_t) supports:

  • Up to 3 candidate I²C addresses (auto-detected at init)
  • Coordinate swap (swap_xy) and inversion (invert_x, invert_y) for rotated panel mounts
  • Optional hardware reset (rst_pin) and interrupt (int_pin) GPIO pins
  • Configurable coordinate range (x_max, y_max; 0 = read from device)

See touch_drivers.md for the full driver API.


The HMI43V3 BSP registers three custom LVGL events for physical controls. The event payload (control_event_t) carries all information needed by UI screens without them needing to know the originating hardware.

control_event_t payload

lv_event_register_id()

lv_event_register_id()

lv_event_register_id()

lv_event_send(obj, event, &evt)

control_events_init()

LV_EVENT_SWITCH_PRESSED

LV_EVENT_SWITCH_RELEASED

LV_EVENT_POTENTIOMETER_CHANGED

UI screen

event handlers

indev → originating lv_indev_t*

btn_id → control identifier

type → lv_indev_type_t

family_id → control_family_t

steps → signed encoder delta

press_duration → ms held

Registration timing: IDs are assigned at runtime by lv_event_register_id() in control_events_init(), which is called by board_init() after init_lvgl() completes. The three global variables (LV_EVENT_SWITCH_PRESSED, LV_EVENT_SWITCH_RELEASED, LV_EVENT_POTENTIOMETER_CHANGED) hold the assigned numeric codes and are safe to use from any task after board_init() returns.


When ESP3D_SNAPSHOT_FEATURE is defined, lvgl_flush_cb() captures every rendered tile into an open file handle before the pixels are sent to the display. The snapshot accumulates multiple flush calls until the full logical frame has been captured.

Capture flow (inside lvgl_flush_cb, guarded by ESP3D_SNAPSHOT_FEATURE):

  1. Check g_snapshot.ongoing (non-atomic read — safe because only the LVGL task writes it here).
  2. Take g_snapshot.mutex (non-blocking xSemaphoreTake(... , 0)) — skip the tile if the mutex is busy rather than block the render pipeline.
  3. Write pixels in CHUNK_SIZE = 120 byte chunks via fwrite() to avoid large stack usage.
  4. Accumulate g_snapshot.captured_pixels; when >= g_snapshot.expected_pixels, clear g_snapshot.ongoing.
  5. On any fwrite error: set g_snapshot.error = true, clear ongoing, and stop.

This integrates with the higher-level snapshot API documented in lvgl_system.md.


Module: esp32s3_hmi43v3_build Path: boards/esp32s3_hmi43v3/build_scripts/

CMake -D args list

yes

no

build_one.py

main()

check_variant()

common.py

make_variant_args(*on_flags)

variants.py

build_variant(config)

common.py

--clean

flag?

clean_build_dir()

+ installer dir

generate_resources()

ui_resources partition

run_cmake_build()

idf.py -B build_dir

_show_size_report()

copy_factory_artifacts()

_copy_ui_resources_bin()

_package_user_resources_kit()

generate_flash_map()

make_variant_args() (variants.py): starts from DEFAULT_OFF_CMAKE_ARGS (all features off) and enables only the flags passed in, producing a deterministic CMake argument list. This controls which features are compiled (WiFi, BT, WebUI, transport selection, etc.).

Build pipeline steps:

  1. Generate the ui_resources binary partition (icons, fonts, theme palettes) before any compilation.
  2. Run idf.py via CMake in the board-specific build directory.
  3. Print a size report to catch memory regressions early.
  4. Copy factory app artifacts into the installer directory.
  5. Copy the ui_resources.bin into the installer.
  6. Package the ui_resources_kit/ for end-user SD-card updates (non-fatal if it fails).
  7. Write the flash address map.

See docs/guides/board_build_guidelines.md for the full cross-board build workflow and docs/ui_resources/development.md for the ui_resources partition format.


Module: esp32s3_hmi43v3_factory_app Path: boards/esp32s3_hmi43v3/Factory/

The factory application is a standalone ESP-IDF project used for production flashing and board validation. It does not use LVGL — it drives the RM68120 directly with a minimal framebuffer stack to keep the binary small and fully independent of the main firmware.

Flash Tools

Factory App - esp32s3_hmi43v3/Factory/

Menu UI (framebuffer)

Recovery Actions

Storage

Input

Display

esptool

esptool

main.c · app_main()

rm68120.c

rm68120_flush_ready_cb()

(IRAM_ATTR ISR → semaphore)

gfx.c

gfx_init / gfx_flush

gfx_draw_string / gfx_fill_rect

gfx_snapshot_begin/end

touch.c

touch_init / touch_read

buttons.c

buttons_init

button_is_pressed

button_wait_press

encoder.c

encoder_init / encoder_read

buzzer.c

buzzer_init

buzzer_beep_short

sdcard.c

sdcard_mount / unmount

action_boot_partition()

select OTA slot

action_sd_update()

flash firmware from SD

action_sd_update_res()

flash ui_resources from SD

restore_otadata_from_backup()

draw_menu / draw_menu_item

draw_header / draw_footer_zone

draw_progress / draw_result

tools/flash_all.py

tools/flash_factory.py

Key difference from the main BSP: the factory rm68120_flush_ready_cb is IRAM_ATTR and posts to a FreeRTOS semaphore from ISR context (xSemaphoreGiveFromISR + portYIELD_FROM_ISR). This synchronous semaphore-wait pattern is appropriate for the single-purpose factory app but is incompatible with the LVGL async pipeline used in the main firmware.

See docs/Factory/ for factory app architecture details shared across all boards.


optional

esp32s3_hmi43v3_bsp

display_drivers_i80

disp_rm68120

touch_drivers

touch_ft5x06

io_expander_drivers

io_tca9554

communication_bus_drivers

bus_i2c

activity_manager

logging_system

esp3d_log

lvgl_object_core

lvgl_display

input_device

lv_indev

lvgl_event_system

esp3d_snapshot

(ESP3D_SNAPSHOT_FEATURE)

DependencyRole in this BSP
display_drivers_i80RM68120 panel driver over I80 bus; provides disp_rm68120_configure(), disp_rm68120_get_panel_handle(), and the internal flush-ready ISR hook
touch_driversFT5x06 capacitive touch; provides touch_ft5x06_configure() and touch_ft5x06_read()
io_expander_driversTCA9554 8-bit IO expander; provides io_tca9554_configure() for backlight enable and touch reset GPIO
communication_bus_driversShared I²C bus helper; bus_i2c_init() is idempotent and safe to call once for the shared bus
activity_managerScreen-idle / wake-up logic; called in board_init() (init) and touch_read_cb() (event)
logging_systemesp3d_log / esp3d_log_e macros throughout init and callbacks
LVGL corelv_display_t, lv_indev_t, lv_event_register_id(), lv_tick_inc() — see lvgl_object_core, lvgl_display, input_device
lvgl_systemSnapshot feature integration (g_snapshot, esp3d_snapshot.h)

Sibling BSPs using the same I80 interface (for cross-reference):

Boards using RGB parallel bus (different display architecture):


14. Configuration Constants (Quick Reference)

Section titled “14. Configuration Constants (Quick Reference)”

All board-specific constants are defined in board_config.h and tasks_def.h. The table below lists every symbol referenced in board_init.c:

ConstantUsed inMeaning
BOARD_NAME_STRboard_init, board_get_nameHuman-readable board name string
BOARD_VERSION_STRboard_init, board_get_versionHardware revision string
DISPLAY_WIDTH_PXinit_lvglLogical display width in pixels
DISPLAY_HEIGHT_PXinit_lvglLogical display height in pixels
DISP_BUF_SIZE_BYTESinit_lvglByte size of each DMA draw buffer
DISPLAY_USE_DOUBLE_BUFFER_FLAGinit_lvgl1 = allocate buf2 for double-buffered rendering
DISPLAY_SWAP_COLOR_FLAGlvgl_flush_cb1 = swap RGB565 byte order before panel write
LVGL_TICK_PERIOD_MSinit_lvgl, increase_lvgl_tickLVGL tick increment period in milliseconds
TOUCH_I2C_PORT_IDXinit_io_expanderESP-IDF I²C port number for the shared bus
TOUCH_I2C_SDA_PINinit_io_expanderGPIO number for I²C SDA
TOUCH_I2C_SCL_PINinit_io_expanderGPIO number for I²C SCL
TOUCH_I2C_FREQ_HZinit_io_expanderI²C clock frequency in Hz
FlagEffect when defined
ESP3D_DISPLAY_FEATUREEnables IO expander + display + touch + LVGL initialisation in board_init()
ESP3D_TOUCH_FEATUREEnables init_touch_controller() and lv_indev_t registration in init_lvgl()
ESP3D_SNAPSHOT_FEATUREEnables pixel-capture path inside lvgl_flush_cb()

For the general display driver architecture (SPI vs I80 vs RGB-parallel), see docs/architecture/display_drivers.md. For memory constraints relevant to DMA buffer sizing, see docs/guides/esp32_memory_constraints.md.


components/bsp/lv_conf.h was regenerated from the LVGL 9.6.0 template (Configuration file for v9.6.0), and the sdkconfig files set CONFIG_LV_CONF_SKIP=n so this file is actually used (see Board_Support_Packages.md). The project values are kept (LV_MEM_SIZE 48 KB, monitors positions), LV_USE_COLOR_FILTER is 0 (color filters are not used: pressed feedback is a darker local style, see ui_components.md). LV_COLOR_DEPTH is no longer set in the file: LVGL takes CONFIG_LV_COLOR_DEPTH, or 16 by default.