Skip to content

BSP Bus Drivers

The bsp_bus_drivers module provides low-level communication bus abstractions for the Hardware Abstraction Layer (HAL). It contains exactly two drivers:

DriverProtocolPurpose
bus_i2cI²C (hardware)Shared I²C master bus — serves multiple peripherals simultaneously (touch controllers, IO expanders)
bus_sw_spiSPI (bit-banged)Software SPI bus — for touch controllers that cannot share the display’s hardware SPI bus

These drivers are infrastructure components, not end-user APIs. They sit below the touch controllers and IO expanders and above the ESP-IDF peripheral drivers. No application code calls them directly — they are always called by bsp_touch_controllers and bsp_io_expanders during bsp_board_initialization.


Position in the Hardware Abstraction Layer

Section titled “Position in the Hardware Abstraction Layer”

bsp_io_expanders

bsp_touch_controllers

bsp_bus_drivers - Current Module

calls bus_i2c_init()

calls bus_sw_spi_init()

BSP - board_init.c

(bsp_board_initialization)

bus_i2c

Hardware I²C Master

bus_sw_spi

Bit-banged SPI

FT5x06 (I²C)

FT6336U (I²C)

GT911 (I²C)

XPT2046 (SPI)

TCA9554 (I²C)

CH422G (I²C)

ESP-IDF

i2c_driver_install / GPIO

Each board’s board_init.c selects its bus based on which peripherals are fitted:

Hardware SPI boards

(no bus_sw_spi)

esp32_3248s035r

XPT2046 on display SPI

Software SPI boards

esp32_2432s028r

XPT2046

I²C bus boards

esp32s3_4827s043c

GT911

esp32s3_8048_touch_lcd_7

CH422G + GT911

esp32s3_8048s043c/050c/070c

GT911

esp32s3_bzm_tft35_gt911

GT911

esp32s3_hmi43v3

FT5x06 + TCA9554

esp32s3_zx3d50ce02s_usrc_4832

FT5x06

pibot_pendant_v1_0

FT6336U

bus_i2c

bus_sw_spi

ESP-IDF spi_bus_add_device

(direct - no bus wrapper)

Note on esp32_3248s035r: On this board the XPT2046 touch controller is attached directly to the display’s hardware SPI bus using spi_bus_add_device(). It does not use bus_sw_spi. The bus_sw_spi driver is needed only when the touch controller runs on a completely separate, dedicated SPI bus (as on esp32_2432s028r).


Files: hardware/common/drivers/bus_i2c/bus_i2c.c / bus_i2c.h

Provides an idempotent I²C master bus wrapper. The critical problem it solves: on boards where both a touch controller and an IO expander share the same physical I²C bus, both drivers call bus_i2c_init() independently. Without this wrapper, the second call to i2c_driver_install() would fail — ESP-IDF does not reliably return ESP_ERR_INVALID_STATE on re-install (observed returning ESP_FAIL on esp32s3_hmi43v3). The wrapper tracks installed ports in a static array and makes init safe to call multiple times.

FunctionSignatureDescription
bus_i2c_initesp_err_t bus_i2c_init(i2c_port_t port, gpio_num_t sda, gpio_num_t scl, int clk_speed)Install I²C master driver on port. Idempotent — returns ESP_OK immediately if already installed. Enables pull-ups on both SDA and SCL.
bus_i2c_deinitesp_err_t bus_i2c_deinit(i2c_port_t port)Delete the I²C driver for port and clear its tracking flag.
/* Per-port installation state */
static bool s_installed[I2C_NUM_MAX] = {false};

This static array persists for the lifetime of the firmware. A port is marked installed after a successful i2c_driver_install() and cleared only by bus_i2c_deinit().

The driver always configures I²C as master with hardware pull-ups enabled:

i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda_pin,
.scl_io_num = scl_pin,
.sda_pullup_en = GPIO_PULLUP_ENABLE, // always ON
.scl_pullup_en = GPIO_PULLUP_ENABLE, // always ON
.master.clk_speed = clk_speed,
};

Files: hardware/common/drivers/bus_sw_spi/bus_sw_spi.c / bus_sw_spi.h

Implements a bit-banged (software) SPI bus for the XPT2046 touch controller when it cannot share the display’s hardware SPI bus. This is necessary on boards such as esp32_2432s028r where the display and touch are on separate SPI buses with incompatible timing or CS handling.

typedef struct {
int8_t cs_pin; // Chip Select GPIO
int8_t clk_pin; // Clock GPIO
int8_t mosi_pin; // MOSI GPIO
int8_t miso_pin; // MISO GPIO
} bus_sw_spi_config_t;
FunctionSignatureDescription
bus_sw_spi_initesp_err_t bus_sw_spi_init(const bus_sw_spi_config_t *config)Configure the four GPIO pins for bit-banged SPI operation.
bus_sw_spi_read_reg16uint16_t bus_sw_spi_read_reg16(uint8_t reg)Send a command/register byte and clock in a 16-bit response. Used by touch_xpt2046_spi_read_reg12 to read the raw 12-bit ADC value.

The board-level touch_xpt2046_def.h provides a thin adapter that maps bus_sw_spi_read_reg16() to the XPT2046 driver’s callback interface:

static inline uint16_t touch_xpt2046_spi_read_reg12(uint8_t reg) {
return bus_sw_spi_read_reg16(reg) >> 3; // 16-bit raw → 12-bit ADC value
}

This callback is wired into touch_xpt2046_config_t.read_reg12_fn at board init time, keeping the XPT2046 driver itself bus-agnostic — it only sees the callback, not the bus type.


I²C Bus Initialization (shared bus pattern)

Section titled “I²C Bus Initialization (shared bus pattern)”
touch_gt911 / touch_ft5x06io_ch422g / io_tca9554bus_i2cboard_init.ctouch_gt911 / touch_ft5x06io_ch422g / io_tca9554bus_i2cboard_init.calt[Port not yet installed][Port already installed]opt[IO Expander present (e.g. esp32s3_8048_touch_lcd_7)]bus_i2c_init(port, sda, scl, freq)check s_installed[port]i2c_param_config()i2c_driver_install()s_installed[port] = trueESP_OK (no-op)ESP_OKio_ch422g_configure(&cfg)bus_i2c_init(port, ...) ← idempotent, returns immediatelyi2c_master_write_read_device(...)touch_gt911_configure(&cfg)bus_i2c_init(port, ...) ← idempotent, returns immediatelyi2c_master_write_read_device(...)

Software SPI Initialization (touch-only bus)

Section titled “Software SPI Initialization (touch-only bus)”
touch_xpt2046bus_sw_spiboard_init.c (esp32_2432s028r)touch_xpt2046bus_sw_spiboard_init.c (esp32_2432s028r)cfg.read_reg12_fn = touch_xpt2046_spi_read_reg12loop[LVGL touch polling (every 10 ms)]bus_sw_spi_init(&sw_spi_cfg)gpio_set_direction(cs, clk, mosi, miso)ESP_OKtouch_xpt2046_configure(&cfg)bus_sw_spi_read_reg16(reg)bit-bang CS/CLK/MOSI/MISOuint16_t raw_valueraw_value >> 3 → 12-bit ADC

The most important design pattern in this module is the shared I²C bus. On many boards, a single I²C bus connects two independent peripherals — typically a touch controller and an IO expander. Each driver calls bus_i2c_init() independently during its own initialization.

Physical I²C Bus (shared)

1st call: installs driver

io_ch422g_configure()

touch_gt911_configure()

GT911 Touch

(addr 0x5D or 0x14)

CH422G IO Expander

(addr 0x24)

bus_i2c

s_installed tracking

board_init:

init_touch_controller()

Why the wrapper is needed: On esp32s3_hmi43v3, the TCA9554 expander init and the FT5x06 touch driver both attempt i2c_driver_install() on the same port. Without idempotency, the second call returns ESP_FAIL (not ESP_ERR_INVALID_STATE as ESP-IDF documentation implies), causing a spurious initialization failure. The static s_installed[] guard prevents this entirely.


BoardI²C BusSW SPI BusTouch ControllerIO Expander
esp32_2432s028r✗✓XPT2046—
esp32_3248s035r✗✗XPT2046 (HW SPI, direct)—
esp32s3_4827s043c✓✗GT911—
esp32s3_8048_touch_lcd_7✓✗GT911CH422G
esp32s3_8048s043c✓✗GT911—
esp32s3_8048s050c✓✗GT911—
esp32s3_8048s070c✓✗GT911—
esp32s3_bzm_tft35_gt911✓✗GT911—
esp32s3_hmi43v3✓✗FT5x06TCA9554
esp32s3_zx3d50ce02s_usrc_4832✓✗FT5x06—
pibot_pendant_v1_0✓✗FT6336U—
dlc32_max_lcd——(board-specific)—
fysetc_wifi_pro——none—

DependencyRole
ESP-IDF I²C driveri2c_param_config, i2c_driver_install, i2c_driver_delete, i2c_master_write_read_device
ESP-IDF GPIO driverGPIO configuration and direction setting for software SPI pins

Downstream (modules that depend on this module)

Section titled “Downstream (modules that depend on this module)”
ModuleDocumentationUses
Touch Controllersbsp_touch_controllers.mdbus_i2c_init (FT5x06, FT6336U, GT911) · bus_sw_spi_init / bus_sw_spi_read_reg16 (XPT2046)
IO Expandersbsp_io_expanders.mdbus_i2c_init (TCA9554, CH422G)
Board Initializationbsp_board_initialization.mdCalls bus_i2c_init / bus_sw_spi_init at startup

I²C transport

I²C transport

bus_i2c_init / bus_sw_spi_init

touch_read_cb → LVGL indev

expander GPIO

bsp_bus_drivers

(current module)

bsp_touch_controllers

FT5x06 · FT6336U · GT911 · XPT2046

bsp_io_expanders

TCA9554 · CH422G

bsp_board_initialization

board_init.c

UI Framework

(LVGL)

display panel init

SD card mux


Both drivers use only static storage (s_installed[] for I²C; GPIO state for SW SPI). There is no heap allocation at runtime. This is intentional on a memory-constrained ESP32 where dynamic allocation during the init path risks fragmentation. See esp32_memory_constraints.md for the general heap budget.

  • bus_i2c_init / bus_i2c_deinit are not ISR-safe. They must be called from a task context only — always from board_init() before the LVGL task starts.
  • bus_sw_spi_read_reg16() is called from the LVGL touch-read callback (touch_read_cb), which runs on the LVGL task (Core 1) every 10 ms. The bit-bang loop must complete within a safe margin of the LVGL tick period. Do not call this from a high-frequency ISR.
  • I²C reads issued by touch controllers and IO expanders during LVGL polling use i2c_master_write_read_device(), which blocks for the duration of the I²C transaction. Keep I²C clock speeds within proven ranges (typically 400 kHz) to avoid stalling the LVGL task. See display_drivers.md for LVGL task timing constraints.

When both a touch controller and an IO expander share one I²C port, the board BSP must initialize the bus once before configuring either peripheral. The recommended pattern (from esp32s3_8048_touch_lcd_7):

// 1. Initialize shared bus once
bus_i2c_init(I2C_PORT_IDX, I2C_SDA_PIN, I2C_SCL_PIN, I2C_FREQ_HZ);
// 2. Configure IO expander (calls bus_i2c_init internally — no-op)
io_ch422g_configure(&ch422g_cfg);
// 3. Configure touch controller (calls bus_i2c_init internally — no-op)
touch_gt911_configure(&touch_gt911_default_config);

The idempotency of bus_i2c_init() makes step ordering flexible — any peripheral driver may safely call it even if the bus is already up.

The bit-banged SPI implementation is single-instance: it stores pin state in module-level statics. Only one software SPI bus can be active at a time per firmware build. This is sufficient because only one board variant compiles at a time and at most one XPT2046 touch controller is ever present per board.