1.8" TFT w/SD loading files from 'if' statement

Help needed please.

I have a 1.8" TFT with integraded SD card attached to an Arduino UNO. This also has an RF receiver.

On a second Arduino UNO, I have an RF transmitter and four buttons. When I press each of the buttons in turn, the relative messages are received on the first UNO and a corresponding message is displayed on the TFT. All works well until I added code in an attempt to load a corresponding graphics file from the integrated SD card reader.

When I load the sketch, I get "Initializing SD card...OK!", but the messages don't display nor do the graphics. I have pasted the sketch below. Any help will be very much appreciated.

Thanks

#define mosi 11
#define cs   10
#define dc   9
#define rst  8
#include <VirtualWire.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <SD.h>
#include <VirtualWire.h>
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
#define SD_CS    4// Chip select line for SD card



void setup(void) {
  tft.initR(INITR_BLACKTAB);
  Serial.begin(9600);


  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    return;
  }
  Serial.println("OK!");
}

void loop() {

  tft.fillScreen(ST7735_BLACK);

  tft.setTextSize(2);


  tft.setCursor(5, 20);

  tft.setTextWrap(false);

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    int i;
    char msg;	
    for (i = 0; i < buflen; i++) 
    {
      char nChar=buf[i];

      if(nChar == 49){
        Serial.print ("Message 1");
        Serial.println (" ");
        tft.fillScreen(ST7735_BLUE);
        tft.setTextColor(ST7735_WHITE);
        tft.print ("Message 1");
        bmpDraw("message1.bmp", 0, 0);
      }
      delay (1000);

      if(nChar == 50){
        Serial.print ("Message 2");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_BLACK);
        tft.print ("Message 2");
        bmpDraw("message2.bmp", 0, 0);
      }
      delay (1000);

      if(nChar == 51){
        Serial.print ("Message 3");
        Serial.println (" ");
        tft.fillScreen(ST7735_YELLOW);
        tft.setTextColor(ST7735_BLUE);
        tft.print ("Message 3");
        bmpDraw("message3.bmp", 0, 0);
      }
      delay (1000);

      if(nChar == 52){
        Serial.print ("Message 4");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_RED);
        tft.print ("Message 2");
        bmpDraw("message2.bmp", 0, 0);
      }
      delay (1000);

    }
  }
}

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint8_t 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 buffer (R+G+B 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();

  if((x >= tft.width()) || (y >= tft.height())) return;

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

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

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); 
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); 
    Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("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("Bit Depth: "); 
      Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("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) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line. .
          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 pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r,g,b));
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

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

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

Not all SPI clients are mutually compatible - in particular some require the clock to be LOW when idle,
others require the clock to be HIGH when idle (or at least are documented to do so).
If the devices use different clock speeds, clock phases or bit order you will need to reset the
relevant state of the SPI hardware when switching from one device to another - it gets a little tricky.

Some devices are SPI-like, but are not actually SPI compatible (don't release the MISO line, or fail to
honour the CS line). Check carefully for this - some chips allow exact SPI behaviour to be setup in
control registers too (which are accessed by SPI!)

Why are you delay()ing 4 seconds between processing characters in the message?

Hi Paul

They delay was (is) in there as I had some other processes going on before I cut the sketch down. I don't think that would affect anything (would it?)

I don't think that would affect anything (would it?)

How long would it take you to read this post with a four second delay after reading each character?

Hi Paul

Good point. I've commented out the all of the delays, but still no graphics or text on the TFT or text in the seial monitor.

I've commented out the all of the delays, but still no graphics or text on the TFT or text in the seial monitor.

I'd suspect, then, that there is some reason that tft.initR() is blocking, and not returning. Do the Serial.begin() and Serial.print() FIRST.

Hi

I tried the Serial.begin() and Serial.print() first but the problem still exists.

Thanks

Still not got it to work. Any suggestions will be very much appreciated

Still not got it to work.

Still not posted your current code.

Have you tried any of the examples that came with the library? Comment out 90% of your code. Add stuff back one function at a time until the code no longer works properly. Let us know what function causes issues.

Hi

Yes, the examples with the library work fine. Here is the latest version of my code

#define mosi 11
#define cs   10
#define dc   9
#define rst  8
#include <VirtualWire.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <SD.h>
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);
#define SD_CS    4// Chip select line for SD card



void setup(void) {

  Serial.begin(9600);


  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    return;
  }
  Serial.println("OK!");
  tft.initR(INITR_BLACKTAB);
}

void loop() {

  tft.fillScreen(ST7735_BLACK);

  tft.setTextSize(2);


  tft.setCursor(5, 20);

  tft.setTextWrap(false);

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    int i;
    char msg;	
    for (i = 0; i < buflen; i++) 
    {
      char nChar=buf[i];

      if(nChar == 49){
        Serial.print ("Message 1");
        Serial.println (" ");
        tft.fillScreen(ST7735_BLUE);
        tft.setTextColor(ST7735_WHITE);
        tft.print ("Message 1");
        bmpDraw("dinner.bmp", 0, 0);     
      }


      if(nChar == 50){
        Serial.print ("Message 2");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_BLACK);
        tft.print ("Message 2");
        bmpDraw("message2.bmp", 0, 0);
      }


      if(nChar == 51){
        Serial.print ("Message 3");
        Serial.println (" ");
        tft.fillScreen(ST7735_YELLOW);
        tft.setTextColor(ST7735_BLUE);
        tft.print ("Message 3");
        bmpDraw("message3.bmp", 0, 0);
      }


      if(nChar == 52){
        Serial.print ("Message 4");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_RED);
        tft.print ("Message 2");
        bmpDraw("message2.bmp", 0, 0);
      }


    }
  }
}

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint8_t 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 buffer (R+G+B 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();

  if((x >= tft.width()) || (y >= tft.height())) return;

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

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

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); 
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); 
    Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("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("Bit Depth: "); 
      Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("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) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line. .
          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 pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r,g,b));
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

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

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

Yes, the examples with the library work fine.

Then, it's some addition to the example that you made that is causing you problems. Perhaps you are out of memory. The UNO has only 2K of SRAM, and the SD class takes over 1/4 of that for the buffer alone.

http://playground.arduino.cc/Code/AvailableMemory

Hi

Thanks for the link. I used the following example

Firstly I declared :

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

and called it using:

Serial.println(freeRam());

from within my program. The result was "961"

The result was "961"

In which code? The one that didn't even print the first message to the Serial Monitor?

In both codes that I've posted no message has been displayed on the TFT or Seial Monitor.

The memory figure of 961 that I quoted in my last reply above was after I had commented out the bmpDraw() and Serial.Print() lines of code. When not commented out, the memory was "689"

In both codes that I've posted no message has been displayed on the TFT or Seial Monitor.

So, you are just making shit up?

The memory figure of 961 that I quoted in my last reply above was after I had commented out the bmpDraw() and Serial.Print() lines of code.

You didn't mention that little fact. Nor, show what you commented out.

When not commented out, the memory was "689"

Not likely. The free space might have been 689. It was most certainly not "689".

If you comment out nothing, and get nothing printed to the serial monitor, how do you KNOW that the value is 689 (or "689"). You need to be consistent in what you are saying.

Something like "This code:

prints nothing.

This code:
<more code, with stuff commented out>
prints "xxxx".

This code:

prints "yyyy"".

Paul

I mentioned in my original post

I have a 1.8" TFT with integraded SD card attached to an Arduino UNO. This also has an RF receiver.

On a second Arduino UNO, I have an RF transmitter and four buttons. When I press each of the buttons in turn, the relative messages are received on the first UNO and a corresponding message is displayed on the TFT. All works well until I added code in an attempt to load a corresponding graphics file from the integrated SD card reader.

When I load the sketch, I get "Initializing SD card...OK!", but the messages don't display nor do the graphics. I have pasted the sketch below. Any help will be very much appreciated

i.e that messages don't display nor do the graphics.

I apologise for any confusion with putting quotation marks around the memory figures. Also for apologies for not mentioning that I was working through the sketch, commenting various things out (as you suggested) when I tried the memory test. I later did say that there were two figures for the available memory, one with lines commented out and one with no lines commented out.

When I load the sketch, I get "Initializing SD card...OK!", but the messages don't display nor do the graphics.

But, you also said:

I've commented out the all of the delays, but still no graphics or text on the TFT or text in the seial monitor.

To me, there's a big difference between NO text and no text after...

I know that it can be a pain, but you need to comment out stuff until the code will work, then uncomment stuff one line at a time, until the program no longer works. The last line uncommented is the culprit.

Sorry again for the confusion.

When I said

no graphics or text on the TFT or text in the seial monitor

I meant no messages that were received over RF (I was still seeing "Initializing SD card...OK!" in the Serial Monitor).

I will try your suggestion and post if I can find the culprit.

Thanks again for all your help

By commenting out all of the Serial.Print() statements in void setup() I have managed to get the received messages to display in the Serial Monitor and on the TFT.

The problem now is that I am unable to load the associated images even though I have confirmed that they are on the SD card by running the Cardinfo example sketch. I get the following error message:

Loading image 'dinner.bmp'
File not found

Here is the code of the sketch:

#define mosi 11
#define cs   10
#define dc   9
#define rst  8
#include <SD.h>
#include <VirtualWire.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library
#include <SPI.h>
#include <VirtualWire.h>
Adafruit_ST7735 tft = Adafruit_ST7735(cs, dc, rst);

#define SD_CS    4

void setup()
{
  // tft.initR(INITR_BLACKTAB);

  Serial.begin(9600);
  /* Serial.print("Initializing SD card...");
   if (!SD.begin(SD_CS)) {
   Serial.println("failed!");
   return;*/
  vw_set_rx_pin(7);          
  vw_set_ptt_inverted(true); 
  vw_setup(2000);	 
  vw_rx_start();      
  tft.initR(INITR_BLACKTAB);

  //}
  // Serial.println("OK!");

}


void loop()
{

  tft.fillScreen(ST7735_BLACK);
  tft.setTextSize(2);
  tft.setCursor(5, 20);
  tft.setTextWrap(false);

  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // Non-blocking
  {
    int i;
    char msg;

    for (i = 0; i < buflen; i++) 
    {
      char nChar=buf[i];
      if(nChar == 49){
        Serial.print ("Message 1");
        Serial.println (" ");
        tft.fillScreen(ST7735_BLUE);
        tft.setTextColor(ST7735_WHITE);
        tft.print ("Message 1");
        //delay (1000);
        bmpDraw("dinner.bmp", 0, 0);
        delay (1000);
      }


      if(nChar == 50){
        Serial.print ("Message 2");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_BLACK);
        tft.print ("Message 2");
        //delay (1000);
        bmpDraw("drink.bmp", 0, 0);
        delay (1000);
      }


      if(nChar == 51){
        Serial.print ("Message 3");
        Serial.println (" ");
        tft.fillScreen(ST7735_YELLOW);
        tft.setTextColor(ST7735_BLUE);
        tft.print ("Message 3");
        //delay (1000);
        bmpDraw("phone.bmp", 0, 0);
        delay (1000);
      }


      if(nChar == 52){
        Serial.print ("Message 4");
        Serial.println (" ");
        tft.fillScreen(ST7735_WHITE);
        tft.setTextColor(ST7735_RED);
        tft.print ("Message 4");
        //delay (1000);
        bmpDraw("noise.bmp", 0, 0);
        delay (1000);
      }



    }
  }
}
#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint8_t 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 buffer (R+G+B 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();

  if((x >= tft.width()) || (y >= tft.height())) return;

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

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

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print("File size: "); 
    Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print("Image Offset: "); 
    Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print("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("Bit Depth: "); 
      Serial.println(bmpDepth);
      if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed

        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print("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) >= tft.width())  w = tft.width()  - x;
        if((y+h-1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x+w-1, y+h-1);

        for (row=0; row<h; row++) { // For each scanline...

          // Seek to start of scan line. .
          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 pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }

            // Convert pixel from BMP to TFT format, push to display
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            tft.pushColor(tft.Color565(r,g,b));
          } // end pixel
        } // end scanline
        Serial.print("Loaded in ");
        Serial.print(millis() - startTime);
        Serial.println(" ms");
      } // end goodBmp
    }
  }

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

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