LCD Image Loading Speed Issue

I purchased a 4.3inch LCD display and Arduino shield from BuyDisplay (https://www.buydisplay.com/low-cost-lcd-display-4-3-inch-arduino-spi-i2c-tft-touchscreen-800x480 and it works just fine with the supplied (downloaded) files on a UNO when it comes to rendering vector graphics, but there's a problem with the unit displaying bitmaps, or at least the bitmaps supplied as part of the download.

They display ok, but each takes around a minute to load, line by line. If I modify (increase) the memory buffer size in the supplied sketch, it simply bricks and I have to do a complete reset. The supplied graphics are 800x480, 96dpi as far as I can make out, but unfortunately if I reduce the resolution of the files, the sketch responds with "BMP format not recognised).

As a complete newcomer to the display side of the hobby, I really don't know how to manipulate the graphic files to make them load faster. I also can't get my tiny brain around the library used either I'm afraid!

Can anyone offer me any ideas I could try to speed up the image loading? I've attached the code I'm using.

/***************************************************
//Web: http://www.buydisplay.com
EastRising Technology Co.,LTD
Examples for ER-TFTMC043-7  SDcard(bitmap) test
Display is Hardward SPI 4-Wire SPI Interface and 5V Power Supply
Tested and worked with:
Works with Arduino 1.6.0 IDE  
Test ok:  Arduino Due,Arduino UNO,Arduino MEGA2560
****************************************************/
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include "TFTMC043_7.h"

#define sd_cs 5   

void setup() {
  Serial.begin(9600);
  if (!SD.begin(sd_cs)) 
  {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done."); 
   
  ER5517.Parallel_Init();
  ER5517.HW_Reset();
  ER5517.System_Check_Temp();
  delay(100);
  while(ER5517.LCD_StatusRead()&0x02);
  ER5517.initial();
  ER5517.Display_ON();
     pinMode(2,   OUTPUT);
    digitalWrite(2, HIGH);////Disable SD RTP
}
void loop() {
  ER5517.Select_Main_Window_16bpp();
  ER5517.Main_Image_Start_Address(layer1_start_addr);        
  ER5517.Main_Image_Width(LCD_XSIZE_TFT);
  ER5517.Main_Window_Start_XY(0,0);
  ER5517.Canvas_Image_Start_address(0);
  ER5517.Canvas_image_width(LCD_XSIZE_TFT);
  ER5517.Active_Window_XY(0,0);
  ER5517.Active_Window_WH(LCD_XSIZE_TFT,LCD_YSIZE_TFT); 
  
  ER5517.Foreground_color_65k(Blue);
  ER5517.Line_Start_XY(0,0);
  ER5517.Line_End_XY(LCD_XSIZE_TFT-1,LCD_YSIZE_TFT-1);
  ER5517.Start_Square_Fill(); 
  
   ER5517.Foreground_color_65k(White);
  ER5517.Background_color_65k(Red);
  ER5517.CGROM_Select_Internal_CGROM();  
  ER5517.Font_Select_12x24_24x24();
  ER5517.Goto_Text_XY(0,0); 
  ER5517.Show_String( "www.buydisplay.com"); 
  
    bmpDraw("BMP_1.bmp", 0, 0);
   delay(1000);
   bmpDraw("BMP_2.bmp", 0, 0); 
  delay(1000);
   bmpDraw("BMP_3.bmp", 0, 0); 
  delay(1000);
   bmpDraw("BMP_4.bmp", 0, 0);    
    delay(1000);
   bmpDraw("BMP_5.bmp", 0, 0);   
     delay(1000);
   bmpDraw("BMP_6.bmp", 0, 0); 
    delay(1000);
   bmpDraw("BMP_7.bmp", 0, 0);   
     delay(1000);
   bmpDraw("BMP_8.bmp", 0, 0);  
     delay(1000);
   bmpDraw("BMP_9.bmp", 0, 0);  
     delay(1000);
   bmpDraw("BMP_10.bmp", 0, 0);  
     delay(1000);   
}


// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

#define BUFFPIXEL 20

void bmpDraw(char *filename, int x, int y) {
  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;

  if((x >= LCD_XSIZE_TFT) || (y >= LCD_YSIZE_TFT)) return;

  Serial.println();
  Serial.print(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL) {
    Serial.println(F("File not found"));
    return;
  }

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.println(F("File size: ")); 
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); 
    Serial.println(bmpImageoffset, DEC);

    // Read DIB header
    Serial.print(F("Header size: ")); 
    Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);

    if(read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); 
      Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size: "));
        Serial.print(bmpWidth);
        Serial.print('x');
        Serial.println(bmpHeight);

        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;

        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if(bmpHeight < 0) {
          bmpHeight = -bmpHeight;
          flip      = false;
        }

        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if((x+w-1) >= LCD_XSIZE_TFT)  w = LCD_XSIZE_TFT  - x;
        if((y+h-1) >= LCD_YSIZE_TFT) h = LCD_YSIZE_TFT - y;

        // Set TFT address window to clipped image bounds

        for (row=0; row<h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
          // (avoids a lot of cluster math in SD library).
          if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
          pos = bmpImageoffset + row * rowSize;
          if(bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }

          for (col=0; col<w; col++) { // For each column...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              // Push LCD buffer to the display first
              if(lcdidx > 0) {
                  ER5517.DrawPixel(col, row, lcdbuffer[lcdidx]);
                lcdidx = 0;
                first  = false;
              }

              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx] = color565(r,g,b);
              ER5517.DrawPixel(col, row, lcdbuffer[lcdidx]);
          } // end pixel

        } // end scanline

        // Write any remaining data to LCD
        if(lcdidx > 0) {
            ER5517.DrawPixel(col, row, lcdbuffer[lcdidx]);
        } 

        Serial.print(F("Loaded in "));
        Serial.print(millis() - startTime);
        Serial.println(" ms");

      } // end goodBmp
    }
  }

  bmpFile.close();
  if(!goodBmp) Serial.println(F("BMP format not recognized."));

}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
  return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}

byte decToBcd(byte val){
  // Convert normal decimal numbers to binary coded decimal
  return ( (val/10*16) + (val%10) );
}

The TFTMC043_7.h file is fairly big (over 77000 characters), but I can add it if needed (if it'll fit!!).

TIA

800 * 480 = 8 * 48000 bit = 48000 bytes, so how do you get 77000 ?

Looks like the sketch is reading in a bitmap stored as 24-bit color (8 bits each for red/green/blue), then converting to 16-bit RGB565 format. Storing the bitmap in the correct RGB565 format would reduce the amount of data read from the SD card by 1/3, and free up some of the memory used for the conversion.

File size, not bitmap size ??

That's the size of the .h file from the LCD's library. It's not the size of an image file.

I don't think that would help. If there were functions in there to display images in a faster way, I guess the example sketch would be using them.

With enough expertise, determination, ingenuity etc. it's almost always possible to speed things up. But it would take a great effort and lots of time, if you already had the years of expertise that would be needed.

However, would that effort be worth it? This is a very high resolution display, both in terms of pixel count and colour depth. I've not heard of anyone using such a display with a controller as basic as an Uno before. Using an Uno to drive this display is like digging a tunnel with a teaspoon.

To do justice to this display, in my opinion, would require an Arduino with much more speed and memory capacity, like an ESP32 or a RP2040, and a probably complete re-write of the library.

Sorry to be giving bad news. There's an important lesson here: ask the forum for advice before you purchase hardware.

There are rumors and own experience that SPI is terribly slow on Arduino UNO.
The SPI class for Arduino UNO makes things worse. It doesn't allow the maximum possible speed as possible by HW, because of the read/write functionality on the transfer method.
(long gaps when looking at the signals with a logic analyzer).

You could speed things up a bit by converting the bitmap image to RGB565 format and storing that as binary data on the SD card. That allows you to read data off the SD card and directly write to the display, without having to do any data conversion, and more importantly reduces the amount of data read from the SD card.

The sketch you posted uses a 20-pixel buffer, 40 bytes for lcdbuffer, and 60 bytes for sdbuffer, plus a bit more ram for breaking out the r/g/b components of the 24-bit color and converting to RGB565. Eliminating all the code and ram needed for the conversion would allow for at least a 50-pixel buffer, and you can get a bit more available ram if you go through all the Serial.print() code and make sure to use the F() macro when possibly.

Thanks to all for the thoughts and suggestions.

The display and Arduino shield was chosen by my client who wanted to be able to display hi-res images in a project case he is building. Luckily I don't think the speed issue will be a deal breaker for him, but it may convince him to rethink how he uses the display!

And just to confirm, yes the 77000 char file size is indeed the size of the .h file from the TFTMC043_7 library

I've decided to take @PaulRB 's advice and throw myself at the feet of the forum members and ask the question(s) I should have asked originally!

Can anyone suggest a 4.3" lcd display and processor combination that would enable me to display full screen colour bitmaps without having to wait for an inordinately long time when switching from one to another?

It would be handy if I could make use of the display screen I have described above, but I'm open to suggestions.

TIA!

I think this indicates that the Uno's SPI is too fast relative to the speed of its CPU. The CPU isn't fast enough to keep the SPI hardware busy.

If you don't have the time and coding experience to completely or significantly re-write that library, then I suspect the only thing that's going to help much is an Arduino with a much faster CPU (more megahertz).

Teensy 4.0 could be an option. At 600MHz, those loading times might come down to around 2 seconds. Teensy 4.1 has a built-in SD card reader, which may also help by being better optimised than using an external SD card reader.

The link you gave for the display has a demo video showing the images loading in approximately 10 seconds using a Mega, but unfortunately the code for the demo is not in the downloads.

Is your code the SD test example? Doesn't really seem like it should take 60 seconds to display an image. The SPI bus is set to 8MHz, around 1MB of data per second, so even with inefficient SPI code and a lot of overhead it shouldn't take that long to transfer an 800x480 image.