21 seconds to display BMP from SD Card on Elegoo 2.8 TFT Display with Mega2560 Pro

I'm working on a project which uses a Elegoo 2.8" 240x320 TFT LCD display hardwired (breakout) to a Mega2560 Pro (mini) board. Everything works as expected, but the bmp image display draws extremely slowly on the screen, taking about 21 seconds to reveal, every time. My project displays several lines of text over top of the image, and also drives a 24 LED NeoPixel ring, which all work perfectly. I load the BMP upon power up as a background image one time, which takes 21 seconds, then everything works with very good speed thereafter. I've stripped my code down to only include the necessary lines to load and display the BMP. It's pretty much the example code which came with the screen. Are there any obvious errors or changes I can make to improve the drawing speed of the image? Here is my code:

//External Libraries to Include
#include <Elegoo_TFTLCD.h> // Hardware-specific library
#include <SD.h>            // SD Card library

//LCD Screen Settings
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0
#define PIN_SD_CS 53 // Elegoo SD shields and modules: pin 53
#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

// ************  LCD Screen Setup ************************
// If using the shield, all control and data lines are fixed, and
// a simpler declaration can optionally be used:
// Elegoo_TFTLCD tft;

#define MAX_BMP         10                      // bmp file num
#define FILENAME_LEN    20                      // max file name length

const int __Gnbmp_height = 320;                 // bmp hight
const int __Gnbmp_width  = 240;                 // bmp width
unsigned char __Gnbmp_image_offset  = 0;        // offset
int __Gnfile_num = 1;                           // num of file
char __Gsbmp_files[1][FILENAME_LEN] =           // add file name here
File bmpFile;
#define BUFFPIXEL       60                      // must be a divisor of 240 
#define BUFFPIXEL_X3    180                     // BUFFPIXELx3

// Assign human-readable names to some common 16-bit color values:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

bool imgDisp = true;            //Flag to display (true) or hide (false) background image

void setup() {


  //Load Image
  uint16_t identifier = 0x9341;
  tft.fillScreen(BLACK);  //Blackout screen
  //Init SD_Card
  pinMode(53, OUTPUT);
  if (!SD.begin(53)) {
    Serial.println("SD Initialization Failed!");
    tft.setCursor(120, 10);
    tft.println("SD Card Init fail.");   
  Serial.println("SD Initialization Success");
    for(unsigned char i=0; i<__Gnfile_num; i++) {
        bmpFile = SD.open(__Gsbmp_files[i]);
        if (! bmpFile) {
            Serial.println("Didn't Find BMP Image");
            tft.setTextColor(WHITE);    tft.setTextSize(1);
            tft.println("Didn't Find BMP Image");
            while (1);
        if(! bmpReadHeader(bmpFile)) {
            Serial.println("Bad BMP Image");
            tft.setTextColor(WHITE);    tft.setTextSize(1);
            tft.println("Bad BMP Image");

          if(imgDisp == true){
            tft.setRotation(0);  //Set screen orientation for BMP
            bmpdraw(bmpFile, 0, 0);

void loop() {

void bmpdraw(File f, int x, int y)

    uint32_t time = millis();

    uint8_t sdbuffer[BUFFPIXEL_X3];                 // 3 * pixels to buffer

    for (int i=0; i< __Gnbmp_height; i++) {
        for(int j=0; j<(240/BUFFPIXEL); j++) {
            bmpFile.read(sdbuffer, BUFFPIXEL_X3);
            uint8_t buffidx = 0;
            int offset_x = j*BUFFPIXEL;
            unsigned int __color[BUFFPIXEL];
            for(int k=0; k<BUFFPIXEL; k++) {
                __color[k] = sdbuffer[buffidx+2]>>3;                        // read
                __color[k] = __color[k]<<6 | (sdbuffer[buffidx+1]>>2);      // green
                __color[k] = __color[k]<<5 | (sdbuffer[buffidx+0]>>3);      // blue
                buffidx += 3;

      for (int m = 0; m < BUFFPIXEL; m ++) {
              tft.drawPixel(m+offset_x, i,__color[m]);
//    Serial.print(millis() - time, DEC);
//    Serial.println(" ms");

boolean bmpReadHeader(File f) 
    // read header
    uint32_t tmp;
    uint8_t bmpDepth;
    if (read16(f) != 0x4D42) {
        // magic bytes missing
        return false;

    // read file size
    tmp = read32(f);
//    Serial.print("size 0x");
//    Serial.println(tmp, HEX);

    // read and ignore creator bytes

    __Gnbmp_image_offset = read32(f);
//    Serial.print("offset ");
//    Serial.println(__Gnbmp_image_offset, DEC);

    // read DIB header
    tmp = read32(f);
//    Serial.print("header size ");
//    Serial.println(tmp, DEC);
    int bmp_width = read32(f);
    int bmp_height = read32(f);
    if(bmp_width != __Gnbmp_width || bmp_height != __Gnbmp_height)  {    // if image is not 320x240, return false
        return false;

    if (read16(f) != 1)
    return false;

    bmpDepth = read16(f);
//    Serial.print("bitdepth ");
//    Serial.println(bmpDepth, DEC);

    if (read32(f) != 0) {
        // compression not supported!
        return false;

//    Serial.print("compression ");
//    Serial.println(tmp, DEC);

    return true;

// These read data from the SD card file and convert them to big endian
// (the data is stored in little endian format!)

uint16_t read16(File f)
    uint16_t d;
    uint8_t b;
    b = f.read();
    d = f.read();
    d <<= 8;
    d |= b;
    return d;

uint32_t read32(File f)
    uint32_t d;
    uint16_t b;

    b = read16(f);
    d = read16(f);
    d <<= 16;
    d |= b;
    return d;

Just a thought....

Is the BMP resolution the same as the display resolution? If it is much bigger it might be taking time to convert.


Did you test the speed of your SD card ? Did you try another one ?

How much time it takes to do this ?

bmpFile.read(sdbuffer, BUFFPIXEL_X3);

And this ?

for (int m = 0; m < BUFFPIXEL; m ++) {
  tft.drawPixel(m+offset_x, i,__color[m]);

Edit: in the examples of that library GitHub - adafruit/TFTLCD-Library: Arduino library for 8-bit TFT LCDs such as ILI9325, ILI9328, etc they use method pushColors, which will be (much) faster than drawPixel because it doesn't need to set coordinates for every pixel. I suggest that you try this library.

The Elegoo library has the pushColors function, as well as drawRGBBitmap in the Elegoo_GFX library ( the single line of image data can be treated as an image that is the width of the line with a height of one pixel).

From the library comments, it appears the Elegoo libraries are derived from the Adafruit libraries.

Yes, the image is 240x320, 24bit BMP, which is what was recommended with the TFT LCD documentation. Good thought though!

Can the display reproduce 24bit color? Perhaps you could reduce the color pallet.

I tried a second, and much newer card with the same result.

I also put a timer on:
bmpFile.read(sdbuffer, BUFFPIXEL_X3); which was 0-5ms, 1280 times, totaling under 2 seconds.

When I put a timer on the tft.drawPixel line though, it was 15ms, 1280 times, for a total of 19.2s, which is the majority of the delay. I'll see if I can figure out the pushColors option and see if that helps.

Thanks for the great suggestions!

I do have the Elegoo_GFX Library, but I don't see pushColors or drawRGBBitmap. I downloaded an Arduino_GFX_AS that appears to have it. I'll see if I can get that one to work with my project.

Thanks for the help!

I must have gotten confused looking at all the libraries in multiple windows.
The Elegoo_TFTLCD has the pushColors function, but I don't see the drawRGBBItmap function in either library so it was probably in Adafruit_GFX.