Thursday, August 7, 2014

Arduino - TFT LCD display refresh rate part 2

Since January I have been thinking about how to reduce the flashing effect of redrawing characters on the Adafruit 2.2" TFT display running off of an Arduino. Initially I thought I was doing something wrong but then I saw that other projects experience the same slow screen redraw performance. This post isn't about rewriting/optimizing the ILI9340 library that talks to the display. I'm not a good enough programmer to even think about digging into that code. Also I don't think it really is a problem with the library or the display itself. I've seen videos with this display hooked up to a Raspberry Pi smoothly playing video files. I suspect the limitation is the CPU and/or RAM of the Arduino. For my speedometer project I wanted to use an Arduino because it starts up almost instantly and I don't have to worry about shutting it down which is not the case with a Raspberry Pi.

So my goal was to optimize the refresh rate by reducing the amount of screen redraw to the smallest amount possible. To do this I first draw the static elements on the screen (the red box and the mph text). These are never redrawn. Redrawing static items causes a flashing effect. Then the speed value is only updated if it changes. When it does change only digits that have changed are redrawn. For example say the speed is 35 mph and then on the next loop the speed is 36 mph. The 5 would be drawn over with black and then the 6 is drawn in white. This performs the least amount of screen drawing possible.

Here is a video of the progress so far. The code is below the video.



Here is the code


#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9340.h"
#include <SD.h>
#if defined(__SAM3X8E__)
#undef __FlashStringHelper::F(string_literal)
#define F(string_literal) string_literal
#endif
// These are the pins used for the Mega
// for Due/Uno/Leonardo use the hardware SPI pins (which are different)
#define _sclk 52
#define _miso 50
#define _mosi 51
#define _cs 53
#define _rst 7
#define _dc 6
#define SD_CS 5
Adafruit_ILI9340 tft = Adafruit_ILI9340(_cs, _dc, _rst);
int potPin=0;
int count=0;
int prevCount=1;
int countdigits[] = {0, 0, 0};
int prevdigits[] = {0, 0, 0};
int digitpos[] = {30, 90, 150};
int x=0;
void setup() {
Serial.begin(9600);
//Init the SD Card
Serial.print("Initializing SD card...");
if (!SD.begin(SD_CS)) {
Serial.println("failed!");
return;
}
Serial.println("OK!");
//Start the TFT screen and paint it black
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9340_BLACK);
//Draw TRD Logo before turing on backlight
bmpDraw("trdlogo.bmp", 40, 60);
//fade in back lighting
for(int b=0; b < 230; b++){
analogWrite(2, b);
delay(20);
}
//Sleep a while to display the logo
delay(2000);
tft.fillScreen(ILI9340_BLACK);
tft.drawFastHLine(0, 180, 320, ILI9340_RED);
//Draw static screen elements
bmpDraw("trdsml24.bmp", 110, 195);
tft.setTextColor(ILI9340_WHITE);
tft.setTextSize(4);
tft.setCursor(230, 90);
tft.print("mph");
}
void loop(void) {
//This simulates input of a VSS
//This will be replaced with pulse counting of the VSS sensor.
count = analogRead(potPin) / 8;
//Split each digit of the speed into an array
//This will allow printing of just numbers that have changed.
//The speed of a car can exceed a two digit number, certantly
//in kph and occasionally in mph. The splitting is done by
//taking the modulo, dividing or a combination of both.
//Grab the last digit of the speed
countdigits[2] = count % 10;
//How to handle the middle digit depends on if the
//the speed is a two or three digit number
if(count > 99){
countdigits[1] = (count / 10) % 10;
}else{
countdigits[1] = count / 10;
}
//Grab the first digit
countdigits[0] = count / 100;
//Split out the digits of the previous speed
prevdigits[2] = prevCount % 10;
if(prevCount > 99){
prevdigits[1] = (prevCount / 10) % 10;
}else{
prevdigits[1] = prevCount / 10;
}
prevdigits[0] = prevCount / 100;
//Now print the digits on the TFT screen.
//Only execute this block if the speed has changed.
if(count != prevCount){
tft.setTextSize(10);
//Compare each digit to the value from the previous loop.
//The digit will only be redrawn if it has changed.
for(x=0; x < 3; x++){
if(countdigits[x] != prevdigits[x]){
//black out old value first.
//Draw digit in black over the top of white digit
tft.setCursor(digitpos[x], 70);
tft.setTextColor(ILI9340_BLACK);
tft.print(prevdigits[x]);
//print new value in white
if((x == 0) and (count > 99) and (countdigits[x] > 0)){
tft.setCursor(digitpos[x], 70);
tft.setTextColor(ILI9340_WHITE);
tft.print(countdigits[x]);
}
if((x == 1) and (count >= 99)){
tft.setCursor(digitpos[x], 70);
tft.setTextColor(ILI9340_WHITE);
tft.print(countdigits[x]);
}else if((x == 1) and (count < 99) and (countdigits[x] > 0)){
tft.setCursor(digitpos[x], 70);
tft.setTextColor(ILI9340_WHITE);
tft.print(countdigits[x]);
}
if(x == 2){
tft.setCursor(digitpos[x], 70);
tft.setTextColor(ILI9340_WHITE);
tft.print(countdigits[x]);
}
}
}
}
delay(999); //Delay screen updates to simulate 1 second pulse counting.
prevCount = count; //Store current speed for comparison on the next loop.
}
//--------------------------------------------------------------------------------
//I didn't write anything below here. The code below is from the
//Adafruit library and it is used to load images from the SD card.
//--------------------------------------------------------------------------------
// 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 50
void bmpDraw(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
if ((bmpFile = SD.open(filename)) == NULL) {
Serial.print("File not found");
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'
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.");
}
// 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;
}

6 comments:

  1. Hello,

    In case you are still interested, I "re-mixed" (re-wrote) the Adafruit GFX library and some drivers (including IL9341 & IL9342) to squeeze out any wasted cycles (there were a lot). My "PDQ" version is sketch compatible with Adafruit GFX (with a tiny change to init), so you might give it a try (should be "drop in"). All the code and an example sketch/benchmark is at https://github.com/XarkLabs/PDQ_GFX_Libs. You can see a speedup of about 3.5x to 13x (depending on the primitive). Smaller code size too.

    Enjoy,
    Xark

    ReplyDelete
  2. why i got this im new in arduino Documents\Arduino\sketch_apr05b\sketch_apr05b.ino:2:26: fatal error: Adafruit_GFX.h: No such file or directory

    #include "Adafruit_GFX.h"

    ^

    compilation terminated.

    exit status 1
    Error de compilación

    ReplyDelete
    Replies
    1. I think you just need to add the Adafruit-GFX library to your Aduino IDE. https://github.com/adafruit/Adafruit-GFX-Library

      Delete
  3. it does not work i am also having the same issues

    ReplyDelete
  4. Thanks for posting this. Your example saved me tons of time. I'm putting together a display for my car that will show metrics that are available on the OBDII bus. (I'm using a Freematics OBDII Arduino interface). Thanks again!

    ReplyDelete
  5. tft.setTextColor (textColor, BLACK);
    no coding necessary...
    :)

    ReplyDelete

Please note all comments are moderated by me before they appear on the site. It may take a day or so for me to get to them. Thanks for your feedback.