Display images on ST7796S from SD card on RP2040 board

I have an Adafruit Feather RP2040 Adalogger with bmp files on the feather’s SD card that I want to display on an ST7796S TFT SPI screen. The closest I have gotten is with this code:

/***************************************************
  This is our Bitmap drawing example for the Adafruit HX8357 Breakout
  ----> http://www.adafruit.com/products/2050

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include <Adafruit_GFX.h>    // Core graphics library
#include "Adafruit_ST7796S_kbv.h"
#include <SPI.h>
#include "SdFat.h"
//#include <SD.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_RST 8
#define TFT_DC 9
#define TFT_CS 10
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, TFT_RST);

#define SD_CS_PIN 23

SdFat SD;
//FsFile myFile;
SdSpiConfig config(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16), &SPI1);

void setup(void) {
  Serial.begin(9600);

  tft.begin();
  tft.fillScreen(ST7796S_BLUE);
  
  Serial.print("Initializing SD card...");
  if (!SD.begin(config)) {
    Serial.println("failed!");
  }
  Serial.println("OK!");

  bmpDraw("AP1.bmp", 0, 0);
}

void loop() {
}

// 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, uint8_t x, uint16_t y) {

  FsFile     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");
        delay(1000);
      } // 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(FsFile &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(FsFile &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;
}

It gives a Serial output of:

Initializing SD card...OK!

Loading image 'AP1.bmp'
File size: 1027962
Image Offset: 138
Header size: 124
Bit Depth: 24
Image size: 582x588
Loaded in 1372 ms

However it does not display anything other than static on the screen. The information about the file on the SD card is accurate, and it’s consistently accurate when I choose a different file. The file just doesn’t show up on the screen. I will eventually want to display the images in rapid succession to play a video but I am working on 1 frame atm.

It seems like it is able to find, open and read the file, but not able to push it to the display.

Any ideas on where I am going wrong?

Is this not using SPI in the end?

And where in the code are the setSCK, setMOSI and setMISO calls made to tell the library which pins to use?

And this appears to be using SPI1 rather than SPI.

The included libraries haven’t needed me to define the SCK/MOSI/MISO pins for their included examples, such as this one:

//#include "Adafruit_ST7735.h"
//Adafruit_ST7735 tft(10, 9, 8);
//#include "Adafruit_ST7789.h"
//Adafruit_ST7789 tft(10, 9, 8);
#include "Adafruit_ST7796S_kbv.h"
Adafruit_ST7796S_kbv tft(10, 11, 9);
//#include "Adafruit_ILI9341.h"
//Adafruit_ILI9341 tft(10, 9, 8);

#include "bitmap_mono.h"
#include "bitmap_RGB.h"
#include "bitmap_gray.h"
#include "xbm_mono.h"

#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 GREY    0x8410
#define ORANGE  0xE880

void setup()
{
    Serial.begin(9600);

    Serial.println(F("DEMONSTRATE Adafruit_GFX DRAWING METHODS"));
    Serial.println(F(""));

    Serial.println(F("GFX drawBitmap() plots one mono pixel at a time"));
    Serial.println(F("it defaults to transparent plotting"));
    Serial.println(F("unless you specify foreground and background colours"));
    Serial.println(F("it can plot a monochrome bitmap from Flash or SRAM"));

    Serial.println(F(""));
    Serial.println(F("GFX drawXBitmap() plots an XBM bitmap stored in flash"));
    Serial.println(F("GFX drawGrayscaleBitmap() is not shown"));
    Serial.println(F("GFX drawRGBBitmap() plots one colour pixel at a time"));
    Serial.println(F("from Flash or SRAM and with a monochrome transparent bitmask"));
    Serial.println(F(""));
    Serial.println(F("Adafruit TFT libraries optimise drawRGBBitmap() from SRAM"));
    Serial.println(F("it is worth reading one row at a time from flash"));
    Serial.println(F("look at the user functions that do this"));
    Serial.println(F("they are much faster than the GFX drawxxx() base methods"));
    Serial.println(F(""));

#if 0
#elif defined(_ADAFRUIT_ST7735H_)
    tft.initR(INITR_BLACKTAB);
#elif defined(_ADAFRUIT_ST7789H_)
    tft.init(240, 240);
#else
    tft.begin();
#endif
    tft.setRotation(0);
    tft.fillScreen(BLACK);
    delay(1000);
}

int adv_space(int req, String msg = "")
{
    static int cur_y = 0;
    static uint32_t prev_time;
    int ht = tft.height();
    uint32_t t = millis();
    int y = cur_y + req - 16;
    tft.setCursor(0, y); //  64, 144, 224, 304
    tft.print(msg);
    //    tft.print(" ");
    tft.print(t - prev_time);
    tft.print(F("ms"));
    cur_y += req;
    if (cur_y + req > ht) {
        cur_y = 0;
        delay(5000);
        tft.fillScreen(BLACK);
    }
    prev_time = millis();
    return cur_y;
}

void RGB_2_SRAM(int16_t x, int16_t y, const uint16_t rgb_flash[], int16_t w, int16_t h)
{
    uint16_t sram[w];
    for (int row = 0; row < h; row++) {
        memcpy_P(sram, rgb_flash + row * w, w * 2);
        tft.drawRGBBitmap(x, y + row, sram, w, 1);  //Adafruit is fast from SRAM        
    }
}

/*void bmap_2_SRAM(int16_t x, int16_t y, const uint8_t bmap_flash[], int16_t w, int16_t h, uint16_t color, uint16_t bg = 0)
{
    uint16_t sram[w];
    uint8_t c, mask, wid = (w + 7) / 8; //bytes per row
    for (int row = 0; row < h; row++) {
        uint8_t *p = bmap_flash + row * wid;
        mask = 0;
        for (int col = 0; col < w; col++, mask >>= 1) {
            if (mask == 0) c = pgm_read_byte(p++), mask = 0x80;
            sram[col] = (c & mask) ? color : bg;
        }
        tft.drawRGBBitmap(x, y + row, sram, w, 1);  //Adafruit is fast from SRAM        
    }
}*/

void gray_2_SRAM(int16_t x, int16_t y, const uint8_t gray_flash[], int16_t w, int16_t h = 0)
{
    const uint8_t *p = gray_flash;
    uint8_t depth = 4;
    if (h == 0) {  //read geometry from image header instead of function arguments
        depth = pgm_read_word(p + 1);
        w = pgm_read_byte(p + 2);
        h = pgm_read_byte(p + 4);
        p += 6;
    }
    uint16_t sram[w];
    uint8_t sigmask = 0xFF << (8 - depth);
    for (int16_t row = 0; row < h; row++) {
        uint8_t mask = 0, c;
        for (int16_t i = 0; i < w; i++ ) {
            if (mask == 0) {
                c = pgm_read_byte(p++);
                mask = 0xFF;
            }
            uint8_t r = c & sigmask;
            uint16_t color = tft.color565(r, r, r);
            //tft.drawPixel(x + i, y + row, color); //plot each pixel
            sram[i] = color;  //store in buffer
            c <<= depth;
            mask <<= depth;
        }
        tft.drawRGBBitmap(x, y + row, sram, w, 1);  //Adafruit is fast from SRAM        
    }
}

void loop(void)
{
    static int y = 0;
    int x = 5, w = 128, h = 64;
    const int SZ = w * h / 8;
    uint8_t sram[SZ];
    memcpy_P(sram, tractor_128x64, SZ);

    tft.drawBitmap(x, y, tractor_128x64, 128, 64, BLUE);
    y = adv_space(80, F("drawBitmap() transparent flash "));
    tft.drawBitmap(x, y, tractor_128x64, 128, 64, RED, YELLOW);
    y = adv_space(80, F("drawBitmap() background flash "));
    tft.fillRect(x, y, w, h, GREY);
    tft.drawBitmap(x, y, sram, 128, 64, GREEN);
    y = adv_space(80, F("drawBitmap() fill bgd SRAM "));
    tft.drawXBitmap(x, y, truck_128x64_xbm, 128, 64, BLUE);
    y = adv_space(80, F("drawXBitmap() XBM from flash "));

    tft.drawRGBBitmap(x, y, marilyn_64x64, 64, 64);
    y = adv_space(80, F("drawRGBBitmap() flash "));
    w = 64;
    tft.fillRect(x, y, w, h, GREY);
    tft.drawRGBBitmap(x, y, marilyn_64x64, magnify_64x64, 64, 64);
    y = adv_space(80, F("drawRGBBitmap() with mask "));
    w = 64;
    tft.fillRect(x, y, w, h, GREY);
    tft.drawRGBBitmap(x, y, marilyn_64x64, magnify_inv_64x64, 64, 64);
    y = adv_space(80, F("drawRGBBitmap() with mask "));
    //bmap_2_SRAM(x, y, tractor_128x64, 128, 64, YELLOW, RED);
    y = adv_space(80, F("copy bmap to RGB SRAM "));
    RGB_2_SRAM(x, y, marilyn_64x64, 64, 64);
    y = adv_space(80, F("copy RGB flash to RGB SRAM "));
    gray_2_SRAM(x, y, gImage_flower, 123, 0);
    y = adv_space(80, F("copy GrayScale to RGB SRAM "));
#if !defined(__AVR_ATmega328P__)
    gray_2_SRAM(x, y, marilyn_grayx4 + 0, 128, 0);
    y = adv_space(144, F("copy GrayScale to RGB SRAM "));
#endif
}

That code displays bitmaps as expected without me defining those SPI pins.

When I remove the “1” from “&SPI1” to change it to “&SPI”, the SD card fails to initialize.

SdSpiConfig config(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16), &SPI1);

SdSpiConfig config(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16), &SPI);

Those do not seem to be the issue as to why I am unable to display bmp images from my SD card.

Does the same thing happen when you use an image the same size as the display?

Yes. It initializes the SD card, finds the image, loads the image, prints out accurate information about the image, but does not display the image on the screen.

I used this image.

That's hardly surprising if you think about it for a moment. You've changed busses. What else might have changed?

Oh, okay. If you know what the problem is and isn't then I'll leave you to it. I'm sure that the fact that I've had SD cards working with ILI9341, ILI9486, ILI9488, ST7735 and other display types I can't recall off the top of my head with all kinds of boards including 4 different RP2040 based boards means that I don't have any idea of the kinds of problems that tend to occur.

Good luck with your project.

You had mentioned defining the pins in the code but this code from the same library does not require them to be defined. Could you give me some insight as to why that might be?

I say that not because I know what the problem is and isn’t but because, as I previously mentioned, the pins did not need to be defined in the example code and because switching between SPI and SPI1 did not advance the plot any.

Why would it matter if the image is loaded into memory from SPI1? Shouldn’t it be able to be pushed to a display on SPI no matter where it was loaded from?

OK , but that is not the image mentioned in your first post, that image had a size of 582x588 and the image last mentioned is 320x480.

So your program loads an image from the SD card and successfully lists the image name, file size, offset, image size but does not successfully load the image data to your display is that correct?

That is correct.

I don’t have that particular display so I plugged an ILI9341 into your code and modified the pin numbering for the SPI bus and used the default pins for SPI(0). If you want to use SPI(1) you will see the comments on which pins need to be moved. This worked well for me although very slow to load an image. Look at everything I have before the “loop” function and where you need to replace everything ILI9341 with everything ST7796S, don’t forget the one in setup that initializes with a blue screen.



#include <Adafruit_GFX.h>  // Core graphics library
// #include "Adafruit_ST7796S_kbv.h"
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include <SD.h>

#define SD_CS_PIN 15
#define TFT_MISO 16  // default MISO for SPI(0)
#define TFT_CS 17
#define TFT_SCK 18   // default clock for SPI(0)
#define TFT_MOSI 19  // default MOSI for SPI(0)
#define TFT_RST 20
#define TFT_DC 21

#define BUFFPIXEL 20

//Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, TFT_RST);
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

File myFile;

void setup(void) {
  Serial.begin(9600);
  //while (!Serial);
  Serial.print("Initializing SD card...");
  while (!SD.begin(SD_CS_PIN)) {
    Serial.println("Failed!");
    return;
  }
  Serial.println("OK!");

  pinMode(22, OUTPUT);
  digitalWrite(22, HIGH);
  tft.begin();
  tft.setSPISpeed(30000000);
  tft.fillScreen(ILI9341_BLUE);
  tft.setRotation(3);


  bmpDraw("mypicture.bmp", 0, 0);
}

void loop() {
}

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...

          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");
        delay(1000);
      }  // end goodBmp
    }
  }

  bmpFile.close();
  if (!goodBmp) Serial.println(F("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;
}

I forgot to mention I set pin 22 high for the lcd backlight, you probably don’t need that.

Thank you sumguy, I must not have had the SPI pins defined. This loads my images, which I need to resize. You’re right, this loads slow. I will work on something to load faster, or at least push the image all at once rather than from one side to the other. Here is the code I used for anyone interested.

/***************************************************
  This is our Bitmap drawing example for the Adafruit HX8357 Breakout
  ----> http://www.adafruit.com/products/2050

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include <Adafruit_GFX.h>    // Core graphics library
#include "Adafruit_ST7796S_kbv.h"
#include <SPI.h>
#include "SdFat.h"
//#include <SD.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_RST 8
#define TFT_DC 9
#define TFT_CS 10
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
//Adafruit_ST7796S_kbv tft = Adafruit_ST7796S_kbv(TFT_CS, TFT_DC, TFT_RST);

Adafruit_ST7796S_kbv tft(10, 11, 9);

#define SD_CS_PIN 23

SdFat SD;
//FsFile myFile;
SdSpiConfig config(SD_CS_PIN, DEDICATED_SPI, SD_SCK_MHZ(16), &SPI1);

void setup(void) {
  Serial.begin(9600);

  tft.begin();
  tft.setRotation(2);
  tft.fillScreen(ST7796S_BLACK);
  
  Serial.print("Initializing SD card...");
  if (!SD.begin(config)) {
    Serial.println("failed!");
  }
  Serial.println("OK!");

  bmpDraw("AP1.bmp", 0, 0);
}

void loop() {
}

// 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 40

void bmpDraw(char *filename, uint8_t x, uint16_t y) {

  FsFile     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");
        delay(1000);
      } // 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(FsFile &f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}

uint32_t read32(FsFile &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;
}