Need help with ST7735_t3 library and bmpDraw function! Images are not loading right!

Status
Not open for further replies.

NeTgHoSt

Member
Teensy32OLEDSDcard.jpg

20200524_215138.jpg

Hello, first post. New owner of a Teensy 3.2, purchased to replace my Arduino Uno's limited ram situation. I have struggled for months with this "simple" endeavour, so the hardware and libraries used has changed a few times since the beginning of my attempts to make this work; displaying a bitmap image from an attached (Adafruit SPI) MicroSD Card Reader+, to an SPI ST7735s 80x160 "OLED" (clone) display. (I couldn't find the clone display for Fritzing, so I used the Adafruit one. Pins are a bit different, and it runs w/3.3V but my clone is all 5v/3.3v tolerant, running on 5v. I just wired it in fritzing as similar as I could to the real thing.) The current sketch is supposed to play a startup tune, load an image to the display, erase it, and then display temperature readings from a thermistor. The sketch works as intended until after it has loaded maybe 100 or so pixels of the image, and then starts printing repeating blocks of pixels/colors to the display. The patterns of pixels change with every run of the code, and change alot when I change the pixel buffer value in the bmpDraw function. I am currently using Teensyduino, the Teensy Optimized ST7735_t3 library, and SD library. Here is my current code and a photo of the image trying to load on the display:

20200524_212958.jpg

Code:
#include <Adafruit_GFX.h>
#include <SD.h>
#include <ST7735_t3.h> // Hardware-specific library
#include <SPI.h>
#include "pitches.h"

#define TFT_MOSI  11  //a12
#define TFT_SCLK   13  //a13
#define TFT_DC   20 
#define TFT_CS   15  
#define TFT_RST  14
#define SD_CS 17

int tempPin = A4;

int melody[] = {
  NOTE_C4, NOTE_G3, NOTE_G3, NOTE_A3, NOTE_G3, 0, NOTE_B3, NOTE_C4
};

int noteDurations[] = {
  4, 8, 8, 4, 4, 4, 4, 4
};

ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);


void setup(void) {
  pinMode(SD_CS, INPUT_PULLUP);    // keep SD CS high when not using SD card
  analogReference(EXTERNAL);
  tft.initR(INITR_MINI160x80_ST7735S);
  tft.setRotation(3);
  tft.fillScreen(ST7735_BLACK);
  Serial.begin(9600);
  Serial.print("TFT Init!");       // Screen Initialized
  
  musicalnotes();       //Play Startup Sound

   Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    tft.fillScreen(ST7735_BLACK);
    tft.setCursor(5, tft.height()/2 - 6);
    tft.print("Unable to access");
    tft.setCursor(32, tft.height()/2 + 6);
    tft.print("SD card");
    while (1) {
      // do nothing if SD card not working
    }
  }
  Serial.println("OK!");            // Sd Initialized

  bmpDraw("parrot.bmp", 24, 0);
  delay(5000);
  tft.fillScreen(ST7735_GREEN);                 //Green Border (background)
  tft.fillRect(2,2,156,76, ST7735_BLACK);       //Fill rest w/Black
  tft.drawLine(79, 2, 79, 18, ST7735_GREEN);    //Vertical Line
  tft.drawLine(89, 19, 89, 87, ST7735_GREEN);   //Vertical Line 2
  tft.drawLine(2, 19, 157, 19, ST7735_GREEN);   //Horizontal Line
  tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
  tft.setTextSize(2);
  tft.setCursor(4, 3);
  tft.print("Temp*F");
  tft.setCursor(82, 3);
  tft.print("Temp*C");
  tft.setTextSize(4);
  tft.setTextColor(ST7735_RED, ST7735_BLACK);
  
}

void loop() {
  int tempReading = analogRead(tempPin);
  double tempK = log(10000.0 * ((1024.0 / tempReading - 1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK )) * tempK );       //  Temp Kelvin
  float tempC = tempK - 273.15;            // Convert Kelvin to Celcius
  float tempF = (tempC * 9.0)/ 5.0 + 32.0; // Convert Celcius to Fahrenheit

  int TempFInt = tempF;
  int TempCInt = tempC;


  tft.drawNumber(TempFInt, 8, 34);
  tft.drawNumber(TempCInt, 102, 34);
  if (TempFInt < 100) {
    tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
    tft.setCursor(58, 34);
    tft.print("F");
    tft.setTextColor(ST7735_RED, ST7735_BLACK);
    //tft.fillRect(54,34,24,32, ST7735_BLACK);
  }
  delay(100);
  
}

void musicalnotes() {
  for (int thisNote = 0; thisNote < 8; thisNote++) {

    // to calculate the note duration, take one second divided by the note type.
    //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
    int noteDuration = 1000 / noteDurations[thisNote];
    tone(6, melody[thisNote], noteDuration);

    // to distinguish the notes, set a minimum time between them.
    // the note's duration + 30% seems to work well:
    int pauseBetweenNotes = noteDuration * 1.30;
    delay(pauseBetweenNotes);
    // stop the tone playing:
    noTone(6);
  }
}

#define BUFFPIXEL 20

void bmpDraw(const 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
  bmpFile = SD.open(filename);
  if (!bmpFile) {
    Serial.print("File not found");
    tft.fillScreen(ST7735_BLACK);
    tft.setCursor(12, tft.height()/2 - 12);
    tft.print("Unable to");
    tft.setCursor(12, tft.height()/2 - 0);
    tft.print("read file: ");
    tft.setCursor(12, tft.height()/2 + 12);
    tft.setTextColor(ST7735_YELLOW);
    tft.print(filename);
    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'
      Serial.println("No. of Planes: 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.  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("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;
}
Sorry for the long post.. tyia for your help!
 

Attachments

  • 20200524_213822.jpg
    20200524_213822.jpg
    151.6 KB · Views: 44
Last edited by a moderator:
Hi, and welcome!

Might help if you maybe included the bitmap, so we can give it a try.

Also might help if you included the Serial output, which may give some hints about what was read and what is not.

Note: with Teensy and things like this, I often add code at the beginning of setup, to wait for the USB serial port to be ready.

something like:
Code:
void setup() {
    while (!Serial && millis() < 5000) ; // wait up to 5 seconds for USB serial port to be ready
...
 
Thanks KurtE! It looks like I cannot edit my post anymore? I couldn't find the "code" tag before, ty for fixing. Here is the photo I am trying to load:

View attachment parrot.bmp

Uncropped original:

View attachment parrot.bmp


It is the standard 128x160 "parrot.bmp" sample image that is used in the "spitftbitmap" example I based my code from, although I cropped it to 80x80px. I double checked in photoshop that there is no Alpha (transparency) layer screwing anything up. The bmpDraw function should crop the image automatically to fit my display, so for testing, resizing wasn't needed, but was part of my troubleshooting. Here is the output from the serial monitor for the uncropped parrot.bmp:

Code:
TFT Init!Initializing SD card...OK!



Loading image 'parrot.bmp'

File size: 61496

Image Offset: 54

Header size: 40

No. of Planes: 1

Bit Depth: 24

Image size: 128x160

Loaded in 82 ms

The serial monitor seems to work fine without that code, I'd always heard it was necessary for Arduino Leonardo only? If it ever gives me trouble I will add that. The program seems to think the image loads just fine, so I'm wondering what else I should be looking for? I do not have an oscilloscope, or those kind of skills yet, which is why I went for the "foolproof" "Adafruit MicroSD Breakout Board+" which includes level shifting etc.. was trying to avoid having to do this kind of troubleshooting! XD
 
Quick update - Sorry I missed when you posted the image.

Also taking a closer look, I don't think the setAddrWindow and pushColor stuff works in the ST7735_t3 library.

Plus you run into issues with SD and display holding SPI data at the same time...

Here is a quick hacked up version. Note: I did on ST7789 and T4, but should work...

Code:
//#include <Adafruit_GFX.h>
#include <SD.h>
#include <ST7735_t3.h> // Hardware-specific library
#include <ST7789_t3.h> // Hardware-specific library
#include <SPI.h>
#define TFT_MOSI  11  //a12
#define TFT_SCLK   13  //a13
#define TFT_DC   9
#define TFT_CS   10
#define TFT_RST  8
#define SD_CS 7

int tempPin = A4;


ST7789_t3 tft = ST7789_t3(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);


void setup(void) {
  pinMode(SD_CS, INPUT_PULLUP);    // keep SD CS high when not using SD card
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(3);
  tft.fillScreen(ST7735_BLACK);
  Serial.begin(9600);
  Serial.print("TFT Init!");       // Screen Initialized

  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
    tft.fillScreen(ST7735_BLACK);
    tft.setCursor(5, tft.height() / 2 - 6);
    tft.print("Unable to access");
    tft.setCursor(32, tft.height() / 2 + 6);
    tft.print("SD card");
    while (1) {
      // do nothing if SD card not working
    }
  }
  Serial.println("OK!");            // Sd Initialized

  bmpDraw("parrot.bmp", 24, 0);
  delay(5000);
  tft.fillScreen(ST7735_GREEN);                 //Green Border (background)
  tft.fillRect(2, 2, 156, 76, ST7735_BLACK);    //Fill rest w/Black
  tft.drawLine(79, 2, 79, 18, ST7735_GREEN);    //Vertical Line
  tft.drawLine(89, 19, 89, 87, ST7735_GREEN);   //Vertical Line 2
  tft.drawLine(2, 19, 157, 19, ST7735_GREEN);   //Horizontal Line
  tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
  tft.setTextSize(2);
  tft.setCursor(4, 3);
  tft.print("Temp*F");
  tft.setCursor(82, 3);
  tft.print("Temp*C");
  tft.setTextSize(4);
  tft.setTextColor(ST7735_RED, ST7735_BLACK);

}

void loop() {
  int tempReading = analogRead(tempPin);
  double tempK = log(10000.0 * ((1024.0 / tempReading - 1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK )) * tempK );       //  Temp Kelvin
  float tempC = tempK - 273.15;            // Convert Kelvin to Celcius
  float tempF = (tempC * 9.0) / 5.0 + 32.0; // Convert Celcius to Fahrenheit

  int TempFInt = tempF;
  int TempCInt = tempC;


  tft.drawNumber(TempFInt, 8, 34);
  tft.drawNumber(TempCInt, 102, 34);
  if (TempFInt < 100) {
    tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
    tft.setCursor(58, 34);
    tft.print("F");
    tft.setTextColor(ST7735_RED, ST7735_BLACK);
    //tft.fillRect(54,34,24,32, ST7735_BLACK);
  }
  delay(100);

}

#define BUFFPIXEL 20
uint16_t row_buff[320];
void bmpDraw(const 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
  bmpFile = SD.open(filename);
  if (!bmpFile) {
    Serial.print("File not found");
    tft.fillScreen(ST7735_BLACK);
    tft.setCursor(12, tft.height() / 2 - 12);
    tft.print("Unable to");
    tft.setCursor(12, tft.height() / 2 - 0);
    tft.print("read file: ");
    tft.setCursor(12, tft.height() / 2 + 12);
    tft.setTextColor(ST7735_YELLOW);
    tft.print(filename);
    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'
      Serial.println("No. of Planes: 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
        Serial.printf("SAW: %u %u %u %u\n", x, y, x + w - 1, y + h - 1);
        //tft.setAddrWindow(x, y, x + w - 1, y + h - 1);

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

          // 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));
            row_buff[row_buff_index++] = tft.Color565(r, g, b);
          } // end pixel
          tft.writeRect(x, y + row, w, 1, row_buff);
        } // 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;
}
 
Status
Not open for further replies.
Back
Top