Optimizing TFT LCD Performance

Greetings! I'm using a 3.5", 480x320 TFT LCD on an Arduino MEGA 2560. The LCD uses 8-bit parallel data transmission and has an ILI9486 controller. The MCUFRIEND library is able to populate the screen about 3 times as fast as my own code, despite me using digital port manipulation in the same way as the MCUFRIEND library. Below I've given stripped down versions of my code that isolate the issue. The MCUFRIEND version is 3 times as fast as my version once it begins populating the screen, though there is a pause before it begins doing so. Any advice to bring my version up to the same speed or faster as the MCUFRIEND library without using it would be greatly appreciated. I am avoiding using the library so I can get a low-level understanding of LCD displays.

MCUFRIEND version

#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h>

MCUFRIEND_kbv tft;

void setup() {  
  tft.begin(tft.readID());

  tft.fillRect(0, 0, 320, 480, 0);
}

void loop() {}

My version

void writeBits(uint8_t bits) {
  PORTF &= ~(1 << 3);

  PORTH &= ~0x78; PORTG &= ~0x20; PORTE &= ~0x38; 
  PORTH |= (((bits) & (3<<0)) << 5); 
  PORTE |= (((bits) & (3<<2)) << 2); 
  PORTG |= (((bits) & (1<<4)) << 1); 
  PORTE |= (((bits) & (1<<5)) >> 2); 
  PORTH |= (((bits) & (3<<6)) >> 3); 

  PORTF &= ~(1 << 1);
  PORTF |= (1 << 1); 

  PORTF |= (1 << 3);
}
void writeCmd(uint8_t command) {
  PORTF &= ~(1 << 2);

  writeBits(command);
}

void lcdInit(void) {
  digitalWrite(A4, HIGH);
  delay(10);
  digitalWrite(A4, LOW);
  delay(10);
  digitalWrite(A4, HIGH);
  delay(100);

  digitalWrite(A1, HIGH);
  digitalWrite(A0, HIGH);
  digitalWrite(A3, LOW);  

  writeCmd(0xC5);  
  PORTF |= 1 << 2;
  writeBits(0x54);      
  writeBits(0x00);      
  
  writeCmd(0x36);  
  PORTF |= 1 << 2;
  writeBits(B0000100);

  writeCmd(0x3A);   
  PORTF |= 1 << 2;
  writeBits(0x55); 

  writeCmd(0x11);    
  delay(10);              

  writeCmd(0x29);   

  writeCmd(0x2c);   
}

void addressSet(int16_t x1, int16_t y1, int16_t x2, int16_t y2) {
  writeCmd(0x2a);  //Y Address Set
  PORTF |= 1 << 2;
  writeBits(y1 >> 8); 
  writeBits(y1);      
  writeBits(y2 >> 8);  
  writeBits(y2);       

  writeCmd(0x2b);  //X Address Set 
  PORTF |= 1 << 2;
  writeBits(x1 >> 8);
  writeBits(x1);   
  writeBits(x2 >> 8);
  writeBits(x2);     
}

void drawRect(long x1, long y1, long x2, long y2, int16_t color) {
  addressSet(x1, y1, x2, y2);

  writeCmd(0x2c);
  PORTF |= 1 << 2;

  long i = (y2 - y1) * (x2 - x1);

  while (i >= 0) {
    writeBits(color >> 8);
    writeBits(color);

    i--; 
  }
}

void pinSetup() {
  DDRG = B00100000;
  DDRE = B00111000;
  DDRH = B01111000;
  DDRF = B00011111;
}

void setup() {
  pinSetup(); 
  lcdInit();
  
  drawRect(0, 0, 480, 320, 0);
}


void loop() { }

But what about all those digital writes and delays in your LcdInit function? Those are going to take time. Along with all those writeBits calls, they can be replaced with direct port manapulation.

Why did you post this in the LEDs and Multiplexing forum category instead of the Displays category?

I will move it for you.

My advice would be to focus your optimisations on your writeBits() function. It gets called a very large number of times, so improvements there will give you biggest improvements.

What you need to do is reduce the number of operations performed in that function to a minimum.

I noticed that each port is read/updated more than once. I guess for some bits in PORTF, this may be necessary for signal timing. But for PORTE, PORTG, and PORTH, there are multiple updates that might be reduced to a single update for each port.

A few ideas:

  1. If the value of bits is the same as the previous call to writeBits(), avoid updating PORTE, PORTG and PORTH at all.
  2. If the bits in bits that go to PORTE haven't changed since previous call, avoid updating PORTE. Same for PORTG, PORTH.
  3. You can assign to PINE, PINF, PING and PINH to toggle bits (0-->1 or 1-->0). This is slightly faster, when you already know what the current value of the bits is and want to toggle it.
  4. Maybe pre-define 3 lookup tables, one each for PORTE, PORTG and PORTH, where all the necessary bit-manipulation has already been done, for every possible value of data. Using these, writeBits() only needs to update each port once to update all 8 bits of data.

Here's a start:

void writeBits(uint8_t bits) {

  static uint8_t prevBits = 0;

  PORTF &= ~(1 << 3);

  if (bits != prevBits) {

    PORTH &= ~0x78; PORTG &= ~0x20; PORTE &= ~0x38; 
    PORTH |= (((bits) & (3<<0)) << 5); 
    PORTE |= (((bits) & (3<<2)) << 2); 
    PORTG |= (((bits) & (1<<4)) << 1); 
    PORTE |= (((bits) & (1<<5)) >> 2); 
    PORTH |= (((bits) & (3<<6)) >> 3); 

    prevBits = bits;
  }

  PORTF &= ~(1 << 1);
  PORTF |= (1 << 1); 

  PORTF |= (1 << 3);
}

Some further optimisations (I hope) to avoid multiple updates:

void writeBits(uint8_t bits) {

  static uint8_t prevBits = 0;

  PORTF &= ~(1 << 3);

  if (bits != prevBits) {

    PORTH = PORTH & ~0x78 | (((bits) & (3<<0)) << 5) | (((bits) & (3<<6)) >> 3); 
    PORTE = PORTE & ~0x38 | (((bits) & (3<<2)) << 2) | (((bits) & (1<<5)) >> 2); 
    PORTG = PORTG & ~0x20 | (((bits) & (1<<4)) << 1); 

    prevBits = bits;
  }

  PORTF &= ~(1 << 1);
  PORTF |= (1 << 1); 

  PORTF |= (1 << 3);
}

Now avoiding updating ports that don't need it:

void writeBits(uint8_t bits) {

  static uint8_t prevBits = 0;
  uint8_t diffBits = bits ^ prevBits;

  PORTF &= ~(1 << 3);

  if (diffBits) {

    if (diffBits & ((3<<0) | (3<<6)))
      PORTH = PORTH & ~0x78 | (((bits) & (3<<0)) << 5) | (((bits) & (3<<6)) >> 3); 

    if (diffBits & ((3<<2) | (1<<5)))
      PORTE = PORTE & ~0x38 | (((bits) & (3<<2)) << 2) | (((bits) & (1<<5)) >> 2); 

    if (diffBits & (1<<4))
      PORTG = PORTG & ~0x20 | (((bits) & (1<<4)) << 1); 

    prevBits = bits;
  }

  PORTF &= ~(1 << 1);
  PORTF |= (1 << 1); 

  PORTF |= (1 << 3);
}