Skip to content

esp32s3_8048s070c_factory_app_storage

The esp32s3_8048s070c_factory_app_storage module is the SD card storage layer for the Factory recovery application on the ESP32-S3 8048S070C board (800×480, 7.0-inch capacitive-touch panel). It wraps ESP-IDF’s VFS FAT over SDSPI into a minimal two-function API — sdcard_mount / sdcard_unmount — that the factory application’s main logic calls whenever it needs to read a firmware binary or UI-resources image from an SD card.

This module is intentionally narrow in scope. It owns only the SPI bus configuration, card handle lifecycle, and VFS registration. File I/O, OTA flashing, and progress reporting are handled exclusively by the parent module esp32s3_8048s070c_factory_app_main.


Module Position within the Factory Application

Section titled “Module Position within the Factory Application”

The factory application is structured as five sibling sub-modules under the parent esp32s3_8048s070c_factory_app. The storage module is the only one concerned with persistent mass storage:

esp32s3_8048s070c_factory_app

esp32s3_8048s070c_factory_app_main

(main.c - orchestration, OTA, menu)

esp32s3_8048s070c_factory_app_display

(gfx.c - framebuffer, draw primitives)

esp32s3_8048s070c_factory_app_input

(buttons, encoder, touch, buzzer)

esp32s3_8048s070c_factory_app_storage

(sdcard.c - SPI SD mount/unmount) ◀ current module

esp32s3_8048s070c_factory_app_tools

(flash_all.py, flash_factory.py, …)

The BSP layer used by the main firmware (LVGL task, touch, display flush) lives in the sibling module esp32s3_8048s070c_bsp; that code is not active during factory app execution.


FileRole
boards/esp32s3_8048s070c/Factory/main/sdcard.cImplementation — SPI bus init, VFS FAT mount, card handle lifecycle
boards/esp32s3_8048s070c/Factory/main/sdcard.hPublic header — declares sdcard_mount() and sdcard_unmount()
boards/esp32s3_8048s070c/Factory/main/hw_config.hPin and bus constants consumed by sdcard.c
boards/esp32s3_8048s070c/Factory/main/factory_log.hFACTORY_LOGD macro + factory_log_silence_sd_stack()

All SD-related constants are defined in hw_config.h and consumed at compile time by sdcard.c. No runtime pin selection occurs.

SignalGPIODirection
SD_MOSIGPIO 11Host → Card
SD_MISOGPIO 13Card → Host
SD_CLKGPIO 12Clock
SD_CSGPIO 10Chip Select (active low)
ConstantValueDescription
SD_SPI_HOSTSPI2_HOSTESP-IDF SPI peripheral identifier
SD_FREQ_KHZ20000 (20 MHz)Maximum SPI clock for SD transfers
SD_MOUNT_POINT"/sdcard"VFS mount path for POSIX file access

SPI bus sharing note: The SPI bus (SPI2_HOST) is dedicated exclusively to the SD card within the factory app. The 7-inch panel uses a 16-bit parallel RGB interface (EK9716 driver), not SPI — so there is no bus conflict between the display and SD card on this board. See esp32s3_8048s070c_factory_app_display for the EK9716 parallel bus configuration.


ESP-IDF Drivers

esp32s3_8048s070c_factory_app_storage

sdcard_mount()

sdcard_unmount()

compile-time constants

Factory App Main

(main.c)

sdcard_mount() / sdcard_unmount()

Static State

card: sdmmc_card_t*

spi_bus_inited: bool

driver/sdspi_host

(SPI↔SD protocol)

driver/spi_common

(SPI2_HOST bus)

esp_vfs_fat

(FAT over SD → POSIX VFS)

sdmmc_cmd

(card info / sdmmc_card_t)

SD Card Hardware

GPIO 10/11/12/13

hw_config.h

Pin + frequency constants


esp_err_t sdcard_mount(void);

Mounts the SD card at the VFS path /sdcard via SPI.

Behavior:

  • If the card is already mounted (card != NULL), returns ESP_OK immediately — idempotent, safe to call multiple times.
  • If the SPI bus (SPI2_HOST) has not yet been initialized for this session, calls spi_bus_initialize() with the pin constants from hw_config.h and sets the internal spi_bus_inited flag.
  • Configures the SDSPI device slot with SD_CS and SD_SPI_HOST.
  • Configures the host with SD_FREQ_KHZ (20 MHz max).
  • Mounts via esp_vfs_fat_sdspi_mount() with:
    • format_if_mount_failed = false — the factory app never formats an SD card.
    • max_files = 4 — conservative limit; only one file is ever open at a time during flash operations.
    • disk_status_check_enable = true — enables card-present detection.
  • On success, logs the card information (only when FACTORY_LOG_LEVEL is set).

Returns: ESP_OK on success, or the esp_err_t code from the failing ESP-IDF call.


void sdcard_unmount(void);

Unmounts the SD card from the VFS.

Behavior:

  • Calls esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, card) and sets the card handle to NULL.
  • Does NOT de-initialize the SPI bus. The bus handle is intentionally kept alive across mount/unmount cycles to prevent interference with other SPI peripherals. See Design Decisions below.

The module uses two file-scoped (static) variables:

VariableTypeInitial ValueDescription
cardsdmmc_card_t *NULLHandle to the mounted SD card. NULL = not mounted.
spi_bus_initedboolfalseTracks whether spi_bus_initialize() has been called. Prevents re-initialization on subsequent mount calls.

Data Flow: Mount and File Operation Sequence

Section titled “Data Flow: Mount and File Operation Sequence”

This diagram shows the complete flow from a factory-app menu action through this module to the SD card hardware and back up to the OTA write path:

SD Card HWESP-IDF VFS FATSPI Driver(SPI2_HOST)sdcard.c(this module)main.c(Factory App Main)SD Card HWESP-IDF VFS FATSPI Driver(SPI2_HOST)sdcard.c(this module)main.c(Factory App Main)User selects 'SD → app0'alt[card already mounted][first mount]loop[Read 1 KB chunks]SPI bus kept alive (spi_bus_inited=true)sdcard_mount()ESP_OK (immediate return)spi_bus_initialize(SPI2_HOST, bus_cfg)ESP_OKesp_vfs_fat_sdspi_mount("/sdcard", ...)SPI init + CMD0/CMD8/ACMD41 sequenceCard readyESP_OK + sdmmc_card_t*ESP_OKfopen("/sdcard/esp3dfw.bin", "rb")FILE*fread(buf, 1, 1024, fw_file)SPI readdatabytes readesp_ota_write(ota_handle, buf, len)draw_progress(percent)fclose(fw_file)sdcard_unmount()esp_vfs_fat_sdcard_unmount("/sdcard", card)donecard = NULL(void)

Power on / reset

spi_bus_initialize() OK\n(spi_bus_inited = true)

spi_bus_initialize() FAIL\n(returns error to caller)

esp_vfs_fat_sdspi_mount() OK\n(card != NULL)

mount FAIL\n(card stays NULL, returns error)

sdcard_mount() called again\n(idempotent, returns ESP_OK)

sdcard_unmount()\n(card = NULL, bus stays alive)

Uninitialized

BusReady

Mounted

SPI bus initialized but

no card registered with VFS.

Re-mount skips bus init.

/sdcard accessible via

standard POSIX calls

(fopen, fread, rename, remove).


Integration: How the Factory App Main Uses This Module

Section titled “Integration: How the Factory App Main Uses This Module”

The factory app main (main.c) calls this module in three distinct contexts:

Called once at boot to detect which update files are present on the SD card. The result drives the menu item color coding (green = firmware available, cyan = resources available):

app_main()
└─ probe_sd_files()
├─ sdcard_mount() ← storage module
├─ fopen(FW_FILENAME) → sd_has_fw = true/false
├─ fopen(RES_FILENAME) → sd_has_res = true/false
└─ sdcard_unmount() ← storage module

Mounts the card, opens esp3dfw.bin, streams it in 1 KB chunks through esp_ota_write, then unmounts. On success the source file is renamed to esp3dfw.ok; on failure to esp3dfw.bad:

action_sd_update(target_label)
├─ sdcard_mount() ← storage module
├─ fopen("/sdcard/esp3dfw.bin")
├─ [OTA write loop with progress]
├─ fclose()
├─ rename(FW_FILENAME → FW_OK_FILENAME or FW_BAD_FILENAME)
└─ sdcard_unmount() ← storage module

3. UI Resources Flash (action_sd_update_res)

Section titled “3. UI Resources Flash (action_sd_update_res)”

Mounts the card, validates the ui_resources.bin build header (4-byte "ESP3" magic + 12-char variant tag), erases the ui_resources flash partition, and writes the image directly. The file is renamed to ui_resources.ok or ui_resources.bad on completion:

action_sd_update_res()
├─ sdcard_mount() ← storage module
├─ fopen("/sdcard/ui_resources.bin")
├─ fread(16-byte header) → variant validation
├─ esp_partition_erase_range(ui_resources)
├─ [partition write loop with progress]
├─ fclose()
├─ rename(RES_FILENAME → RES_OK_FILENAME or RES_BAD_FILENAME)
└─ sdcard_unmount() ← storage module
Filename on SDRole
esp3dfw.binFirmware image to flash (input)
esp3dfw.okRenamed after successful firmware flash
esp3dfw.badRenamed after failed firmware flash
ui_resources.binUI resources image to flash (input)
ui_resources.okRenamed after successful resources flash
ui_resources.badRenamed after failed resources flash

The module uses two logging mechanisms:

Macro / FunctionSourceActive when
FACTORY_LOGD(TAG, ...)factory_log.hFACTORY_LOG_LEVEL > 0 (compile-time)
ESP_LOGE(TAG, ...)ESP-IDF esp_log.hAlways (errors are never suppressed)
sdmmc_card_print_info(stdout, card)sdmmc_cmd.hFACTORY_LOG_LEVEL > 0

At app_main() startup, factory_log_silence_sd_stack() sets all SD-related ESP-IDF log tags (sdmmc, vfs_fat_sdmmc, sdmmc_periph, sdmmc_req, sdmmc_common, fatfs, sdspi, sd_diskio) to ESP_LOG_NONE. This prevents the ESP-IDF SD stack from flooding the UART console during normal recovery menu operation. Errors from this module still surface because ESP_LOGE bypasses per-tag suppression at the ERROR severity level.


sdcard_unmount() intentionally omits a spi_bus_free() call. The comment in the source reads:

“Keep SPI bus initialized — freeing it can interfere with TFT SPI. The bus will be reused on next mount.”

On this specific board the TFT uses a 16-bit parallel RGB bus (not SPI), so the conflict risk is lower than on SPI-panel boards. However, the deliberate choice to not free the bus is maintained for two reasons:

  1. Consistency across all boards in the BSP family — the same sdcard.c pattern appears across pibot_pendant_v1_0, esp32s3_8048s043c, esp32s3_8048s050c, and others.
  2. Safety — future board variants or additional SPI peripherals cannot inadvertently be disrupted by a bus teardown.

The practical benefit is that repeated mount → unmount → mount cycles (e.g., probe at boot, then flash later) avoid the overhead of re-calling spi_bus_initialize().

The factory app must never silently format the operator’s SD card. If mount fails (card absent, wrong format, filesystem corruption), an error is returned to the caller and displayed on-screen. The operator is expected to prepare the SD card externally.

Conservative limit. Only one file is open at any given time during factory operations. The headroom guards against unexpected ESP-IDF internal file descriptor allocation.

sdcard_mount() checks card != NULL before doing any work. This allows callers to call mount unconditionally without bookkeeping, and makes the API safe under any future refactoring that might invoke mount from multiple code paths.


An identical storage module pattern is used by other boards in this BSP family. The sibling module in pibot_pendant_v1_0 is documented at pibot_pendant_v1_0_factory_app_storage. The only differences across boards are the pin constants in each board’s hw_config.h and the SPI host identifier — the sdcard.c logic itself is functionally identical across all boards.


DocumentRelevance
esp32s3_8048s070c_factory_app_mainMain orchestrator — calls sdcard_mount/sdcard_unmount, owns OTA and partition-write logic
esp32s3_8048s070c_factory_app_displayGFX layer — draws flash progress bars during SD update operations
esp32s3_8048s070c_factory_app_inputInput layer — provides button/encoder/touch events that trigger SD actions
esp32s3_8048s070c_factory_app_toolsPC-side flash scripts for initial factory programming
esp32s3_8048s070c_bspBSP layer — used by main firmware only, not active during factory app
pibot_pendant_v1_0_factory_app_storageCounterpart on the PiBot Pendant board (same pattern, different pins)
docs/Factory/Factory app and bootloader architecture overview
docs/ui_resources/development.mdui_resources partition binary format validated during action_sd_update_res