Skip to content

esp32s3_8048s043c — Factory / Recovery Application

esp32s3_8048s043c — Factory / Recovery Application

Section titled “esp32s3_8048s043c — Factory / Recovery Application”

The esp32s3_8048s043c_factory module is the standalone recovery firmware for the ESP32-S3 8048S043C board: an 800 × 480 capacitive-touch LCD board driven by an ST7262 RGB-parallel panel and a GT911 I2C touch controller.

Unlike boards that enter recovery by holding a physical button at power-on (e.g. pibot_pendant_v1_0), this board has no physical buttons, encoder, or buzzer populated — all three peripherals are wired to GPIO_NUM_NC. Recovery is entered exclusively via the software [ESP444]FACTORY command issued from the main firmware, which backs up the OTA-data partition before switching the boot partition to factory. The factory app then restores that backup on startup so a subsequent power-cycle returns to the correct application partition.

The factory app is entirely self-contained: it implements its own lightweight graphics stack (no LVGL), its own touch driver, and its own display driver — all built without any dependency on the main firmware’s infrastructure.

Key board differentiator vs. similar factory apps: The ST7262 is an RGB-parallel panel that requires a PSRAM framebuffer. Every other board ported so far uses SPI panels (ST7796, ILI9341, ILI9485). This means st7262_flush() must un-swap byte order before calling esp_lcd_panel_draw_bitmap(), whereas SPI panels consume big-endian data directly on the wire.


boards/esp32s3_8048s043c/Factory/
├── main/
│ ├── main.c # App entry point, recovery menu, OTA actions
│ ├── gfx.c # Graphics primitives + optional snapshot capture
│ ├── st7262.c # ST7262 RGB-parallel display driver wrapper
│ ├── touch.c # GT911 I2C touch controller wrapper
│ ├── touch.h # touch_point_t type + API declaration
│ ├── sdcard.c # SD card mount / unmount (SPI, FAT-VFS)
│ ├── buttons.c # Button driver (no-op on this board)
│ ├── buzzer.c # Buzzer driver (no-op on this board)
│ ├── encoder.c # Rotary encoder driver (no-op on this board)
│ └── factory_log.h # Compile-time debug logging gate
├── CMakeLists.txt
└── sdkconfig # Base sdkconfig (+ sdkconfig.prod_log overlay)

No custom bootloader directory: This board has no physical button to detect at power-on, so ENABLE_CUSTOM_BOOT_LOADER is not set and there is no custom_bootloader/ or bootloader_components/ folder. The OTA-data backup/restore contract is fulfilled entirely in software.


Hardware

ST7262

RGB Parallel Panel

800×480 native landscape

GT911

I2C Capacitive Touch

SD Card

SPI / FAT-VFS

Buttons / Encoder / Buzzer

(GPIO_NUM_NC - not populated)

Driver Wrappers

st7262.c

RGB panel init + flush

(PSRAM framebuffer)

touch.c

GT911 configure + read

+ coordinate rescaling

sdcard.c

SPI bus + FAT mount

buttons.c

(guards GPIO_NUM_NC)

buzzer.c

(guards GPIO_NUM_NC)

encoder.c

(guards GPIO_NUM_NC)

Application

gfx.c

Pixel rendering

+ snapshot to SD

main.c

Recovery menu

OTA + SD flash

Touch navigation

factory_log.h

Conditional log gate


No

Yes

Yes

No

No

Yes

Power on

via ESP444 software trigger

app_main

factory_log_silence_sd_stack

Silence SD/FAT ESP-IDF tags

restore_otadata_from_backup

check 0xB000 for magic 0xAA55AA55

Backup found?

Skip restore

Read two OTA entries

from backup @ 0xB000

Both entries

all 0xFF?

Skip write

Erase otadata

@ 0x10000 + 0x11000

Write entries back

to otadata sectors

Erase backup sector

@ 0xB000

st7262_init

GPIO backlight + RGB panel + PSRAM FB

Init OK?

abort after 200 ms UART flush

st7262_backlight ON

gfx_init

buttons_init

encoder_init

touch_init

buzzer_init

Detect app1 partition

Build menu_items array

probe_sd_files

detect esp3dfw.bin + ui_resources.bin

draw_menu

Main event loop


All input sources are unified through dispatch_button(button_id_t), so physical buttons, encoder turns, and virtual touch buttons produce identical outcomes. On this board, physical buttons and encoder always return BTN_NONE / 0.

ENABLE_SNAPSHOT (optional)

CW → menu_move −1

CCW → menu_move +1

BTN_1/2/3

not pressed

BTN_NONE

vbtn

BTN_1

BTN_2

BTN_3

Loop tick

snapshot_check

GPIO0 debounce

encoder_read

PCNT pulses ÷ 4

button_wait_press

100 ms timeout

dispatch_button

touch_read

GT911 polling

touch_hint_hit_test

x/y → virtual BTN_1/2/3

draw_button_hint_pressed

buzzer_beep_short

80 ms delay

draw_button_hints

menu_move −1

← Up

menu_move +1

← Down

execute_selected_action


typedef struct {
const char *label; // Text shown in the menu list
menu_action_t action; // Action executed on BTN_3 / touch OK
uint16_t color; // Foreground color when not selected
} menu_item_t;
ActionShown whenDescription
MENU_ACTION_BOOT_APP0Alwaysesp_ota_set_boot_partition("app0") → esp_restart()
MENU_ACTION_BOOT_APP1app1 partition existsesp_ota_set_boot_partition("app1") → esp_restart()
MENU_ACTION_SD_UPDATE_APP0AlwaysFlash /sdcard/esp3dfw.bin to app0 via OTA API
MENU_ACTION_SD_UPDATE_APP1app1 existsFlash /sdcard/esp3dfw.bin to app1 via OTA API
MENU_ACTION_SD_UPDATE_RESAlwaysFlash /sdcard/ui_resources.bin to ui_resources partition

This board ships with only app0; the app1 branch is compiled in but the menu items for it are suppressed at runtime if the partition is absent.


This board uses no custom bootloader. The main firmware’s esp444.cpp writes the otadata backup before switching to the factory partition; restore_otadata_from_backup() runs as the very first operation in app_main().

See factory_update_actions_otadata.md for the general backup/restore design. The board-specific constants are:

ConstantValueConstraint
OTADATA_OFFSET0x10000Standard ESP32-S3 otadata location
OTADATA_SECTOR_SIZE0x1000 (4 KB)Two sectors (one per OTA slot)
OTADATA_BACKUP_OFFSET0xB000After bootloader end, before partition table (0xC000)
BACKUP_MAGIC_OFFSET0x40Offset within backup sector
BACKUP_MAGIC0xAA55AA55Sentinel value matching esp444.cpp exactly

⚠️ CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED required. The factory app writes to 0xB000, which is below the first partition. ESP-IDF’s default CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS would abort this write. The factory sdkconfig must enable CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED=y. This is intentional — the factory app is a privileged recovery tool.

Factory App\nmain.cSPI FlashMain Firmware\nesp444.cppFactory App\nmain.cSPI FlashMain Firmware\nesp444.cppFactory app boots (app_main)alt[Magic valid (0xAA55AA55)][No valid backup]Read otadata entries @ 0x10000Write entries to backup @ 0xB000Write magic 0xAA55AA55 @ 0xB040esp_ota_set_boot_partition(factory)esp_restart()Read magic @ 0xB040Read entry1, entry2 from 0xB000Erase otadata @ 0x10000 and 0x11000Write entries back to otadata sectorsErase backup sector @ 0xB000Skip restore - continue normallyProceed to display init and menu

See also factory_update_actions_sd_flash.md for the cross-board description of this pattern.

Not found

Fail

Fail

Invalid

Read/write error

All bytes written

Fail

action_sd_update

target_label

esp_partition_find_first

APP by label

show_status: Partition not found

sdcard_mount

show_status: No SD card

fopen /sdcard/esp3dfw.bin

show_status: No esp3dfw.bin on SD

fseek SEEK_END

check size ≤ partition size

show_status: Invalid firmware size

draw_flashing_screen

esp_ota_begin

Loop: fread 1 KB

esp_ota_write

draw_progress(%)

draw_result: FAILED

rename → esp3dfw.bad

sdcard_unmount

3 s

draw_menu

esp_ota_end

esp_ota_set_boot_partition

draw_result: Success

rename → esp3dfw.ok

sdcard_unmount

2 s

esp_restart


Not found

Fail

Fail

Invalid

Fail

Error

Done

action_sd_update_res

esp_partition_find_first

DATA: ui_resources

show_status: No ui_resources part

sdcard_mount

show_status: No SD card

fopen /sdcard/ui_resources.bin

show_status: No ui_resources.bin

Read 16-byte build header

log variant string if magic == 'ESP3'

Check file size ≤ partition size

show_status: Invalid res size

esp_partition_erase_range

full ui_resources partition

draw_result: FAILED

rename → ui_resources.bad

sdcard_unmount

3 s

draw_menu

Loop: fread 1 KB

esp_partition_write

draw_progress(%)

draw_result: Done

rename → ui_resources.ok

sdcard_unmount

2 s

esp_restart


Thin wrapper over the shared hardware/drivers_video_rgb/disp_st7262 component. See display_drivers_rgb.md for the shared driver reference.

How This Board Differs from SPI-Panel Boards

Section titled “How This Board Differs from SPI-Panel Boards”
AspectSPI boards (ST7796 / ILI9341 / ILI9485)This board — ST7262 RGB
FramebufferNone (pixels streamed via SPI DMA)PSRAM mandatory (fb_in_psram=true)
Draw APIlcd_panel_draw_bitmap → SPI transactionesp_lcd_panel_draw_bitmap → PSRAM copy
Byte orderBig-endian on wire (SPI convention)Native endian in PSRAM
BacklightOften PWM / LEDCSimple GPIO toggle (TFT_LED)
OrientationOften 90° rotated (portrait enclosures)Native landscape — DISP_ST7262_ORIENTATION_LANDSCAPE (pure passthrough, no swap_xy/mirror)

gfx.c writes pixels in big-endian RGB565 (shared convention across all boards). st7262_flush() must un-swap to native order for the PSRAM framebuffer:

for (size_t i = 0; i < count; i++) {
uint16_t v = data[i];
scratch[i] = (uint16_t)((v >> 8) | (v << 8)); // big-endian → native
}
esp_lcd_panel_draw_bitmap(s_panel, x0, y0, x1 + 1, y1 + 1, scratch);

A local scratch[SCREEN_WIDTH] buffer is used instead of in-place swapping because gfx_hline() / gfx_fill_rect() / gfx_clear() fill line_buf once and call gfx_flush() once per row with the same pointer — in-place swapping would produce alternating wrong-color rows on every other call.


A minimal, LVGL-free pixel renderer. See factory_graphics.md for the cross-board design description.

ENABLE_SNAPSHOT

Application

gfx_clear / gfx_fill_rect

gfx_draw_string / gfx_rect

gfx_hline / gfx_vline

gfx.c

Byte-swap color → big-endian

Fill static line_buf[SCREEN_WIDTH]

gfx_flush(x0,y0,x1,y1,data,count)

st7262_flush()

Un-swap → native endian

into scratch[SCREEN_WIDTH]

esp_lcd_panel_draw_bitmap

snap_write()

fseek to pixel offset

fwrite native RGB565

ST7262 Panel

PSRAM framebuffer

→ display

/sdcard/snap###.raw

OffsetSizeContent
04 BWidth in pixels (little-endian uint32)
44 BHeight in pixels (little-endian uint32)
8W × H × 2 BRaw RGB565 pixels, native endian, row-major

Convert to PNG with tools/images_converter/raw2png.py. See factory_snapshot.md for the complete snapshot design.


Wrapper over the shared hardware/common/drivers/touch_gt911 component. See factory_touch.md for the cross-board touch driver pattern and touch_controllers.md for the shared GT911 hardware driver.

The GT911’s internal x_max / y_max registers do not match the physical 800 × 480 panel resolution on this board (measured at approximately 468 × 253 on real hardware). Raw coordinates must be rescaled:

pt.x = (int)data.x * SCREEN_WIDTH / touch_gt911_get_x_max();
pt.y = (int)data.y * SCREEN_HEIGHT / touch_gt911_get_y_max();

This is identical to how the main firmware’s board_init.c handles the same controller (see esp32s3_8048s043c.md).

Three virtual button icons (up arrow / down arrow / check mark) are drawn in the bottom-bar area. touch_hint_hit_test() maps a raw touch coordinate to a button_id_t using column midpoints between the actual icon centers — not a simple screen-width ÷ 3 split, which would mismap the clustered icons on a wide 800 px canvas:

SCREEN_WIDTH = 800 px
BTN_HINT_CX1 = 400 − 100 = 300 (BTN_1 — Up)
BTN_HINT_CX2 = 400 (BTN_2 — Down)
BTN_HINT_CX3 = 400 + 100 = 500 (BTN_3 — OK)
boundary_1_2 = (300 + 400) / 2 = 350
boundary_2_3 = (400 + 500) / 2 = 450
x < 350 → BTN_1 (Up)
x < 450 → BTN_2 (Down)
x ≥ 450 → BTN_3 (OK)

y= 0 ┌────────────────────────────────────────────────────────────────────────────┐
│ ╔══════════════════════════════════════════════════════════════════════════╗ │
│ ║ Recovery <version> ← title, cyan ║ │
│ ╚══════════════════════════════════════════════════════════════════════════╝ │
y= 40 │────────────────────────────────────────────────────────────────────────────│ hline
y= 45 │ Active: <partition label> ← yellow │
y= 68 │ SD: FW (green) RES (cyan) ← right-aligned SD status │
y= 88 │────────────────────────────────────────────────────────────────────────────│ hline
y= 94 │ ┌──────────────────────────────────────────────────────────────────────┐ │ ← MENU_START_Y
│ │ ▶ Boot app0 ← highlight box (dark blue) + bright-blue text │ │ item 0, h=34
│ └──────────────────────────────────────────────────────────────────────┘ │
│ SD -> app0 ← green │ item 1
│ SD -> app1 ← green (if app1 exists) │ item 2
│ SD -> resources ← cyan │ item 3
│ │
│────────────────────────────────────────────────────────────────────────────│ STATUS_Y−7 hline
STATUS_Y │ Power off to cancel OR <status message> ← footer zone │
BTN_BASE │━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ separator
│ ⬆ blue (CX1=300) ⬇ blue (CX2=400) ✓ green (CX3=500) │ virtual btns
y=480 └────────────────────────────────────────────────────────────────────────────┘
ConstantValueDerivation
MENU_START_Y94Scaled ×0.85 from the 320×480 portrait reference boards
MENU_ITEM_H34Item row height (includes 2 px gap)
MENU_PAD_X20Horizontal margin
FONT_WIDTH12Monospace font column width (font12x24)
FONT_HEIGHT24Monospace font row height
BTN_CIRCLE_R30Virtual button circle radius
BTN_HINT_CX1/2/3300 / 400 / 500Icon centers (clustered around SCREEN_WIDTH/2 = 400)

The SD card uses SPI (not SDIO) with the ESP-IDF FAT-VFS layer, mounted at /sdcard. See factory_sdcard.md for the cross-board pattern.

FileStatus after successful flashStatus after failed flash
/sdcard/esp3dfw.binRenamed to esp3dfw.okRenamed to esp3dfw.bad
/sdcard/ui_resources.binRenamed to ui_resources.okRenamed to ui_resources.bad
/sdcard/snap###.rawCreated by snapshot feature—

The SPI bus is initialized once and never freed between mount/unmount cycles to avoid interference with other SPI peripherals (pattern shared across all factory SD drivers).


All three peripheral drivers follow the same guard pattern — they check for GPIO_NUM_NC and become no-ops on this board. The code is identical to other boards so it works unchanged if these parts are ever populated.

See factory_buttons.md, factory_encoder.md, and factory_buzzer.md for the cross-board driver designs.

DriverPinBehavior on this board
buttons.c — buttons_init()BUTTON_1/2/3_PIN = GPIO_NUM_NCpin_mask == 0 → early return; button_wait_press() always returns BTN_NONE after timeout
encoder.c — encoder_init()ENCODER_A/B_PIN = GPIO_NUM_NC!GPIO_IS_VALID_GPIO() → skip PCNT; encoder_read() always returns 0
buzzer.c — buzzer_beep_short()BUZZER_PIN = GPIO_NUM_NC!GPIO_IS_VALID_GPIO() → early return

An optional screen-capture mechanism triggered by pressing the BOOT button (GPIO_NUM_0). See factory_snapshot.md for the complete design.

No

Yes

No

Yes

Yes

No

snapshot_check

GPIO0 == 0?

return

50 ms debounce

Still pressed?

sdcard_mount

snap_find_next_number

first snap###.raw not on SD

gfx_snapshot_begin

create file, write header,

pre-fill zeros

s_snap_in_flash?

draw_flashing_screen

draw_progress last %

draw_menu

show_status if pending

gfx_snapshot_end

close file

Wait for GPIO0 release + 50 ms

snapshot_check() is called:

  • On each main loop tick
  • At the start of action_sd_update() (before OTA begin)
  • On each progress update during firmware / resources flashing
  • After draw_result() completes

See factory_logging.md for the cross-board logging design. This board follows the standard pattern.

MacroActive whenMaps to
ESP_LOGE / ESP_LOGWAlwaysESP-IDF error / warning
FACTORY_LOGD(tag, fmt, ...)FACTORY_LOG_LEVEL != 0ESP_LOGI
FACTORY_LOGD(tag, fmt, ...)FACTORY_LOG_LEVEL == 0No-op

factory_log_silence_sd_stack() mutes the following ESP-IDF tags at runtime: sdmmc, vfs_fat_sdmmc, sdmmc_periph, sdmmc_req, sdmmc_common, fatfs, sdspi, sd_diskio.


CMake optionEffect
ENABLE_FACTORY_DEBUG_LOGFACTORY_LOG_LEVEL=1 → FACTORY_LOGD active; sdkconfig log ceiling raised to INFO
ENABLE_SNAPSHOTCompiles GPIO0 snapshot trigger and gfx_snapshot_begin/end/snap_write
ENABLE_CUSTOM_BOOT_LOADERNot set on this board — no bootloader hooks

When ENABLE_FACTORY_DEBUG_LOG is OFF, sdkconfig.prod_log is applied on top of the base sdkconfig, silencing ESP-IDF startup logs emitted before app_main().


ESP-IDF OTAsdcard.cst7262.cgfx.ctouch.c / GT911buttons.cencoder.cMain LoopESP-IDF OTAsdcard.cst7262.cgfx.ctouch.c / GT911buttons.cencoder.cMain Looploop[per 1 KB chunk]encoder_read() → 0 (no encoder)button_wait_press(100 ms) → BTN_NONEtouch_read(){pressed=true, x=410, y=440}touch_hint_hit_test(410, 440) → BTN_3draw_button_hint_pressed(BTN_3)st7262_flush(pixels)execute_selected_action → action_sd_update("app0")sdcard_mount()ESP_OKdraw_flashing_screen()fread(buf, 1, 1024, fw_file)esp_ota_write(handle, buf, len)draw_progress(percent)st7262_flush(progress bar pixels)esp_ota_end()esp_ota_set_boot_partition("app0")draw_result(true, "Success! Rebooting...")sdcard_unmount()vTaskDelay(2000 ms) → esp_restart()
PropertyValue
MCUESP32-S3
Display controllerST7262 RGB parallel
Panel resolution800 × 480 (physical landscape, no rotation)
Data bus16-bit RGB parallel
FramebufferPSRAM, 1 frame (fb_in_psram=true)
Orientation settingDISP_ST7262_ORIENTATION_LANDSCAPE (passthrough)
Touch controllerGT911
Touch busI2C
Touch coordinate scalingRequired (GT911 reports ~468 × 253, not 800 × 480)
Physical buttonsNone — GPIO_NUM_NC
Rotary encoderNone — GPIO_NUM_NC
BuzzerNone — GPIO_NUM_NC
SD card interfaceSPI
Recovery entrySoftware only (ESP444 from main firmware)
Custom bootloaderNot used
Snapshot triggerGPIO0 (BOOT button) when ENABLE_SNAPSHOT

DocumentRelationship
esp32s3_8048s043c.mdBoard overview; main firmware BSP for this board
Factory_Application_&_Bootloader.mdTop-level factory app overview across all boards
factory_app_core.mdCore factory app design (menu, OTA, shared patterns)
factory_graphics.mdCross-board gfx.c graphics layer design
factory_touch.mdCross-board touch driver pattern
factory_sdcard.mdCross-board SD card driver pattern
factory_buttons.mdCross-board button driver pattern
factory_encoder.mdCross-board encoder driver pattern
factory_buzzer.mdCross-board buzzer driver pattern
factory_snapshot.mdSnapshot feature design
factory_logging.mdfactory_log.h conditional logging
factory_update_actions_otadata.mdOTA data backup/restore design
factory_update_actions_sd_flash.mdSD firmware flash flow design
display_drivers_rgb.mdShared disp_st7262 hardware driver
touch_controllers.mdShared touch_gt911 hardware driver
bus_drivers.mdShared bus_i2c used in touch.c
esp32s3_4827s043c_factory.mdSimilar ESP32-S3 factory app (RGB panel, GT911)
esp32s3_8048_touch_lcd_7_factory.mdSimilar ESP32-S3 factory app (RGB panel, GT911, 7-inch)