Multiple TFT Displays with Due using multiplexed SPI CS

Next Version with 8 + 1 TFT displays.

I have added more displays to the multiplexer and another larger one I will need for a main menu.

Here is a video showing the function: 8 plus 1 TFT screen with Arduino DUE using SPI and multiplexed CS - YouTube

And here is the new schematic:

The code changes between drawing something on all displyas at once and individual addressing of singular displays. This is easy with this multiplexer arrangement. It saves time for booting up the system. You can see in the video how fast it initialises all displays at once.

Learnings:

  • The displays just need a hard coded CS line in order to work. No need to use SPI CS outputs for that
  • Pretty tricky to get the SPI bus running with that amount of cabling. I had to insert buffers. However, the DUE SPI port will not drive more than 3 74HC244 inputs. Then the clock becomes very slow and rounded. Not sure why that is the case.
  • This setup requires an extra 3.3V PSU, it draws 540mA for the 9 TFT's and the logic. The power rail needs to be stable.
  • The Adafruit libraries take dec numbers as color codes, no need to fiddle with HEX

Next Steps

  • add an SD Card slot and a RAM module to the SPI bus. I had both already running together but not with those displays
  • replace the multiplexer by a shift register. then I can address arbitrary combinations of displays as groups at once.
  • build the final structure with 24 TFT screens 160x128 and 1 TFTscreen 320x240 + SD Card + RAM on SPI.

Here is the code:

#include <Adafruit_GFX.h>    // Core graphics library
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>

// some custom fonts
#include <arial6pt7b.h>
#include <arial7pt7b.h>
#include <arial8pt7b.h>
#include <ariblk6pt7b.h>

#include <Adafruit_ST7735.h> // Library for the 8 160x128 diplays
#include <Adafruit_ILI9341.h> // Library for the larger 2.8" 320x240 display
#include <SPI.h>


Adafruit_ST7735 tft = Adafruit_ST7735(-1, 8, -1); // Initialized w.o. CS, CS is handled by the multiplexer
Adafruit_ILI9341 tft2 = Adafruit_ILI9341(27, 8); // Using a custom CS line keeping hardware CS lines free for SD Card and RAM

// global variables used in the code
char CHR = 0;
int BGCol = 0; //Color Variable
int TCol= 0; //TextColor Variable
int NrDisplays = 8; //Number of displays connected
int NrLogDisplays = 24; //Number of logical display addresses
int ColorIndex = 0; //an index used for selecting colors
String BtnMainMenu[7] = {"Cmd", "Type", "Delay", "Ch", "CC", "On", "Off"}; // Menu items in a screen
  
//32 Background Colors for the small displays
long TFTCol[32] = {20480, 34816, 57344, 57568, 63488, 55968, 55584, 64864, 64480, 41600, 21024, 960, 1606, 4642, 486, 650, 198, 12, 31, 1023, 1756, 49182, 63839, 40991, 57356, 0, 8516, 12808, 17034, 25582, 46712, 65535};
//32 TextColors for each Background Color
long TFTTCol[32] = {46712, 65535, 65535, 65535, 0, 65535, 65535, 0, 0, 65535, 65535, 65535, 0, 65535, 65535, 65535, 46712, 65535, 65535, 0, 0, 65535, 0, 65535, 65535, 46712, 65535, 65535, 65535, 0, 0, 0};
int ActBtnMenu = 0; //current MenuCursor
int LastBtnMenu = 0; //last MenuCursor
  
void setup(void) {

  pinMode(22, OUTPUT); //Out 22-24 control the mutiplexer address inputs
  pinMode(23, OUTPUT);
  pinMode(24, OUTPUT); 
  pinMode(25, OUTPUT); //feeding the pull-up resistors
  pinMode(26, OUTPUT); //4051 enable, low = active

// some push-button inputs:
  pinMode(50, INPUT_PULLUP);
  pinMode(51, INPUT_PULLUP);
  pinMode(52, INPUT_PULLUP);
  pinMode(53, INPUT_PULLUP);

  SetBtnDisplay(-2); // All BtnDisplays 
  randomSeed(analogRead(A0));
  tft.setSPISpeed(2000000); //Initialization works more reliable at lower SPI speed
      tft.initR(INITR_BLACKTAB); //all displays are initialized at once, not in sequence
      tft.setFont(&FreeSansBold12pt7b);
      tft.setRotation(3); //I use them horizontally 
  tft.setSPISpeed(16000000);  
  tft.fillScreen(0); 
  
  SetBtnDisplay(-1); // deactivate btn displays
  delay(20);  
  
  tft2.begin(8000000); //start Display #2 with 8MHz SPI Clock
  
  tft2.setRotation(3); //I use them horizontally
  tft2.fillScreen(0);
  MainRunDisplay(12);
  MainBtnMenu(0,0); //0 calls menu without cursor
  delay(200);  

  Serial.begin(38400);

  // BtnRunDisplay(0);
  
} // end setup

void loop() {

  BtnRunDisplay(0);
      for (int m = 0; m < NrLogDisplays ; m++){
      
          BtnNameDisplay(m);

          // This section takes input from two pushbuttons for the menu cursor
          if (digitalRead(50) == LOW){
            Serial.println("- gedrückt");
            if (ActBtnMenu > 0) {
              ActBtnMenu--;
              MainBtnMenu(LastBtnMenu,0);
              MainBtnMenu(ActBtnMenu,1);
              LastBtnMenu = ActBtnMenu;
            }
          }
        
          if (digitalRead(51) == LOW){
            Serial.println("+ gedrückt");
            if (ActBtnMenu < 12) {
              ActBtnMenu++;
              MainBtnMenu(LastBtnMenu,0);
              MainBtnMenu(ActBtnMenu,1);
              LastBtnMenu = ActBtnMenu;
            }
          }
      
      }
      ColorIndex = ColorIndex + 8;  //just making colors change for demo purposes
      if (ColorIndex + NrLogDisplays > 32) {ColorIndex = 0;}

} // end loop



void SetBtnDisplay(int DNo){  // This function selects the display by setting three binary outputs to the multiplexer chip
                              // It also controls the feed to the pull-up resistors and the enable input of the multiplexer
   digitalWrite(26, LOW); //set enable input of multiplexer to active
   digitalWrite(25, HIGH); //all pull-up resistors are on HIGH, only selected displays are active (active = LOW)
   
  if(DNo == 0){
    digitalWrite(22, LOW);
    digitalWrite(23, LOW); 
    digitalWrite(24, LOW); //binary 0
  }
  
  else if(DNo == 1){
    digitalWrite(22, HIGH);
    digitalWrite(23, LOW);   
    digitalWrite(24, LOW); //binary 1
  }
  
  else if(DNo == 2){
    digitalWrite(22, LOW);
    digitalWrite(23, HIGH); 
    digitalWrite(24, LOW); //binary 2
  }
  
  else if(DNo == 3){
    digitalWrite(22, HIGH);
    digitalWrite(23, HIGH); 
    digitalWrite(24, LOW); //binary 3
  }
  else if(DNo == 4){
    digitalWrite(22, LOW);
    digitalWrite(23, LOW); 
    digitalWrite(24, HIGH); //binary 4
  }
  
  else if(DNo == 5){
    digitalWrite(22, HIGH);
    digitalWrite(23, LOW);   
    digitalWrite(24, HIGH); //binary 5
  }
  
  else if(DNo == 6){
    digitalWrite(22, LOW);
    digitalWrite(23, HIGH); 
    digitalWrite(24, HIGH); //binary 6
  }
  
  else if(DNo == 7){
    digitalWrite(22, HIGH);
    digitalWrite(23, HIGH); 
    digitalWrite(24, HIGH); //binary 7
  }
   else if(DNo == -1){ //make btn displays inactive
    digitalWrite(26, HIGH); //set enable input of multiplexer to inactive
    digitalWrite(25, HIGH); //all pull-up resistors are on HIGH, all displays are inactive
  }

  else if(DNo == -2){ //address all displays at once
    digitalWrite(26, HIGH); //set enable input of multiplexer to inactive, all outputs high-impedance
    digitalWrite(25, LOW); //all pull-up resistors are on LOW, all displays are now CS = active
  }


}


void BtnRunDisplay(uint32_t BCol){ //fill all displays with standard background and lines
                                   //This is performed for all displays at once which saves approx. 200ms per disply
  SetBtnDisplay(-2);  //Address all displays at once
  tft.setFont(&FreeSansBold12pt7b);
  tft.setTextColor(65535);

  //Top row
  tft.fillRect(0, 0, 60, 30, 34816); //fill the red top left number area
  tft.fillRect(61, 0, 160, 30, 0); //fill the black area right beside the number area
  tft.drawFastHLine(0, 31, 160, 65535);
  tft.drawFastHLine(0, 64, 160, 65535);
      
  //Cmd Info
  tft.drawFastHLine(0, 109, 160, 65535);
  tft.fillRect(0, 110, 160, 18, TFTCol[27]); //grey bottom line
  tft.setFont(&arial7pt7b);
  //tft.setFont(&ariblk6pt7b);
  
  //Draw 12 rectangles:
  tft.setTextColor(65535);
  for (int N = 0; N < 12; N++){
    int XP = 2 + (N * 13);
    tft.drawRect(XP, 113, 14, 15, 21130); 
    tft.setCursor(XP + 2,124);
    tft.print(RNDLetter()); // Print a series of 12 random letters in each rectangle
  }
  SetBtnDisplay(-1);  //Deactivate Btn Displays
}

void BtnNameDisplay(int BCol){

  BtnNoDisplay(BCol);
  
  int ActDisplay = ((BCol + NrDisplays) % NrDisplays); //Only address 8 physical displays
  SetBtnDisplay(ActDisplay);  //select the actual display

  BGCol = TFTCol[BCol + ColorIndex]; //Select the color from the array variable
  TCol = TFTTCol[BCol + ColorIndex]; //Select the text color

  tft.setFont(&FreeSansBold12pt7b);
  tft.setTextColor(TCol);

   //GRP area
  tft.setFont(&FreeSansBold9pt7b);
  tft.fillRect(0, 32, 160, 32, BGCol); // Background of GRP label section
  tft.setCursor(5, 55);
  tft.print("GRP COLOR ");
  tft.print(BCol + ColorIndex);
  

  //Btn area
  tft.setTextColor(65535);
  tft.setFont(&FreeSansBold12pt7b);
  tft.setCursor(5, 92);
  tft.print("BtnName ");
  tft.print(BCol);
  
  SetBtnDisplay(-1);  //Deactivate Btn Displays

}

void BtnNoDisplay(int No){ //just a demo/test routine printing the Button numbers into the top area of each display
  
if (((No + NrDisplays)% NrDisplays) == 0){ //every 8 btns
  SetBtnDisplay(-2); //address all displays, this saves a lot of time !!!
  tft.fillRect(0, 0, 60, 30, 34816); //fill the top left red area
  tft.fillRect(0, 65, 160, 43, 0); //fill the btn name area black
}

  int ActDisplay = ((No + NrDisplays) % NrDisplays); //Only address 8 physical displays
  SetBtnDisplay(ActDisplay);  //select the actual display

  //tft.fillRect(0, 0, 60, 30, 34816); //need to fill the red area again for printing a new number
  tft.setFont(&FreeSansBold12pt7b);
  tft.setTextColor(65535);
  tft.setCursor(15, 22);
  tft.print(No);
  SetBtnDisplay(-1);  //Deactivate Btn Displays
}

char* RNDLetter(){ // just for demo, delivers a random character
  int z = random(1, 9);
  switch (z){
    case 1:
      return "P";
      break;
    case 2:
      return "C";
      break;
    case 3:
      return "S";
      break;
    case 4:
      return "T";
      break;
    case 5:
      return "O";
      break;
    case 6:
      return "B";
      break;
    case 7:
      return "-";
      break;
    case 8:
      return "/";
      break;
      
  }
}

void MainRunDisplay(int Btn){ // large main display content
  tft2.setTextColor(65535);

  tft2.fillRect(0, 0, 320, 34, 34816); //fill top area red
  tft2.fillRect(0, 37, 320, 203, 0); //fill rest of display black

  tft2.setTextColor(65535);
  tft2.setFont(&FreeSansBold9pt7b);
  tft2.setCursor(180, 20);
  tft2.print("MIDI FACTORY");
  
  if (Btn > 0){
    tft2.setFont(&FreeSansBold12pt7b);
    tft2.setCursor(5, 25);
    tft2.print("Button  ");
    tft2.print(Btn);
  }
}

void MainBtnMenu(int LineCursor, int Mode){ // Shows 12 lines with Command details for a Midi Control Button
//LineCursor = 0 = write only the headline
//LineCursor 1-12 = set or delete a line cursor
//Mode 0 = delete, 1 = set

Serial.print("MainBTnMenu called with LineCursor = ");
Serial.print(LineCursor);
Serial.print("  and Mode =  ");
Serial.println(Mode);

  tft2.setTextColor(TFTCol[31]); // 31 = White
  // tft2.setFont(&arial8pt7b);
  tft2.setFont(&ariblk6pt7b);

  //Headline
  int TSpace = 4;
  int LPos = 0;
  int MenuWd[7] = {44, 59, 59, 29, 39, 39, 39};

  if (LineCursor == 0){ //headline only if called with 0
      for(int n=0; n<7;n++){
          tft2.fillRect(LPos, 36, MenuWd[n], 20, TFTCol[15]);
          tft2.setCursor(LPos + TSpace, 50);
          tft2.print(BtnMainMenu[n]);    
          LPos = LPos + MenuWd[n]+ 2;
      }
  }
  //Cmd numbers
  int LineSpace = 15;
  int FirstLine = 55;
  int LinePos = 0;

  if (LineCursor == 0){ //if called without cursor print entire screen content
      for (int n = 1; n < 13; n++){
          LinePos = FirstLine + (n * LineSpace);      
          tft2.setCursor(10, LinePos);
          tft2.print(n);
          // here is to print all data    
      }
  }
  else { //if called with a cursor 
          LinePos = FirstLine + (LineCursor * LineSpace); //Calculating the vertical postion of a line      
    
          if (Mode == 1){
            tft2.fillRect(0, LinePos-11, 360, 15, TFTCol[28]); //Fill Line grey as a cursor
          }
          else if (Mode == 0){
            tft2.fillRect(0, LinePos-11, 360, 15, 0); //Fill Line black - 'delete' the cursor       
          }
          tft2.setCursor(10, LinePos); //move to the text position
          tft2.print(LineCursor); //print the line number
          // here is all other data in that line to be printed
  }  
} //end MainBnMenu