Issue using multiple ILI9341 displays

Curious issue, likely easily solved but not seeing it.

I'm using the arduino IDE, programming a Pi Pico MCU which is connected to a pair of ILI9341 displays using the Adafruit driver. Displays have separate CS pins but otherwise wiring is the same on the hardware SPI bus.

with a single display it works fine, add a second display and that also works but the behaviour is curious, the code is below but in outline is to declare two instances of the Adafruit_ILI9341 object class, using the two different pins, and then in theory they should work individually.

behaviour however is a tad odd.

the first screen initialises correctly, however the second screen duplicates it - this is with or without the second screen even being initialised.

I can then write to the second screen which only goes to the second screen, however later in the code anything written to either screen appears on both.

the full code is as below, this also talks to the Kerbal Space Programme game, that side of the code works however the screens keep overwriting each other with the same display, which is the bug I'm trying to nail down.

I tried a google search on the library with multiple screens but didn't get very far

any help of thoughts appreciated

#include "KerbalSimpit.h"
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_DC 9
#define TFT_CS0 10
#define TFT_CS1 11
#define TFT_CS1 12
#define TFT_CS1 13

KerbalSimpit ksp(Serial);
// screens, could use an array but with only four in the end this seems reasonable
Adafruit_ILI9341 tft0 = Adafruit_ILI9341(TFT_CS0, TFT_DC);
Adafruit_ILI9341 tft1 = Adafruit_ILI9341(TFT_CS1, TFT_DC);
//Adafruit_ILI9341 tft2 = Adafruit_ILI9341(TFT_CS2, TFT_DC);
//Adafruit_ILI9341 tft3 = Adafruit_ILI9341(TFT_CS3, TFT_DC);

bool isInFlight = false;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  tft0.begin();
  tft1.begin();

  tft0.setRotation(3);
  tft0.fillScreen(ILI9341_BLACK);
  tft0.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft0.setTextSize(1);
  tft0.drawRect(0, 0, 320, 240, ILI9341_YELLOW);
  WriteText("KSP Data Viewer", 2, &tft0);
  WriteText("Waiting for Connection", 12, &tft0);
  
  tft1.setRotation(3);
  tft1.fillScreen(ILI9341_BLACK);
  tft1.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft1.setTextSize(1);
  tft1.drawRect(4, 0, 316, 240, ILI9341_YELLOW);
  WriteText("Screen 1", 2, &tft1);


  while (!ksp.init()) {
    delay(100);
  }

  WriteText("Connected!", 20, &tft0);
  delay(1000);
  tft0.fillScreen(ILI9341_BLACK);
  tft1.fillScreen(ILI9341_BLACK);

  // write the headers
  tft0.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft0.setTextSize(3);
  WriteText("Apoapsis", 2, &tft0);
  WriteText("Periapsis", 120, &tft0);

  tft1.setTextSize(3);
  WriteText("Altitude", 2, &tft1);
  WriteText("Velocity", 120, &tft1);

  ksp.inboundHandler(messageHandler);
  ksp.registerChannel(APSIDES_MESSAGE);
  ksp.registerChannel(APSIDESTIME_MESSAGE);
  ksp.registerChannel(SCENE_CHANGE_MESSAGE);
  ksp.registerChannel(FLIGHT_STATUS_MESSAGE);
  ksp.registerChannel(ALTITUDE_MESSAGE);
  ksp.registerChannel(APSIDES_MESSAGE);
}

void loop() {
  // put your main code here, to run repeatedly:
  ksp.update();
}

void WriteText(const char *S, uint16_t y, Adafruit_ILI9341 *tft)
{
  int16_t  x1, y1;
  uint16_t w, h;

  tft->getTextBounds(S, 0, y, &x1, &y1, &w, &h);

  tft->setCursor(160 - (w/2), y);
  tft->print(S);
}

void messageHandler(byte messageType, byte msg[], byte msgSize) 
{
  switch (messageType)
  {
    case ALTITUDE_MESSAGE:
    {
      if (msgSize == sizeof(altitudeMessage)) 
      {
        altitudeMessage D = parseMessage<altitudeMessage>(msg);

        if (!isInFlight)
        {
          char Buffer[20];

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(3);
          FormatDistance(D.sealevel, Buffer); sprintf(Buffer, "Abs: %s", Buffer); WriteText(Buffer, 40, &tft0);
          FormatDistance(D.surface, Buffer); sprintf(Buffer, "Radar: %s", Buffer); WriteText(Buffer, 80, &tft0);
        }
      }
      break;      
    }

    case VELOCITY_MESSAGE:
    {
      if (msgSize == sizeof(velocityMessage)) 
      {
        velocityMessage D = parseMessage<velocityMessage>(msg);

        if (!isInFlight)
        {
          char Buffer[20];

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(3);
          FormatDistance((int)D.orbital, Buffer); sprintf(Buffer, "Orb: %s", Buffer); WriteText(Buffer, 160, &tft0);
          FormatDistance((int)D.surface, Buffer); sprintf(Buffer, "Surf: %s", Buffer); WriteText(Buffer, 200, &tft0);
        }
      }
      break;      
    }

    case FLIGHT_STATUS_MESSAGE:
    {
      if (msgSize == sizeof(flightStatusMessage)) 
      {
        flightStatusMessage D = parseMessage<flightStatusMessage>(msg);

        isInFlight = D.isInFlight();

        if (!isInFlight)
        {
          char Buffer[20] = "            ";

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(4);

          WriteText(Buffer, 40, &tft0);
          WriteText(Buffer, 80, &tft0);
          WriteText(Buffer, 160, &tft0); 
          WriteText(Buffer, 200, &tft0);

          tft0.setTextSize(3);

          WriteText(Buffer, 40, &tft1);
          WriteText(Buffer, 80, &tft1);
          WriteText(Buffer, 160, &tft1); 
          WriteText(Buffer, 200, &tft1);
        }

      }
      break;
    }

    case SCENE_CHANGE_MESSAGE:
    {
      char Buffer[20] = "            ";

      tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
      tft0.setTextSize(4);

      WriteText(Buffer, 40, &tft0);
      WriteText(Buffer, 80, &tft0);
      WriteText(Buffer, 160, &tft0); 
      WriteText(Buffer, 200, &tft0);

      break;
    }

    case APSIDES_MESSAGE:
    {
      if (isInFlight)
      {
        if (msgSize == sizeof(altitudeMessage)) 
        {
          apsidesMessage D = parseMessage<apsidesMessage>(msg);

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(3);

          char Buffer[30] = {0};

          FormatDistance(D.apoapsis, Buffer); WriteText(Buffer, 40, &tft0);
          FormatDistance(D.periapsis, Buffer); WriteText(Buffer, 160, &tft0);
        
        }
      }
      
      break;
    }

    case APSIDESTIME_MESSAGE:
    {
      if (isInFlight)
      {
        if (msgSize == sizeof(apsidesTimeMessage)) 
        {
          apsidesTimeMessage D = parseMessage<apsidesTimeMessage>(msg);

          tft0.setTextColor(ILI9341_GREEN, ILI9341_BLACK);  
          tft0.setTextSize(3);

          char Buffer[30] = {0};


          FormatTime(D.apoapsis, Buffer); WriteText(Buffer, 80, &tft0);
          FormatTime(D.periapsis, Buffer); WriteText(Buffer, 200, &tft0);        

        }
      }

      break;
    }
  }
}

void FormatDistance(float myDistance, char *S)
{
  // need to add the thousands seperators and set appropriate units

  // first determine the units, the value is in "m" to start with, anything below 10km should use meters
  // anything over 999,999,000m should use "M", everything else is using Km

  float Divider = 1.0;            // m
  float V = 0.0;
  char Units[3] = "m";

  if (myDistance > 10000)
  {
    if (myDistance > 999999999)
    {
      Divider = 1000000.0;        // Mm
      sprintf(Units, "Mm");
    }
    else
    {
      Divider = 1000.0;           // Km
      sprintf(Units, "Km");
    }
  }

  V = myDistance / Divider;

  // now we need to split V into thousands groupings
  int U0 = (int)(V * 1000.0) % 1000;   // produces three digit fraction
  int U1 = (int)(V) % 1000;
  int U2 = (int)(V / 1000.0) % 1000;

  // now ready for printing
  if (U2 > 0)
  {
      sprintf(S, "%d,%03d.%02d %s", U2, U1, U0, Units);
  }
  else
  {
    sprintf(S, "%3d.%02d %s", U1, U0, Units);
  }

  
}

void FormatTime(uint32_t myTime, char *S)
{
  uint32_t Y = (myTime/9201600);     // how many years do we have (426 days to a year)
  uint32_t D = (myTime/21600) % 426;     // how many days do we have (six hour days)
  uint32_t h = (myTime/3600) % 6;    // how many hours do we have?
  uint32_t m = (myTime/60) % 60;    // how many minutes do we have?
  uint32_t s = myTime % 60;    // how many seconds do we have? 

  // how we format this depends what we have
  if (Y > 0)
  {
    // we have a time measured in years, formatting in years and days works
    sprintf(S, " %d Y %d D ", Y, D);
  }
  else if (D > 0)
  {
    // time is at least a day
    sprintf(S, " %d D %02d:%02d ", D, h, m);
  }
  else
  {
    sprintf(S, " %02d:%02d:%02d ", h, m, s);
  }

  
}

That's a bit odd.

Try use GPIO_NUM_5 and GPIO_NUM_15 for the CS pins.

Why not have each display have its own DC pin?

why not do one at a time?

tft0.begin();

  tft0.setRotation(3);
  tft0.fillScreen(ILI9341_BLACK);
  tft0.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft0.setTextSize(1);
  tft0.drawRect(0, 0, 320, 240, ILI9341_YELLOW);
  WriteText("KSP Data Viewer", 2, &tft0);
  WriteText("Waiting for Connection", 12, &tft0);

  tft1.begin();  
  tft1.setRotation(3);
  tft1.fillScreen(ILI9341_BLACK);
  tft1.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft1.setTextSize(1);
  tft1.drawRect(4, 0, 316, 240, ILI9341_YELLOW);
  WriteText("Screen 1", 2, &tft1);

sorry, corrected the definitions to be

#define TFT_CS0 10
#define TFT_CS1 11
#define TFT_CS2 12
#define TFT_CS3 13

do the displays need different DC pins? thought with different CS pins it should work, it is talking just getting some duplications and some commands not working right - e.g. once its connected to the game both screens should fill with black, the first does, the second does not

what does the "GPIO_NUM_5" & "GPIO_NUM_15" bit do? pin 5 is currently connected for something else (not used here but the same hardware is used for a few different applications, or will be)

also I did initially have the two displays initialise separately, put them together at the top in case that helped, it didn't, but did wonder if the first screen was picking up the commands initially as its CS pin would not have been initialised yet

found, and fixed, a few other coding bugs, which have sorted some of the issues (the altitude and velocity message sections were writing to the wrong screen, but still appearing on both, now they appear where they are meant to) - however some things are still not right, both screens get a coloured border at start up, one yellow, one green. on connection both screens are meant to wipe to black, the first does, the second does not.

will keep playing with it

when you change your code. POST THE CHANGED CODE IN A NEW MESSAGE, please.

Adafruit_ILI9341 tft0 = Adafruit_ILI9341(TFT_CS0, TFT_DC);
Adafruit_ILI9341 tft1 = Adafruit_ILI9341(TFT_CS1, TFT_DC);

notice both display devices use the same TFT_DC pin? Do you think that will be a problem?

revised code

#include "KerbalSimpit.h"
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_DC 9
#define TFT_CS0 10
#define TFT_CS1 11
#define TFT_CS2 12
#define TFT_CS3 13

KerbalSimpit ksp(Serial);
// screens, could use an array but with only four in the end this seems reasonable
Adafruit_ILI9341 tft0 = Adafruit_ILI9341(TFT_CS0, TFT_DC);
Adafruit_ILI9341 tft1 = Adafruit_ILI9341(TFT_CS1, TFT_DC);
//Adafruit_ILI9341 tft2 = Adafruit_ILI9341(TFT_CS2, TFT_DC);
//Adafruit_ILI9341 tft3 = Adafruit_ILI9341(TFT_CS3, TFT_DC);

bool isInFlight = false;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  tft0.begin();
  tft1.begin();

  tft0.setRotation(3);
  tft0.fillScreen(ILI9341_BLACK);
  tft0.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft0.setTextSize(1);
  tft0.drawRect(0, 0, 320, 240, ILI9341_YELLOW);
  WriteText("KSP Data Viewer", 2, &tft0); // <-- this text appears on both screens
  WriteText("Waiting for Connection", 12, &tft0); // <-- so does this
  
  tft1.setRotation(3);
  tft1.fillScreen(ILI9341_BLACK); // <-- this blanks screen 1
  tft1.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft1.setTextSize(1);
  tft1.drawRect(4, 0, 316, 240, ILI9341_GREEN);
  WriteText("Screen 1", 2, &tft1);


  // testing, all of this appears where it should
  WriteText("Screen 0", 120, &tft0);
  tft1.setTextColor(ILI9341_BLUE, ILI9341_BLACK); WriteText("Screen 1", 130, &tft1);
  WriteText("Screen 0", 140, &tft0);
  WriteText("Screen 1", 150, &tft1);

  while (!ksp.init()) {
    delay(100);
  }

  WriteText("Connected!", 20, &tft0);
  delay(1000);
  tft0.fillScreen(ILI9341_BLACK);  // <-- this clears screen 0
  tft1.fillScreen(ILI9341_BLACK);  // <-- this appears to do nothing, it should clear screen 1

  // write the headers
  tft0.setTextColor(ILI9341_WHITE, ILI9341_BLACK);  
  tft0.setTextSize(3);
  WriteText("Apoapsis", 2, &tft0);
  WriteText("Periapsis", 120, &tft0);

  tft1.setTextColor(ILI9341_WHITE, ILI9341_BLACK); 
  tft1.setTextSize(3);
  WriteText("Altitude", 2, &tft1);
  WriteText("Velocity", 120, &tft1);

  ksp.inboundHandler(messageHandler);
  ksp.registerChannel(APSIDES_MESSAGE);
  ksp.registerChannel(APSIDESTIME_MESSAGE);
  ksp.registerChannel(SCENE_CHANGE_MESSAGE);
  ksp.registerChannel(FLIGHT_STATUS_MESSAGE);
  ksp.registerChannel(ALTITUDE_MESSAGE);
  ksp.registerChannel(VELOCITY_MESSAGE);
}

void loop() {
  // put your main code here, to run repeatedly:
  ksp.update();
}

void WriteText(const char *S, uint16_t y, Adafruit_ILI9341 *tft)
{
  int16_t  x1, y1;
  uint16_t w, h;

  tft->getTextBounds(S, 0, y, &x1, &y1, &w, &h);

  tft->setCursor(160 - (w/2), y);
  tft->print(S);
}

void messageHandler(byte messageType, byte msg[], byte msgSize) 
{
  switch (messageType)
  {
    case ALTITUDE_MESSAGE:
    {
      if (msgSize == sizeof(altitudeMessage)) 
      {
        altitudeMessage D = parseMessage<altitudeMessage>(msg);

        if (!isInFlight)
        {
          char Buffer[20], Buffer2[20];

          tft1.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft1.setTextSize(2);
          FormatDistance(D.sealevel, Buffer); sprintf(Buffer2, "Abs: %s", Buffer); WriteText(Buffer2, 40, &tft1);
          FormatDistance(D.surface, Buffer); sprintf(Buffer2, "Radar: %s", Buffer); WriteText(Buffer2, 80, &tft1);
        }
      }
      break;      
    }

    case VELOCITY_MESSAGE:
    {
      if (msgSize == sizeof(velocityMessage)) 
      {
        velocityMessage D = parseMessage<velocityMessage>(msg);

        if (!isInFlight)
        {
          char Buffer[20], Buffer2[20];

          tft1.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft1.setTextSize(2);
          FormatDistance((int)D.orbital, Buffer); sprintf(Buffer2, "Orb: %s", Buffer); WriteText(Buffer2, 160, &tft1);
          FormatDistance((int)D.surface, Buffer); sprintf(Buffer2, "Surf: %s", Buffer); WriteText(Buffer2, 200, &tft1);
        }
      }
      break;      
    }

    case FLIGHT_STATUS_MESSAGE:
    {
      if (msgSize == sizeof(flightStatusMessage)) 
      {
        flightStatusMessage D = parseMessage<flightStatusMessage>(msg);

        isInFlight = D.isInFlight();

        if (!isInFlight)
        {
          char Buffer[20] = "            ";

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(2);

          WriteText(Buffer, 40, &tft0);
          WriteText(Buffer, 80, &tft0);
          WriteText(Buffer, 160, &tft0); 
          WriteText(Buffer, 200, &tft0);

          tft0.setTextSize(2);

          WriteText(Buffer, 40, &tft1);
          WriteText(Buffer, 80, &tft1);
          WriteText(Buffer, 160, &tft1); 
          WriteText(Buffer, 200, &tft1);
        }

      }
      break;
    }

    case SCENE_CHANGE_MESSAGE:
    {
      char Buffer[20] = "            ";

      tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
      tft0.setTextSize(2);

      WriteText(Buffer, 40, &tft0);
      WriteText(Buffer, 80, &tft0);
      WriteText(Buffer, 160, &tft0); 
      WriteText(Buffer, 200, &tft0);

      break;
    }

    case APSIDES_MESSAGE:
    {
      if (isInFlight)
      {
        if (msgSize == sizeof(altitudeMessage)) 
        {
          apsidesMessage D = parseMessage<apsidesMessage>(msg);

          tft0.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  
          tft0.setTextSize(2);

          char Buffer[30] = {0};

          FormatDistance(D.apoapsis, Buffer); WriteText(Buffer, 40, &tft0);
          FormatDistance(D.periapsis, Buffer); WriteText(Buffer, 160, &tft0);
        
        }
      }
      
      break;
    }

    case APSIDESTIME_MESSAGE:
    {
      if (isInFlight)
      {
        if (msgSize == sizeof(apsidesTimeMessage)) 
        {
          apsidesTimeMessage D = parseMessage<apsidesTimeMessage>(msg);

          tft0.setTextColor(ILI9341_GREEN, ILI9341_BLACK);  
          tft0.setTextSize(2);

          char Buffer[30] = {0};


          FormatTime(D.apoapsis, Buffer); WriteText(Buffer, 80, &tft0);
          FormatTime(D.periapsis, Buffer); WriteText(Buffer, 200, &tft0);        

        }
      }

      break;
    }
  }
}

void FormatDistance(float myDistance, char *S)
{
  // need to add the thousands seperators and set appropriate units

  // first determine the units, the value is in "m" to start with, anything below 10km should use meters
  // anything over 999,999,000m should use "M", everything else is using Km

  float Divider = 1.0;            // m
  float V = 0.0;
  char Units[3] = "m";

  if (myDistance > 10000)
  {
    if (myDistance > 999999999)
    {
      Divider = 1000000.0;        // Mm
      sprintf(Units, "Mm");
    }
    else
    {
      Divider = 1000.0;           // Km
      sprintf(Units, "Km");
    }
  }

  V = myDistance / Divider;

  // now we need to split V into thousands groupings
  int U0 = (int)(V * 1000.0) % 1000;   // produces three digit fraction
  int U1 = (int)(V) % 1000;
  int U2 = (int)(V / 1000.0) % 1000;

  // now ready for printing
  if (U2 > 0)
  {
      sprintf(S, "%d,%03d.%02d %s", U2, U1, U0, Units);
  }
  else
  {
    sprintf(S, "%3d.%02d %s", U1, U0, Units);
  }

  
}

void FormatTime(uint32_t myTime, char *S)
{
  uint32_t Y = (myTime/9201600);     // how many years do we have (426 days to a year)
  uint32_t D = (myTime/21600) % 426;     // how many days do we have (six hour days)
  uint32_t h = (myTime/3600) % 6;    // how many hours do we have?
  uint32_t m = (myTime/60) % 60;    // how many minutes do we have?
  uint32_t s = myTime % 60;    // how many seconds do we have? 

  // how we format this depends what we have
  if (Y > 0)
  {
    // we have a time measured in years, formatting in years and days works
    sprintf(S, " %d Y %d D ", Y, D);
  }
  else if (D > 0)
  {
    // time is at least a day
    sprintf(S, " %d D %02d:%02d ", D, h, m);
  }
  else
  {
    sprintf(S, " %02d:%02d:%02d ", h, m, s);
  }

  
}

Both devices are using the same TFT_DC pin, do you think that is a problem?

they are SPI devices, so they shouldn't care as long as the CS pin is different, unless thats correct they shouldn't be listening to the bus, given in general the code isn't ghosting everything to both screens it seems to be working

OK, good luck.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.