Skip to content

BSP Board Initialization — Touch Subsystem

BSP Board Initialization — Touch Subsystem

Section titled “BSP Board Initialization — Touch Subsystem”

The bsp_board_initialization_touch module provides the touch-controller initialization and LVGL input-device integration layer for every board that includes a capacitive touchscreen. It sits inside the board-level BSP and is called as part of board_init(), wiring a hardware touch IC to LVGL’s pointer input device model through a shared state machine that also handles activity-manager wake-up events.


  1. Overview
  2. Module Position in the BSP Hierarchy
  3. Supported Boards and Touch Controllers
  4. Architecture
  5. Component Descriptions
  6. Wake-Up State Machine
  7. Data Flow
  8. Board-Specific Variations
  9. LVGL Integration
  10. Initialization Sequence
  11. Dependencies
  12. Key Constraints

Every board supported by this module uses a capacitive touch IC connected over I2C. The module is responsible for two tasks:

  • Hardware initialization (init_touch_controller): bring up the I2C bus and configure the touch IC driver so it starts producing raw touch coordinates.
  • LVGL bridge (touch_read_cb): act as the LVGL input-device read callback, translating raw touch data into LVGL pointer events while enforcing the activity-manager wake-up protocol.

The module is guarded by the ESP3D_TOUCH_FEATURE compile-time flag; on boards where that flag is disabled the functions are compiled out entirely.


board_init()

init_touch_controller()

init_lvgl() - [bsp_board_initialization_lvgl]

touch_read_cb() - registered as lv_indev

lvgl_flush_cb() - [bsp_board_initialization_display]

activity_manager - [Core Platform]

Touch IC Driver - [bsp_touch_controllers]

bus_i2c - [bsp_bus_drivers]

bsp_board_initialization_touch is a child of bsp_board_initialization. Its sibling sub-modules are:

Sibling moduleResponsibility
bsp_board_initialization_lvglLVGL library init, tick timer, draw buffers
bsp_board_initialization_displaylvgl_flush_cb, VSYNC/i80 flush-ready callbacks
bsp_board_initialization_inputsButtons, encoder, switch, potentiometer (pibot only)

BoardSource fileTouch ICI2C bus setupCoordinate scaling
esp32s3_8048s070cboards/esp32s3_8048s070c/components/bsp/board_init.cGT911Dedicated portYes — GT911 native resolution → display pixels
esp32s3_bzm_tft35_gt911boards/esp32s3_bzm_tft35_gt911/components/bsp/board_init.cGT911Dedicated portNo
esp32s3_hmi43v3boards/esp32s3_hmi43v3/components/bsp/board_init.cFT5x06Shared with TCA9554 IO expanderNo
esp32s3_zx3d50ce02s_usrc_4832boards/esp32s3_zx3d50ce02s_usrc_4832/components/bsp/board_init.cFT5x06Dedicated portNo
pibot_pendant_v1_0boards/pibot_pendant_v1_0/components/bsp/board_init.cFT6336UDedicated portNo

Driver details for GT911, FT5x06, and FT6336U are documented in bsp_touch_controllers.


LVGL

activity_manager

bsp_board_initialization_touch

bsp_touch_controllers

Hardware

I2C

I2C

I2C

GT911

(I2C)

FT5x06

(I2C)

FT6336U

(I2C)

touch_gt911_read()

touch_gt911_configure()

touch_ft5x06_read()

touch_ft5x06_configure()

touch_ft6336u_read()

touch_ft6336u_configure()

init_touch_controller()

touch_read_cb()

static state

last_pressed_state

touch_consumed_for_wakeup

activity_process_event()

lv_indev_t POINTER

lv_indev_data_t


static esp_err_t init_touch_controller(void);

Purpose: Bring up the I2C bus and configure the board-specific touch IC.

Behavior (all boards except hmi43v3):

No

Yes

No

Yes

init_touch_controller called

bus_i2c_init

(port, SDA, SCL, freq)

I2C OK?

esp3d_log_e

return ESP_FAIL

touch_xxx_configure

(default_config)

Config OK?

esp3d_log_e

return ESP_FAIL

return ESP_OK

Special case — esp32s3_hmi43v3:

On this board the I2C bus is shared between the TCA9554 IO expander (backlight enable, touch reset) and the FT5x06. The bus is therefore initialized in init_io_expander(), which runs before init_touch_controller() in board_init(). Consequently, init_touch_controller() on hmi43v3 only calls touch_ft5x06_configure() — no bus_i2c_init() call is made.

touch_ft5x06init_touch_controllerio_tca9554bus_i2cinit_io_expanderboard_inittouch_ft5x06init_touch_controllerio_tca9554bus_i2cinit_io_expanderboard_initI2C already up - skip bus_i2c_initcallbus_i2c_init(shared port)io_tca9554_configure()calltouch_ft5x06_configure()

I2C configuration per board:

BoardPort constantSDASCLFrequency
esp32s3_8048s070cI2C_PORT_IDXI2C_SDA_PINI2C_SCL_PINI2C_FREQ_HZ
esp32s3_bzm_tft35_gt911TOUCH_I2C_PORT_IDXTOUCH_I2C_SDA_PINTOUCH_I2C_SCL_PINTOUCH_I2C_FREQ_HZ
esp32s3_hmi43v3TOUCH_I2C_PORT_IDX (via io_expander)TOUCH_I2C_SDA_PINTOUCH_I2C_SCL_PINTOUCH_I2C_FREQ_HZ
esp32s3_zx3d50ce02s_usrc_4832TOUCH_I2C_PORT_IDXTOUCH_I2C_SDA_PINTOUCH_I2C_SCL_PINTOUCH_I2C_FREQ_HZ
pibot_pendant_v1_0from touch_ft6336u_default_config.i2c_portTOUCH_SDA_PINTOUCH_SCL_PINfrom config struct

All pin and port constants are defined per-board in board_config.h (not part of this module).


static void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data);

Purpose: LVGL input-device read callback for the touch pointer. Called by LVGL’s input task every 10 ms (period explicitly set via lv_timer_set_period(lv_indev_get_read_timer(touch_indev), 10)).

Responsibilities:

  1. Read raw touch data from the board-specific driver.
  2. Apply coordinate scaling if needed (esp32s3_8048s070c only).
  3. Run the activity-manager wake-up state machine.
  4. Populate data->state and data->point for LVGL.

Static state variables (per callback instance):

VariableTypePurpose
last_pressed_stateboolTracks whether the previous poll reported a press — used to detect edge transitions
touch_consumed_for_wakeupboolSet when the initial press was used only to wake the system; suppresses the corresponding release event
last_x, last_yuint16_tSaved scaled coordinates — esp32s3_8048s070c only, for stable sustained-press reporting

All boards implement the same wake-up protocol. activity_process_event() is called on press and release transitions only, never during a sustained press.

system start

touch pressed AND was released before

activity_process_event returns true\ndeliver PRESSED\ntouch_consumed_for_wakeup = false

activity_process_event returns false\ndeliver RELEASED\ntouch_consumed_for_wakeup = true

still pressed\ndeliver PRESSED\nupdate point.x and point.y

still pressed\ndeliver RELEASED

touch released AND was pressed before

touch released AND was pressed before

activity_process_event\ndeliver RELEASED

clear touch_consumed_for_wakeup\nrelease silently ignored by LVGL

Idle

PressTransition

ActivePress

SuppressedPress

ReleaseTransition

SuppressedRelease

Rule summary:

Conditiondata->state delivered to LVGLActivity notified
Transition released→pressed, system activeLV_INDEV_STATE_PRESSEDYes (returns true)
Transition released→pressed, system sleepingLV_INDEV_STATE_RELEASEDYes (returns false, system wakes)
Sustained press, not a wake-up pressLV_INDEV_STATE_PRESSEDNo
Sustained press, was a wake-up pressLV_INDEV_STATE_RELEASEDNo
Transition pressed→released, normalLV_INDEV_STATE_RELEASEDYes
Transition pressed→released, was wake-upLV_INDEV_STATE_RELEASEDNo (release silently ignored)

LVGL Coreactivity_managerTouch IC Drivertouch_read_cbLVGL Timer 10 msLVGL Coreactivity_managerTouch IC Drivertouch_read_cbLVGL Timer 10 msalt[System active][Wake-up]alt[Normal release][Wake-up release]alt[Touch pressed and transition released to pressed][Touch still pressed][Touch released and transition pressed to released]polltouch_xxx_read()touch_xxx_data_t {is_pressed, x, y}activity_process_event()true active or false wake-upstate=PRESSED, point={x,y}state=RELEASED, press consumedstate=PRESSED if not wake-up else RELEASEDactivity_process_event()state=RELEASEDstate=RELEASED silently ignored

Coordinate Scaling — esp32s3_8048s070c (GT911)

Section titled “Coordinate Scaling — esp32s3_8048s070c (GT911)”

The GT911’s internal touch resolution is independent of the panel’s pixel grid. On this board the touch area can be larger than the display, so raw coordinates must be mapped:

uint16_t x = touch_data.x * DISPLAY_WIDTH_PX / touch_gt911_get_x_max();
uint16_t y = touch_data.y * DISPLAY_HEIGHT_PX / touch_gt911_get_y_max();

touch_gt911_get_x_max() and touch_gt911_get_y_max() are provided by the GT911 driver (see bsp_touch_controllers). The scaled result is cached in last_x/last_y static variables so sustained-press reports remain stable even if subsequent raw reads return slightly different intermediate values.

Shared I2C Bus — esp32s3_hmi43v3 (FT5x06 + TCA9554)

Section titled “Shared I2C Bus — esp32s3_hmi43v3 (FT5x06 + TCA9554)”

This board routes backlight enable and touch reset through a TCA9554 IO expander that shares the I2C bus with the FT5x06 touch IC. The bus must be up before the IO expander is configured, and the IO expander must be configured before the touch IC is released from reset. board_init() enforces this strict order:

activity_manager_init()
→ init_io_expander() ← bus_i2c_init() + io_tca9554_configure()
→ disp_rm68120_configure()
→ init_touch_controller() ← touch_ft5x06_configure() only (bus already up)
→ init_lvgl()

See bsp_io_expanders for TCA9554 driver details.

Sustained-Press Position Caching — esp32s3_8048s070c only

Section titled “Sustained-Press Position Caching — esp32s3_8048s070c only”

Because coordinate scaling is applied on every read, the 8048s070c variant stores the last valid scaled position in last_x/last_y and reuses it when reporting a continued press. The other four boards pass touch_data.x/touch_data.y directly without caching because no mapping is needed.


Touch registration happens inside init_lvgl() (owned by bsp_board_initialization_lvgl), immediately after LVGL is initialized:

touch_indev = lv_indev_create();
lv_indev_set_type(touch_indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(touch_indev, touch_read_cb);
lv_indev_set_display(touch_indev, lvgl_display);
lv_timer_set_period(lv_indev_get_read_timer(touch_indev), 10); // 10 ms

The touch_indev handle is exposed via get_touch_indev() for use by the UI layer.

Critical LVGL constraints:

  • touch_read_cb executes on Core 1 inside the LVGL task — no blocking calls, no heap allocation, no FreeRTOS task creation.
  • The callback must return quickly; it is called every 10 ms unconditionally by LVGL’s input timer.
  • LV_INDEV_TYPE_POINTER means LVGL treats this as an absolute pointer (touchscreen), not a relative encoder or keypad device.

No

Yes

No

Yes

Yes

No

board_init called

activity_manager_init

ESP3D_DISPLAY_FEATURE?

return ESP_OK

backlight_configure

backlight_set 0

Display IC configure

e.g. disp_ek9716_configure

ESP3D_TOUCH_FEATURE?

init_lvgl

init_touch_controller

bus_i2c_init

except hmi43v3 - already done

touch_xxx_configure

lv_init

lv_display_create / set buffers

lv_display_set_flush_cb → lvgl_flush_cb

esp_timer start → increase_lvgl_tick

ESP3D_TOUCH_FEATURE?

lv_indev_create POINTER

lv_indev_set_read_cb → touch_read_cb

timer period = 10 ms

backlight_set DEFAULT_LEVEL

control_events_init


DependencyRoleDocumentation
bus_i2cI2C bus initialization (bus_i2c_init)bsp_bus_drivers
Touch IC drivers (GT911, FT5x06, FT6336U)Raw touch data read, driver configuration, max-resolution queriesbsp_touch_controllers
io_tca9554IO expander on hmi43v3; shared I2C bus must init before touchbsp_io_expanders
activity_managerWake-up event tracking via activity_process_event()bsp_board_initialization
bsp_board_initialization_lvglOwns init_lvgl() where the LVGL indev is created and registeredbsp_board_initialization_lvgl
bsp_board_initialization_displayOwns the display flush path; touch and display share the init_lvgl() callbsp_board_initialization_display
APIPurpose
lv_indev_create()Create LVGL input device
lv_indev_set_type(…, LV_INDEV_TYPE_POINTER)Declare as absolute pointer (touchscreen)
lv_indev_set_read_cb(…, touch_read_cb)Register the read callback
lv_indev_set_display()Bind input device to the display
lv_timer_set_period(lv_indev_get_read_timer(…), 10)Override default poll period to 10 ms
touch_gt911_get_x_max() / touch_gt911_get_y_max()Query GT911 native resolution for coordinate scaling

ConstraintDetail
Single-threaded LVGLtouch_read_cb runs on Core 1 inside the LVGL task. It must not block, allocate memory dynamically, or call any API that is not task-safe at this priority.
Activity manager protocolactivity_process_event() is called only on press/release transitions, never during a sustained press. Calling it every poll would cause spurious activity resets and incorrect wake-up detection.
Wake-up press suppressionThe first press after a wake-up is intentionally hidden from LVGL to prevent accidental taps from triggering UI actions when waking the screen.
Coordinate scaling availability (8048s070c)touch_gt911_get_x_max() and touch_gt911_get_y_max() must return valid values after init_touch_controller() returns. These are populated by the GT911 driver during touch_gt911_configure().
I2C bus ordering (hmi43v3)On hmi43v3, init_io_expander() must run before init_touch_controller(). The TCA9554 expander controls the FT5x06 reset line; calling touch_ft5x06_configure() before the expander is ready leaves the touch IC in reset and causes initialization failure.
10 ms polling periodLVGL’s default input poll period is LV_DEF_REFR_PERIOD (~30 ms). This module overrides it to 10 ms to reduce touch-to-event latency and avoid missing fast press/release cycles on all supported boards.
No heap allocation in callbackTouch data structs are declared as local stack variables inside touch_read_cb — no dynamic allocation per poll cycle.