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