Load pictures from SD card and display them on the screen through LVGL

Description

What MCU/Processor/Board and compiler are you using?

ESP32 ST7789 LVGL V7.11

What do you want to achieve?

Load pictures from SD card and display them on the screen through LVGL

What have you tried so far?

The file system was added according to the manual, but an error occurred during operation

Code to reproduce

Add the relevant code snippets here.

The code block(s) should be between ```c and ``` tags:

/*You code here*/

#include "sd_card_app.h"
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdspi_host.h"
#include "driver/spi_common.h"
#include "sdmmc_cmd.h"
#include "sdkconfig.h"

#include "esp_vfs_fat.h"

/* Littlevgl specific */
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif



#ifdef CONFIG_IDF_TARGET_ESP32
#include "driver/sdmmc_host.h"
#endif

// 这个例子可以使用 SDMMC 和 SPI 外设与 SD 卡通信。
// 默认情况下,使用 SDMMC 外设。
// 要启用 SPI 模式,取消注释以下行:

// #define USE_SPI_MODE

// ESP32-S2 没有 SD Host 外设,始终使用 SPI:
#ifdef CONFIG_IDF_TARGET_ESP32S2
#ifndef USE_SPI_MODE
#define USE_SPI_MODE
#endif // USE_SPI_MODE
// 在 ESP32-S2 上,DMA 通道必须与主机 ID 相同
#define SPI_DMA_CHAN    host.slot
#endif //CONFIG_IDF_TARGET_ESP32S2

// SPI 外设要使用的 DMA 通道
#ifndef SPI_DMA_CHAN
#define SPI_DMA_CHAN    1
#endif //SPI_DMA_CHAN

// 在测试 SD 和 SPI 模式时,请记住,一旦卡已
// SPI模式下初始化,SD模式下不可重新初始化
// 切换卡的电源。

#ifdef USE_SPI_MODE
// 使用 SPI 模式时的引脚映射。
// 通过这个映射,SD 卡可以在 SPI 和 1-line SD 模式下使用。
// 请注意,在 SD 模式下需要在 CS 线上进行上拉。
#define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 15
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   13
#endif //USE_SPI_MODE



static const char *TAG = "sd_card_app";
FATFS FatFs;   /*逻辑驱动器的工作区(文件系统对象) */



/**
 * Open a file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable
 * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
 * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
 */
static lv_fs_res_t my_open_cb (
	lv_fs_drv_t * drv,
	void * file_p,
	const char * path,
	lv_fs_mode_t mode
)
{
  	ESP_LOGI(TAG, "path (%s)\n", path);
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;

    if(mode == LV_FS_MODE_WR)
    {
        /*Open a file for write*/
    	// 调用FatFs的函数
      	FRESULT fres = f_open((FIL*)file_p, path, FA_OPEN_ALWAYS | FA_WRITE);
    	if (fres != FR_OK) {
    		ESP_LOGE(TAG, "f_open error (%d)\n", fres);
        	res = LV_FS_RES_NOT_IMP;
      	} else
        	res = LV_FS_RES_OK;
    }
    else if(mode == LV_FS_MODE_RD)
    {
        /*Open a file for read*/
      	FRESULT fres = f_open((FIL*)file_p, path, FA_OPEN_EXISTING | FA_READ);
    	if (fres != FR_OK) {
    		ESP_LOGE(TAG, "f_open error (%d)\n", fres);
        	res = LV_FS_RES_NOT_IMP;
      	} else
        	res = LV_FS_RES_OK;
    }
    else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
    {
        /*Open a file for read and write*/
      	FRESULT fres = f_open((FIL*)file_p, path, FA_WRITE | FA_READ);
    	if (fres != FR_OK) {
    		ESP_LOGE(TAG, "f_open error (%d)\n", fres);
        	res = LV_FS_RES_NOT_IMP;
      	} else
        	res = LV_FS_RES_OK;
    }
    return res;
}

/**
 * Close an opened file
 * @param drv pointer to a driver where this function belongs
 * @param file_p pointer to a file_t variable. (opened with lv_ufs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv_fs_res_t enum
 */
static lv_fs_res_t my_close_cb (lv_fs_drv_t * drv, void * file_p)
{
	 ESP_LOGI(TAG, "my_close_cb");

    if (f_close((FIL*)file_p) != FR_OK)
    	return LV_FS_RES_NOT_IMP;
  	else
    	return LV_FS_RES_OK;
}

/**
* 从打开的文件中读取数据
  * @param drv 指向此函数所属驱动程序的指针
  * @param file_p 指向 file_t 变量的指针。
  * @param buf 指向存储读取数据的内存块的指针
  * @param btr 要读取的字节数
  * @param br 实际读取字节数(Byte Read)
  * @return LV_FS_RES_OK: 没有错误,文件被读取
  * lv_fs_res_t 枚举中的任何错误
 */
 static lv_fs_res_t my_read_cb(
 	lv_fs_drv_t* drv,
 	void* file_p,
 	void* buf,
 	uint32_t btr,
 	uint32_t* br
 )
{
	 ESP_LOGI(TAG, "my_read_cb");
	FRESULT fres = f_read((FIL*)file_p, buf, btr, br);
  	if (fres != FR_OK) {
    	printf("f_read error (%d)\n", fres);
    	return LV_FS_RES_NOT_IMP;
  	} else
    	return LV_FS_RES_OK;
}

 /**
* 写入文件
   * @param drv 指向此函数所属驱动程序的指针
   * @param file_p 指向 file_t 变量的指针
   * @param buf 指向要写入字节的缓冲区的指针
   * @param btr 要写入的字节数
   * @param br 实际写入的字节数(Bytes Written)。 如果未使用,则为 NULL。
   * @return LV_FS_RES_OK 或来自 lv_fs_res_t 枚举的任何错误
  */
 static lv_fs_res_t my_write_cb(
 	lv_fs_drv_t* drv,
 	void* file_p,
 	const void* buf,
 	uint32_t btw,
 	uint32_t* bw
 )
 {
	 ESP_LOGI(TAG, "my_write_cb");
     /* Add your code here*/
   	FRESULT fres = f_write((FIL*)file_p, buf, btw, bw);
   	if (fres != FR_OK) {
     	printf("f_read error (%d)\n", fres);
     	return LV_FS_RES_NOT_IMP;
   	} else
     	return LV_FS_RES_OK;
 }

 /**
  * Set the read write pointer. Also expand the file size if necessary.
  * @param drv pointer to a driver where this function belongs
  * @param file_p pointer to a file_t variable. (opened with lv_ufs_open )
  * @param pos the new position of read write pointer
  * @return LV_FS_RES_OK: no error, the file is read
  *         any error from lv_fs_res_t enum
  */
 static lv_fs_res_t my_seek_cb(lv_fs_drv_t* drv, void* file_p, uint32_t pos)
 {
	 ESP_LOGI(TAG, "my_seek_cb");

     /* Add your code here*/
   	FRESULT fres = f_lseek((FIL*)file_p, pos);
  	 if (fres != FR_OK) {
     	printf("f_lseek error (%d)\n", fres);
     	return LV_FS_RES_NOT_IMP;
   	} else
     	return LV_FS_RES_OK;
 }

 /**
  * Give the position of the read write pointer
  * @param drv pointer to a driver where this function belongs
  * @param file_p pointer to a file_t variable.
  * @param pos_p pointer to to store the result
  * @return LV_FS_RES_OK: no error, the file is read
  *         any error from lv_fs_res_t enum
  */
 static lv_fs_res_t my_tell_cb (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
 {
	 lv_fs_res_t res = LV_FS_RES_UNKNOWN;

	     off_t pos = f_tell((FIL*)file_p);
	     if (pos >= 0) {
	         *pos_p = pos;
	         res = LV_FS_RES_OK;
	     }

	     return res;
 }


void app_sdmmc(void)
{    esp_err_t ret;
// 挂载文件系统的选项。
// 如果 format_if_mount_failed 设置为 true,则 SD 卡将被分区并
// 格式化以防挂载失败。
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
    .format_if_mount_failed = true,
#else
    .format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
    .max_files = 5,
    .allocation_unit_size = 16 * 1024
};
sdmmc_card_t* card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");

// 使用上面定义的设置来初始化 SD 卡并挂载 FAT 文件系统。
// 注意:esp_vfs_fat_sdmmc/sdspi_mount 是多合一的便捷功能。
// 开发时请检查其源代码并实现错误恢复
// 生产应用程序。
#ifndef USE_SPI_MODE
ESP_LOGI(TAG, "Using SDMMC peripheral");
sdmmc_host_t host = SDMMC_HOST_DEFAULT();

// 这会在没有卡检测 (CD) 和写保护 (WP) 信号的情况下初始化插槽。
// 如果您的电路板有这些信号,请修改 slot_config.gpio_cd 和 slot_config.gpio_wp。
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();

// 要使用 1-line SD 模式,请取消注释以下行:
// slot_config.width = 1;

// GPIO 15、2、4、12、13 应该有外部 10k 上拉。
// 内部上拉是不够的。 但是,启用内部上拉
// 确实对某些板产生影响,所以我们在这里这样做。
gpio_set_pull_mode(15, GPIO_PULLUP_ONLY);   // CMD, needed in 4- and 1- line modes
gpio_set_pull_mode(2, GPIO_PULLUP_ONLY);    // D0, needed in 4- and 1-line modes
gpio_set_pull_mode(4, GPIO_PULLUP_ONLY);    // D1, needed in 4-line mode only
gpio_set_pull_mode(12, GPIO_PULLUP_ONLY);   // D2, needed in 4-line mode only
gpio_set_pull_mode(13, GPIO_PULLUP_ONLY);   // D3, needed in 4- and 1-line modes

ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
#else
ESP_LOGI(TAG, "Using SPI peripheral");

sdmmc_host_t host = SDSPI_HOST_DEFAULT();
spi_bus_config_t bus_cfg = {
    .mosi_io_num = PIN_NUM_MOSI,
    .miso_io_num = PIN_NUM_MISO,
    .sclk_io_num = PIN_NUM_CLK,
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN);
if (ret != ESP_OK) {
    ESP_LOGE(TAG, "Failed to initialize bus.");
    return;
}

// 这会在没有卡检测 (CD) 和写保护 (WP) 信号的情况下初始化插槽。
// 如果您的电路板有这些信号,请修改 slot_config.gpio_cd 和 slot_config.gpio_wp。
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_NUM_CS;
slot_config.host_id = host.slot;

ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
#endif //USE_SPI_MODE

if (ret != ESP_OK) {
    if (ret == ESP_FAIL) {
        ESP_LOGE(TAG, "Failed to mount filesystem. "//无法挂载文件系统。
        	//如果要格式化卡,请设置EXAMPLE_FORMAT_IF_MOUNT_FAILED 菜单配置选项。
            "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
    } else {
        ESP_LOGE(TAG, "Failed to initialize the card (%s). "//卡初始化失败
            "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
    }
    return;
}

	// 卡片已经初始化,打印其属性
	sdmmc_card_print_info(stdout, card);



	lv_fs_drv_t drv;
	lv_fs_drv_init(&drv);                     /*基本初始化*/

	drv.letter = 'S';                         /*用于标识驱动器的大写字母*/
	//drv.file_size = sizeof(my_file_object);   /*存储文件对象所需的大小*/
	//drv.rddir_size = sizeof(my_dir_object);   /*存储目录对象所需的大小(由 dir_open/close/read 使用)*/
	//drv.ready_cb = my_ready_cb;               /*回调以告知驱动器是否已准备好使用*/
	drv.open_cb = my_open_cb;                 /*打开文件的回调*/
	drv.close_cb = my_close_cb;               /*关闭文件的回调*/
	drv.read_cb = my_read_cb;                 /*回调读取文件*/
	drv.write_cb = my_write_cb;               /*写文件的回调*/
	drv.seek_cb = my_seek_cb;                 /*回调以在文件中查找(移动光标)*/
	drv.tell_cb = my_tell_cb;                 /*回调告诉光标位置*/
	//drv.trunc_cb = my_trunc_cb;               /*回调删除文件*/
	//drv.size_cb = my_size_cb;                 /*回调告诉文件的大小*/
	//drv.rename_cb = my_rename_cb;             /* 重命名文件的回调 */


	//drv.dir_open_cb = my_dir_open_cb;         /*回调打开目录读取其内容*/
	//drv.dir_read_cb = my_dir_read_cb;         /*回调读取目录内容*/
	//drv.dir_close_cb = my_dir_close_cb;       /* 关闭目录的回调 */

	//drv.free_space_cb = my_free_space_cb;     /*回调告诉驱动器上的可用空间*/

	//drv.user_data = my_user_data;             /*任何需要的自定义数据*/

	lv_fs_drv_register(&drv);                 /*最后注册驱动*/

}


void lvgl_fs_test(void)
{
    char rbuf[30] = {0};
    uint32_t rsize = 0;
    lv_fs_file_t fd;
    lv_fs_res_t res;

    res = lv_fs_open(&fd, "S:"MOUNT_POINT"/123.txt", LV_FS_MODE_RD);
    if (res != LV_FS_RES_OK) {
    	ESP_LOGI(TAG,"open /123.txt ERROR\n");
        return ;
    }

    res = lv_fs_read(&fd, rbuf, 100, &rsize);
    if (res != LV_FS_RES_OK) {
    	ESP_LOGI(TAG,"read ERROR\n");
        return ;
    }

    ESP_LOGI(TAG,"READ(%d): %s",rsize , rbuf);

    lv_fs_close(&fd);
}

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

type or paste code here

I (29) boot: ESP-IDF v4.2.1-dirty 2nd stage bootloader
I (29) boot: compile time 08:39:12
I (29) boot: chip revision: 1
I (32) boot_comm: chip revision: 1, min. bootloader chip revision: 0
I (39) boot.esp32: SPI Speed      : 40MHz
I (44) boot.esp32: SPI Mode       : DIO
I (49) boot.esp32: SPI Flash Size : 2MB
I (53) boot: Enabling RNG early entropy source...
I (59) boot: Partition Table:
I (62) boot: ## Label            Usage          Type ST Offset   Length
I (69) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (77) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (84) boot:  2 factory          factory app      00 00 00010000 00100000
I (92) boot: End of partition table
I (96) boot_comm: chip revision: 1, min. application chip revision: 0
I (103) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x0f6f4 ( 6
3220) map
I (136) esp_image: segment 1: paddr=0x0001f71c vaddr=0x3ffb0000 size=0x008fc (
2300) load
I (137) esp_image: segment 2: paddr=0x00020020 vaddr=0x400d0020 size=0x39404 (23
4500) map
0x400d0020: _stext at ??:?

I (232) esp_image: segment 3: paddr=0x0005942c vaddr=0x3ffb08fc size=0x0190c (
6412) load
I (235) esp_image: segment 4: paddr=0x0005ad40 vaddr=0x40080000 size=0x00404 (
1028) load
0x40080000: _WindowOverflow4 at C:/Users/baoan/Desktop/esp-idf/esp-idf-v4.2.1/co
mponents/freertos/xtensa/xtensa_vectors.S:1730

I (239) esp_image: segment 5: paddr=0x0005b14c vaddr=0x40080404 size=0x0b614 ( 4
6612) load
I (275) boot: Loaded app from partition at offset 0x10000
I (275) boot: Disabling RNG early entropy source...
I (275) cpu_start: Pro cpu up.
I (279) cpu_start: Application information:
I (284) cpu_start: Project name:     esp32-i2s-driver-example
I (290) cpu_start: App version:      1
I (295) cpu_start: Compile time:     Jun 10 2021 15:05:13
I (301) cpu_start: ELF file SHA256:  c1641c078894495c...
I (307) cpu_start: ESP-IDF:          v4.2.1-dirty
I (312) cpu_start: Starting app cpu, entry point is 0x40081814
0x40081814: call_start_cpu1 at C:/Users/baoan/Desktop/esp-idf/esp-idf-v4.2.1/com
ponents/esp32/cpu_start.c:287

I (304) cpu_start: App cpu up.
I (323) heap_init: Initializing. RAM available for dynamic allocation:
I (330) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (336) heap_init: At 3FFBB120 len 00024EE0 (147 KiB): DRAM
I (342) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (348) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (355) heap_init: At 4008BA18 len 000145E8 (81 KiB): IRAM
I (361) cpu_start: Pro cpu start user code
I (379) spi_flash: detected chip: generic
I (380) spi_flash: flash io: dio
W (380) spi_flash: Detected size(4096k) larger than the size in the binary image
 header(2048k). Using the size in the binary image header.
I (390) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (30) lvgl_helpers: Display hor size: 320, ver size: 240
I (30) lvgl_helpers: Display buffer size: 12800
I (30) lvgl_helpers: Initializing SPI master for display
I (40) lvgl_helpers: Configuring SPI host HSPI_HOST (1)
I (50) lvgl_helpers: MISO pin: -1, MOSI pin: 19, SCLK pin: 17, IO2/WP pin: -1, I
O3/HD pin: -1
I (50) lvgl_helpers: Max transfer size: 25600 (bytes)
I (60) lvgl_helpers: Initializing SPI bus...
I (70) disp_spi: Adding SPI device
I (70) disp_spi: Clock speed: 20000000Hz, mode: 2, CS pin: 16
ST7789 initialization.
I (480) st7789: Display orientation: PORTRAIT
I (480) st7789: 0x36 command value: 0xC0
I (480) sd_card_app: Initializing SD card
I (480) sd_card_app: Using SDMMC peripheral
I (490) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldo
wn: 0| Intr:0
Name: �����
Type: SDSC
Speed: 20 MHz
Size: 120MB
I (730) sd_card_app: path (sdcard/123.txt)

Guru Meditation Error: Core  1 panic'ed (InstrFetchProhibited). Exception was un
handled.

Core  1 register dump:
PC      : 0x01000000  PS      : 0x00060530  A0      : 0x800d3b8f  A1      : 0x3f
fc0db0
A2      : 0x3ffc0e14  A3      : 0x3ffc0df0  A4      : 0x00000064  A5      : 0x3f
fc0e10
A6      : 0x400d3e4c  A7      : 0x00000000  A8      : 0x801078d0  A9      : 0x00
000000
0x400d3e4c: my_open_cb at C:\Users\baoan\eclipse-workspace\i2s\build/../main/sd_
card_app.c:92

A10     : 0x3ffb0001  A11     : 0x3ffcd78c  A12     : 0x3ffc0df0  A13     : 0x00
000064
A14     : 0x3ffc0db0  A15     : 0x00000000  SAR     : 0x00000004  EXCCAUSE: 0x00
000014
EXCVADDR: 0x01000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0xff
ffffff

Backtrace:0x00fffffd:0x3ffc0db0 0x400d3b8c:0x3ffc0de0 0x400d3bf3:0x3ffc0e40 0x40
0d3cb2:0x0002f038 |<-CORRUPTED
0x400d3b8c: lvgl_fs_test at C:\Users\baoan\eclipse-workspace\i2s\build/../main/m
ain.c:211

0x400d3bf3: create_demo_application at C:\Users\baoan\eclipse-workspace\i2s\buil
d/../main/main.c:246

0x400d3cb2: guiTask at C:\Users\baoan\eclipse-workspace\i2s\build/../main/main.c
:161 (discriminator 2)



ELF file SHA256: c1641c078894495c

Rebooting...
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x1f (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:4
load:0x3fff0034,len:7240
load:0x40078000,len:13212
load:0x40080400,len:4568
0x40080400: _init at ??:?

entry 0x400806f4

The direct call of FatFs function is successful

Blockquote

/*Open a file for write*/
// 调用FatFs的函数
FRESULT fres = f_open(&file_p, MOUNT_POINT"/123.txt", FA_OPEN_EXISTING | FA_READ);
if (fres != FR_OK) {
	ESP_LOGE(TAG, "f_open error (%d)\n", fres);
} 


ESP_LOGI(TAG, "my_read_cb");
 fres = f_read(&file_p, buffer, 200, &br);

if (fres != FR_OK) {
	ESP_LOGE(TAG, "f_read error (%d)\n", fres);
}

ESP_LOGI(TAG, "buffer=   %s",buffer);

f_close(&file_p);

Hi xbee,

Did you find a solution to this problem? From your log, it looks like your LVGL implementation failed on lv_fs_open or lv_fs_read(). I have a similar problem of failure on lv_fs_read() but working fine on direct call of SD functions. Pls do share if you solved this.

Thank you.