Initializing SD card on a TFT display

I'm trying to load a bit map image from the on board SD card of a 3.5" TFT display. I get an error message that the initializing has failed. I've tried using the methods from a couple of the examples, YouTube videos and a past project, just can't get it right. At this point I don't know if I have something wrong or if I actually have the logic to initialize.

The display is communicating with a Nano Every via SPI. The sketch below has most of the loop logic commented out, I want to concentrate on loading the bit map, once it's working I'll add that as the last case of the switch function.

The SD card is formated as FAT32 with a single file "sunrise.bmp" on the root.

Here is the sketch

// mp3 player with TFT display
// Cycles through circles, and verses sync'd with song
// With Bit map image at end

#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_HX8357.h"
#include <SD.h>
#include <SPI.h>


// These are 'flexible' lines that can be changed
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8 // RST can be set to -1 if you tie it to Arduino's reset
#define SD_CS 7

// Color definitions
#define BLACK    0x0000
#define BLUE     0x001F
#define RED      0xF800
#define GREEN    0x07E0
#define CYAN     0x07FF
#define MAGENTA  0xF81F
#define YELLOW   0xFFE0 
#define WHITE    0xFFFF

#define MAX_BMP 10  // bmp file num
#define FILENAME_LEN  20 //Max filename length

 
File bmpfile;

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

char *verse[] = {"zero", "one", "two", "three", "four"};
byte verseNumber = 1;
const unsigned long verseChange = 2000;
unsigned long markTime = millis();
unsigned long currentTime;
long int test = 0;
char *color[] = {BLACK, BLUE, RED, GREEN, CYAN, MAGENTA, YELLOW, WHITE};
byte colorNumber;
int unsigned long verseDly[20];
int verseDlyIndx;


void setup() {

Serial.begin(9600);
 
  tft.begin();
  tft.fillScreen(BLACK); //clears screen, sets to Black
  tft.setRotation(3);  // rotates screen 90' for landscape mode
  tft.setTextSize(2);
  
// delays between verses
  verseDlyIndx = 1;
  verseDly[1] = 23000;
  verseDly[2] = 27000;
  verseDly[3] = 37000;
  verseDly[4] = 28000;
  verseDly[5] = 27000;
  verseDly[6] = 15000;
  verseDly[7] = 11000;
  verseDly[8] = 32000;
  verseDly[9] = 32000;
  verseDly[10] = 1000;

  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
  }
  else Serial.println("OK!");
  
  bmpDraw("sunrise.bmp", 0, 0);
  
  } // end setup

void loop() {
  // cycle through verses
  currentTime = millis();
  //Serial.print("verseNumber =");
  //Serial.print("\t");
  //Serial.println(verse[verseNumber]);
/*
// fill screen with circles
if(verseNumber == 1){
  circles();
}
  
// Verse delays
if ((currentTime - markTime) > verseDly[verseDlyIndx])
{
  verseNumber ++;
  verseDlyIndx ++;
  Serial.print(verseNumber);
  Serial.print("\t");
  Serial.println(verseDlyIndx);
   
  switch (verseNumber)
  {
    case 1:
      
      tft.fillScreen(BLACK); //clears screen, sets to Black;
      circles();
     
      markTime = millis();
      
     break;
  
    case 2:
      
      tft.fillScreen(BLUE); //clears screen, sets to Black
      tft.setTextColor(CYAN);
           
        // Print verse 2
      tft.setCursor(10, 70);
      tft.print("Every day is so wonderful"); 
    
      tft.setCursor(10, 100);
      tft.print("Then suddenly it's hard to breathe");
    
      tft.setCursor(10, 130);
      tft.print("Now and then I get insecure");
    
      tft.setCursor(10, 160);
      tft.print("From all the pain");
    
      tft.setCursor(10, 190);
      tft.print("I'm so ashamed");
      
      markTime = millis();
      
      break;
  
   case 3:
      
      tft.fillScreen(MAGENTA); //clears screen, sets to Black
      tft.setTextColor(BLACK);
          
        // Print verse 3
      tft.setCursor(10, 70);
      tft.print("I am beautiful no matter what they say"); 
    
      tft.setCursor(10, 100);
      tft.print("Words can't bring me down");
    
      tft.setCursor(10, 130);
      tft.print("I am beautiful in every single way");
    
      tft.setCursor(10, 160);
      tft.print("Yes, words can't bring me down, oh no");
    
      tft.setCursor(10, 190);
      tft.print("So don't you bring me down today");
      
      markTime = millis();
    
      break;

   case 4:
      
      tft.fillScreen(RED); //clears screen, sets to Black
      tft.setTextColor(WHITE);
           
        // Print verse 4
      tft.setCursor(10, 70);
      tft.print("To all your friends you're delirious"); 
    
      tft.setCursor(10, 100);
      tft.print("So consumed in all your doom");
    
      tft.setCursor(10, 130);
      tft.print("Trying hard to fill the emptiness");
    
      tft.setCursor(10, 160);
      tft.print("The pieces gone, left the puzzle undone");
    
      tft.setCursor(10, 190);
      tft.print("Is that the way it is?");
      
      markTime = millis();
    
      break;

   case 5:
      
      tft.fillScreen(CYAN); //clears screen, sets to Black
      tft.setTextColor(BLACK);
            
        // Print verse 5
      tft.setCursor(10, 50);
      tft.print("You are so beautiful"); 

      tft.setCursor(60, 70);
      tft.print("no matter what they say");
    
      tft.setCursor(10, 100);
      tft.print("Words can't bring you down, oh no");
    
      tft.setCursor(10, 130);
      tft.print("you are so beautiful");

      tft.setCursor(60, 150);
      tft.print("in every single way");
    
      tft.setCursor(10, 180);
      tft.print("Yes, words can't bring you down, oh no");
    
      tft.setCursor(10, 210);
      tft.print("So don't you bring me down today");
      
      markTime = millis();
    
      break;

   case 6:
      
      tft.fillScreen(GREEN); //clears screen, sets to Black
      tft.setTextColor(BLACK);
            
        // Print verse 6
      tft.setCursor(10, 70);
      tft.print("No matter what we do"); 
    
      tft.setCursor(10, 100);
      tft.print("(No matter what we do)");
    
      tft.setCursor(10, 130);
      tft.print("No matter what we say");

      tft.setCursor(10, 160);
      tft.print("(No matter what we say)");
    
      tft.setCursor(10, 190);
      tft.print("We're the song inside the tune (Yeah)");
    
      tft.setCursor(10, 220);
      tft.print("Full of beautiful mistakes");
      
      markTime = millis();
    
      break;

   case 7:
      
      tft.fillScreen(WHITE); //clears screen, sets to Black
      tft.setTextColor(MAGENTA);
           
        // Print verse 7
      tft.setCursor(10, 70);
      tft.print("And everywhere we go"); 
    
      tft.setCursor(10, 100);
      tft.print("(And everywhere we go)");
    
      tft.setCursor(10, 130);
      tft.print("The sun will always shine");

      tft.setCursor(10, 160);
      tft.print("(The sun will always, always shine)");
    
      tft.setCursor(10, 190);
      tft.print("And tomorrow we might wake up");

      tft.setCursor(60, 210);
      tft.print("on the other side");
      
      markTime = millis();
    
      break;

   case 8:
      
      tft.fillScreen(BLACK); //clears screen, sets to Black
      tft.setTextColor(BLUE);
            
        // Print verse 8
      tft.setCursor(10, 50);
      tft.print("'cause we are beautiful"); 

      tft.setCursor(60, 70);
      tft.print("no matter what they say");
    
      tft.setCursor(10, 100);
      tft.print("Yes, words won't bring us down, no, no");
    
      tft.setCursor(10, 130);
      tft.print("We are beautiful in every single way");

      tft.setCursor(10, 160);
      tft.print("Yes, words won't bring us down, no, no");
    
      tft.setCursor(10, 190);
      tft.print("So don't you bring me down today");
      
      markTime = millis();
    
      break;

    case 9:
      
      tft.fillScreen(YELLOW); //clears screen, sets to Black
      tft.setTextColor(BLACK);
            
        // Print verse 9
      tft.setCursor(10, 70);
      tft.print("Ooh-oh-oh,yeah"); 
    
      tft.setCursor(10, 100);
      tft.print("Don't you bring me down today");
    
      tft.setCursor(10, 130);
      tft.print("Yeah, ooh");

      tft.setCursor(10, 160);
      tft.print("Don't you bring me down today");
    
      tft.setCursor(10, 190);
      tft.print("Ooh,today");
      
      markTime = millis();
    
      break;
      
   default:
      
      tft.fillScreen(BLACK); //clears screen, sets to Black
            
      } // end switch
    
  } // end ((currentTime - markTime))
*/
} // end loop

void circles() {

 tft.fillCircle(random(0,480), random(0,320), random(10, 40), color[random(1, 8)]);
 delay(250);

} // end void circles

#define BUFFPIXEL 20

void bmpDraw(char *filename, uint8_t x, uint16_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(F("Loading image '"));
  Serial.print(filename);
  Serial.println('\'');

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

  // Parse BMP header
  if(read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.print(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) >= 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.startWrite(); // Start TFT transaction
        tft.setAddrWindow(x, y, w, h);

        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?
            tft.endWrite(); // End TFT transaction
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
            tft.startWrite(); // Start new TFT transaction
          }

          for (col=0; col<w; col++) { // For each pixel...
            // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              tft.endWrite(); // End TFT transaction
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
              tft.startWrite(); // Start new TFT transaction
            }

            // 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
        tft.endWrite(); // End last TFT transaction
        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;
}

Thanks for any help and comments
John

Board? Screen? Schematics of the connection?

The board as mentioned is a Nano Every, the display is a Adafruit 3.5 TFT display, 3.5 TFT 320x480 + Touchscreen Breakout Board w/MicroSD Socket [HXD8357D] : ID 2050 : $39.95 : Adafruit Industries, Unique & fun DIY electronics and kits. The connections are as follows, (I don't know how to draw in out)

Nano ------- TFT

Gnd ------- Gnd
5V ---------- 3 -5V
pin 9 -------D/C
pin 10 ----- CS
pin 11 ----- MOSI
pin 12 ----- MISO
pin 13 ----- CLK
pin 7 ------- card CS

If I uncomment the loop code it works fine to step through the different displays, it's just getting it to read the bmp. Let me know if you need any more info
Thanks

What about that jumper IM2 on board that says connect for SPI

Yes I did solder that together. The sketch displays the verses to a song as the song plays, next step is to add the DFPlayer. That works, each verse is displayed so the display part of it works. For the last step I want to be able to load that image in from the SD card on the display.

thanks for looking into this for me

so this statement fails?

Yes I believe that's true. On the Serial Monitor I get the message
"Initializing SD card ...... failed"

that comes from this if function in Setup.

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

what if you use pin 7 for TFT_CS and let SD library use default pin 10? then use SD.begin() and see if it works

So

  1. swap cs pins on the board TFT_CS goes to 7 and Card_CS goes to 10
  2. Change to
    #define TFT_CS 7
    in code
    3 . change to
    SD.begin();

Good idea, but I'll have to wait until I get home from work to give it a try. I'll let you know as soon as I do.

thanks again

That did it! Works perfectly. I swapped the wiring for the TFT display's CS and the SD card's CS. In the sketch I set TFT_CS to 7 and SD_CS to 10. Got the Initialized OK message and it loaded the bmp. Just to confirm I set it back to what it was and it still failed. On your item #3 above I assumed you wanted me to remove SD_CS from the SD.begin statement. I did and the card fails to initialize.

I also went back to the Adafruit tutorial and confirmed that the wiring for the TFT CS does state to put it to pin 10 but that it can be changed to another pin if needed. In the section on loading the image in the example code they have SD_CS =4. So I assumed I could leave TFT_CS on 10 and set SD_CS to 7. As you have shown me that didn't work so I guess the question is why not?

I never would have thought of swapping those two. Thanks so much for your help.
John

Strange, the Arduino states that SD.begin() will use pin 10 for CS by default, so SD.begin(10) or SD.begin() should be identical. Ah well, I'm glad you got it working