Framebuffer for 2 or more LCDs

jondibar

Member
Hi!

I have just got an 2'' 240x320 TFT display(EA TFT020-23AINN) working on a Teensy 3.5 using the T3/4 optimised library.

My goal is to be able to drive 5 of these ST7789 displays from the same Teensy 3.5 on the same SPI bus. Because the remaining pins are already used for other purposes, I have to limit myself to an SPI bus.
At the moment I have the test programme below for 5 LCDs. A very simple program that controls 5 LCDs and outputs the "position" of the encoder.

Problem:
The library says you can only attach to one LCD framebuffer, is there a way around this and do I have enough RAM with the Teensy 3.5 to do this. I could switch to teensy 4.1.
Or are there other ways to control multiple LCDs without flickering?


Thanks in advance for your thoughts!

-Jonas






Code:
#include <Adafruit_GFX.h>    // Core graphics library
//#include <ST7735_t3.h> // Hardware-specific library
#include <ST7789_t3.h> // Hardware-specific library
#include <SPI.h>

//Fonts
#include <st7735_t3_font_ComicSansMS.h>
#include <FreeSans24pt7b.h>

#include "Encoder_JB.h"


#define inputA 16
#define inputB 15
  // For the breakout board, you can use any 2 or 3 pins.
  // These pins will also work for the 1.8" TFT shield.
#define NUM_LCDS        5
#define TFT_DC          8
#define TFT_RST        10
#define TFT_RST_DUMMY  0xff
#define DIM_LCD        14
#define TFT_CS0         2
#define TFT_CS1         3
#define TFT_CS2         4
#define TFT_CS3         5
#define TFT_CS4         6


// For 1.14", 1.3", 1.54", 1.69", and 2.0" TFT with ST7789:
ST7789_t3 tft0 = ST7789_t3(TFT_CS0, TFT_DC, TFT_RST_DUMMY);
ST7789_t3 tft1 = ST7789_t3(TFT_CS1, TFT_DC, TFT_RST_DUMMY);
ST7789_t3 tft2 = ST7789_t3(TFT_CS2, TFT_DC, TFT_RST_DUMMY);
ST7789_t3 tft3 = ST7789_t3(TFT_CS3, TFT_DC, TFT_RST_DUMMY);
ST7789_t3 tft4 = ST7789_t3(TFT_CS4, TFT_DC, TFT_RST_DUMMY);
const ST7789_t3 *Tfts[NUM_LCDS] = { &tft0, &tft1, &tft2, &tft3, &tft4  };



void setup() {
  Serial.begin(9600);
  Serial.print("hello!");

  // // Set Dim for LCDs
  // pinMode(DIM_LCD, OUTPUT);
  // analogWrite(DIM_LCD, 255);

  /* reset the displays manually */
  pinMode(TFT_RST, OUTPUT);
  digitalWriteFast(TFT_RST, HIGH);
  delay(100);
  digitalWriteFast(TFT_RST, LOW);
  delay(100);
  digitalWriteFast(TFT_RST, HIGH);
  delay(200);

  

  /* initializer using a 2.0" 320x240 tft */
  Serial.println("init");

  ST7789_t3 *tft;
  for (int i = NUM_LCDS - 1; i >= 0; i--) 
  {
    tft = (ST7789_t3*)Tfts[i];
    tft->init(240, 320, SPI_MODE3);
    if(!(tft->useFrameBuffer(true)))
    {
      Serial.print("FrameBuffer failed on LCD: ");
      Serial.println(i);
    }
  }

  uint16_t time = millis();
  tft0.fillScreen(ST77XX_YELLOW);
  tft1.fillScreen(ST77XX_BLUE);
  time = millis() - time;

  Serial.println(time, DEC);
  delay(500);

  for (int i = NUM_LCDS - 1; i >= 0; i--) 
  {
     tft = (ST7789_t3*)Tfts[i];
     tft->setTextSize(1);
     tft->setFont(&FreeSans24pt7b);

  }



  //init Encoder
  ENCODER_JB(inputA, inputB, false);    //init Encoder
  setBoundaries(-99, 999);               //minBoundarie: -21, maxBoundarie: 999
  
  Serial.println("done");
  delay(500);
}

void loop() {
  unsigned int oldmillis = millis();
  ST7789_t3 *tft;
  while(1)
  {   
    for (int i = NUM_LCDS - 1; i >= 0; i--) 
    {
      tft = (ST7789_t3*)Tfts[i];
      tft->fillRect(25,25,200,50,ST77XX_BLACK);
      tft->setCursor(30, 30);
      tft->print(getPosition());
    }
  

    if((millis() - oldmillis) > 16 ) //update every 16ms
    {
      updateTFT(); 
      oldmillis = millis();
    }
  }
}


void updateTFT()
{
  ST7789_t3 *tft;
  for (int i = NUM_LCDS - 1; i >= 0; i--) //update each LCD
  {
	  tft = (ST7789_t3*)Tfts[i];
	  tft->updateScreen();
  }
}
 
I only have cursory experience with this, but it really depends on the situation wether you need the extra framebuffers or not. I feel like most people generally redraw the entire framebuffer for each update so there is no practical advantage to having multiple framebuffers outside of updating 2 or more screens at the same time with separate SPI/SPI1/SPI2 connections.

That being said you have to adapt your code to share one framebuffer, right now you have 2 separate loops one updating all framebuffers and one updating all screens. This is counterproductive when sharing a framebuffer as all screens will show the data from the last one that was updated. Both updates should take place within the same loop, ie. update framebuffer, update display1, update framebuffer, update display2. This also saves a little bit of processing time since there is no need to update the framebuffer at while(1) speed since the display update is limited to 16ms.

To use a shared framebuffer you would have to allocate the buffer yourself then pass the same one to the useFrameBuffer function for all displays. The library you are using also supports DMA so you can do other stuff between each display updating instead of waiting for all 5 to finish as well if you choose to go that route.
 
Two parts to this:

a) Frame buffers: For a 320x240 display each frame buffer takes 153600 bytes Plus overhead. And the board has 256KB of memory, so two won't fit.
T4.x has more memory and can easily fit a couple of them. Normally malloc will go out of the 512kb RAM2. You may also be able to fit another one in main memory, but that depends on
what else is in the DTCM/ITCM... With T4.1, you can fit a whole lot of them if you solder on a PSRAM, but that runs slower...

But if your display code is as simple as you have:
Code:
    for (int i = NUM_LCDS - 1; i >= 0; i--) 
    {
      tft = (ST7789_t3*)Tfts[i];
      tft->fillRect(25,25,200,50,ST77XX_BLACK);
      tft->setCursor(30, 30);
      tft->print(getPosition());
    }
You might try changing it to something like:
Code:
    for (int i = NUM_LCDS - 1; i >= 0; i--) 
    {
      tft = (ST7789_t3*)Tfts[i];
      tft->setCursor(30, 30);
      tft->print(getPosition());
      int cursorX = tft.getCursorX();
      tft->fillRect(cursorX,25,200,50,ST77XX_BLACK);
    }
To see if that takes care of your flickering....

Note: the fillrect will be clipped to the edge of the screen, and may be clearing a lot more than it needs to...

So sometimes I have done it something like:
Code:
    // probably wher eyou defined Tfts, define something like:
    int last_cursorXs[NUM_LCDS] = {0};

...
    for (int i = NUM_LCDS - 1; i >= 0; i--) 
    {
      tft = (ST7789_t3*)Tfts[i];
      tft->setCursor(30, 30);
      tft->print(getPosition());
      int cursorX = tft.getCursorX();
      // only blank out if new text is shorter than previous text...
      if (cursorX < last_cursorXs[i]) {
         tft->fillRect(cursorX,25, last_cursorXs[i] - cursorX,50,ST77XX_BLACK);
      }
       last_cursorXs[i] = cursorX;
    }

Warning typed on fly so could be typos and logic issues, but hopefully makes some sense.
 
Back
Top