Skip to content

Factory SD Card Module (`factory_sdcard`)

The factory_sdcard module provides SD card mount and unmount operations for the Factory Application. It is the sole storage access layer used during factory-mode firmware updates — the factory app mounts the card, reads binary files from it (firmware, UI resources), flashes them to the appropriate partitions, then unmounts when done.

Every supported board provides its own sdcard.c, but all implementations are functionally identical: SPI-based access via ESP-IDF’s esp_vfs_fat, with board-specific pin numbers injected through hw_config.h.


  1. Architecture Overview
  2. Module Position in the Factory App
  3. Supported Boards
  4. Public API
  5. Internal State
  6. Mount Process Flow
  7. SPI Bus Sharing Design
  8. Configuration Reference (hw_config.h)
  9. Logging
  10. Dependencies
  11. Related Modules

Hardware

ESP-IDF Stack

Factory Application

sdcard_mount / sdcard_unmount

draw_progress / draw_result

factory_core / main.c

app_main

factory_update_actions

action_sd_update

action_sd_update_res

probe_sd_files

factory_sdcard

sdcard_mount / sdcard_unmount

factory_graphics

gfx_*

driver/sdspi_host

SDSPI_HOST_DEFAULT

driver/spi_common

spi_bus_initialize

esp_vfs_fat

esp_vfs_fat_sdspi_mount

esp_vfs_fat_sdcard_unmount

sdmmc_cmd

sdmmc_card_t

SPI Bus

MOSI / MISO / CLK

CS Pin

SD Card

FAT Filesystem


The factory application runs as a standalone ESP-IDF app (separate from the main pendant firmware). factory_sdcard sits at the bottom of the storage call stack — it has no knowledge of what files are read; that responsibility belongs to factory_update_actions.

sdcard_mount()

action_sd_update / probe_sd_files

sdcard_unmount()

SD_MOUNT_POINT e.g. /sdcard

app_main

factory_menu_system

factory_input_dispatch

factory_update_actions

factory_sdcard

Flash partitions via esp_ota_*

factory_sdcard is called by factory_update_actions for three operations:

Caller functionfactory_sdcard call
probe_sd_filessdcard_mount() → scan files → sdcard_unmount()
action_sd_updatesdcard_mount() → stream firmware binary → sdcard_unmount()
action_sd_update_ressdcard_mount() → stream resources binary → sdcard_unmount()

All boards listed below use the identical sdcard_mount / sdcard_unmount implementation. Pin assignments differ and are controlled entirely through each board’s hw_config.h.

BoardFile path
esp32_2432s028rboards/esp32_2432s028r/Factory/main/sdcard.c
esp32_3248s035cboards/esp32_3248s035c/Factory/main/sdcard.c
esp32_3248s035rboards/esp32_3248s035r/Factory/main/sdcard.c
esp32s3_4827s043cboards/esp32s3_4827s043c/Factory/main/sdcard.c
esp32s3_8048_touch_lcd_7boards/esp32s3_8048_touch_lcd_7/Factory/main/sdcard.c
esp32s3_8048s043cboards/esp32s3_8048s043c/Factory/main/sdcard.c
esp32s3_8048s050cboards/esp32s3_8048s050c/Factory/main/sdcard.c
esp32s3_8048s070cboards/esp32s3_8048s070c/Factory/main/sdcard.c
esp32s3_bzm_tft35_gt911boards/esp32s3_bzm_tft35_gt911/Factory/main/sdcard.c
esp32s3_hmi43v3boards/esp32s3_hmi43v3/Factory/main/sdcard.c
esp32s3_zx3d50ce02s_usrc_4832boards/esp32s3_zx3d50ce02s_usrc_4832/Factory/main/sdcard.c
pibot_pendant_v1_0boards/pibot_pendant_v1_0/Factory/main/sdcard.c

esp_err_t sdcard_mount(void);

Mounts the SD card over the FAT VFS at SD_MOUNT_POINT.

Behavior:

  • Idempotent: returns ESP_OK immediately if the card is already mounted (card != NULL).
  • Initializes the SPI bus on the first call only (guarded by spi_bus_inited).
  • Mounts using esp_vfs_fat_sdspi_mount() with format_if_mount_failed = false — the card is never reformatted by the factory app.
  • Enables disk status checking (disk_status_check_enable = true).
  • Allows up to 4 simultaneously open files.
  • Prints card info to stdout when FACTORY_LOG_LEVEL is non-zero.

Returns:

ValueMeaning
ESP_OKCard successfully mounted (or was already mounted)
ESP_ERR_* (from spi_bus_initialize)SPI bus initialization failed
ESP_ERR_* (from esp_vfs_fat_sdspi_mount)Mount failed (card absent, format error, etc.)

Callers may call sdcard_mount() multiple times safely — duplicate calls are no-ops.


void sdcard_unmount(void);

Unmounts the SD card from the VFS. The SPI bus peripheral is intentionally left initialized after unmount (see SPI Bus Sharing Design).

Behavior:

  • No-op if card == NULL (not currently mounted).
  • Calls esp_vfs_fat_sdcard_unmount(SD_MOUNT_POINT, card).
  • Resets card to NULL.
  • Does not call spi_bus_free().

The module uses two file-scope static variables, making the implementation non-reentrant (acceptable — the factory app is single-tasked):

static sdmmc_card_t *card = NULL; // NULL = not mounted
static bool spi_bus_inited = false; // true once spi_bus_initialize() succeeds

power-on

sdcard_mount()\n[spi_bus_inited == false]

esp_vfs_fat_sdspi_mount() OK

spi_bus_initialize() FAIL

sdcard_mount()\n[spi_bus_inited == true]

sdcard_mount()\n[card != NULL - no-op]

sdcard_unmount()

sdcard_unmount()\n[card == NULL - no-op]

Unmounted

BusInit

Mounted

spi_bus_inited stays true

even after unmount


Yes

No

No

No

Yes

Yes

No

Yes

Yes

No

sdcard_mount called

card != NULL?

return ESP_OK

already mounted

spi_bus_inited?

spi_bus_initialize(SD_SPI_HOST, bus_cfg, SPI_DMA_CH_AUTO)

ret == ESP_OK?

return ret

SPI init failed

spi_bus_inited = true

esp_vfs_fat_sdspi_mount(SD_MOUNT_POINT, &host, &slot_config, &mount_config, &card)

ret == ESP_OK?

return ret

Mount failed

FACTORY_LOG_LEVEL != 0?

sdmmc_card_print_info stdout

return ESP_OK


Many boards share the same SPI bus between the TFT display driver and the SD card. The factory app initialises the display before ever accessing the SD card.

Key constraint: calling spi_bus_free() after unmounting the SD card would destroy the shared SPI bus, causing the display driver to malfunction for any subsequent redraws (progress bars, result screens, etc.).

Solution: sdcard_unmount() never calls spi_bus_free(). The spi_bus_inited flag ensures the bus is only initialized once per power cycle. The bus stays alive for the entire lifetime of the factory app.

SPI Bus Hardwarefactory_sdcardLCD Driver (SPI)Factory App (main)SPI Bus Hardwarefactory_sdcardLCD Driver (SPI)Factory App (main)spi_bus_free() intentionally NOT calledLCD still functional - bus intactlcd_init() - acquires SPI bussdcard_mount()spi_bus_initialize() [first call only]ESP_OK[reads firmware files over FAT VFS]sdcard_unmount()donedraw_progress(), draw_result()

Note: This design is specific to the factory app context. The main pendant firmware manages SD card sharing through a different arbitration mechanism — see shared_sd_mechanism_V2.0.md.


Every board provides a hw_config.h defining the constants consumed by sdcard.c. All of the following symbols must be present:

SymbolTypeDescription
SD_MOSIint / gpio_num_tSPI MOSI GPIO number
SD_MISOint / gpio_num_tSPI MISO GPIO number
SD_CLKint / gpio_num_tSPI clock GPIO number
SD_CSint / gpio_num_tSD card chip-select GPIO number
SD_SPI_HOSTspi_host_device_tSPI peripheral (e.g., SPI2_HOST, SPI3_HOST)
SD_FREQ_KHZintMaximum SPI clock frequency in kHz (e.g., 4000)
SD_MOUNT_POINTconst char *VFS mount path string (e.g., "/sdcard")

These are the only board-specific differences — the sdcard.c body is completely board-agnostic.


Macro / callActive whenPurpose
FACTORY_LOGD(TAG, ...)FACTORY_LOG_LEVEL != 0Debug messages: mount attempt, success, unmount confirmation. Silenced in production builds by factory_log_silence_sd_stack.
ESP_LOGE(TAG, ...)AlwaysHard error: SPI bus init failure — always visible regardless of FACTORY_LOG_LEVEL.
ESP_LOGW(TAG, ...)AlwaysWarning: mount failure (card absent, filesystem error, etc.).
sdmmc_card_print_info(stdout, card)#if FACTORY_LOG_LEVELPrints card capacity, speed class and CID on successful mount.

See factory_logging for how FACTORY_LOGD and factory_log_silence_sd_stack are defined and how they suppress the verbose ESP-IDF SD stack output during normal factory operation.


Factory App

Board Layer

ESP-IDF

factory_sdcard

sdcard.c

sdcard_mount()

sdcard_unmount()

esp_vfs_fat

esp_vfs_fat_sdspi_mount

esp_vfs_fat_sdcard_unmount

driver/sdspi_host

sdspi_device_config_t

SDSPI_HOST_DEFAULT

driver/spi_common

spi_bus_initialize

spi_bus_config_t

sdmmc_cmd

sdmmc_card_t

sdmmc_card_print_info

esp_log

ESP_LOGE / ESP_LOGW

hw_config.h

SD_MOSI, SD_MISO, SD_CLK

SD_CS, SD_SPI_HOST

SD_FREQ_KHZ, SD_MOUNT_POINT

factory_log.h

FACTORY_LOGD

FACTORY_LOG_LEVEL

ESP-IDF APIs used:

APIHeaderPurpose
spi_bus_initialize()driver/spi_common.hInitialize SPI bus peripheral (once per power cycle)
SDSPI_DEVICE_CONFIG_DEFAULT()driver/sdspi_host.hInitialize SD SPI slot config to safe defaults
SDSPI_HOST_DEFAULT()driver/sdspi_host.hInitialize SDSPI host config to safe defaults
esp_vfs_fat_sdspi_mount()esp_vfs_fat.hMount FAT filesystem over SPI into the VFS
esp_vfs_fat_sdcard_unmount()esp_vfs_fat.hGracefully unmount FAT filesystem from VFS
sdmmc_card_print_info()sdmmc_cmd.hPrint card info to stdout (debug builds only)

ModuleRelationship
factory_update_actionsPrimary caller — wraps every SD-based flash operation with sdcard_mount() / sdcard_unmount()
factory_loggingProvides FACTORY_LOGD macro and factory_log_silence_sd_stack used inside sdcard.c
factory_lcd_driversShares the SPI bus on many boards — the reason spi_bus_free() is never called on unmount
factory_coreTop-level factory app entry; drives the overall mount → flash → unmount lifecycle
bsp_bus_driversMain firmware SPI/I2C bus abstraction — not used by the factory app; factory manages SPI directly
filesystem (main firmware)Main firmware SD abstraction (esp3d_sd.h, esp_sd_spi.cpp) — different code path and lifecycle; see shared_sd_mechanism_V2.0.md for coexistence details