Create image from sd card with Teensy 4.0

Description

I have created an extensive GUI so far using the awesome LVGL and now wish to add several .bmp images. The images are held on a microSd card. I am using a 320 x 480 display with capacitive touchscreen and a Teensy 4.0 processor. The microSd card reader is hardwired to the Teensy SDIO pins on the underside of the board. Libraries can be seen in the code included. The example image file on the sd card is taken from the LVGL examples for 16bit.

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

Arduino IDE 1.8.13
Teensyduino 1.8.5

What LVGL version are you using?

LVGL 8.1

What do you want to achieve?

I would like to display images. The display currently is just white… although see below…
Are there some tips to check if I have the conf.h correct?
The sd card is formatted FAT32 . Is this OK?
I am not sure the file path for Arduino is OK when trying to use LVGL files when ‘S’ is selected…some guidance would help?
Many thanks for any assistance.

What have you tried so far?

I have looked at a number of other examples and tried to recreate the code used. I have read the LVGL info for 8.1 images and file systems. I can only find a few examples using Teensy and the code I have used is based on these.
I have included the conf.h . #define LV_USE_BMP 1. #define LV_USE_FS_STDIO ‘S’
#define LV_FS_STDIO_PATH “/home/john/” /*Set the working directory… I am unsure whether I need to put a path here… an indication would be appreciated.
If I leave #define LV_USE_FS_STDIO as’\0’ the code will compile but no image is displayed. If I define ‘S’ in conf.h I get error message…

/*Arduino: 1.8.13 (Windows 10), TD: 1.53, Board: "Teensy 4.0 Flash, Serial, 150 MHz, Faster, US English"

In file included from c:\program files (x86)\arduino\hardware\tools\arm\arm-none-eabi\include\dirent.h:7:0,

                 from C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:15:

c:\program files (x86)\arduino\hardware\tools\arm\arm-none-eabi\include\sys\dirent.h:10:2: error: #error "<dirent.h> not supported"

 #error "<dirent.h> not supported"

  ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c: In function 'fs_dir_open':

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:213:12: warning: implicit declaration of function 'opendir' [-Wimplicit-function-declaration]

     return opendir(path);

            ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:213:12: warning: return makes pointer from integer without a cast [-Wint-conversion]

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c: In function 'fs_dir_read':

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:261:17: warning: implicit declaration of function 'readdir' [-Wimplicit-function-declaration]

         entry = readdir(dir_p);

                 ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:261:15: warning: assignment makes pointer from integer without a cast [-Wint-conversion]

         entry = readdir(dir_p);

               ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:263:21: error: dereferencing pointer to incomplete type 'struct dirent'

             if(entry->d_type == DT_DIR) sprintf(fn, "/%s", entry->d_name);

                     ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:263:33: error: 'DT_DIR' undeclared (first use in this function)

             if(entry->d_type == DT_DIR) sprintf(fn, "/%s", entry->d_name);

                                 ^

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:263:33: note: each undeclared identifier is reported only once for each function it appears in

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c: In function 'fs_dir_close':

C:\Users\Andy\Documents\Arduino\libraries\lvgl-release-v8.1\src\extra\libs\fsdrv\lv_fs_stdio.c:303:5: warning: implicit declaration of function 'closedir' [-Wimplicit-function-declaration]

     closedir(dir_p);

     ^

Error compiling for board Teensy 4.0 Flash.

Unable to open COM7*/

This report would have more information with
“Show verbose output during compilation”
option enabled in File → Preferences.

Code to reproduce

The code block(s) should be formatted like:

/*#include <HX8357_t3n.h> // Hardware-specific library
#include <SPI.h>
#include "SdFat.h"
// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.  For Arduino Uno,
// Duemilanove, etc., pin 11 = MOSI, pin 12 = MISO, pin 13 = SCK.
#define TFT_DC  20
#define TFT_CS  10
#define TFT_RST 14

#include <Arduino.h>
#include <lvgl.h>

//#include "lv_bmp.h"

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
HX8357_t3n tft = HX8357_t3n(TFT_CS, TFT_DC, TFT_RST);

// SDCARD_SS_PIN is defined for the built-in SD on some boards.
const uint8_t SD_CS_PIN = SS;

// Try to select the best SD card configuration.
#define SD_CONFIG SdioConfig(FIFO_SDIO)

SdFs sd;

static const uint32_t screenWidth = 320;
static const uint32_t screenHeight = 480;

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

lv_disp_t *disp;

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

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

//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);
   // 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);
      fp->seek(0);
 //     Serial.println("whence seek 0");
    }
  if (whence == LV_FS_SEEK_CUR)
    {
//      fp->seek(pos, 1);
      fp->seek(1);
    }
  if (whence == LV_FS_SEEK_END)
    {
//      fp->seek(pos, 2);
      fp->seek(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:SeelyHole1.bin");
//  lv_img_set_src(img, "S:blue_flower_16.bin");
  lv_img_set_src(img, "S:example_16bit.bmp");
  
  lv_obj_center(img);
}

void setup()
{

  if (!sd.begin(SdioConfig(FIFO_SDIO))) {
      Serial.println(F("SD card begin failed"));
}

  Serial.println("SD card begin OK!");

  Serial.begin(115200);

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

  lv_init();
//  lv_bmp_init();

  tft.begin();
  tft.invertDisplay(1);// had to add this line as without the original code shows the colors inverted   
  tft.setRotation(0); 
  delay(100);
  
  tft.fillScreen(HX8357_BLUE);

  lv_disp_draw_buf_init(&draw_buf, buf, 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*/

  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

  lv_fs_drv_init(&drv);       /*Basic initialization*/
  drv.letter = 'S'; /*An uppercase letter to identify the drive */
  
//    drv.cache_size = sd_cache_size; 
//    drv.file_size = sizeof(File);
//  drv.file_size = sizeof(File*);
  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!");

}

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

//  res = lv_fs_open(&fileSD, "S:SeelyHole1.bin", LV_FS_MODE_RD);
//  res = lv_fs_open(&fileSD, "S:blue_flower_16.bin", LV_FS_MODE_RD);
  res = lv_fs_open(&fileSD, "S:example_16bit.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
  }
}*/

Screenshot and/or video

With no ‘S’ in the conf.h, the function…

void lv_bmpLogo(void)
{
lv_obj_t *img = lv_img_create(lv_disp_get_scr_act(NULL));
// lv_img_set_src(img, “S:SeelyHole1.bin”);
// lv_img_set_src(img, “S:blue_flower_16.bin”);
lv_img_set_src(img, “S:example_16bit.bmp”);

lv_obj_center(img);
}
… will display a 100 x 100 (correct size) image as per photo…

and the function…
openCloseFile();
… just has white screen

Hi, did you get anywhere with this? I have a teensy 4 and haven’t been able to access any images from the SD card either.

I’m not sure if using the SD will help anyway, as even with the Teensy 4, I am running into space issues even when just displaying a background…

Keen to understand if you achieved this, or how I can use my own functions to draw from the SD whist still being able to use the widgets.

J