Skip to content

ESP32S3 ZX3D50CE02S USRC 4832 — Board Support Package

ESP32S3 ZX3D50CE02S USRC 4832 — Board Support Package

Section titled “ESP32S3 ZX3D50CE02S USRC 4832 — Board Support Package”

The esp32s3_zx3d50ce02s_usrc_4832_bsp module is the Board Support Package (BSP) for the ZX3D50CE02S USRC 4832 display board, a compact ESP32-S3 panel featuring a 480×320 TFT driven over the Intel 8080 (i80) parallel interface with a capacitive FT5x06 touch controller. It belongs to the broader Board Support Packages collection and follows the same layered design as all other BSPs in this project.


  1. Hardware Overview
  2. Module Architecture
  3. Component Reference
  4. Initialization Sequence
  5. Display Subsystem
  6. Touch Subsystem
  7. LVGL Integration
  8. Control Event System
  9. Factory Application
  10. Build System & Variants
  11. Dependencies
  12. Related Modules

PropertyValue
SoCESP32-S3
Display controllerST7796
Display busIntel 8080 (i80) 16-bit parallel
Display resolution480 × 320 px (RGB565)
Touch controllerFT5x06 (capacitive multi-touch)
Touch busI²C
Physical buttonsUp to 3 (GPIO, active-LOW)
Rotary encoderOptional (PCNT quadrature decoder)
BuzzerOptional (GPIO)
StoragemicroSD (SPI)

Note: The encoder and buzzer pins are guarded — if ENCODER_A_PIN, ENCODER_B_PIN, or BUZZER_PIN are defined as GPIO_NUM_NC, initialisation is silently skipped. This makes the BSP portable across sub-variants of this panel family without code changes.


Main Firmware

LVGL (Core 1)

Hardware Peripheral Drivers

esp32s3_zx3d50ce02s_usrc_4832_bsp

configures

configures

via

optional

creates

creates

starts

registers IDs after lv_init()

init

get_lvgl_display()

get_lvgl_lock()

get_touch_indev()

board_init.c

control_event.h

control_types.c

disp_st7796_i80

(I80 panel driver)

touch_ft5x06

(capacitive touch)

bus_i2c

(I²C bus)

disp_backlight

(PWM backlight)

lv_display_t

lv_indev_t (pointer)

lv_tick timer

ESP3DXUi / UIManager

activity_manager

The BSP is the sole owner of all hardware handles (panel, touch, LVGL display, tick timer). The main application retrieves them through three accessor functions exposed by board_init.h:

AccessorReturns
get_lvgl_display()lv_display_t *
get_lvgl_lock()_lock_t * — mutual exclusion guard for all LVGL API calls
get_touch_indev()lv_indev_t * (guarded by ESP3D_TOUCH_FEATURE)

Path: boards/esp32s3_zx3d50ce02s_usrc_4832/components/bsp/board_init.c

The single source of truth for hardware bring-up. All internal functions are static; only board_init(), board_get_name(), board_get_version(), and the three accessors are exported.

SymbolSignatureDescription
board_initesp_err_t board_init(void)Master entry point. Calls every sub-initialiser in order. Returns ESP_OK on success; first failure aborts and propagates the error code.
board_get_nameconst char *board_get_name(void)Returns BOARD_NAME_STR from board_config.h.
board_get_versionconst char *board_get_version(void)Returns BOARD_VERSION_STR from board_config.h.
get_lvgl_displaylv_display_t *get_lvgl_display(void)Returns the LVGL display handle (NULL when ESP3D_DISPLAY_FEATURE disabled).
get_lvgl_lock_lock_t *get_lvgl_lock(void)Returns the LVGL API mutex pointer.
get_touch_indevlv_indev_t *get_touch_indev(void)Returns the touch input device handle.
SymbolPurpose
i80_flush_ready_cbISR-context callback registered with disp_st7796_i80_configure(). Called when the I80 DMA transfer completes; signals LVGL via lv_display_flush_ready().
lvgl_flush_cbLVGL flush callback. Optionally captures pixel data to the snapshot file (ESP3D_SNAPSHOT_FEATURE), byte-swaps RGB565 data if DISPLAY_SWAP_COLOR_FLAG is set, then calls esp_lcd_panel_draw_bitmap().
touch_read_cbLVGL input-device poll callback (10 ms period). Reads touch_ft5x06_data_t, routes wake-up events through activity_manager, and suppresses the first touch of a new wake-up cycle to prevent accidental UI activation.
increase_lvgl_tickPeriodic esp_timer callback; calls lv_tick_inc(LVGL_TICK_PERIOD_MS) to advance the LVGL time base.
init_touch_controllerInitialises the I²C bus via bus_i2c_init(), then configures the FT5x06 via touch_ft5x06_configure().
init_lvglFull LVGL setup: lv_init(), display creation, DMA buffer allocation (single or double, per DISPLAY_USE_DOUBLE_BUFFER_FLAG), flush callback, tick timer, and touch input device registration.

3.2 control_event.h — Control Event Type

Section titled “3.2 control_event.h — Control Event Type”

Path: boards/esp32s3_zx3d50ce02s_usrc_4832/components/bsp/control_event.h

Defines the payload carried by all custom LVGL control events dispatched from physical input devices to UI screens.

typedef struct {
lv_indev_t *indev; // Source input device handle
uint32_t btn_id; // Button / switch identifier
lv_indev_type_t type; // LV_INDEV_TYPE_BUTTON / ENCODER / …
control_family_t family_id; // Device family (switch, encoder, potentiometer…)
int32_t steps; // Encoder delta (positive = CW)
uint32_t press_duration; // Duration button was held (milliseconds)
} control_event_t;

This structure is used system-wide wherever physical input state must be forwarded to LVGL screens. See UI_Framework_&_Screens for how screens consume these events.


3.3 control_types.c — LVGL Event Registration

Section titled “3.3 control_types.c — LVGL Event Registration”

Path: boards/esp32s3_zx3d50ce02s_usrc_4832/components/bsp/control_types.c

Registers board-specific custom events with LVGL and exposes them as global lv_event_code_t constants.

Global variableDescription
LV_EVENT_SWITCH_PRESSEDFired when a physical switch/button transitions to the pressed state.
LV_EVENT_SWITCH_RELEASEDFired when a physical switch/button is released.
LV_EVENT_POTENTIOMETER_CHANGEDFired when an analog potentiometer value changes.

control_events_init(void) must be called after lv_init() (it is invoked at the end of board_init()). It calls lv_event_register_id() once per event and stores the returned dynamic ID.

Important: All three IDs are initialised to LV_EVENT_ALL at startup. Reading them before control_events_init() runs produces incorrect results.


control_events_init()LVGLbus_i2c + touch_ft5x06disp_st7796_i80disp_backlightactivity_managerboard_init()app_main / ESP3DX::begin()control_events_init()LVGLbus_i2c + touch_ft5x06disp_st7796_i80disp_backlightactivity_managerboard_init()app_main / ESP3DX::begin()board_init()activity_manager_init()ESP_OKbacklight_configure() → backlight_set(0)ESP_OKdisp_st7796_i80_configure(config, i80_flush_ready_cb)panel_handle readybus_i2c_init()OKtouch_ft5x06_configure()ESP_OKlv_init()lv_display_create(480, 320)heap_caps_malloc(DMA) → buf1 [, buf2]lv_display_set_buffers() + set_flush_cb()esp_timer_start_periodic(lvgl_tick)lv_indev_create(LV_INDEV_TYPE_POINTER)control_events_init()custom event IDs assignedESP_OK

Failure at any step returns the error immediately — subsequent steps are not attempted, preventing partial hardware state.


optional snapshot

chunked write

RGB565 byte-swap

(DISPLAY_SWAP_COLOR_FLAG)

esp_lcd_panel_draw_bitmap()

DMA complete ISR

lv_display_flush_ready()

lvgl_flush_cb()

g_snapshot

(file I/O)

lv_draw_sw_rgb565_swap()

ST7796 i80 Panel

i80_flush_ready_cb()

Key configuration constants (from board_config.h)

Section titled “Key configuration constants (from board_config.h)”
ConstantRole
DISPLAY_WIDTH_PX / DISPLAY_HEIGHT_PXLogical resolution passed to lv_display_create
DISP_BUF_SIZE_BYTESDMA buffer allocation size per buffer
DISPLAY_USE_DOUBLE_BUFFER_FLAG1 → allocate buf2 for double-buffering
DISPLAY_SWAP_COLOR_FLAG1 → apply in-place RGB565 byte-swap before sending to panel
LVGL_TICK_PERIOD_MSTick timer period (milliseconds)

When ESP3D_SNAPSHOT_FEATURE is compiled in, lvgl_flush_cb intercepts each flush and streams pixel data to an open file (g_snapshot.file) in 120-byte chunks. The snapshot is marked complete when captured_pixels >= expected_pixels. Write errors set g_snapshot.error = true and abort the capture.

The 120-byte chunk limit is deliberately small to stay within safe stack and heap budgets on the ESP32-S3. See docs/guides/esp32_memory_constraints.md for fragmentation guidance.


touch_ft5x06_data_t

Yes → suppress

No → forward coords

touch_read_cb()

[LVGL timer, 10 ms]

touch_ft5x06_read()

activity_process_event()

lv_indev_data_t

→ LVGL

First touch

of wake-up cycle?

The touch callback maintains two static bool flags:

FlagPurpose
last_pressed_stateTracks whether a finger was down on the previous poll
touch_consumed_for_wakeupSet when the first press of a new wake-up cycle is consumed by activity_process_event()

A touch event that wakes the display is not forwarded to LVGL — only a subsequent continuous hold or a new press after release is dispatched. This prevents accidental UI activation when the user simply wakes the screen.

The factory application uses its own lightweight touch_init() / touch_read() pair (Factory/main/touch.c) that maps raw FT5x06 coordinates linearly to screen dimensions without the wake-up logic:

pt.x = raw_x * SCREEN_WIDTH / touch_ft5x06_get_x_max();
pt.y = raw_y * SCREEN_HEIGHT / touch_ft5x06_get_y_max();

FreeRTOS Core 1 - LVGL Task

lv_tick_inc()

guards all lv_* calls

from main app

esp_timer ISR

increase_lvgl_tick()

lv_display flush

lvgl_flush_cb()

lv_indev poll 10ms

touch_read_cb()

_lock_t lvgl_api_lock

LVGL engine

ModeConditionDescription
Single bufferDISPLAY_USE_DOUBLE_BUFFER_FLAG = 0One DMA buffer; LVGL waits for flush completion before rendering the next frame
Double bufferDISPLAY_USE_DOUBLE_BUFFER_FLAG = 1Two DMA buffers; LVGL can render into buf2 while buf1 is transferring

Both buffers are allocated from MALLOC_CAP_DMA heap. Allocation failure returns ESP_ERR_NO_MEM immediately and aborts board_init.

All code external to the LVGL task that calls lv_* APIs must acquire lvgl_api_lock first. The main UI task (tft_ui_task) holds this lock around each lv_timer_handler() call. See UI_Framework_&_Screens for the task architecture.

⚠️ LVGL runs exclusively on Core 1. Never call lv_* functions from Core 0 tasks without first acquiring the lock.


The BSP registers three custom LVGL events for physical peripherals that have no native LVGL input type:

control_event_t payload

Custom LVGL Events

Physical Inputs

BSP driver callback

BSP driver callback

BSP driver callback

Toggle switch

Potentiometer (ADC)

LV_EVENT_SWITCH_PRESSED

LV_EVENT_SWITCH_RELEASED

LV_EVENT_POTENTIOMETER_CHANGED

family_id · btn_id

steps · press_duration

control_event_t.family_id (type control_family_t) allows UI screens to route events to the correct handler without inspecting the raw indev pointer. The CNC screens — jog, status, probe — subscribe to these events to react to physical controls without polling. See UI_Framework_&_Screens.

Potentiometer note: LV_EVENT_POTENTIOMETER_CHANGED is registered by this BSP regardless of whether the hardware is populated. The actual ADC read and event dispatch depends on optional board-specific driver hooks.


The companion factory application lives in boards/esp32s3_zx3d50ce02s_usrc_4832/Factory/ and is a standalone ESP-IDF project — it is not linked against the main firmware. It provides first-boot hardware validation and firmware update facilities.

Host-side Python Tools

flash_all.py

flash_factory.py

generate_font/generate_font.py

raw2png/snap2png.py

Menu Actions

action_boot_partition

→ select OTA slot

action_sd_update

→ flash firmware from SD

action_sd_update_res

→ flash ui_resources from SD

snapshot_take

→ capture screen to SD

Storage

sdcard.c - SPI mount / unmount

Input Peripherals

touch.c - FT5x06 (I²C, linear coords)

buttons.c - 3× GPIO active-LOW

encoder.c - PCNT quadrature

buzzer.c - GPIO beep

Display (ST7796 i80 - direct, no LVGL)

gfx.c

gfx_init · gfx_flush

gfx_draw_char · gfx_draw_string

gfx_snapshot_begin/end

st7796_i80.c

st7796_i80_flush_ready_cb

(ISR semaphore)

app_main() - Factory

The factory display flush path uses a binary semaphore instead of the LVGL callback chain. st7796_i80_flush_ready_cb is IRAM_ATTR and yields to a higher-priority task if one was unblocked:

static void IRAM_ATTR st7796_i80_flush_ready_cb(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(s_flush_done_sem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

This is simpler than the LVGL path because the factory app has no LVGL task scheduler to coordinate with.

PeripheralInit guard
Buttonspin_mask == 0 → skip GPIO config (no pins defined as GPIO_NUM_NC)
Encoder!GPIO_IS_VALID_GPIO(ENCODER_A_PIN || ENCODER_B_PIN) → skip PCNT setup
Buzzer!GPIO_IS_VALID_GPIO(BUZZER_PIN) → skip GPIO config

Encoder uses the ESP-IDF PCNT (Pulse Counter) driver in quadrature mode with a 1000 ns glitch filter, matching the main firmware configuration.


cmake args from

generates

runs

packages

produces

build_scripts/build_one.py

main()

common.py

check_variant()

common.py

build_variant()

variants.py

make_variant_args(*on_flags)

ui_resources partition

idf.py cmake build

ui_resources_kit/

flash map JSON + artifacts

FileKey functionDescription
build_scripts/build_one.pymain()CLI entry point; parses --clean flag and dispatches to build_variant()
build_scripts/common.pybuild_variant()Orchestrates the full pipeline: clean → resource generation → cmake → size report → artifact packaging
build_scripts/common.pycheck_variant()Validates the requested variant name against known variants
build_scripts/variants.pymake_variant_args()Starts from DEFAULT_OFF_CMAKE_ARGS (all features off) and enables only the flags passed as arguments
# variants.py — shared pattern across all boards
def make_variant_args(*on_flags):
args = DEFAULT_OFF_CMAKE_ARGS.copy() # baseline: all features disabled
for flag in on_flags:
args.extend(["-D", flag]) # selectively enable per-variant
return args

Each named variant corresponds to a specific combination of CMAKE_OPTION flags (transport type, UI features, CNC firmware target). Build outputs land in a per-variant build/<variant_name>/ subdirectory. See esp32s3_zx3d50ce02s_usrc_4832_build_scripts for the full variant list.


esp32s3_zx3d50ce02s_usrc_4832_bsp

hardware/drivers_video_i80/disp_st7796_i80

panel init · draw_bitmap · notify_flush_ready

hardware/common/drivers/touch_ft5x06

touch_ft5x06_configure · touch_ft5x06_read

hardware/common/drivers/bus_i2c

bus_i2c_init

hardware/common/drivers/disp_backlight

backlight_configure · backlight_set

components/esp3d_activity_manager

activity_manager_init · activity_process_event

main/display/esp3d_snapshot.h

g_snapshot (extern, optional)

components/esp3d_log

esp3d_log · esp3d_log_e

LVGL library

lv_init · lv_display_create · lv_indev_*

ESP-IDF

esp_timer · esp_lcd · driver/gpio · heap_caps

DriverUsed byPurpose
disp_st7796_i80board_init, init_lvglI80 panel configuration and esp_lcd_panel_handle_t retrieval
touch_ft5x06init_touch_controller, touch_read_cbCapacitive touch read over I²C
bus_i2cinit_touch_controllerShared I²C bus initialisation
disp_backlightboard_initPWM-controlled backlight (gated by ESP3D_BRIGHTNESS_CONTROL_FEATURE)
activity_managerboard_init, touch_read_cbScreen timeout and wake-up event processing

See Hardware_Peripheral_Drivers for complete driver API documentation.


ModuleRelationship
Board_Support_PackagesParent collection; defines the common BSP pattern shared by all boards
Hardware_Peripheral_DriversProvides all low-level hardware drivers consumed by this BSP
UI_Framework_&_ScreensConsumes get_lvgl_display(), get_lvgl_lock(), get_touch_indev() and the custom LVGL events
Core_Platform_&_Infrastructureesp3d_log, ESP3DX::begin() which calls board_init(), activity manager component
esp32s3_hmi43v3_bspClosest architectural sibling — also uses ST7796 over i80 with the same i80_flush_ready_cb ISR pattern
esp32s3_zx3d50ce02s_usrc_4832_factory_appCompanion standalone factory application for this board
esp32s3_zx3d50ce02s_usrc_4832_build_scriptsBuild automation for all firmware variants of this board

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.