Hi all,
First post here and probably jumping in the deep end with my current skill level. However, that being said, I'm working on a TFT project which is going to be used in a vehicle to display information like speed, tachometer and the likes. I chose the Teensy 3.5 because I wanted a good amount of performance with a fairly nice GUI interface too. I also wanted it to have the ability to display an animation after initialisation mainly for look and feel reasons.
Now to do this, my logical assumption was to do a quick check to see if even using some code to display standard GIFs, however I saw people saying to instead load the animation frames as BMP and display them sequentially instead. So taking a video I wanted to use, I converted it to BMP frames at around 20 FPS and named the frames 0.bmp, 1.bmp etc... And put them on the SD card under a folder to keep them together.
Using the example code spitftbitmap.ino, I ran the images through without delays and it end up displaying at about what I consider 3 FPS. I wasn't expecting high FPS but it's incredibly slow at about 400ms per frame. From what I've read from previous topics it could just be loading it from SD is slowing it down. I've tried to use USE_TEENSY3_CODE to no avail (compiler says that "BUILTIN_SDCARD" wasn't previously declared).
So, being I'm very much out of my comfort depth, I'd thought Id ask here incase it's been done or I'm doing it entirely wrong.
The end goal is to have the frames displayed as best they can to acheive the look, essentially this video but at a correct frame rate:
To answer some inevitable questions,
I have all the original images that i've use and the barely modified and thus perhaps poor implementation code is here:
First post here and probably jumping in the deep end with my current skill level. However, that being said, I'm working on a TFT project which is going to be used in a vehicle to display information like speed, tachometer and the likes. I chose the Teensy 3.5 because I wanted a good amount of performance with a fairly nice GUI interface too. I also wanted it to have the ability to display an animation after initialisation mainly for look and feel reasons.
Now to do this, my logical assumption was to do a quick check to see if even using some code to display standard GIFs, however I saw people saying to instead load the animation frames as BMP and display them sequentially instead. So taking a video I wanted to use, I converted it to BMP frames at around 20 FPS and named the frames 0.bmp, 1.bmp etc... And put them on the SD card under a folder to keep them together.
Using the example code spitftbitmap.ino, I ran the images through without delays and it end up displaying at about what I consider 3 FPS. I wasn't expecting high FPS but it's incredibly slow at about 400ms per frame. From what I've read from previous topics it could just be loading it from SD is slowing it down. I've tried to use USE_TEENSY3_CODE to no avail (compiler says that "BUILTIN_SDCARD" wasn't previously declared).
So, being I'm very much out of my comfort depth, I'd thought Id ask here incase it's been done or I'm doing it entirely wrong.
The end goal is to have the frames displayed as best they can to acheive the look, essentially this video but at a correct frame rate:
To answer some inevitable questions,
- Yes, I could forego the use of animations and just display a static image, but I would like to try to see if this even works first
- I am loading them from SD card so that I can change out the GUI files and update them without hardboiled code. (Using JSON to define file names, where, how many and how fast in future)
- Screen is using a different set of SPI pins because I would be using the Audio sheild aswell
- I have limited knowledge on the DMAs or Async of the teensy 3.5 even though I've tried to follow it best I can.
I have all the original images that i've use and the barely modified and thus perhaps poor implementation code is here:
Code:
/************************************************** *
This is our Bitmap drawing example for the Adafruit ILI9341 Breakout and Shield
----> http://www.adafruit.com/products/1651
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 <ILI9341_t3n.h> // Hardware-specific library
#include <SPI.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_DC 20
#define TFT_CS 21
#define TFT_RST 255 // 255 = unused, connect to 3.3V
#define TFT_MOSI 7
#define TFT_SCLK 14
#define TFT_MISO 12
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);
#define SD_CS BUILTIN_SDCARD
void setup(void) {
// Keep the SD card inactive while working the display.
pinMode(SD_CS, INPUT_PULLUP);
delay(200);
tft.begin();
tft.fillScreen(ILI9341_BLUE);
tft.useFrameBuffer(1);
tft.updateScreenAsync();
Serial.begin(9600);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.println(F("Waiting for Arduino Serial Monitor..."));
//while (!Serial) {
//if (millis() > 8000) break;
//}
Serial.println(SD_CS);
Serial.print(F("Initializing SD card..."));
tft.println(F("Init SD card..."));
while (!SD.begin(SD_CS)) {
Serial.println(F("failed to access SD card!"));
tft.println(F("failed to access SD card!"));
delay(2000);
}
Serial.println("OK!");
}
void loop() {
tft.fillScreen(ILI9341_GREEN);
// Draw a few frames only to compare load time
bmpDraw("ford/0.bmp", 0, 0);
delay(5000);
bmpDraw("ford/25.bmp", 0, 0);
delay(5000);
bmpDraw("ford/50.bmp", 0, 0);
delay(5000);
}
// 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 for tiny AVR chips.
// Larger buffers are slightly more efficient, but if
// the buffer is too large, extra data is read unnecessarily.
// For example, if the image is 240 pixels wide, a 100
// pixel buffer will read 3 groups of 100 pixels. The
// last 60 pixels from the 3rd read may not be used.
#define BUFFPIXEL 240
//================================================== =========
// Try Draw using writeRect
void bmpDraw(const 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)
uint16_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();
uint16_t awColors[320]; // hold colors for one row at a time...
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))) {
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;
}
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++];
awColors[col] = tft.color565(r, g, b);
} // end pixel
tft.writeRect(0, row, w, 1, awColors);
} // end scanline
Serial.print(F("Loaded in "));
Serial.print(millis() - startTime);
Serial.println(" ms");
} // end goodBmp
}
}
bmpFile.close();
tft.updateScreen();
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;
}