Crashing on "lv_fs_read" in lv_fs.c with SD Card

Description

I have SD card open, seek, read, close functions working outside LV, but when I register a drive and use the LV call backs, it hard crashes on lv_fs_read.
lv_fs_open and close work OK.

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

Teensy 4.1
Platformio
arduinoteensy 1.155.0 (1.55)

What LVGL version are you using?

lvgl@^8.0.1
lv_drivers@^8.0.1

What do you want to achieve?

Eventually load BMP and other files from SD Card

What have you tried so far?

I have dual screens, encoder and SD Card working, showing tabs and charts, etc.
I’m using the Teensy4.1 built in SD Card holder and functions and this can read a file manually.
I could not get lv to read a BMP file from SD card so tried the simple example of using lv SD card functions to read a text file

I’ve used the prototype callback functions and tried to implement them for Teensy
I’ve tried to trace how this works through my code and lv code and look at other examples on Google.

I’ve found the line in lv_fs.c where it is crashing, where it asks the callback function to read. But it seems this line is where is crashes, even if there is nothing in the called back function (completely empty function) if that makes sense? Doesn’t seem to matter what is in the read callback function it always crashes here. If I comment out that line in lv_fs.c it does not crash, but obviously this is useless.

I’m now stuck and would really appreciate help. I must be missing something in my implementation somewhere.

Thanks very much for your help.

Code to reproduce

This works:

  //Direct file open and read:
  File myManualFile;

  if (SD.exists("folder/file.txt"))
  {
    Serial.println("file.txt exists.");
    myManualFile = SD.open("folder/file.txt", O_RDONLY);

    if (myManualFile)
    {
      Serial.print("file.txt Opened! : ");
    }
    myManualFile.seek(3);
    Serial.print(" Contents from Position ");
    Serial.print(myManualFile.position());
    Serial.print(" = ");

    while (myManualFile.available())
    {
      Serial.write(myManualFile.read());
    }
    // close the file:
    myManualFile.close();
    Serial.println("");
    Serial.println("File Closed");
  }
  //End direct file open and read

This does not:

  //Now try LV method:
  lv_fs_file_t fileSD;
  lv_fs_res_t res;

  res = lv_fs_open(&fileSD, "S:folder/file.txt", LV_FS_MODE_RD);

  if (res != LV_FS_RES_OK)
  {
    Serial.println("LV Failed to open SD File!");
  }
  else
  {
    Serial.println("LV Opened SD File!");
  }

  uint32_t read_num;
  uint32_t charTOread = 8;
  uint8_t buf[8];

  Serial.print("LV File Pointer: ");
  Serial.println((int)&fileSD);
  Serial.println("LV Just about to read SD File!");

  res = lv_fs_read(&fileSD, buf, charTOread, &read_num);

  Serial.println("LV did a SD File Read!");

  res = LV_FS_RES_OK;

  if (res != LV_FS_RES_OK)
  {
    Serial.println("LV Couldn't Read!");
  }
  else
  {
    Serial.println("LV GOT DATA!");
  }

  lv_fs_close(&fileSD);
  Serial.println("closed file");

It opens OK but crashes when it gets to lv_fs_read.

Looking at where the code gets to, it seems to crash when it gets to this line in lv_fs.c:

lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);

lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br)
{
    if(br != NULL) *br = 0;
    if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM;
    if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP;

    uint32_t br_tmp = 0;
    LV_LOG_WARN("In lv_fs.c - just about to read!");

    lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp); 
    
    LV_LOG_WARN("I did the lv_fs.c read bit!");
    if(br != NULL) *br = br_tmp;

    return res;
}

So, I’m assuming there is something wrong with my read callback?. But, when I have a completely empty read callback function it still hard crashes on this line.

This is my read callback function:

static lv_fs_res_t sd_read_cb(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{

  (void)drv; /*Unused*/

  Serial.println("Call Back Asked to read file");
  //FsFile *fp = (FsFile *)file_p; /*Just avoid the confusing casings*/

  File *fp = (File *)file_p; /*Just avoid the confusing casings*/
  File fileSD = *fp;

  Serial.println((int)fileSD.available());

  *br = fileSD.read(buf, btr);

  lv_fs_res_t res = LV_FS_RES_OK;
  return res;
}

My file system setup is like this after registering my two displays:

  //setup LVGL File System:
  static lv_fs_drv_t drv;     /*Needs to be static or global*/
  lv_fs_drv_init(&drv);       /*Basic initialization*/
  drv.letter = 'S';           /*An uppercase letter to identify the drive */
  drv.ready_cb = sd_ready_cb; /*Callback to tell if the drive is ready to use */
  drv.open_cb = sd_open_cb;   /*Callback to open a file */
  drv.close_cb = sd_close_cb; /*Callback to close a file */
  drv.read_cb = sd_read_cb;   /*Callback to read a file */
  drv.write_cb = NULL;        /*Callback to write a file */
  drv.seek_cb = sd_seek_cb;   /*Callback to seek in a file (Move cursor) */
  drv.tell_cb = sd_tell_cb;   /*Callback to tell the cursor position  */

  drv.dir_open_cb = NULL;  /*Callback to open directory to read its content */
  drv.dir_read_cb = NULL;  /*Callback to read a directory's content */
  drv.dir_close_cb = NULL; /*Callback to close a directory */

  //drv.user_data = my_user_data; /*Any custom data if required*/
  Serial.println("I got to just before registered drv!");
  lv_fs_drv_register(&drv); /*Finally register the drive*/
  Serial.println("I registered drv!");

Screenshot and/or video

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

If anyone would be kind enough to share some working code in version 8 of SD Card reading though LVGL that would be very much appreciated, thank you!

I’ve tried an example from an earlier version and for me, that crashes as noted. I have not tried an earlier version as I don’t know yet how to down-grade LVGL version in Platformio.

Investigating this further, the lv_fs_seek(), lv_fs_tell(), lv_fs_open() and lv_fs_close() functions all work fine, but lv_fs_read() crashes in lv_fs.c when it gets to the line lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, &buf, btr, &br);

It doesn’t matter what is in the call back function it reads, it will hard crash when it gets to that line in lv_fs.c

I wonder if it is messing up something in memory with this call? I’m guessing it is related to the &buf, btr, &br parameters as drv and file_d are working fine in the lv_fs_seek(), lv_fs_tell(), lv_fs_open() and lv_fs_close() functions, which do not use &buf, btr, &br. I’ve tried a few ideas to no avail.

This is beyond my ability to de-bug now so I’d really appreciate some help to diagnose and share the solution. I really have to get the SD card working for images and I’m sure I must be doing something stupid. Thanks so much for your help!

What about memory allocation for file object ? drv.file_size = ?

Edit: in your worked example to open file you use “myManualFile” of “File” - type. But in lv_fs example:

lv_fs_file_t fileSD;

lv_fs_open() should allocate memory for file_p (used next in read_cb() ) of the size drv.file_size. So, I think your driver should have

drv.file_size = sizeof(File);

Attach the open_cb() code.

Thanks for your reply. Yes, I was wondering how it was allocating memory for the file contents without a size parameter.

I can see that in the v7.11.0 documentation and it makes sense:

drv.file_size = sizeof(my_file_object); /Size required to store a file object/

But this is missing from the vn 8 documentation:

https://docs.lvgl.io/8/overview/file-system.html

If I try to add this to my driver setup I get:

class “_lv_fs_drv_t” has no member "file_size"C/C++(135)

I’ve looked in lv_fs.h and there is no mention of file size allocation. Seems missing in vn8.

Was a new method define for file size in vn 8? Searching for file_size in vn8 documentation brings up nothing, but it does in earlier versions.

My open code is:

/**
 * Open a file
 * @param drv       pointer to a driver where this function belongs
 * @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          a file descriptor or NULL on error
 */
static void *sd_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode)
{
  Serial.println("Asked to open SD file!");

  File mySDfile;

  /*Make the path relative to the current directory (the projects root folder)*/
  char buf[256];
  sprintf(buf, "/%s", path);

  if (mode == LV_FS_MODE_WR)
  {
    /*Open a file for write*/
    mySDfile = SD.open(buf, O_WRONLY);
  }
  else if (mode == LV_FS_MODE_RD)
  {
    /*Open a file for read*/
    mySDfile = SD.open(buf, O_RDONLY);

    Serial.println("SD.open RDONLY");
    Serial.println(mySDfile.size());

  }
  else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
  {
    /*Open a file for read and write*/
    mySDfile = SD.open(buf, O_RDWR);

    Serial.print("SD.open = OK and has this number of Bytes: ");
    Serial.println(mySDfile.size());

  }

  mySDfile.seek(0);
  Serial.println(mySDfile.position());

  File *fp = (File *)drv; /*Just avoid the confusing casings*/
  *fp = mySDfile;

  return fp;
}

Looking in Vn8 lv_fs.c, the open call has no reference to size, but vn 7.11 does the following:

if(file_p->drv->file_size == 0) {  /*Is file_d zero size?*/
        /*Pass file_d's address to open_cb, so the implementor can allocate memory byself*/
        return file_p->drv->open_cb(file_p->drv, &file_p->file_d, real_path, mode);
    }

    file_p->file_d = lv_mem_alloc(file_p->drv->file_size);
    LV_ASSERT_MEM(file_p->file_d);
    if(file_p->file_d == NULL) {
        file_p->drv = NULL;
        return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
    }

    lv_fs_res_t res = file_p->drv->open_cb(file_p->drv, file_p->file_d, real_path, mode);

I wonder why all this is missing from Vn8?

Thanks again!

I don’t know - is .open-method of SD-class allocate memory for File or not ?
What if you manually allocate memory for it inside of open_cb(), something like this:

  File *fp = lv_mem_alloc(sizeof(File));
  if (fp == NULL) return NULL;
  File fileSD = *fp;

  fileSD = SD.open(....)

  return fp;

and free allocated memory (lv_mem_free) inside of close_cb() after file closing.
Now in your open_cb() File object is local and unknown how allocated memory is used after function exit. Also open_cb() should return pointer to open fille-object not to drv. lv_fs_open() moves returned value to file_d

    void * file_d = drv->open_cb(drv, real_path, mode);

    if(file_d == NULL || file_d == (void *)(-1)) {
        return LV_FS_RES_UNKNOWN;
    }

    file_p->drv = drv;
    file_p->file_d = file_d;

which is used to close/read/write file

Thanks. Struggling with this. I agree your direction. In open_cb() I’ve tried:

File *fp = lv_mem_alloc(sizeof(File));

but get:

"a value of type " void * " cannot be used to initialize an entity of type " File * “C/C++(144)”

However, I was able to add this to lv_fs.c in

file_p->file_d = lv_mem_alloc(8);

before opening the file. (I think I know the file is size 8 bytes and I can’t see how I get the size to that function yet.) This compiles and runs, up to the read line in lv-fs.c

lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);

where it still hard crashes as before.

I’ve tried to print out the pointers to the file.
In my main.cpp just after the lv_fs_open() I use

Serial.println((int)&fileSD);

and get 537,133,008.

In the lv_fs.c file, in lv_fs_seek(), which seems to work, I use LV_LOG_WARN to print the file_p pointer that has reached the lv_fs.c functions and I get 2003ff9c, which is 537,132,956. Different. How can this be?

In the lv_fs.c, yes, the open function is as you suggest.

In parallel with this, I’m going to start again using 7.11 as that seems to have way more content in these functions. If that works then that is a starting point. I’ll report back.

Type casting doesn’t help?

File *fp = (File*)lv_mem_alloc(sizeof(File));

Ah yes, I’m showing my lack of understanding with pointers. Thanks, I’ll give that ago.

Thank you glory-man - you are a genius, that fixed the allocating memory and opening correctly and I can now read the file correctly, at least a simple text file. The read call back I ended up with that worked was:

/**
 * Open a file
 * @param drv       pointer to a driver where this function belongs
 * @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          a file descriptor or NULL on error
 */
static void *sd_open_cb(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode)
{
  File *fp = (File *)lv_mem_alloc(sizeof(File));
  if (fp == NULL)
    return NULL;
  File mySDfile = *fp;

  /*Make the path relative to the current directory (the projects root folder)*/
  char buf[256];
  sprintf(buf, "/%s", path);

  if (mode == LV_FS_MODE_WR)
  {
    /*Open a file for write*/
    mySDfile = SD.open(buf, O_WRONLY);
  }
  else if (mode == LV_FS_MODE_RD)
  {
    /*Open a file for read*/
    mySDfile = SD.open(buf, O_RDONLY);
  }
  else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
  {
    /*Open a file for read and write*/
    mySDfile = SD.open(buf, O_RDWR);
  }

  //make sure at the beginning
  mySDfile.seek(0);

  *fp = mySDfile;

  return fp;
}

Thank you. I have to admit though, I’m not clear on how the memory pointer allocated to this file is related the file pointer to the file that “drv” points to? file_p and file_d ? Where is this connection made? These open and read call back functions don’t seem to use any data within the drv structure? I only have one drive so probably not an issue for me.

Last challenge for the moment is how to free the memory in the close() call back.

I tried:

/**
 * 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 fs_open)
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t sd_close_cb(lv_fs_drv_t *drv, void *file_p)
{
  //FsFile *fp = (FsFile *)file_p; /*Just avoid the confusing casings*/
  File *fp = (File *)file_p; /*Just avoid the confusing casings*/
  fp->close();

  Serial.println("About to free mem:");
  
  lv_mem_free(file_p); //<<<Crashes here

  Serial.println("Free!);

  return LV_FS_RES_OK;
}

but that crashes. Also tried lv_mem_free((void *)fp); but that also crashes.

I will be opening and closing a lot of files, so I guess this is important.

Thanks again.

Strange.
As far as I understand SD.open() creates instance of File-class to store each you allocate memory with lv_mem_alloc(). File.close() should free internally allocated memory during instance construction.
Could you call

Serial.println((int)fp);

before and after fp->close() ?

Thanks. Result is there is still a valid pointer:

LV File Pointer before close: 537133008
[Warn] (3.605, +3) lv_fs_close: I did the lv_fs.c close bit! (in lv_fs.c line #129)
LV File Pointer after close: 537133008
closed file

This is in my main:

Serial.print("LV File Pointer before close: ");
Serial.println((int)&fileSD);

  lv_fs_close(&fileSD);

Serial.print("LV File Pointer after close: ");
Serial.println((int)&fileSD);

This is my call back:

static lv_fs_res_t sd_close_cb(lv_fs_drv_t *drv, void *file_p)
{
  //FsFile *fp = (FsFile *)file_p; /*Just avoid the confusing casings*/
  File *fp = (File *)file_p; /*Just avoid the confusing casings*/
  fp->close();
  return LV_FS_RES_OK;
}

This is in lv_fs.c:

lv_fs_res_t lv_fs_close(lv_fs_file_t *file_p)
{
    if (file_p->drv == NULL)
    {
        return LV_FS_RES_INV_PARAM;
    }

    if (file_p->drv->close_cb == NULL)
    {
        return LV_FS_RES_NOT_IMP;
    }

    lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
    LV_LOG_WARN("I did the lv_fs.c close bit!");

    file_p->file_d = NULL;
    file_p->drv = NULL;

    return res;
}

Sorry, if I also put that Serial.println() in the call back I get:

LV File Pointer before close: 537133008
in Call Back before close: 537004956
in Call Back after close: 537004956
[Warn] (3.511, +3) lv_fs_close: I did the lv_fs.c close bit! (in lv_fs.c line #129)
LV File Pointer after close: 537133008
closed file

So, fp doesn’t changed during .close() procedure, this is normal. But memory allocated for File in open_cb() doesn’t freed, and will be allocated for every file you will open.
EDIT:
I can’t understand why lv_mem_free() couldn’t free allocated memory.

maybe try to use new, delete instead of lv_mem alocators

File * fp = new File(); // when opening file

delete fp; // when close

Right, that makes a difference. I’ve now got a 100kb BMP file in the SD Card and a routine to open and close it every 5 seconds.

With lv_mem alloc() the pointer inside the callback increase with each read so I think more memory is being used each time.
The pointer outside the call back, in main does not change.

With new and delete the pointer inside the callback then does not change each time, so I think memory is being freed and used again.

It has now opened and closed over 90 times which is over 10,000kb so I think that must be working.

Thanks very much for your help with that. I’ll do some more testing tomorrow and see if I can move on to opening and decoding some BMPs.

If you planned open files one by one and have only one instance of opened file, then you can made

File f

global and there is no need to free memory. In this case open_cb() should return pointer to f, which will be used as file_p-parameter of read_cb()/close_cb().
EDIT: If you want to have some instances of opened files, then you need to have one instance of File-class for every file, in this case you need to allocate memory for it during open file, and return pointer to newly created object.

I’ve added #define LV_USE_BMP 1 to lv_conf.h and used this to display a file:

  lv_obj_t *img = lv_img_create(lv_disp_get_scr_act(NULL));
  lv_img_set_src(img, "S:Pic1.bmp");
  lv_obj_center(img);

I had to go to Github to load the BMP decoder manually. I added #include “lv_lib_bmp/lv_bmp.h” and then lv_bmp_init(); in setup. That didn’t work until I moved lv_bmp_init(); to after lv_init();

Now it works!! I can load multiple BMP files and displays perfectly on dual screens.

Thanks so much for your help.

I’ll clean up my code, test a bit more and post the complete solution here if any help for others.