Skip to content

ESP32-3248S035R Board Support Package

The ESP32-3248S035R module is the Board Support Package (BSP) for the ESP32-3248S035R development board — a 3.5-inch TFT touchscreen panel built around the original ESP32 SoC. The “R” suffix denotes a Resistive touch panel (XPT2046, shared SPI bus), distinguishing it from the otherwise similar esp32_3248s035c variant which uses a Capacitive touch controller (GT911, I2C).

This BSP provides:

  • A standalone Factory/recovery application flashed to the factory partition, used for OTA firmware and UI-resource updates from an SD card.
  • A BSP component used by the main pendant firmware, wiring the ST7796 display and XPT2046 resistive touch controller into LVGL.
  • Build scripts that define and compile the supported firmware variants for this board.

The module lives entirely under boards/esp32_3248s035r/ and is one board in the wider Board Support Packages family.


FeatureDetail
MCUESP32 (Xtensa dual-core LX6, 240 MHz)
Flash4 MB
PSRAMNone
DisplayST7796 3.5″ TFT, 320 × 480 px, SPI
TouchXPT2046 resistive touch, shared SPI bus (own CS pin)
Physical buttonsNot populated (GPIO_NUM_NC)
Rotary encoderNot populated (GPIO_NUM_NC)
BuzzerNot populated (GPIO_NUM_NC)
SD cardSPI interface
Recovery entrySoftware trigger only ([ESP444]FACTORY from main firmware)
Custom bootloaderNone (no physical boot button to hold at reset)

Touch-only navigation. This board has no encoder, buttons, or buzzer wired. All navigation in the factory app is via virtual on-screen buttons. Driver code for buttons, encoder, and buzzer is compiled in but safely no-ops at runtime through GPIO_IS_VALID_GPIO() guards.

Resistive touch on the display SPI bus. Unlike the esp32_2432s028r which bit-bangs a dedicated SPI bus for the XPT2046, this board routes its XPT2046 on the same hardware SPI bus as the ST7796 display, with a separate CS pin. The touch controller is added as a second SPI device via spi_bus_add_device() after the display driver initializes the bus.


The module is organized into three distinct layers, each documented in its own sub-module file:

Build System

Factory Partition Context

Main Firmware Context

BSP Component

components/bsp/

LVGL (Core 1)

ST7796 SPI Driver

hardware/common/drivers

XPT2046 SPI Driver

hardware/common/drivers

Factory App

Factory/main/

ST7796 Standalone Driver

(no LVGL / esp3d_log)

GFX 2D Library

XPT2046 Polling

(shared SPI bus, own CS)

SD Card SPI

build_scripts/

variants.py

common.py

build_one.py

main/main.cpp → ESP3DX::begin()

flash partition: factory

Developer / CI

0x0000 Bootloader (standard ESP-IDF — no custom hooks on this board)
0xC000 Partition table
0xD000 NVS
0xF000 otadata ← saved to 0xB000 by main firmware before entering recovery
0x10000 app0 ← main pendant firmware
factory ← this module's Factory app
ui_resources ← LVGL images, fonts, theme palette

The main firmware (in esp444.cpp) writes a magic-tagged backup of otadata to offset 0xB000 before switching to the factory partition. The factory app’s restore_otadata_from_backup() restores it on startup so the board correctly returns to app0 after a power cycle during recovery.


The recovery application running from the factory flash partition. It presents a touch-navigable menu to:

  • Boot a selected OTA partition (app0 / app1).
  • Flash a new firmware image from SD card (esp3dfw.bin → OTA via esp_ota_write).
  • Flash new UI resources from SD card (ui_resources.bin → raw partition write).

Key components: main.c (menu loop, OTA actions), gfx.c (2D graphics / snapshot), st7796.c (standalone SPI display driver), touch.c (XPT2046 polling on shared SPI bus, with read_reg12() + calibrate()), sdcard.c (SPI SD mount), buttons.c, buzzer.c, encoder.c (no-op on this board), factory_log.h.


The hardware initialization layer consumed by the main pendant firmware. It:

  • Initializes the ST7796 SPI display and applies a board-specific mirror-Y orientation correction.
  • Attaches the XPT2046 resistive touch controller as a second device on the already-initialized display SPI bus (via spi_bus_add_device()), with per-board ADC calibration constants applied in touch_calibrate().
  • Configures LVGL: display, DMA-capable draw buffers, flush callback, tick timer, and touch input device (touch_read_cb at 10 ms).
  • Registers custom LVGL control events for switch and potentiometer inputs.

Key components: board_init.c, control_event.h, control_types.c, touch_xpt2046_def.h.


Python build system for the firmware variant matrix. It handles:

  • Variant definitions (4 MB flash × transport × CNC firmware target).
  • CMake invocation through ESP-IDF idf.py with forced IDF_TARGET=esp32.
  • UI resource binary (ui_resources_*.bin) generation via generate_resources.py.
  • Factory artifact copying (bootloader, partition table) from the factory build into the installer directory.
  • Flash map JSON generation.

Key components: build_one.py, common.py, variants.py.


LVGLtouch_xpt2046st7796_spiSPI Bus (TFT_HOST)activity_managerboard_init()main.cppESP3DX::begin()LVGLtouch_xpt2046st7796_spiSPI Bus (TFT_HOST)activity_managerboard_init()main.cppESP3DX::begin()SPI bus initialized by ST7796 driverLVGL running on Core 1board_init()activity_manager_init()st7796_spi_configure()esp_lcd_panel_mirror(false, true)spi_bus_add_device(XPT2046 CS)touch_xpt2046_configure()lv_init()lv_display_create(480 × 320)lv_display_set_flush_cb(lvgl_flush_cb)esp_timer (1 ms tick → lv_tick_inc)lv_indev_create() ← touch_read_cb (10 ms)control_events_init()lvgl_flush_cb() → esp_lcd_panel_draw_bitmap()touch_read_cb() → touch_xpt2046_read()notify_lvgl_flush_ready() (SPI DMA done)

encoder CW/CCW

button press

touch tap

virtual btn

GPIO0 press

Boot app

SD → app0/1

SD → resources

app_main

restore_otadata_from_backup

st7796_init + backlight on

buttons / encoder / touch / buzzer init

probe_sd_files

draw_menu

Main Loop

menu_move ±1

dispatch_button

touch_hint_hit_test

snapshot_check → snapshot_take

execute_selected_action

boot_partition → esp_restart

action_sd_update: esp_ota_*

action_sd_update_res: esp_partition_write

esp_restart


The build system defines four production firmware variants and one factory variant:

VariantMemoryTransportCNC Target
4mb_wifi_fluidnc4 MBWiFi + SerialFluidNC
4mb_serial_fluidnc4 MBSerial UARTFluidNC
4mb_serial_grbl4 MBSerial UARTGRBL
4mb_wifi_grblhal4 MBWiFi + SerialgrblHAL
factory_4mb (factory)4 MB—Recovery

All production variants enable TFT_UI_SERVICE, TFT_TOUCH_SERVICE, UPDATE_SERVICE, and SERIAL_SERVICE. WiFi variants additionally enable WIFI_SERVICE, MDNS_SERVICE, and SOCKET_CLIENT_SERVICE.

Transport policy: Per cmake/sanity_check.cmake, WEBUI_SERVER, SSDP_SERVICE, and WS_SERVER_SERVICE are disabled when SOCKET_CLIENT_SERVICE is ON. See feature_resource_matrix.md for the full compatibility matrix.


Comparison with Sibling Board (esp32_3248s035c)

Section titled “Comparison with Sibling Board (esp32_3248s035c)”
Aspectesp32_3248s035resp32_3248s035c
Touch controllerXPT2046 (resistive, SPI)GT911 (capacitive, I2C)
Touch busShared SPI bus with display (own CS)Dedicated I2C bus
Touch calibrationManual per-board TOUCH_CALIBRATION_* constants + calibrate()Auto-detected by GT911
BSP touch initspi_bus_add_device() + touch_xpt2046_configure()bus_i2c_init() + touch_gt911_configure()
Factory touch driverStandalone read_reg12() + calibrate() in touch.cThin polling wrapper over shared touch_gt911
touch_xpt2046_def.h✓ (SPI device config + inline SPI read function)—
Custom bootloaderNoneNone
Physical controlsNone (GPIO_NUM_NC)None (GPIO_NUM_NC)
Display driverST7796 SPI (same)ST7796 SPI (same)
Orientation fixmirror-Y via esp_lcd_panel_mirror()Same panel quirk, same fix

The XPT2046 touch controller is wired to the same hardware SPI bus (TFT_HOST) as the ST7796 display, differentiated only by a dedicated CS pin (TOUCH_SPI_CS_PIN). In the BSP, spi_bus_add_device() is called inside init_touch_controller() after st7796_spi_configure() has already initialized the bus. In the factory app, touch_init() does the same. This contrasts with esp32_2432s028r which uses a separate bit-banged SPI bus for its XPT2046.

TOUCH_CALIBRATION_X_MIN / X_MAX / Y_MIN / Y_MAX constants in board_config.h define the raw 12-bit ADC range that maps to the 0–480 / 0–320 pixel space. Both the BSP (touch_calibrate() in board_init.c) and the factory app (calibrate() in touch.c) implement the same linear rescale formula:

pixel = (raw - cal_min) * range / (cal_max - cal_min)

Values below cal_min are clamped to zero. These constants are the primary touch-quality tuning knob for this board and may need adjustment per panel batch.

Three virtual buttons are rendered at the bottom of the 320×480 screen. touch_hint_hit_test() divides the screen width into thirds: left third = Up (BTN_1), center third = Down (BTN_2), right third = OK (BTN_3). This gives large tap targets (≈107 px wide) independent of the drawn circle radius (27 px). Visual press feedback (draw_button_hint_pressed()) is drawn before dispatch_button() to give tactile-like confirmation. The encoder and hardware button loops are still polled but return immediately with no-ops on this board.

Factory/main/st7796.c reimplements the display driver over the raw ESP-IDF SPI master driver without depending on esp_lcd, esp3d_log, or LVGL. Init sequence, MADCTL rotation map, and gamma tuning mirror the shared hardware driver. This keeps the factory partition self-contained and minimal, matching the approach used in pibot_pendant_v1_0_factory_app.md and esp32_3248s035c_factory.md.

The ST7796 panel requires a mirror-Y correction not expressible via the shared driver’s DISPLAY_ORIENTATION enum. board_init() calls esp_lcd_panel_mirror(handle, false, true) after st7796_spi_configure() as a board-specific one-off, leaving the shared driver unchanged for all other boards. The factory standalone driver encodes the equivalent MADCTL value 0x88 directly in its rotation map.

The backup offset 0xB000 and magic word 0xAA55AA55 at offset 0xB040 must match the values in the main firmware’s esp444.cpp exactly. Both files contain warnings about this coupling. CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED=y is required in the factory sdkconfig because esp_flash_erase_region() targets 0xB000, which is below the first partition and is normally write-protected by ESP-IDF.

When ENABLE_SNAPSHOT is defined at build time, pressing GPIO0 (the BOOT button on this board) triggers snapshot_take(): the SD card is mounted, a .raw file is opened, and a full-screen redraw sends pixel data simultaneously to the SPI display and the file. The raw format is 8 bytes header (width + height as little-endian uint32_t) followed by native-endian RGB565 pixels. The tools/images_converter/raw2png.py script converts these snapshots to PNG.


TopicFile
Hardware schematicspibot-cnc-pendant-hardware-documentation.md
Display driver architecturedisplay_drivers.md
ST7796 SPI driverbsp_display_drivers_spi.md
XPT2046 touch driverbsp_touch_controllers.md
UI resources partitiondevelopment.md
Theme palette tokenstheme_palette.md
Build guidelinesboard_build_guidelines.md
Memory constraintsesp32_memory_constraints.md
Feature / transport matrixfeature_resource_matrix.md
Sibling (capacitive touch)esp32_3248s035c.md
Logging guideesp3d_log_guide.md