Skip to content

esp32s3_bzm_tft35_gt911_factory_app_main

The esp32s3_bzm_tft35_gt911_factory_app_main module is the application logic core of the factory/recovery firmware for the ESP32S3-BZM-TFT35-GT911 board. It runs from the dedicated factory flash partition and provides a touch-navigable recovery menu that allows field operators and developers to:

  • Boot a specific OTA application partition (app0 or app1)
  • Flash a new firmware image from an SD card (esp3dfw.bin → app0 or app1)
  • Flash a UI resources image from an SD card (ui_resources.bin → ui_resources partition)
  • Restore the OTA metadata backup written by the main firmware before entering recovery

Unlike most other boards in the BSP family, the ESP32S3-BZM-TFT35-GT911 has no physical buttons, encoder, or buzzer (all are GPIO_NUM_NC). Navigation is exclusively through the GT911 capacitive touchscreen via virtual buttons rendered at the bottom of the display. All hardware no-op paths are guarded at the driver level, so the code remains identical to the reference pibot_pendant_v1_0 design and will work automatically if physical controls are ever populated.

Entry path: The factory partition is entered only via the [ESP444]FACTORY software command from the main firmware. There is no custom bootloader hook because this board has no physical BOOT button to hold at reset. See esp32s3_bzm_tft35_gt911_bsp for the board-level BSP used by the main firmware.


This module is a child of esp32s3_bzm_tft35_gt911_factory_app and contains two source files:

FilePurpose
boards/esp32s3_bzm_tft35_gt911/Factory/main/main.cApplication entry point, menu logic, OTA operations, UI rendering
boards/esp32s3_bzm_tft35_gt911/Factory/main/factory_log.hCompile-time debug log gate and SD stack silencer
ModuleResponsibility
esp32s3_bzm_tft35_gt911_factory_app_displayFramebuffer GFX API + ST7796 SPI display driver
esp32s3_bzm_tft35_gt911_factory_app_inputButtons (no-op), encoder (no-op), GT911 touch, buzzer (no-op)
esp32s3_bzm_tft35_gt911_factory_app_storageSD card mount/unmount
esp32s3_bzm_tft35_gt911_factory_app_toolsHost-side flash and font generation scripts

ESP-IDF OTA API

factory_app_storage

factory_app_input

factory_app_display

esp32s3_bzm_tft35_gt911_factory_app_main (this module)

app_main

restore_otadata_from_backup

draw_menu / UI render functions

touch_hint_hit_test

dispatch_button

execute_selected_action

action_boot_partition

action_sd_update

action_sd_update_res

snapshot_take / snapshot_check

gfx_* (draw, clear, flush, snapshot)

st7796_init / st7796_flush / st7796_backlight

touch_init / touch_read - GT911

buttons_init / button_wait_press - no-op

encoder_init / encoder_read - no-op

buzzer_init / buzzer_beep_short - no-op

sdcard_mount / sdcard_unmount

esp_ota_begin / write / end / set_boot_partition

esp_partition_write / erase_range

esp_flash_read / write / erase_region


The factory partition is activated by the main firmware’s ESP444 command (esp444.cpp). Before switching the boot partition, the main firmware:

  1. Reads the current OTA data sectors from flash offset 0x10000
  2. Writes them to a backup sector at 0xB000
  3. Stamps the backup with magic 0xAA55AA55 at backup offset 0x40
  4. Sets the boot partition to factory and reboots

On the very next app_main call, restore_otadata_from_backup() detects and restores this backup before any other initialization, ensuring that a power-cut during recovery still returns to the correct OTA partition on the next boot.

Factory app_mainESP-IDF BootloaderMain Firmware (esp444.cpp)Factory app_mainESP-IDF BootloaderMain Firmware (esp444.cpp)Backup otadata → 0xB000 + magic stampesp_ota_set_boot_partition("factory")esp_restart()Start factory partitionrestore_otadata_from_backup()Detect magic at 0xB000 + 0x40Erase 0x10000, write back OTA entriesErase backup sector (0xB000)st7796_init() + gfx_init()buttons / encoder / touch / buzzer initBuild menu items, probe_sd_files()draw_menu()Enter main event loop

Flash Map (relevant regions)

backed up by main firmware

(magic + entry1 + entry2)

restored by factory app_main

on startup

0x0000 Bootloader

0x10000 otadata (2 × 4 KB)

0xB000 Backup sector (4 KB)

0xC000 Partition table

0xD000 NVS

ConstraintValueReason
OTADATA_BACKUP_OFFSET0xB000After bootloader end, before partition table (0xC000), 4 KB-aligned
OTADATA_OFFSET0x10000Fixed ESP-IDF otadata location
OTADATA_ENTRY_SIZE32 bytesOne ESP-IDF OTA state record
BACKUP_MAGIC0xAA55AA55 at +0x40Presence check before restore
CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWEDMust be yesp_flash_erase_region targets address below first partition

⚠️ Porting note: When adapting this factory app to a board with a different flash layout, recalculate OTADATA_BACKUP_OFFSET and update both this file and the main firmware’s esp444.cpp with the same value.


typedef struct {
const char *label; /* Display label */
menu_action_t action; /* Action dispatched by execute_selected_action */
uint16_t color; /* Text color when item is not selected */
} menu_item_t;

Menu items are built dynamically at startup based on detected partitions and SD card contents:

LabelActionShown when
Boot app0MENU_ACTION_BOOT_APP0Always
Boot app1MENU_ACTION_BOOT_APP1app1 partition detected
SD -> app0MENU_ACTION_SD_UPDATE_APP0Always
SD -> app1MENU_ACTION_SD_UPDATE_APP1app1 partition detected
SD -> resourcesMENU_ACTION_SD_UPDATE_RESAlways
┌────────────────────────────────┐ ← Y=0
│ Recovery vX.Y.Z (title) │ Y=13 CYAN
│ ───────────────────────────── │ Y=40 separator
│ Active: <partition> │ Y=53 YELLOW
│ SD: FW RES │ Y=81 SD indicators
│ ───────────────────────────── │ Y=104 separator
│ │
│ [ Boot app0 ] │ Y=111 (MENU_START_Y)
│ [ SD -> app0 ] │ Y=151 each item H=40
│ [ SD -> app1 ] │ Y=191
│ [ SD -> resources ] │ Y=231
│ │
│ ───────────────────────────── │ STATUS_Y − 7 separator
│ <status / "Power off"> │ STATUS_Y footer zone
│ ───────────────────────────── │ BTN_HINT_BASE_Y separator
│ (↑) (↓) (✓) │ BTN_HINT_CY virtual buttons
└────────────────────────────────┘ ← Y=479

Physical buttons are unpopulated (GPIO_NUM_NC). Three virtual buttons are drawn as circle-outline icons at the bottom of the screen. The touch hit zones are column-thirds of the full screen width — much larger than the drawn circles — for easy tapping.

Screen width: 320 px
│ col 0–106 │ col 107–213 │ col 214–320 │
│ BTN_1 (↑ Up) │ BTN_2 (↓ Down) │ BTN_3 (✓ OK) │
│ CX1 = 60 │ CX2 = 160 │ CX3 = 260 │
│ BLUE │ BLUE │ GREEN │

touch_hint_hit_test(x, y) returns BTN_NONE for any y < BTN_HINT_BASE_Y, ensuring that taps on menu items are never misinterpreted as button presses.


yes

no

clicks > 0

clicks < 0

0

BTN_NONE

BTN_1/2/3

pressed AND not was_pressed

else

BTN_NONE

BTN_1/2/3

BTN_1

BTN_2

BTN_3

Main Loop

ENABLE_SNAPSHOT?

snapshot_check GPIO0

encoder_read - no-op on this board

menu_move -1 UP

menu_move +1 DOWN

button_wait_press 100ms - no-op

touch_read GT911

dispatch_button

touch_hint_hit_test x y

draw_button_hint_pressed

buzzer_beep_short

80ms delay

draw_button_hints

menu_move -1

menu_move +1

execute_selected_action

Loop timing: button_wait_press(100) blocks for up to 100 ms each iteration. On this board, since physical buttons return BTN_NONE immediately, the loop runs at approximately 100 ms cadence, providing responsive touch polling.


ESP-IDF OTAMenu UIESP-IDF OTAMenu UIalt[partition found][not found]show_status("Booting <label>...", CYAN)vTaskDelay 500msesp_partition_find_first(label)partition handle or NULLesp_ota_set_boot_partition(part)esp_restart()show_status("<label> not found!", RED)redraw menu

SD Firmware Flash Action (action_sd_update)

Section titled “SD Firmware Flash Action (action_sd_update)”
ESP-IDF OTA APISD CardMenu UIESP-IDF OTA APISD CardMenu UIloop[Read 1 KB chunks until EOF]alt[success][failure]sdcard_mount()fopen("/sdcard/esp3dfw.bin")fseek → get file sizevalidate size ≤ partition sizedraw_flashing_screen()esp_ota_begin(target_part, fw_size, &handle)fread(buf, 1024)esp_ota_write(handle, buf, len)draw_progress(percent)esp_ota_end(handle)esp_ota_set_boot_partition(target_part)rename esp3dfw.bin → esp3dfw.oksdcard_unmount()draw_result(success, "Success! Rebooting...")esp_restart()rename esp3dfw.bin → esp3dfw.badprobe_sd_files() + draw_menu()

SD Resources Flash Action (action_sd_update_res)

Section titled “SD Resources Flash Action (action_sd_update_res)”

Follows the same pattern as the firmware flash but targets the ui_resources DATA partition using esp_partition_erase_range() + esp_partition_write() directly (no OTA handle). The binary header (ESP3 magic + 12-char variant string) is read and logged before flashing to allow early detection of variant mismatches (wrong firmware type or transport).


All drawing calls route through the GFX API defined in esp32s3_bzm_tft35_gt911_factory_app_display.

ConstantValueDescription
SCREEN_WIDTH320Portrait display width (pixels)
SCREEN_HEIGHT480Portrait display height (pixels)
FONT_WIDTH11Pixels per character column
FONT_HEIGHT21Pixels per character row
MENU_START_Y111Y coordinate of first menu item
MENU_ITEM_H40Height of each menu item row
MENU_PAD_X20Horizontal padding for menu items
BTN_CIRCLE_R27Virtual button circle radius
STATUS_YSCREEN_HEIGHT - 33 - BTN_HINT_HFooter message Y coordinate
BTN_HINT_BASE_YSCREEN_HEIGHT - BTN_HINT_HTop edge of button hint bar

The layout constants are scaled ×4/3 relative to the 240×320 reference (pibot_pendant_v1_0) to match the 320×480 screen and the proportionally larger 11×21 font.

FunctionDescription
draw_header()Clears screen, double border, title, separator line
draw_menu()Full menu repaint: header + active partition + SD indicators + all items + footer + button hints
draw_menu_item(index)Redraws a single menu item with or without selection highlight
draw_sd_indicators()Shows FW (green) and/or RES (cyan) badges if files detected on SD
draw_footer_zone()Status message, or “Power off to cancel” if no message pending
draw_flashing_screen()Full-screen flash-in-progress overlay with “Do NOT power off!” warning
draw_progress(percent)Outlined progress bar + percentage text
draw_result(success, msg)Green success or red failure result message
draw_button_hints()Horizontal separator line + three circle icons with ↑ ↓ ✓
draw_button_hint_pressed(btn)Redraws one button in violet for touch-press visual feedback
draw_circle(cx,cy,r,color)Midpoint circle algorithm, outline only
draw_up_arrow(cx,cy,color)Upward arrow icon centered at (cx, cy), fits within r=27
draw_down_arrow(cx,cy,color)Downward arrow icon centered at (cx, cy), fits within r=27
draw_check_mark(cx,cy,color)Checkmark icon centered at (cx, cy), 4 px thick strokes
ConstantRGB565 valueUsed for
MENU_HIGHLIGHTDark blueSelection highlight box
MENU_HIGHLIGHT_TXTBright blueSelected item text
BTN_NAV_COLORBlueUp/Down button icons
BTN_OK_COLORGreenOK/Select button icon
BTN_PRESSED_COLORVioletTouch-press feedback

When compiled with -DENABLE_SNAPSHOT, pressing GPIO0 (BOOT button on evaluation breakout boards) triggers a full screen capture saved to SD card as a raw pixel file.

yes

no

GPIO0 LOW detected in snapshot_check

50ms debounce re-check

sdcard_mount()

snap_find_next_number()

→ first free snap###.raw

gfx_snapshot_begin('/sdcard/snap###.raw')

Write width + height header

(2 × uint32 LE = 8 bytes)

Pre-fill pixel area with zeros

Flash in

progress?

draw_flashing_screen()

draw_progress(last_percent)

draw_menu()

show_status(if any)

gfx_snapshot_end() - fclose

s_snapshot_count++

Wait GPIO0 HIGH + 50ms debounce

Snapshot files are named snap000.raw through snap999.raw. snap_find_next_number() scans existing files on SD to find the first unused index. The raw format (width + height uint32 LE header followed by RGB565 pixel data, row-major) can be converted to PNG with the host tool in esp32s3_bzm_tft35_gt911_factory_app_tools.


The module uses a two-tier logging scheme, entirely independent of the main firmware’s esp3d_log system (see factory_logging):

OFF

ON

ENABLE_FACTORY_DEBUG_LOG

in CMakeLists.txt

FACTORY_LOG_LEVEL = 0

FACTORY_LOGD → no-op

sdkconfig.prod_log applied

(silences ESP-IDF boot chatter)

FACTORY_LOG_LEVEL = 1

FACTORY_LOGD → ESP_LOGI

base sdkconfig only

factory_log_silence_sd_stack()

called at start of app_main

→ mutes sdmmc / fatfs / sdspi

tags at runtime (both modes)

Macro / FunctionBehavior
FACTORY_LOGD(tag, fmt, ...)→ ESP_LOGI when FACTORY_LOG_LEVEL=1, no-op otherwise
ESP_LOGW / ESP_LOGEAlways active regardless of log level
factory_log_silence_sd_stack()Calls esp_log_level_set(ESP_LOG_NONE) for 8 SD/FAT driver tags at runtime

The SD stack silencer is invoked as the very first statement in app_main to suppress internal SD driver noise from polluting the serial monitor even in debug builds.


PeripheralInterfaceNotes
ST7796 displaySPI (TFT_HOST)320×480 portrait, RGB565, SPI DMA chunked transfers
Display DCGPIO TFT_DCData/Command select
Display RSTGPIO TFT_RSTActive low, 10 ms pulse, then 120 ms sleep
Display CSGPIO TFT_CSSPI chip select
Display backlightGPIO TFT_LEDActive high; off during st7796_init, on after gfx_init
GT911 touchI2C (TOUCH_I2C_PORT_IDX)SDA = TOUCH_I2C_SDA_PIN, SCL = TOUCH_I2C_SCL_PIN
GT911 RSTGPIO TOUCH_RST_PINTouch controller reset
GT911 INTGPIO TOUCH_IRQ_PINInterrupt line (polled in factory app, no ISR)
Physical buttonsGPIO_NUM_NCNot populated — buttons_init skips GPIO config, button_wait_press returns BTN_NONE
Rotary encoderGPIO_NUM_NCNot populated — encoder_init returns early, encoder_read returns 0
BuzzerGPIO_NUM_NCNot populated — buzzer_beep_short is a no-op
Snapshot triggerGPIO0Only when ENABLE_SNAPSHOT is compiled in
SD cardSPI or SDIOVia sdcard_mount() / sdcard_unmount()

PathPurposeAfter successAfter failure
/sdcard/esp3dfw.binFirmware image to flashRenamed → esp3dfw.okRenamed → esp3dfw.bad
/sdcard/esp3dfw.okRecord of last successful firmware flash——
/sdcard/esp3dfw.badRecord of last failed firmware flash——
/sdcard/ui_resources.binUI resources image to flashRenamed → ui_resources.okRenamed → ui_resources.bad
/sdcard/ui_resources.okRecord of last successful resources flash——
/sdcard/ui_resources.badRecord of last failed resources flash——
/sdcard/snap###.rawScreen snapshots (optional, ENABLE_SNAPSHOT)——

probe_sd_files() mounts the SD, checks for the presence of esp3dfw.bin and ui_resources.bin, then unmounts. It is called at startup and again after any failed flash to refresh the FW/RES indicator badges in the menu header.


This module is architecturally identical to the reference pibot_pendant_v1_0_factory_app_main. Board-specific differences are limited to:

AspectESP32S3-BZM-TFT35-GT911pibot_pendant_v1_0
Display driverST7796 SPIILI9341 SPI
Screen resolution320 × 480240 × 320
Touch controllerGT911 (I2C, capacitive)XPT2046 (SPI, resistive)
Physical buttonsNone (GPIO_NUM_NC)3× GPIO (populated)
Rotary encoderNone (GPIO_NUM_NC)Quadrature PCNT encoder (populated)
BuzzerNone (GPIO_NUM_NC)GPIO PWM buzzer (populated)
Bootloader hookNot used (software-only entry)is_button_pressed at boot
Font dimensions11 × 21 px (×4/3 scale)8 × 16 px (reference)
UI layout scale×4/3 from referenceReference
Custom bootloaderNot presentpibot_pendant_v1_0_bootloader