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

Png decoder need a lot of memory in runtime. First of all need to store file content, then need to decompress file content to bitmap (32 bit on 1px).

Here is the final code for LVGL Version 8.1.1 that opens and closes multiple files and shows BMPs on multiple screens. Might not be the right way of doing it but is works and is stable on Teensy 4.1.
Thanks glory-man for helping me to get this far. There are very few working SD Card and multiple screen examples that I could find on Google so I hope this helps others:

Re-posted with updated seek callback.

#include <Arduino.h>
//LVGL>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <lvgl.h>
#include <ST7735_t3.h> // Hardware-specific library
#include <ST7789_t3.h> // Hardware-specific library
#include <SPI.h>
#include <MD_REncoder.h>
#include "lv_lib_bmp/lv_bmp.h"
#include <SD.h>
/***************************************************
  Graphics:
 ****************************************************/
#define TFT_MISO 12
#define TFT_MOSI 11 //a12
#define TFT_SCK 13  //a13
#define TFT_DC 9
#define TFT_CS 10
#define TFT_RST 8

#define TFT_CS2 5
#define TFT_RST2 6
#define TFT_BKL 4

// For 1.54" TFT with ST7789
ST7789_t3 tft = ST7789_t3(TFT_CS, TFT_DC, TFT_RST);
ST7789_t3 tft2 = ST7789_t3(TFT_CS2, TFT_DC, TFT_RST2);

/*Change to your screen resolution*/
static const uint32_t screenWidth = 240;
static const uint32_t screenHeight = 240;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * 10];

static lv_disp_draw_buf_t draw_buf2;
static lv_color_t buf2[screenWidth * 10];

lv_disp_t *disp;
lv_disp_t *disp2;
// above is base lvgl architecture stuff

uint32_t timeNow = 0; // for timing in main loop

///////////////////////////////////////////////////
//Encoder
#define REFR_TIME 500
#define ENC_SWITCH_PIN 32
#define ENC_CLK_PIN 33
#define ENC_DATA_PIN 34
MD_REncoder Encoder = MD_REncoder(ENC_CLK_PIN, ENC_DATA_PIN);
volatile uint8_t x = 0;
volatile int32_t encoderCount = 0;    //used to track rotary position
volatile bool encoderPress = false;   //used to track encoder press state
volatile bool encoderRelease = false; //used to track encoder press state
unsigned long encoderPressedTime = 0;
bool encoderShortPress = false;
bool encoderLongPress = false;
bool encoderPlus = false;
bool encoderMinus = false;
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

void encoderRead_ISR()
{
  x = Encoder.read();
  if (x)
  {
    if (x == DIR_CW)
    {
      //Serial.print("NEXT ");
      ++encoderCount;
      encoderPlus = true;
      //Serial.println(encoderCount);
    }
    else
    {
      //Serial.print("PREV ");
      --encoderCount;
      encoderMinus = true;
      //Serial.println(encoderCount);
    }
  }
}

void encoderPress_ISR()
{
  if (digitalRead(ENC_SWITCH_PIN) == true) //must have been released then - check how long ago:
  {
    //encoderRelease = true;
    encoderPressedTime = millis() - encoderPressedTime;
    //encoderRelease = false;
    if ((encoderPressedTime > 10) && (encoderPressedTime < 600))
    {
      encoderShortPress = true;
      encoderLongPress = false;
    }
    if ((encoderPressedTime > 600) && (encoderPressedTime < 2000))
    {
      encoderLongPress = true;
      encoderShortPress = false;
    }
  }
  else
  {
    //encoderPress = true;
    encoderPressedTime = millis();
  }
}

///////////////////////////////////////////////////
/*
Notes:
* Must declare objects before using them!
* Must only initialise once
* Set style then position
*/

void my_log_cb(const char *buf)
{
  Serial.printf(buf, strlen(buf));
}

/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
  lv_disp_flush_ready(disp);
  tft.writeRect(area->x1, area->y1, (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1), (uint16_t *)color_p);
  /* tell lvgl that flushing is done */
  lv_disp_flush_ready(disp);
}

/* Second Display flushing */
void my_disp_flush2(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
  lv_disp_flush_ready(disp);
  tft2.writeRect(area->x1, area->y1, (area->x2 - area->x1 + 1), (area->y2 - area->y1 + 1), (uint16_t *)color_p);
  /* tell lvgl that flushing is done */
  lv_disp_flush_ready(disp);
}

//SD Card Functions:
static bool sd_ready_cb(lv_fs_drv_t *drv)
{
  //Serial.println("Asked if drive ready!");
  return 1;
  //needs real code here...
}

/**
 * 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));

  File *fp = new (File); // when opening 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;
}

/**
 * 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)
{
  File *fp = (File *)file_p; /*Just avoid the confusing casings*/

  fp->close();

  delete (fp); // when close
  return LV_FS_RES_OK;
}

/**
 * Read data from an opened file
 * @param drv       pointer to a driver where this function belongs
 * @param file_p    pointer to a file_t variable.
 * @param buf       pointer to a memory block where to store the read data
 * @param btr       number of Bytes To Read
 * @param br        the real number of read bytes (Byte Read)
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */

static lv_fs_res_t sd_read_cb(lv_fs_drv_t *drv, void *file_p, void *fileBuf, uint32_t btr, uint32_t *br)
{
  //Serial.println("Call Back Asked to read file");

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

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

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

  lv_fs_res_t res = LV_FS_RES_OK;
  return res;
}

/**
 * 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 fs_open )
 * @param pos       the new position of read write pointer
 * @param whence    tells from where to interpret the `pos`. See @lv_fs_whence_t
 * @return          LV_FS_RES_OK: no error or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t sd_seek_cb(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence)
{
  lv_fs_res_t res = LV_FS_RES_OK;

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

  if (whence == LV_FS_SEEK_SET)
    {
      fp->seek(pos, 0);
    }
  if (whence == LV_FS_SEEK_CUR)
    {
      fp->seek(pos, 1);
    }
  if (whence == LV_FS_SEEK_END)
    {
      fp->seek(pos, 2);
    }
  return res;
}

/**
 * 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 or  any error from @lv_fs_res_t enum
 */
static lv_fs_res_t sd_tell_cb(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p)
{
  //FsFile *fp = (FsFile *)file_p; /*Just avoid the confusing casings*/
  File *fp = (File *)file_p; /*Just avoid the confusing casings*/
  *pos_p = fp->position();

  return LV_FS_RES_OK;
}

void lv_bmpLogo(void)
{
  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);
}

void setup()
{
  pinMode(TFT_BKL, OUTPUT);
  analogWrite(TFT_BKL, 0);

  SD.begin(BUILTIN_SDCARD);

  Serial.begin(115200);
  //Init Encoder:
  attachInterrupt(ENC_CLK_PIN, encoderRead_ISR, CHANGE);
  attachInterrupt(ENC_DATA_PIN, encoderRead_ISR, CHANGE);
  Encoder.begin();
  //Init Push Switch on Encoder:
  pinMode(ENC_SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt(ENC_SWITCH_PIN, encoderPress_ISR, CHANGE);

  //Init LVGL and TFT:
  //lv_log_register_print_cb(my_log_cb);

  lv_init();
  lv_bmp_init();

  tft.init(240, 240); // initialize a ST7789 chip, 240x240 pixels
  tft.setRotation(2);
  tft.fillScreen(ST7735_BLACK);

  tft2.init(240, 240); // initialize a ST7789 chip, 240x240 pixels
  tft2.setRotation(2);
  tft2.fillScreen(ST7735_BLACK);

  lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10);
  lv_disp_draw_buf_init(&draw_buf2, buf2, NULL, screenWidth * 10);

  /*Initialize the display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

  /*Initialize the second display*/
  static lv_disp_drv_t disp_drv2;
  lv_disp_drv_init(&disp_drv2);

  disp_drv2.hor_res = screenWidth;
  disp_drv2.ver_res = screenHeight;
  disp_drv2.flush_cb = my_disp_flush2;
  disp_drv2.draw_buf = &draw_buf2;
  disp2 = lv_disp_drv_register(&disp_drv2);
  lv_theme_t *th = lv_theme_default_init(disp2, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED), LV_THEME_DEFAULT_DARK, LV_FONT_DEFAULT);
  lv_disp_set_theme(disp2, th);

  Serial.println("I got to the initing file sys!");
  //setup LVGL File System:
  static lv_fs_drv_t drv; /*Needs to be static or global*/
  drv.file_size = sizeof(File);
  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*/
  lv_fs_drv_register(&drv); /*Finally register the drive*/
  Serial.println("I registered drv!");

  lv_disp_set_default(disp2);
  lv_bmpLogo();

  lv_disp_set_default(disp);
  lv_bmpLogo();

  analogWrite(TFT_BKL, 128); //turn on backlight
}

void openCloseFile()
{
  //Testing LV file open method:
  lv_fs_file_t fileSD;
  lv_fs_res_t res;

  res = lv_fs_open(&fileSD, "S:Pic1.BMP", 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 newFileBuf[8];

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

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

  for (int a = 0; a < 8; a++)
  {
    Serial.print((char)newFileBuf[a]);
    Serial.print(" ");
  }
  Serial.println("");

  res = LV_FS_RES_OK;

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

  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);

  Serial.println("closed file");
}

void loop()
{
  lv_timer_handler(); /* let the GUI do its work */
  delay(5);

  if (millis() > timeNow + 3000)
  {
    timeNow = millis();
    //openCloseFile(); // for testing opening a file multiple times
    lv_bmpLogo(); // for testing opening and closing a BMP image multiple times
  }
}

If anyone notices any improvements, pls share. I notice this takes about 1/10 sec to draw each image where as drawing from compiled .c is near instant. I’m surprised at that as I thought this drew to a buffer before sending to the screen rather than direct from the card?

I’ve tested this now with TXT, BMP, GIF and BIN files.

Thanks.

2 Likes

Hello there!

I’m working on an SD Card as well with an ESP32. I tried using the File system SD card-related code with v8.3 but it’s throwing a lot of errors, are they incompatible with each other?

Hi, I didn’t try on the ESP32 but maybe this helps (but I don’t think this is version 8, which is a bit different in how it works with sd cards I think):

https://github.com/lvgl/lv_fs_if/issues/6

Also, if you post what your errors are, perhaps other with more experience than me can help?

I found it a bit complicated but it helped starting really simple and getting the SD card working without LVGL, just very simple bare code to initialise the SD card, open a file, read a character and then close the file. That proves your hardware and basic file functions work. Then you can link that in to LVGL. The main thing to get your head around is you have to build the LVGL call back functions to match your ESP32 build so LVGL uses the correct functions to access the SD card. Nothing will work until you get those call back functions right. Also, I struggled a bit getting the file pointer between all the functions correct.

1 Like

Thanks for the reply! I’m looking at the link you sent and it seems like it has already been added to the main branch.

I still don’t understand what I’m doing though, and I’m hoping to find some sort of example out here.

Thank you anyway!