Hi Bruno,
A few code snippets.
1.Main routine setup
void app_main(void)
{
// Enable 3.3v GPIO configuration
ESP_LOGI(TAG, "Setting LDO_V04 to 3.3v");
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_cfg, &ldo_handle));
ESP_LOGI(TAG, "Set LDO_V04 to 3.3v OK.");
// Configure timer
const esp_timer_create_args_t timer_args = {
.callback = &timer_callback,
.name = "periodic_timer"};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle));
ESP_LOGI(TAG, "Timer created OK.");
init_file_list();
num_files = 0;
if (Mount_SD() == ESP_OK) List_SD_Files();
// Initialize LVGL and LCD
lvgl_port_cfg_t lvgl_port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_port_cfg.task_priority = 5; // Lower it to a mid-tier priority (Default is often high, like 22)
lvgl_port_cfg.task_stack = 12288; // Allocate safe stack memory
lvgl_port_cfg.task_affinity = 1;
lvgl_port_init(&lvgl_port_cfg);
ESP_LOGI(TAG, "lvgl_port_init done");
esp_err_t err = init_lcd();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "LCD initialization failed; not running APP");
while (true)
vTaskDelay(pdMS_TO_TICKS(1000));
}
vTaskDelay(pdMS_TO_TICKS(500)); // Wait for half a second to ensure LCD is ready
The header file for my prototype SD routines:
#pragma once
#define MAX_FILES 20
#define MAX_FILENAME_LEN 100
#define MAX_MOVES 50
#define MAX_MOVE_LEN 20
typedef struct
{
char filename[MAX_FILENAME_LEN];
char moves[MAX_MOVES][MAX_MOVE_LEN];
} mvxyData;
void list_directory_recursive(char *dir_path, int indent_level);
void List_SD_Files();
void init_file_list();
void update_filename(int index, const char *new_name);
int Read_Moves (int);
void SD_Task (void *pvParameters);
esp_err_t Mount_SD();
void Unmount_SD();
extern mvxyData* SDfileData;
extern int num_files;
extern int num_moves;
extern const char* selectedFile;
And the routines themselves (I decided to use PSRAM for storage because some files can be quite big in future.
#include "sdkconfig.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
#include "esp_ldo_regulator.h" // Required for Waveshare P4 LDO power control
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include "SD_routines.h"
static const char *TAG = "SD_Routines";
#define MOUNT_POINT "/sdcard"
static char *mount_point = "/sdcard";
sdmmc_card_t *card;
int num_files = 0;
int num_moves = 0;
const char* selectedFile = NULL;
mvxyData* SDfileData;
esp_ldo_channel_handle_t ldo_sdio = NULL;
esp_ldo_channel_config_t ldo_sdio_config = {
.chan_id = 4, // GPIO / channel used for board power
.voltage_mv = 3300,
};
//Configure VFS (Virtual File System)
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024};
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
bool ends_with(const char *str, const char *ending)
{
printf("String %s looking for endng %s)\n", str, ending);
if (str == NULL || ending == NULL)
return false;
size_t str_len = strlen(str);
size_t ending_len = strlen(ending);
// If the ending is longer than the string, it can't end with it
if (ending_len > str_len)
return false;
// Compare the end of the string with the ending string
return (strncmp(str + str_len - ending_len, ending, ending_len) == 0);
}
void replace_char(char *str, char old_char, char new_char) {
while (*str) {
if (*str == old_char) {
*str = new_char;
}
str++; // Move to the next character pointer
}
}
void init_file_list()
{
SDfileData = (mvxyData*)heap_caps_malloc(sizeof(mvxyData) * MAX_FILES, MALLOC_CAP_SPIRAM);
//When necessary the PSRAM can be freed by:
//free(SDfileData);
}
// Update function now demands you tell it which list to modify
void update_filename(int index, const char *new_name)
{
if (index < 0 || index >= MAX_FILES)
{
ESP_LOGE(TAG, "SDfileData is null or index out of bounds: %d", index);
return;
}
// Safely copy the text into the specified list slot
strncpy(SDfileData[index].filename, new_name, MAX_FILENAME_LEN - 1);
SDfileData[index].filename[MAX_FILENAME_LEN - 1] = '\0';}
void list_directory_recursive(char *dir_path, int indent_level)
{
static const char *TAG = "sd_tree";
DIR *dir = opendir(dir_path);
if (dir == NULL)
{
ESP_LOGE(TAG, "Failed to open directory: %s", dir_path);
return;
}
struct dirent *entry;
char path[256];
struct stat entry_stat;
while ((entry = readdir(dir)) != NULL)
{
vTaskDelay(pdMS_TO_TICKS(100));
// Skip current directory "." and parent directory ".." pointers
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
snprintf(path, sizeof(path), "%s/%s", dir_path, entry->d_name);
// Get file/folder status metadata
if (stat(path, &entry_stat) == 0)
{
// Create a visual indentation anchor for readability
for (int i = 0; i < indent_level; i++)
{
printf(" ");
}
// Check if the current path is a directory
if (S_ISDIR(entry_stat.st_mode))
{
printf("📁 [%s]\n", entry->d_name);
// Recursively descend into the subfolder, increasing the indent
list_directory_recursive(path, indent_level + 1);
}
else
{
// It is a regular file; print name and size in bytes and add to list
// Only add .mvxy files to the list
printf("📄 %s (%ld bytes)\n", entry->d_name, entry_stat.st_size);
if (!ends_with(entry->d_name, ".mvxy"))
{
ESP_LOGI(TAG, "Skipping non .mvxy file: %s", entry->d_name);
}
else
{
ESP_LOGI(TAG, "Adding filename: %s to Array index %d", path, num_files);
update_filename(num_files, path);
int c = Read_Moves(num_files);
ESP_LOGI(TAG, "Added: %d moves", c);
num_files++;
}
}
}
}
closedir(dir);
}
int Read_Moves (int fn)
{
const char* mvfl = SDfileData[fn].filename;
num_moves = 0;
char line[MAX_MOVE_LEN];
const char *filePath = mvfl;
FILE *f = fopen(filePath, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Error opening file '%s': %s (Error code %d)\n",
filePath, strerror(errno), errno);
}
else
{
num_moves = 0;
while (fgets(line, sizeof(line), f) != NULL) {
// Remove the trailing newline character, if present
replace_char(line, '\t', ' ');
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
ESP_LOGI(TAG, "Read move coords: [%s]", line);
strncpy(SDfileData[fn].moves[num_moves], line, MAX_MOVE_LEN);
num_moves++;
strncpy(SDfileData[fn].moves[num_moves], "\0", MAX_MOVE_LEN);
}
fclose(f);
}
return num_moves;
}
void List_SD_Files()
{
ESP_LOGI(TAG, "Listing files");
ESP_LOGI(TAG, "Card info");
sdmmc_card_print_info(stdout, card);
// List folders and files
int level = 0;
list_directory_recursive(mount_point, level);
ESP_LOGI(TAG, "List of filename found on SD card:");
for (int i = 0; i < num_files; i++)
{
ESP_LOGI(TAG, "Filename[%d] = %s", i, SDfileData[i].filename);
}
strcpy(SDfileData[num_files].filename, "\0");
}
esp_err_t Mount_SD()
{
ESP_LOGI(TAG, "Initializing TF card...");
esp_ldo_acquire_channel(&ldo_sdio_config, &ldo_sdio);
host.max_freq_khz = 10000;
host.flags &= ~SDMMC_HOST_FLAG_DDR;
slot_config.width = 4; // 4-bit mode for fast access
esp_err_t ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. If the card is unformatted, set format_if_mount_failed = true.");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s).", esp_err_to_name(ret));
}
}
ESP_LOGI(TAG, "Card mounted successfully");
return ret;
}
void Unmount_SD()
{
// Unmount the card when done
esp_err_t err = esp_vfs_fat_sdcard_unmount(mount_point, card);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to unmount SD card (%s)", esp_err_to_name(err));
}
else
{
ESP_LOGI(TAG, "SD card unmounted successfully");
}
}
The tail end of MAIN. If I try to read any time after the port unlock the read fails.
lv_obj_add_flag(winfiles, LV_OBJ_FLAG_HIDDEN);
for (int i = 0; i < num_files; i++)
{
lv_table_set_cell_value(Filelist, i, 0, SDfileData[i].filename);
}
lvgl_port_unlock();
// int c = Read_Moves(0); //Only here to test if fs can be used while lvgl is running
// ESP_LOGI(TAG, "Read %d Moves", c);
}
else
{
ESP_LOGE(TAG, "LCD initialization failed | not running APP");
while (true) vTaskDelay(pdMS_TO_TICKS(1000));
}
while (true)
vTaskDelay(pdMS_TO_TICKS(100));
}
If I try to read a file after the port unlock I get this error:
E (3499) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107, failed to get status (0x107)
E (3499) sdmmc_cmd: sdmmc_read_sectors: error 0x107 reading blocks 40960+[0..0]
E (3504) diskio_sdmmc: sdmmc_read_blocks failed (0x107)
E (3509) SD_Routines: Error opening file '/sdcard/bracketNema8.mvxy': I/O error (Error code 5)
I (3517) STXYfs: Read 0 Moves
This is the monitor output:
I (28) boot: ESP-IDF v6.0.1 2nd stage bootloader
I (28) boot: compile time Jun 29 2026 20:10:15
I (28) boot: Multicore bootloader
I (30) boot: chip revision: v1.3
I (31) boot: efuse block revision: v0.3
I (35) qio_mode: Enabling default flash chip QIO
I (39) boot.esp32p4: SPI Speed : 40MHz
I (43) boot.esp32p4: SPI Mode : QIO
I (47) boot.esp32p4: SPI Flash Size : 16MB
I (51) boot: Enabling RNG early entropy source...
I (55) boot: Partition Table:
I (58) boot: ## Label Usage Type ST Offset Length
I (64) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (71) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (77) boot: 2 factory factory app 00 00 00010000 00a00000
I (84) boot: 3 storage Unknown data 01 82 00a10000 00500000
I (91) boot: End of partition table
I (94) esp_image: segment 0: paddr=00010020 vaddr=48090020 size=3c620h (247328) map
I (140) esp_image: segment 1: paddr=0004c648 vaddr=30100000 size=00068h ( 104) load
I (142) esp_image: segment 2: paddr=0004c6b8 vaddr=4ff00000 size=03960h ( 14688) load
I (148) esp_image: segment 3: paddr=00050020 vaddr=48000020 size=891e0h (561632) map
I (240) esp_image: segment 4: paddr=000d9208 vaddr=4ff03960 size=0d400h ( 54272) load
I (251) esp_image: segment 5: paddr=000e6610 vaddr=4ff10d80 size=03f4ch ( 16204) load
I (259) boot: Loaded app from partition at offset 0x10000
I (260) boot: Disabling RNG early entropy source...
I (271) hex_psram: vendor id : 0x0d (AP)
I (271) hex_psram: Latency : 0x01 (Fixed)
I (271) hex_psram: DriveStr. : 0x00 (25 Ohm)
I (272) hex_psram: dev id : 0x03 (generation 4)
I (277) hex_psram: density : 0x07 (256 Mbit)
I (281) hex_psram: good-die : 0x06 (Pass)
I (285) hex_psram: SRF : 0x02 (Slow Refresh)
I (290) hex_psram: BurstType : 0x00 ( Wrap)
I (294) hex_psram: BurstLen : 0x03 (2048 Byte)
I (299) hex_psram: BitMode : 0x01 (X16 Mode)
I (303) hex_psram: Readlatency : 0x04 (14 cycles@Fixed)
I (308) hex_psram: DriveStrength: 0x00 (1/1)
I (312) MSPI Timing: Enter psram timing tuning
I esp_psram: Found 32MB PSRAM device
I esp_psram: Speed: 200MHz
I (506) mmu_psram: .rodata xip on psram
I (550) mmu_psram: .text xip on psram
I (551) hex_psram: psram CS IO is dedicated
I (551) cpu_start: Multicore app
I (999) esp_psram: SPI SRAM memory test OK
I (1008) cpu_start: GPIO 38 and 37 are used as console UART I/O pins
I (1008) cpu_start: Pro cpu start user code
I (1009) cpu_start: cpu freq: 360000000 Hz
I (1011) app_init: Application information:
I (1015) app_init: Project name: STXYfs
I (1018) app_init: App version: 1
I (1022) app_init: Compile time: Jun 29 2026 20:08:56
I (1027) app_init: ELF file SHA256: 440af1623...
I (1031) app_init: ESP-IDF: v6.0.1
I (1035) efuse_init: Min chip rev: v0.0
I (1039) efuse_init: Max chip rev: v1.99
I (1043) efuse_init: Chip rev: v1.3
I (1047) heap_init: Initializing. RAM available for dynamic allocation:
I (1054) heap_init: At 4FF16C60 len 00024360 (144 KiB): RETENT_RAM
I (1060) heap_init: At 4FF3AFC0 len 00004BF0 (18 KiB): RAM
I (1065) heap_init: At 4FF40000 len 00040000 (256 KiB): RAM
I (1070) heap_init: At 30100068 len 00001F98 (7 KiB): SPM
I (1075) esp_psram: Adding pool of 31936K of PSRAM memory to heap allocator
I (1082) esp_psram: Adding pool of 27K of PSRAM memory gap generated due to end address alignment of irom to the heap allocator
I (1093) esp_psram: Adding pool of 14K of PSRAM memory gap generated due to end address alignment of drom to the heap allocator
I (1104) spi_flash: detected chip: gd
I (1108) spi_flash: flash io: qio
I (1111) sleep_gpio: Configure to isolate all GPIO pins in sleep state
I (1117) sleep_gpio: Enable automatic switching of GPIO sleep configuration
I (1124) main_task: Started on CPU0
I (1128) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations
I (1135) main_task: Calling app_main()
I (1138) STXYfs: Setting LDO_V04 to 3.3v
I (1142) STXYfs: Set LDO_V04 to 3.3v OK.
I (1146) STXYfs: Timer created OK.
I (1149) SD_Routines: Initializing TF card...
I (1356) SD_Routines: Card mounted successfully
I (1356) SD_Routines: Listing files
I (1356) SD_Routines: Card info
Name: SS08G
Type: SDHC
Speed: 10.00 MHz (limit: 10.00 MHz)
Size: 7580MB
CSD: ver=2, sector_size=512, capacity=15523840 read_bl_len=9
SSR: bus_width=4
📁 [System Volume Information]
📄 WPSettings.dat (12 bytes)
String WPSettings.dat looking for endng .mvxy)
I (1577) sd_tree: Skipping non .mvxy file: WPSettings.dat
📄 IndexerVolumeGuid (76 bytes)
String IndexerVolumeGuid looking for endng .mvxy)
I (1678) sd_tree: Skipping non .mvxy file: IndexerVolumeGuid
📄 bracketNema8.mvxy (50 bytes)
String bracketNema8.mvxy looking for endng .mvxy)
I (1783) sd_tree: Adding filename: /sdcard/bracketNema8.mvxy to Array index 0
] (1790) SD_Routines: Read move coords: [0.0 15.5
] (1790) SD_Routines: Read move coords: [-8.0 7.5
] (1795) SD_Routines: Read move coords: [8.0 7.5
] (1799) SD_Routines: Read move coords: [8.0 23.5
] (1804) SD_Routines: Read move coords: [-8.0 23.5
I (1809) sd_tree: Added: 5 moves
📄 ESP32-P4_NANO Dimensions.jpg (276575 bytes)
String ESP32-P4_NANO Dimensions.jpg looking for endng .mvxy)
I (1912) sd_tree: Skipping non .mvxy file: ESP32-P4_NANO Dimensions.jpg
📄 ESP32-P4_NANO pins.jpg (349785 bytes)
String ESP32-P4_NANO pins.jpg looking for endng .mvxy)
I (2017) sd_tree: Skipping non .mvxy file: ESP32-P4_NANO pins.jpg
📄 ESP32-P4_NANO.jpg (351315 bytes)
String ESP32-P4_NANO.jpg looking for endng .mvxy)
I (2128) sd_tree: Skipping non .mvxy file: ESP32-P4_NANO.jpg
📄 GearboxNema8.mvxy (34 bytes)
String GearboxNema8.mvxy looking for endng .mvxy)
I (2234) sd_tree: Adding filename: /sdcard/GearboxNema8.mvxy to Array index 1
] (2238) SD_Routines: Read move coords: [0.0 21.0
] (2242) SD_Routines: Read move coords: [20.5 13.50
] (2246) SD_Routines: Read move coords: [32.25 10.5
I (2251) sd_tree: Added: 3 moves
📄 TF Card example code.txt (4518 bytes)
String TF Card example code.txt looking for endng .mvxy)
I (2358) sd_tree: Skipping non .mvxy file: TF Card example code.txt
I (2362) SD_Routines: List of filename found on SD card:
I (2367) SD_Routines: Filename[0] = /sdcard/bracketNema8.mvxy
I (2372) SD_Routines: Filename[1] = /sdcard/GearboxNema8.mvxy
I (2378) STXYfs: lvgl_port_init done
I (2378) LVGL: Starting LVGL task
I (2381) esp_i2c_init: I2C initialized successfully on port 1 (SCL GPIO: 8, SDA GPIO: 7)
I (2392) esp_enable_dsi_phy_power: MIPI DSI PHY Powered on
W (2397) esp_i2c_init: I2C already initialized.
I (2403) esp_display_new_with_handles: Install (RPi 5inch) MIPI DSI LCD control panel
I (2448) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x1c
I (2769) esp_display_new_with_handles: Raspberry Pi 5inch Touch Display V2 initialized
W (2770) esp_i2c_init: I2C already initialized.
I (2770) lcd_touch_new: I2C bus handle: 0x480ce244
I (2774) GT911: I2C address initialization procedure skipped - using default GT9xx setup
I (2782) GT911: TouchPad_ID:0x39,0x31,0x31
I (2785) GT911: TouchPad_Config_Version:65
W (2797) esp_i2c_init: I2C already initialized.
I (2797) init_lcd: Raspberry Pi 5inch Touch Display V2 initialized successfully.
I (3301) STXYfs: Run application
I (3326) STXYfs: Set mode of stepper GPIO pins
I (3326) STXYfs: LVGL objects setup
I (3385) STXYfs: winfiles setup
I (3387) STXYfs: Filelist setup