Avatar
Bitmap Images on ST7735 TFT with Arduino and SD Card
Feb 13th, 2021 | by: ELECTRONOOBS | Views 1442
Views 1442

Drawing bitmap images (.BMP format) on ST7735 TFT display is quite easy because they are uncompressed images unlike JPEG images (.JPG format). This post shows how to draw bitmap images on the ST7735 TFT using Arduino UNO board where the Arduino loads the BMP images from SD card and print them on the display. The ST7735 TFT is a color display that uses SPI protocol, it has a resolution of 128×160 pixel. This display works with 3.3V only.

Hardware Required:
  • Arduino UNO board
  • ST7735S TFT display module
  • microSD card with FAT16 or FAT32 file system
  • microSD card module adapter
  • Push button
  • 5 x 1k ohm resistor
  • Breadboard
  • Jumper wires
Arduino with ST7735 TFT and SD card circuit
Connections

All Arduino UNO output pins are 5V, connecting a 5V pin directly to the ST7735 display board may damage its controller circuit. To avoid that, I connected each control line of the display to the Arduino board through 1k ohm resistor.

So, the ST7735TFT display is connected to the Arduino board as follows (each one through 1k ohm resistor):
RST pin is connected to Arduino digital pin 5,
CS pin is connected to Arduino digital pin 6,
DC pin is connected to Arduino digital pin 7,
DIN pin is connected to Arduino digital pin 11,
CLK pin is connected to Arduino digital pin 13.

In this project I used microSD card module, this module is supplied from circuit 5V source that comes from the Arduino UNO board. This module contains AMS1117-3V3 voltage regulator which is used to supply the micro SD card with 3.3V. Also this module contains an IC which is 74LVC125A and it’s used as level translator (from 5V to 3.3V).

The microSD card module is connected to the Arduino as follows (from left to right):
The first pin of the micro SD card module (GND) is connected to Arduino GND,
The second pin of the micro SD card module (VCC) is connected to Arduino 5V,
The third pin of the micro SD card module (MISO) is connected to Arduino digital pin 12,
The fourth pin of the micro SD card module (MOSI) is connected to Arduino digital pin 11,
The fifth pin of the micro SD card module (SCK) is connected to Arduino digital pin 13,
The last pin of the micro SD card module (CS) is connected to Arduino digital pin 10.

The digital pins 10, 11, 12 and 13 are hardware SPI module pins of ATmega328P microcontroller (Arduino UNO microcontroller). The SD card and the ST7735 TFT share the same SPI bus. The button which is connected to Arduino digital pin 2 is used as ‘next image’, when pressed the display will show the next image.

Arduino with ST7735 TFT and SD card code:

As an addition to Arduino SPI library and SD library which are both built-in libraries (comes with Arduino IDE), the following code requires two libraries from Adafruit Industries:
The first library is a driver for the ST7735 TFT display which can be installed from Arduino IDE library manager (Sketch —> Include Library —> Manage Libraries …, in the search box write “st7735” and install the one from Adafruit).

The second library is Adafruit graphics library which can be installed also from Arduino IDE library manager.

Both libraries can be installed manually, first download them from the following 2 links:
Adafruit ST7735 TFT library    —->  direct link
Adafruit graphics library        —->  direct link

After the download, go to Arduino IDE —> Sketch —> Include Library —> Add .ZIP Library … and browse for the .zip file (previously downloaded).
The same thing for the other library file.

Hints:
There is no need to specify image names, the Arduino will search and draw all the BMP images located in the main root of the SD card.

The 4 libraries are included in the main code as shown below.
Including Arduino SPI library is optional!


#include <Adafruit_GFX.h>    // include Adafruit graphics library
#include <Adafruit_ST7735.h> // include Adafruit ST7735 display library
#include <SPI.h>             // include Arduino SPI library
#include <SD.h>              // include Arduino SD library


More code

The ST7735 TFT module pins (RST, CS and DC) connections are defined as shown below "1". The other display pins (DIN and CLK) are connected to Arduino hardware SPI pins (digital pin 11 and digital pin 13). The Adafruit ST7735 library is initialized with this line below as well "2". And the TFT display is initialized using the following command "3".


//1. define ST7735 TFT display connections
#define TFT_RST  5   // reset line (optional, pass -1 if not used)
#define TFT_CS   6   // chip select line
#define TFT_DC   7   // data/command line

//2. initialize Adafruit ST7735 TFT library
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

//3. initialize ST7735S TFT display
tft.initR(INITR_BLACKTAB);


Full Arduino code

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include <SD.h>


#define TFT_RST  5          // Reset line for TFT (or see below...)
#define TFT_CS   6          // Chip select line for TFT display
#define TFT_DC   7          // Data/command line for TFT
#define SD_CS    10         // Chip select line for SD card
//D11 -> MOSI SD + SDA TFT
//D12 -> MISO SD
//D13 -> SCK SD + SCL TFT

// For 0.96", 1.44" and 1.8" TFT with ST7735 use
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS,  TFT_DC, TFT_RST);

// For 1.54" TFT with ST7789
//Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS,  TFT_DC, TFT_RST);

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

  while (!Serial) {
    delay(10);  // wait for serial console
  }

  pinMode(TFT_CS, OUTPUT);
  digitalWrite(TFT_CS, HIGH);
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);

  // Use this initializer if you're using a 1.8" TFT
  tft.initR(INITR_BLACKTAB);

  // Use this initializer (uncomment) if you're using a 1.44" TFT
  //tft.initR(INITR_144GREENTAB);

  // Use this initializer (uncomment) if you're using a 0.96" 180x60 TFT
  //tft.initR(INITR_MINI160x80);   // initialize a ST7735S chip, mini display

  // Use this initializer (uncomment) if you're using a 1.54" 240x240 TFT
  //tft.init(240, 240);   // initialize a ST7789 chip, 240x240 pixels

  tft.fillScreen(ST77XX_BLACK);

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

  File root = SD.open("/");
  printDirectory(root, 0);
  root.close();
  
  // change the name here!
  bmpDraw("TTT.BMP", 0, 0);
  // wait 5 seconds
  delay(5000);
}

void loop() {
// uncomment these lines to draw bitmaps in different locations/rotations!
/*
  tft.fillScreen(ST7735_BLACK); // Clear display
  for(uint8_t i=0; i<4; i++)    // Draw 4 parrots
    bmpDraw("parrot.bmp", tft.width() / 4 * i, tft.height() / 4 * i);
  delay(1000);
  tft.setRotation(tft.getRotation() + 1); // Inc rotation 90 degrees
*/
}

// 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) {

  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.setAddrWindow(x, y, x+w-1, y+h-1);

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


void printDirectory(File dir, int numTabs) {
  while (true) {

    File entry =  dir.openNextFile();
    if (! entry) {
      // no more files
      break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
      Serial.print(' ');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
      Serial.println("/");
      printDirectory(entry, numTabs + 1);
    } else {
      // files have sizes, directories do not
      Serial.print(" ");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}


Test Video

This project was tested with original Samsung 32GB and 4GB micro SD cards. The following video shows my breadboard test circuit. If you need the Bitmap images, you have the kink here -> download link: download Images are downloaded from https://wallpaper.mob.org/

2 Comments

  • Rick Sanchez

    about 9 months ago

    Cool tutorial! Thanks!

    ELECTRONOOBS

    about 9 months ago

    Thanks

Login or Sign Up to post comments on this tutorial.