ILI9341 Display craziness after long run between screen refreshes

Status
Not open for further replies.

paynterf

Well-known member
I have a simple clock program running on a new ILI9341 from the PJRC store (code included below), and it seems to be working fine. However, after several hours of run time, it starts to show some artifacts in the blank areas, and then some hours later it starts displaying 'ghost' images of the intended date and numbers.

When coding the application, I initialized the screen with 'tft.fillScreen(ILI9341_BLACK);' in setup(), but deliberately didn't refresh the background in loop(), to avoid the annoying 'blink' each time the background refreshed. Instead I used ' tft.fillRect(x,y,h,w, ILI9341_BLACK);' to just overwrite the sections of the screen used to display the date and time information. Apparently something along the line is losing synch with where it is supposed to be writing to the screen, or something.

Here's the code:


Code:
/*
    Name:       Teensy_LCDClockV2.ino
    Created:	2/11/2021 3:57:01 PM
    Author:     FRANKNEWXPS15\Frank
*/


/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include <Wire.h>
#include "RTClib.h"
#include "SPI.h"
#include "ILI9341_t3.h"
#include "font_Arial.h"

#pragma region RTC Support
//#define FORCE_RTC_TO_LAST_COMPILE_TIME //uncomment to manually set RTC to last compile time

RTC_DS3231 rtc;

//char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
char daysOfTheWeek[7][12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
DateTime now, lastTime;
char buffer[100];

struct tStamp
{
  unsigned long mSec;
  byte RTC_Status;
};

#define DS3231_ADDRESS 0x68   ///< I2C address for DS3231
#define DS3231_STATUSREG 0x0F ///< Status register

#define RTCStatArraySize 10
#define RTCStatTime_IntervalMsec 100
tStamp RTCStatArray[RTCStatArraySize];


#pragma endregion RTC Support

#pragma region TFT Display Support
// For the Adafruit shield, these are the default.
#define TFT_DC  9
#define TFT_CS 10

#define N0_DIAGNOSTICS

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);
#pragma endregion TFT Display Support

void setup() 
{
  Serial.begin(9600);
  unsigned long mSec = millis();
  while (!Serial && millis()-mSec < 3000) // wait for Arduino Serial Monitor
  {
    delay(100);
  }
  Serial.println("Teensy 3.2 TFT Clock Program");

#pragma region TFT INITIALIZATION
  Serial.println("Initializing TFT display");

  tft.begin();
  // Note: you can now set the SPI speed to any value
  // the default value is 30Mhz, but most ILI9341 displays
  // can handle at least 60Mhz and as much as 100Mhz
  //  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.setRotation(1);
#pragma endregion region TFT INITIALIZATION

#pragma region TFT DIAGNOSTICS
#ifndef N0_DIAGNOSTICS

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(200);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(600);

  Serial.print(F("Proportional Text        "));
  Serial.println(testProportionalText());
  delay(600);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(200);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(200);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(200);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(200);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(200);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(200);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(200);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(200);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(200);

  Serial.println(F("Done!"));
#endif // !N0_DIAGNOSTICS
#pragma endregion region TFT DIAGNOSTICS

#pragma region RTC_SETUP
    Serial.println("Initializing RTC...");
    tft.println("Initializing RTC...");
    delay(1000);

  if (!rtc.begin())
  {
    Serial.println("Couldn't find RTC");
    tft.println("Couldn't find RTC");
    while (1);
  }

  bool lp = rtc.lostPower();
  Serial.print("lostPower() reports "); Serial.println(lp);
  tft.printf("lostPower() = %d\n", lp);
  delay(1000);
  if (rtc.lostPower())
  {
    Serial.println("RTC lost power.  Setting RTC to last compile time");
    tft.println("RTC lost power.  Setting RTC to last compile time");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

#ifdef FORCE_RTC_TO_LAST_COMPILE_TIME
  Serial.println("Forcing RTC to last compile time");
  // following line sets the RTC to the date & time this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
#endif // 

  //DateTime now = rtc.now();
  now = rtc.now();
  //char buffer[100];
  memset(buffer, '\0', 100);
  GetDayDateTimeStringFromDateTime(now, buffer);
  Serial.println("Retrieving Date/Time from RTC....");
  Serial.print("Date String = "); Serial.println(buffer);
#pragma endregion RTC_SETUP

//DEBUG!!
  //tft.println("Setting time to just before midnight");
  //rtc.adjust(DateTime(2021, 02, 13, 07, 59, 45));
//DEBUG!!
}


void loop(void) 
{
  now = rtc.now();

  //Display day and date
  //tft.fillRect(0, 0, 290, 40, ILI9341_RED);
  tft.fillRect(0, 0, 290, 40, ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(0, 0);
  tft.setFont(Arial_32);
  tft.printf("%s %02d/%02d/%02d\n", 
    daysOfTheWeek[now.dayOfTheWeek()], now.month(), now.day(), now.year());

  //Serial.printf("%d:%d:%02d\t%d:%d:%02d\n", now.hour(), now.minute(), now.second(),
  //lastTime.hour(), lastTime.minute(), lastTime.second());

  //Display time
  tft.setCursor(0, 100);
  tft.setTextColor(ILI9341_RED);
  tft.setFont(Arial_60);
  tft.fillRect(0, 100, 350, 65, ILI9341_BLACK);
  tft.setCursor(0, 100);
  int hournum = now.hour();
  hournum = (hournum > 12) ? hournum - 12 : hournum;
  tft.printf("%2d:%02d:%02d", hournum, now.minute(), now.second());
  lastTime = now;

  delay(1000);
}

#pragma region DATE_TIME_FUNCTIONS
void GetDayDateTimeStringFromDateTime(DateTime dt, char* bufptr)
{
  int mydayofweek = dt.dayOfTheWeek();
  //mydayofweek = (mydayofweek < 0) ? 0 : mydayofweek; //guard for return of 0 from weekday()
  int myday = dt.day();
  int mymonth = dt.month();
  int myyear = dt.year();
  int myhour = dt.hour();
  int mymin = dt.minute();
  int mysec = dt.second();
  char* dayofweek = (char*)daysOfTheWeek[mydayofweek];

  sprintf(bufptr, "%s %4d/%02d/%02d at %02d:%02d:%02d", dayofweek, mymonth, myday, myyear, myhour, mymin, mysec);
}


void HourStringFromDateTime(DateTime dt, char* bufptr)
{
  int hourval = dt.hour();
  if (hourval > 12)
  {
    hourval -= 12;
  }

  //sprintf(bufptr, "%02d", hourval);
  sprintf(bufptr, "%d", hourval);
}

void MinuteStringFromDateTime(DateTime dt, char* bufptr)
{
  sprintf(bufptr, "%02d", dt.minute());
}
#pragma endregion DATE_TIME_FUNCTIONS

And here are some photos of the screen acting normally, and then as it progressively gets worse

Normal Display:
IMG_4020.JPG

Start Seeing Artifacts:
IMG_4016.jpg

More Artifacts:
IMG_4017.jpg

Complete Craziness:
IMG_4018.jpg
IMG_4019.jpg

I know (or at least I think) I can avoid all this by refreshing the entire display every second, but then I have to put up with that annoying 'blink'. Is there any other option here?

Frank
 
So I tried to fix this problem by refreshing the screen completely at the top of each hour. Imagine my surprise when I woke up this morning to this display

IMG_4027.jpg

Here is the entire clock program I am using for this display. Clearly something is wrong, but I don't see how it can be in the sketch, as it takes several hours - at least - for the display to go awry, and the loop in my program runs once each second. Anyone have any idea what might be causing this?

Frank


Code:
/*
/*
    Name:       Teensy_LCDClockV2.ino
    Created:	2/11/2021 3:57:01 PM
    Author:     FRANKNEWXPS15\Frank
*/


/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include <Wire.h>
#include "RTClib.h"
#include "SPI.h"
#include "ILI9341_t3.h"
#include "font_Arial.h"

#pragma region RTC Support
//#define FORCE_RTC_TO_LAST_COMPILE_TIME //uncomment to manually set RTC to last compile time

RTC_DS3231 rtc;

//char daysOfTheWeek[7][12] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
char daysOfTheWeek[7][12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
DateTime now, lastTime;
char buffer[100];
int lasthour = 0; //02/15/21 added to refresh screen on the hour

struct tStamp
{
  unsigned long mSec;
  byte RTC_Status;
};

#define DS3231_ADDRESS 0x68   ///< I2C address for DS3231
#define DS3231_STATUSREG 0x0F ///< Status register

#define RTCStatArraySize 10
#define RTCStatTime_IntervalMsec 100
tStamp RTCStatArray[RTCStatArraySize];


#pragma endregion RTC Support

#pragma region TFT Display Support
// For the Adafruit shield, these are the default.
#define TFT_DC  9
#define TFT_CS 10

#define N0_DIAGNOSTICS

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC);
#pragma endregion TFT Display Support

void setup() 
{
  Serial.begin(9600);
  unsigned long mSec = millis();
  while (!Serial && millis()-mSec < 3000) // wait for Arduino Serial Monitor
  {
    delay(100);
  }
  Serial.println("Teensy 3.2 TFT Clock Program");

#pragma region TFT INITIALIZATION
  Serial.println("Initializing TFT display");

  tft.begin();
  // Note: you can now set the SPI speed to any value
  // the default value is 30Mhz, but most ILI9341 displays
  // can handle at least 60Mhz and as much as 100Mhz
  //  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.setRotation(1);
#pragma endregion region TFT INITIALIZATION

#pragma region TFT DIAGNOSTICS
#ifndef N0_DIAGNOSTICS

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(200);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(600);

  Serial.print(F("Proportional Text        "));
  Serial.println(testProportionalText());
  delay(600);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(200);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(200);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(200);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(200);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(200);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(200);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(200);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(200);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(200);

  Serial.println(F("Done!"));
#endif // !N0_DIAGNOSTICS
#pragma endregion region TFT DIAGNOSTICS

#pragma region RTC_SETUP
    Serial.println("Initializing RTC...");
    tft.println("Initializing RTC...");
    delay(1000);

  if (!rtc.begin())
  {
    Serial.println("Couldn't find RTC");
    tft.println("Couldn't find RTC");
    while (1);
  }

  bool lp = rtc.lostPower();
  Serial.print("lostPower() reports "); Serial.println(lp);
  tft.printf("lostPower() = %d\n", lp);
  delay(1000);
  if (rtc.lostPower())
  {
    Serial.println("RTC lost power.  Setting RTC to last compile time");
    tft.println("RTC lost power.  Setting RTC to last compile time");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

#ifdef FORCE_RTC_TO_LAST_COMPILE_TIME
  Serial.println("Forcing RTC to last compile time");
  // following line sets the RTC to the date & time this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
#endif // 

  //DateTime now = rtc.now();
  now = rtc.now();
  //char buffer[100];
  memset(buffer, '\0', 100);
  GetDayDateTimeStringFromDateTime(now, buffer);
  Serial.println("Retrieving Date/Time from RTC....");
  Serial.print("Date String = "); Serial.println(buffer);
#pragma endregion RTC_SETUP

//DEBUG!!
  //tft.println("Setting time to just before midnight");
  //rtc.adjust(DateTime(2021, 02, 13, 07, 59, 45));
//DEBUG!!

  //02/15/21 needed detect hour changes for screen refresh
  now = rtc.now();
  lasthour = now.hour();
}


void loop(void) 
{
  now = rtc.now();

  //Display day and date
  //tft.fillRect(0, 0, 290, 40, ILI9341_RED);
  tft.fillRect(0, 0, 290, 40, ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(0, 0);
  tft.setFont(Arial_32);
  tft.printf("%s %02d/%02d/%02d\n", 
    daysOfTheWeek[now.dayOfTheWeek()], now.month(), now.day(), now.year());

  //Serial.printf("%d:%d:%02d\t%d:%d:%02d\n", now.hour(), now.minute(), now.second(),
  //lastTime.hour(), lastTime.minute(), lastTime.second());

  //Display time
  tft.setCursor(0, 100);
  tft.setTextColor(ILI9341_RED);
  tft.setFont(Arial_60);
  tft.fillRect(0, 100, 350, 65, ILI9341_BLACK);
  tft.setCursor(0, 100);
  int hournum = now.hour();
  hournum = (hournum > 12) ? hournum - 12 : hournum;
  tft.printf("%2d:%02d:%02d", hournum, now.minute(), now.second());
  lastTime = now;

  delay(1000);

  //02/15/21 have to refresh screen occasionally to avoid screen artifacts
  if (lasthour != now.hour())
  {
    lasthour = now.hour();
    Serial.printf("Refreshing screen at %2d:%2d:%2d\n", now.hour(), now.minute(), now.second());
    tft.fillScreen(ILI9341_BLACK);
  }
}

#pragma region DATE_TIME_FUNCTIONS
void GetDayDateTimeStringFromDateTime(DateTime dt, char* bufptr)
{
  int mydayofweek = dt.dayOfTheWeek();
  //mydayofweek = (mydayofweek < 0) ? 0 : mydayofweek; //guard for return of 0 from weekday()
  int myday = dt.day();
  int mymonth = dt.month();
  int myyear = dt.year();
  int myhour = dt.hour();
  int mymin = dt.minute();
  int mysec = dt.second();
  char* dayofweek = (char*)daysOfTheWeek[mydayofweek];

  sprintf(bufptr, "%s %4d/%02d/%02d at %02d:%02d:%02d", dayofweek, mymonth, myday, myyear, myhour, mymin, mysec);
}


void HourStringFromDateTime(DateTime dt, char* bufptr)
{
  int hourval = dt.hour();
  if (hourval > 12)
  {
    hourval -= 12;
  }

  //sprintf(bufptr, "%02d", hourval);
  sprintf(bufptr, "%d", hourval);
}

void MinuteStringFromDateTime(DateTime dt, char* bufptr)
{
  sprintf(bufptr, "%02d", dt.minute());
}
#pragma endregion DATE_TIME_FUNCTIONS
 
Since it is on a breadboard with long wires I suggest trying a slower clock, 10 mhz or slower: // tft.setClock(10000000);

It could be a faulty display that forgets its rotation setting. You could refresh that each second to see if that makes a difference.
Or maybe your power supply is too weak.
 
I had a similar problem with my Teensy 4.1 and Adafruit HX8357 display. It went to inverse colors and a mirrored image. It turned out to be a loose connection. I reseated the connectors and the problem went away.
 
The problem seems to be related to the use of the Arial proportional font. I changed to non-proportional text and it seems to be working well. After about 6 hours I see no artifacts at all, without refreshing the entire screen at all (I only refresh the rectangles associated with the text I want to display, to reduce the amount of 'blink'.

IMG_4028.jpg

The date & time text doesn't look anywhere near as good as the Arial proportional font, but I'll take that to avoid the artifacts and mirroring ;-).

Regarding the other poster's thoughts about connections and clock speeds - not sure how that would affect the font display, but I'll try lowering the speed and see what happens.



Frank
 

Attachments

  • IMG_4028.jpg
    IMG_4028.jpg
    93.3 KB · Views: 45
So, I let the clock run overnight with no hourly refresh, with the non-proportional font, and it is still OK this morning. So, there is definitely an issue with the proportional font (memory leak?)
 
I’m a beginner with the ILI9341 but found that the great library from KurtE here was easy to use.

https://github.com/KurtE/ILI9341_t3n

Wonder if the t3n library version might work for you. My screen flickered a bit with updates but all was fixed by using the screen buffer options t3n provides, fine as long as you have available memory for buffering. If you enable buffering as shown in the demo code then tft. writes just update the buffer. You can then just update all the screen data in one update screen call. Saved me messing about with partial screen updates. I’m running the SPI interface at 100 MHz too, this library allows a choice of rates. Don’t know if this library might help with your font issues too.

Steve
 
SteveW,

I took a look at the double-buffering example, but it says it only works with the t4, as the t3x series doesn't have enough memory for double-buffering. Are you using a t4?
 
SteveW,

I took a look at the double-buffering example, but it says it only works with the t4, as the t3x series doesn't have enough memory for double-buffering. Are you using a t4?

Yes, using a T4.1 , sorry that wasn’t clear, I missed that you might not have enough memory. Using the buffer about half of the available memory is in use with a fairly simple program.

Steve
 
After getting my clock program finished (to the degree that any of my projects are 'finished'), I went back to investigate the display 'craziness' issue, but this time with the ILI9341_t3n library. The examples that come with the library don't work with the new library (yet, anyway), but I managed to get one to work after changing to the appropriate '_t3n' font files and cutting the sketch down to a bare minimum. After almost 24 hours of run time, the display still looks solid as a rock - YAY!

Here's the sketch I used.

Code:
#include <Adafruit_GFX.h>

#include <SPI.h>
#include <ILI9341_t3n.h>

#include "ili9341_t3n_font_Arial.h"


#define CENTER ILI9341_t3n::CENTER


// *************** Change to your Pin numbers ***************
#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 7
#define TFT_SCK 13
#define TFT_MISO 12
#define TFT_MOSI 11
#define TOUCH_CS  6

ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO);

uint8_t test_screen_rotation = 0;


void setup() {
  Serial.begin(38400);
  long unsigned debug_start = millis();
  while (!Serial && ((millis() - debug_start) <= 5000));
  Serial.println("Setup");
  tft.begin();

  tft.fillWindow(ILI9341_BLACK);
  tft.setRotation(1);

  tft.setTextColor(ILI9341_WHITE);
  tft.setFont(Arial_20);
  tft.println("Arial_20");
  displayStuff();

}

void loop()
{
}

uint32_t displayStuff()
{
  elapsedMillis elapsed_time = 0;
  tft.println("ABCDEFGHIJKLM");
  tft.println("nopqrstuvwxyz");
  tft.println("0123456789");
  tft.println("!@#$%^ &*()-");
  tft.println(); tft.println();
  return (uint32_t)elapsed_time;
}

uint32_t displayStuff1()
{
  elapsedMillis elapsed_time = 0;
  tft.println("ABCDEFGHIJKLM");
  tft.println("nopqrstuvwxyz");
  tft.println("0123456789");
  tft.println("!@#$%^ &*()-");

  int16_t cursorX = tft.getCursorX();
  int16_t cursorY = tft.getCursorY();

  uint16_t width = tft.width();
  uint16_t height = tft.height();
  Serial.printf("DS1 (%d,%d) %d %d\n", cursorX, cursorY, width, height);
  uint16_t rect_x = width / 2 - 50;
  uint16_t rect_y = height - 50;
  tft.drawRect(rect_x, rect_y, 100, 40, ILI9341_WHITE);
  for (uint16_t y = rect_y + 5; y < rect_y + 40; y += 5)
    tft.drawFastHLine(rect_x + 1, y, 98, ILI9341_PINK);
  for (uint16_t x = rect_x + 5; x < rect_x + 100; x += 5)
    tft.drawFastVLine(x, rect_y + 1, 38, ILI9341_PINK);
  tft.setCursor(width / 2, height - 30, true);
  tft.print("Center");

  // Lets try again with CENTER X keyword.
  rect_y -= 60;
  tft.drawRect(rect_x, rect_y, 100, 40, ILI9341_PINK);
  for (uint16_t y = rect_y + 5; y < rect_y + 40; y += 5)
    tft.drawFastHLine(rect_x + 1, y, 98, ILI9341_CYAN);
  for (uint16_t x = rect_x + 5; x < rect_x + 100; x += 5)
    tft.drawFastVLine(x, rect_y + 1, 38, ILI9341_CYAN);
  tft.setCursor(CENTER, rect_y);
  tft.print("XCENTR");

  // Lets try again with CENTER Y keyword.
  rect_x = 50;
  rect_y = tft.height() / 2 - 25;
  tft.drawRect(rect_x, rect_y, 100, 50, ILI9341_CYAN);
  for (uint16_t y = rect_y + 5; y < rect_y + 50; y += 5)
    tft.drawFastHLine(rect_x + 1, y, 98, ILI9341_PINK);
  for (uint16_t x = rect_x + 5; x < rect_x + 100; x += 5)
    tft.setCursor(50, CENTER);
  tft.print("YCENTR");



  tft.setCursor(cursorX, cursorY);
  static const char alternating_text[] = "AbCdEfGhIjKlM\rNoPqRsTuVwXyZ";

  for (uint8_t i = 0; i < (sizeof(alternating_text) - 1); i++) {
    if (i & 1) tft.setTextColor(ILI9341_WHITE, ILI9341_RED);
    else tft.setTextColor(ILI9341_YELLOW, ILI9341_BLUE);
    tft.write(alternating_text[i]);
  }

  tft.println(); tft.println();



  return (uint32_t)elapsed_time;
}

void nextPage()
{
  Serial.println("Press anykey to continue");
  while (Serial.read() == -1);
  while (Serial.read() != -1);

  tft.fillWindow(ILI9341_BLACK);
  tft.setCursor(0, 0);
}

After getting this to work, I then modified my clock program to use the Arial font instead of the non-proportional text font, and ran it overnight. After more than 24 hours it seems to be rock-steady, as shown in the following photo.

IMG_4073.jpg

And here is the modified clock program, with the 'CustomBox' class file as well.

Code:
/*
    Name:       Teensy_LCDClockV4.ino
    Created:	3/9/2021 11:12:31 AM
    Author:     FRANKNEWXPS15\Frank

    This project was cloned from Teensy_LCDClockV3 to re-investigate the use of proportional fonts
*/

#include <XPT2046_Touchscreen.h>
#include <ILI9341_t3n.h>
#include <Wire.h>
#include "RTClib.h"
#include "elapsedMillis.h"
#include "CustomBox.h"
#include "ili9341_t3n_font_Arial.h"

#define N0_DIAGNOSTICS
//****************************************************************************
// Settings and objects
//****************************************************************************

#pragma region DISPLAY_PIN_ASSIGNMENTS
//02/15/21 pin assignments from https://www.pjrc.com/store/display_ili9341_touch.html
#define TFT_DC  9
#define TFT_CS 10
#define TFT_RST 7
#define TFT_SCK 13
#define TFT_MISO 12
#define TFT_MOSI 11
#define TOUCH_CS  8
#pragma endregion DISPLAY_PIN_ASSIGNMENTS

XPT2046_Touchscreen ts(TOUCH_CS);
ILI9341_t3n tft = ILI9341_t3n(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCK, TFT_MISO);
elapsedMillis mSecSinceLastClockUpdate;
const int CLOCK_UPDATE_MSEC = 1000;

#pragma region DISP_AND_ADJ_BOX_PARAMETERS
// Size of the color selection boxes and the paintbrush size
#define MAX_DISPLAY_X 240
#define MAX_DISPLAY_Y 320
#define SCREEN_ORIENTATION_1

#define TS_MINX 600
#define TS_MINY 600
#define TS_MAXX 3758
#define TS_MAXY 3612

const int PLUSBOX_X = 20;
const int PLUSBOX_Y = 180;
const int PLUSBOX_WIDTH = 50;
const int PLUSBOX_HEIGHT = 50;
const int PLUSBOX_COLOR = ILI9341_BLUE;

const int MINUSBOX_X = 120;
const int MINUSBOX_Y = 180;
const int MINUSBOX_WIDTH = 50;
const int MINUSBOX_HEIGHT = 50;
const int MINUSBOX_COLOR = ILI9341_YELLOW;

const String nextstr = "NEXT";
const int nextstr_width = 70;
const int nextstr_height = 20;
const int NEXTBOX_X = 220;
const int NEXTBOX_Y = 180;
const int NEXTBOX_COLOR = ILI9341_GREEN;
const int NEXTBOX_WIDTH = 100;
const int NEXTBOX_HEIGHT = 50;

const int TIME_BOX_X = 0;
//const int TIME_BOX_Y = 98;
const int TIME_BOX_Y = 82;
const int TIME_BOX_WIDTH = 350;
//const int TIME_BOX_HEIGHT = 74;
const int TIME_BOX_HEIGHT = 80;

const int DATE_BOX_X = 0;
const int DATE_BOX_Y = 0;
const int DATE_BOX_WIDTH = 320;
const int DATE_BOX_HEIGHT = 30;

//03/05/21 used to hide adjustment boxes
const int ALL_ADJ_BOXES_WIDTH = NEXTBOX_X + NEXTBOX_WIDTH;
const int ALL_ADJ_BOXES_HEIGHT = 50;

const int TIME_HIGHLIGHT_BOX_WIDTH = 75;
const int TIME_HIGHLIGHT_BOX_HEIGHT = 5;
const int TIME_HIGHLIGHT_BOX_YLOC = 90;
const int ALL_TIME_HIGHLIGHT_BOX_WIDTH = ALL_ADJ_BOXES_WIDTH;

const int DATE_HIGHLIGHT_BOX_WIDTH = 35;
const int DATE_HIGHLIGHT_BOX_HEIGHT = 5;
const int DATE_HIGHLIGHT_BOX_YLOC = 30;
const int ALL_DATE_HIGHLIGHT_BOX_WIDTH = ALL_ADJ_BOXES_WIDTH;

CustomBox PlusBox(PLUSBOX_X, PLUSBOX_Y, PLUSBOX_WIDTH, PLUSBOX_HEIGHT, PLUSBOX_COLOR);
CustomBox MinusBox(MINUSBOX_X, MINUSBOX_Y, MINUSBOX_WIDTH, MINUSBOX_HEIGHT, MINUSBOX_COLOR);
CustomBox NextBox(NEXTBOX_X, NEXTBOX_Y, NEXTBOX_WIDTH, NEXTBOX_HEIGHT, NEXTBOX_COLOR);

CustomBox HourHighlightBox(0, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox MinuteHighlightBox(100, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox SecondHighlightBox(210, TIME_HIGHLIGHT_BOX_YLOC, TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);

CustomBox MonthHighlightBox(70, DATE_HIGHLIGHT_BOX_YLOC, DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox DayHighlightBox(120, DATE_HIGHLIGHT_BOX_YLOC, DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);
CustomBox YearHighlightBox(180, DATE_HIGHLIGHT_BOX_YLOC, 2 * DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_WHITE);

//CustomBox TimeBox(TIME_BOX_X, TIME_BOX_Y, TIME_BOX_WIDTH, TIME_BOX_HEIGHT, ILI9341_BLACK);
CustomBox TimeBox(TIME_BOX_X, TIME_BOX_Y, TIME_BOX_WIDTH, TIME_BOX_HEIGHT, ILI9341_YELLOW);
CustomBox DateBox(DATE_BOX_X, DATE_BOX_Y, DATE_BOX_WIDTH, DATE_BOX_HEIGHT, ILI9341_BLACK);

enum AdjustmentState
{
  ADJ_NONE = 0,
  ADJ_HOUR,
  ADJ_MIN,
  ADJ_SEC,
  ADJ_DAYOFWEEK,
  ADJ_MONTH,
  ADJ_DAY,
  ADJ_YEAR
};

int aDaysInMonth[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };

AdjustmentState CurAdjState = ADJ_NONE;


const int TOUCH_TIMEOUT_MSEC = 5000; //reverts to regular display if no further touch input
elapsedMillis mSecSinceLastTouch; //timer for touch timout
#pragma endregion Display & Adj Box Parameters


#pragma region RTC Support
#define FORCE_RTC_TO_LAST_COMPILE_TIME //uncomment to manually set RTC to last compile time

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
DateTime now, lastTime;
char buffer[100];
int lasthour = 0; //02/15/21 added to refresh screen on the hour

struct tStamp
{
  unsigned long mSec;
  byte RTC_Status;
};

#define DS3231_ADDRESS 0x68   ///< I2C address for DS3231
#define DS3231_STATUSREG 0x0F ///< Status register

#define RTCStatArraySize 10
#define RTCStatTime_IntervalMsec 100
tStamp RTCStatArray[RTCStatArraySize];


#pragma endregion RTC Support

void setup()
{
  Serial.begin(9600);
  delay(3000);

  Serial.println("Teensy 3.2 TFT Clock Program");

#pragma region DISPLAY INITIALIZATION
  Serial.println("Initializing TFT display");

  tft.begin();
  // Note: you can now set the SPI speed to any value
  // the default value is 30Mhz, but most ILI9341 displays
  // can handle at least 60Mhz and as much as 100Mhz
  //  tft.setClock(60000000);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.setRotation(1);
#pragma endregion region DISPLAY INITIALIZATION

#pragma region TFT DIAGNOSTICS
#ifndef N0_DIAGNOSTICS

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX);

  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(200);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(600);

  Serial.print(F("Proportional Text        "));
  Serial.println(testProportionalText());
  delay(600);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(200);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(200);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(200);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(200);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(200);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(200);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(200);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(200);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(200);

  Serial.println(F("Done!"));
#endif // !N0_DIAGNOSTICS
#pragma endregion region TFT DIAGNOSTICS

#pragma region RTC_SETUP
  DateBox.Draw(&tft); //erase previous text
  tft.setCursor(0, 0);
  Serial.println("Initializing RTC...");
  tft.println("Initializing RTC...");
  delay(1000);

  if (!rtc.begin())
  {
    DateBox.Draw(&tft); //erase previous text
    tft.setCursor(0, 0);
    Serial.println("Couldn't find RTC");
    tft.println("Couldn't find RTC");
    while (1);
  }

  bool lp = rtc.lostPower();
  Serial.print("lostPower() reports "); Serial.println(lp);
  DateBox.Draw(&tft); //erase previous text
  tft.setCursor(0, 0);
  tft.printf("lostPower() = %d\n", lp);
  delay(1000);
  if (rtc.lostPower())
  {
    Serial.println("RTC lost power.  Setting RTC to last compile time");
    DateBox.Draw(&tft); //erase previous text
    tft.setCursor(0, 0);
    tft.println("RTC lost power.  Setting RTC to last compile time");

    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

#ifdef FORCE_RTC_TO_LAST_COMPILE_TIME
  Serial.println("Forcing RTC to last compile time");
  // following line sets the RTC to the date & time this sketch was compiled
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
#endif // 

  //DateTime now = rtc.now();
  now = rtc.now();
  //char buffer[100];
  memset(buffer, '\0', 100);
  GetDayDateTimeStringFromDateTime(now, buffer);
  Serial.println("Retrieving Date/Time from RTC....");
  Serial.print("Date String = "); Serial.println(buffer);
#pragma endregion RTC_SETUP


  //DEBUG!!
    //tft.println("Setting time to just before midnight");
    //rtc.adjust(DateTime(2021, 02, 13, 00, 59, 45));
  //DEBUG!!

    //02/15/21 needed detect hour changes for screen refresh
  now = rtc.now();
  lasthour = now.hour();
  mSecSinceLastClockUpdate = 0; //used to space out clock updates in loop()
}


void loop(void)
{

  //Serial.printf("%lu: mSecSinceLastClockUpdate = %lu\n", millis(), (long int)mSecSinceLastClockUpdate);
  delay(100);

  //see if there is a touch anywhere
  if (ts.touched())
  {
    mSecSinceLastTouch = 0; //reset the timeout watchdog

    while (mSecSinceLastTouch < TOUCH_TIMEOUT_MSEC)
    {
      //Serial.printf("mSecSinceLastTouch = %lu\n", (unsigned long int)mSecSinceLastTouch);

      TS_Point Tp = ts.getPoint();// Retrieve the touch point
      if (Tp.z > 1000)
      {
        mSecSinceLastTouch = 0; //reset the timeout watchdog
        TS_Point Dp = GetTouchPointInDisplayCoords(Tp);

        CurAdjState = (AdjustmentState)AdjustTimeAndDate(Dp);
        //Serial.printf("Tp.z = %d: AdjustTimeAndDate(%d, %d) returns %d\n", Tp.z, Dp.x, Dp.y, (int)CurAdjState);

        if (CurAdjState > ADJ_NONE)
        {
          ShowLongTimeDisplay();
          ShowAdjButtons();
        }
      }
    }

    HideButtons();
    HideHighlightBars();
    ShowNormalTimeDisplay();
    ShowDateDisplay();
    CurAdjState = ADJ_NONE;

    mSecSinceLastClockUpdate = 0;
  }


  if (mSecSinceLastClockUpdate > CLOCK_UPDATE_MSEC)
  {
    mSecSinceLastClockUpdate -= CLOCK_UPDATE_MSEC;
    now = rtc.now();

    ShowDateDisplay(); //Display day and date

    //Display time
    if (CurAdjState > ADJ_NONE)
    {
      ShowLongTimeDisplay();
    }
    else
    {
      ShowNormalTimeDisplay();
    }
  }
}

#pragma region DATE_TIME_FUNCTIONS
void GetDayDateTimeStringFromDateTime(DateTime dt, char* bufptr)
{
  int mydayofweek = dt.dayOfTheWeek();
  //mydayofweek = (mydayofweek < 0) ? 0 : mydayofweek; //guard for return of 0 from weekday()
  int myday = dt.day();
  int mymonth = dt.month();
  int myyear = dt.year();
  int myhour = dt.hour();
  int mymin = dt.minute();
  int mysec = dt.second();
  char* dayofweek = (char*)daysOfTheWeek[mydayofweek];

  sprintf(bufptr, "%s %4d/%02d/%02d at %02d:%02d:%02d", dayofweek, mymonth, myday, myyear, myhour, mymin, mysec);
}


void HourStringFromDateTime(DateTime dt, char* bufptr)
{
  int hourval = dt.hour();
  if (hourval > 12)
  {
    hourval -= 12;
  }

  //sprintf(bufptr, "%02d", hourval);
  sprintf(bufptr, "%d", hourval);
}

void MinuteStringFromDateTime(DateTime dt, char* bufptr)
{
  sprintf(bufptr, "%02d", dt.minute());
}
#pragma endregion DATE_TIME_FUNCTIONS

AdjustmentState AdjustTimeAndDate(TS_Point Dp)
{
  AdjustmentState adjstate = ADJ_NONE;
  DateTime newtime;
  DateTime now = rtc.now();
  //int new_hour, new_minute, new_month, new_day, new_year;
  int new_hour, new_minute, new_month, new_day;

  //Serial.printf("In GetCurAdjState with CurAdjState = %d, at point (%d,%d)\n", CurAdjState, Dp.x, Dp.y);

  switch (CurAdjState)
  {
  case ADJ_NONE:
    if (TimeBox.TouchHit(Dp.x, Dp.y))
    {
      adjstate = ADJ_HOUR; //always start with hour
      ShowHighlightBar(ADJ_HOUR);
    }
    else if (DateBox.TouchHit(Dp.x, Dp.y))
    {
      adjstate = ADJ_MONTH; //always start with month
      ShowHighlightBar(ADJ_MONTH);
    }
    break;
  case ADJ_HOUR:
    Serial.print("In GetCurAdjState ADJ_HOUR case\n");
    adjstate = ADJ_HOUR;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_hour = now.hour() + 1;
      if (new_hour >= 24)
      {
        new_hour -= 24;
      }
      newtime = DateTime(now.year(), now.month(), now.day(), new_hour, now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_hour = now.hour() - 1;
      if (new_hour < 0)
      {
        new_hour += 24;
      }
      newtime = DateTime(now.year(), now.month(), now.day(), new_hour, now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_MIN;
      ShowHighlightBar(ADJ_MIN);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_MIN:
    Serial.print("In GetCurAdjState ADJ_MIN case\n");
    adjstate = ADJ_MIN;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_minute = now.minute() + 1;
      if (new_minute >= 60)
      {
        new_minute = 0;
      }

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), new_minute, now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute() - 1);
      Serial.printf("ADJ_HOUR case in DoMinusOp().  Decrementing Hour Value\n");
      Serial.printf("Old time is %d:%d:%d:%d\n", now.day(), now.month(), now.year(), now.hour());
      Serial.printf("New time is %d:%d:%d:%d\n", newtime.day(), newtime.month(), newtime.year(), newtime.hour());
      rtc.adjust(newtime);
      Serial.printf("RTC now set to %d:%d:%d:%d\n", now.day(), now.month(), now.year(), now.hour());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_SEC;
      ShowHighlightBar(ADJ_SEC);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_SEC:
    Serial.print("In GetCurAdjState ADJ_SEC case\n");
    adjstate = ADJ_SEC;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute(), now.second());

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), 0);
      Serial.printf("New time is %d/%d/%d %d:%d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute(), newtime.second());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute(), now.second());

      newtime = DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), 0);
      Serial.printf("New time is %d/%d/%d %d:%d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute(), newtime.second());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second());
      ShowLongTimeDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_HOUR;
      ShowHighlightBar(ADJ_HOUR);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_DAYOFWEEK:
    break;
  case ADJ_MONTH:
    Serial.print("In GetCurAdjState ADJ_MONTH case\n");
    adjstate = ADJ_MONTH;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_month = now.month() + 1;
      if (new_month > 12)
      {
        new_month -= 12;
      }
      newtime = DateTime(now.year(), new_month, now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_month = now.month() - 1;
      new_month = (new_month <= 0) ? 12 : new_month;
      newtime = DateTime(now.year(), new_month, now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_DAY;
      ShowHighlightBar(ADJ_DAY);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_DAY:
    Serial.print("In GetCurAdjState ADJ_DAY case\n");
    adjstate = ADJ_DAY;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_day = AdjustDayValue(now.month(), now.day() + 1); //not all months have same number of days
      newtime = DateTime(now.year(), now.month(), new_day, now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      new_day = now.day() - 1;
      new_day = (new_day <= 0) ? aDaysInMonth[now.month() - 1] : new_day;
      newtime = DateTime(now.year(), now.month(), new_day, now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_YEAR;
      ShowHighlightBar(ADJ_YEAR);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  case ADJ_YEAR:
    Serial.print("In GetCurAdjState ADJ_YEAR case\n");
    adjstate = ADJ_YEAR;

    if (PlusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Plus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      newtime = DateTime(now.year() + 1, now.month(), now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (MinusBox.TouchHit(Dp.x, Dp.y))
    {
      Serial.printf("Minus box touch at (%d,%d)\n", Dp.x, Dp.y);
      Serial.printf("Old time is %d/%d/%d %d:%d\n", now.month(), now.day(), now.year(), now.hour(), now.minute());
      newtime = DateTime(now.year() - 1, now.month(), now.day(), now.hour(), now.minute(), now.second());
      Serial.printf("New time is %d/%d/%d %d:%d\n", newtime.month(), newtime.day(), newtime.year(), newtime.hour(), newtime.minute());
      rtc.adjust(newtime);
      now = rtc.now();
      Serial.printf("RTC now set to %d/%d/%d %d:%d\n", now.day(), now.month(), now.year(), now.hour(), now.minute());
      ShowDateDisplay();
      delay(500);  //delay for a while to avoid multiple 'add' operations
    }
    else if (NextBox.TouchHit(Dp.x, Dp.y))
    {
      //Serial.printf("Next box touch at (%d,%d)\n", Dp.x, Dp.y);
      adjstate = ADJ_MONTH;
      ShowHighlightBar(ADJ_MONTH);
      delay(500); //prevent multiple NEXT transitions
    }
    break;
  default:
    break;
  }

  //Serial.printf("AdjustTimeAndDate(%d, %d) returns %d\n", Dp.x, Dp.y, (int)adjstate);
  return adjstate;
}

#pragma region Helper Functions
TS_Point GetTouchPointInDisplayCoords(TS_Point Tp)
{
  //Purpose:  Retrieve a touch point in display coordinate system
  //Inputs:
  //  Rotation value
  //  Mapping values
  //  TS_Point from touch chip
  //Outputs:
  //  TS_Point object containing touch (X,Y) in display coordinates
  //Plan:
  //  Step1: Retrieve touch point from touch chip
  //  Step2: Map to display coordinates taking rotation into account

//Step1: Retrieve touch point from touch chip
  //TS_Point Tp = ts.getPoint();
  TS_Point Dp;
  //Step2: Map to display coordinates taking rotation into account
#ifdef SCREEN_ORIENTATION_1
  Dp.x = map(Tp.x, TS_MAXX, TS_MINX, 0, tft.width());
  Dp.y = map(Tp.y, TS_MAXY, TS_MINY, 0, tft.height());
#else
#endif

  ////DEBUG!!
  //Serial.printf(" GetTouchPointInDisplayCoords: %lu\t%d\t%d\t%d\t%d\t%d\n",
  //  millis(), Tp.x, Tp.y, Tp.z, Dp.x, Dp.y);
  ////DEBUG!!

  return Dp;
}

void ShowAdjButtons()
{
  PlusBox.Draw(&tft, '+', ILI9341_BLACK);
  MinusBox.Draw(&tft, '-', ILI9341_BLACK);

  //03/04/21 getTextBounds() reports wrong info - use 70w x 20 tall
  int txt_x = NEXTBOX_X + round((float)(NEXTBOX_WIDTH / 2.)) - round((float)(70 / 2.));
  int txt_y = NEXTBOX_Y + round((float)(NEXTBOX_HEIGHT / 2.)) - round((float)(20 / 2.));
  tft.setCursor(txt_x, txt_y);
  tft.setTextSize(3);
  tft.setTextColor(ILI9341_BLACK);
  String boxstr = "NEXT";
  NextBox.Draw(&tft);
  tft.print(boxstr);
}

void HideButtons()
{
  PlusBox.Draw(&tft, ILI9341_BLACK);
  MinusBox.Draw(&tft, ILI9341_BLACK);
  NextBox.Draw(&tft, ILI9341_BLACK);
}

void ShowNormalTimeDisplay()
{
  //Serial.printf("In ShowNormalTimeDisplay\n");
  now = rtc.now();
  int hournum = now.hour();

  //guard against 1259->1300 && 2359->0000 transitions
  if (hournum > 12)
  {
    hournum -= 12;
  }
  else if (hournum == 0)
  {
    hournum += 12;
  }

  hournum = (hournum > 12) ? hournum - 12 : hournum;

  tft.setCursor(0, 100);
  TimeBox.Draw(&tft); //redraws black background

  tft.setTextColor(ILI9341_RED);
  //tft.setTextSize(10);
  tft.setFont(Arial_72);
  //tft.setCursor(0, 100);
  tft.setCursor(160, 120, true);//try the text centering feature
  tft.printf("%2d:%02d", hournum, now.minute());
}

void ShowLongTimeDisplay()
{
  //Serial.printf("In ShowLongTimeDisplay\n");
  now = rtc.now();
  tft.setCursor(0, 100);
  tft.setTextColor(ILI9341_RED);
  //tft.setTextSize(6);
  tft.setFont(Arial_28);
  tft.fillRect(0, 98, 350, 74, ILI9341_BLACK);
  //tft.fillRect(0, 98, 350, 74, ILI9341_YELLOW);
  tft.setCursor(0, 100);
  int hournum = now.hour();

  //guard against 1259->1300 && 2359->0000 transitions
  if (hournum > 12)
  {
    hournum -= 12;
  }
  else if (hournum == 0)
  {
    hournum += 12;
  }

  hournum = (hournum > 12) ? hournum - 12 : hournum;

  tft.printf("%2d:%02d:%02d", hournum, now.minute(), now.second());
}


void ShowHighlightBar(AdjustmentState adjstate)
{
  switch (adjstate)
  {
  case ADJ_NONE:
    Serial.printf("ADJ_NONE case in ShowHighlightBox()\n");
    HideHighlightBars();
    break;
  case ADJ_HOUR:
    HideHighlightBars();
    HourHighlightBox.Draw(&tft);
    break;
  case ADJ_MIN:
    HideHighlightBars();
    MinuteHighlightBox.Draw(&tft);
    break;
  case ADJ_SEC:
    HideHighlightBars();
    SecondHighlightBox.Draw(&tft);
    break;
  case ADJ_DAYOFWEEK:
    break;
  case ADJ_MONTH:
    HideHighlightBars();
    MonthHighlightBox.Draw(&tft);
    break;
  case ADJ_DAY:
    HideHighlightBars();
    DayHighlightBox.Draw(&tft);
    break;
  case ADJ_YEAR:
    HideHighlightBars();
    YearHighlightBox.Draw(&tft);
    break;
  default:
    break;
  }
}

void HideHighlightBars()
{
  tft.fillRect(0, DATE_HIGHLIGHT_BOX_YLOC, ALL_DATE_HIGHLIGHT_BOX_WIDTH, DATE_HIGHLIGHT_BOX_HEIGHT, ILI9341_BLACK);
  tft.fillRect(0, TIME_HIGHLIGHT_BOX_YLOC, ALL_TIME_HIGHLIGHT_BOX_WIDTH, TIME_HIGHLIGHT_BOX_HEIGHT, ILI9341_BLACK);
}

void ShowDateDisplay()
{
  DateTime now = rtc.now();
  DateBox.Draw(&tft);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setCursor(0, 0);
  //tft.setTextSize(3);
  tft.setFont(Arial_28);
  tft.printf("%s %02d/%02d/%02d\n",
    daysOfTheWeek[now.dayOfTheWeek()], now.month(), now.day(), now.year());
}

int AdjustDayValue(int month, int day)
{
  if (day > aDaysInMonth[month - 1])
  {
    Serial.printf("%d is greater than max days (%d) for this month - setting to 1\n", day, aDaysInMonth[month - 1]);
    return 1;
  }
  else
  {
    return day;
  }
}
#pragma endregion Helper Functions

This 'CustomBox' class is a helper class for the clock program

Code:
#pragma once

#ifndef _ILI9341_t3NH_	
#include <ILI9341_t3n.h>
#endif // !_ILI9341_t3NH_

class CustomBox
{
public:
	int x;
	int y;
private:
	int w;
	int h;
	int color;
public:
	CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color);
	CustomBox(int in_w, int in_h, int in_color);
	void Draw(ILI9341_t3n* disp);
	void Draw(ILI9341_t3n* disp, char c, int txtcolor, int size);
	void Draw(ILI9341_t3n* disp, String txt, int size);
	void Draw(ILI9341_t3n* disp, char* buff, int size);
	void Draw(ILI9341_t3n* disp, int color);
	bool TouchHit(int in_x, int in_y);
};


CustomBox::CustomBox(int in_w, int in_h, int in_color)
{
	x = 0;
	y = 0;
	w = in_w;
	h = in_h;
	color = in_color;
}

CustomBox::CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color)
{
	x = in_x;
	y = in_y;
	w = in_w;
	h = in_h;
	color = in_color;
}

inline void CustomBox::Draw(ILI9341_t3n* disp)
{
	disp->fillRect(x, y, w, h, color);
}

//class CustomBoxWithText :CustomBox
//{
//private:
//	char boxtext[6];
//public:
//	CustomBoxWithText(int in_x, int in_y, int in_w, int in_h, int in_color, char* txt);
//};
//
//
//CustomBox::CustomBox(int in_x, int in_y, int in_w, int in_h, int in_color)
//{
//	x = in_x;
//	y = in_y;
//	w = in_w;
//	h = in_h;
//	color = in_color;
//}

inline void CustomBox::Draw(ILI9341_t3n* disp, char c, int txtcolor, int size = 0)
{
	disp->fillRect(x, y, w, h, color);
	if (size==0)
	{
		size = round((float)h / 10.);
	}
	int char_x = x + round((float)w / 4.);
	//int char_x = x;
	disp->setCursor(char_x, y+size);
	disp->setTextSize(size);
	disp->setTextColor(txtcolor);
	disp->print(c);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, String txt, int size)
{
	disp->fillRect(x, y, w, h, color);
	disp->setCursor(x, y);
	disp->setTextSize(size);
	disp->print(txt);
}

inline void CustomBox::Draw(ILI9341_t3n* disp, char* buff, int size)
{
	Serial.printf("In Draw() with buff = %s\n", buff);
	disp->fillRect(x, y, w, h, color);
	//disp->setCursor(x, y);
	disp->setTextSize(size);
	//disp->setTextDatum(MC_DATUM);
	//disp->drawString1(buff, strlen(buff), x, y);
	disp->drawString(buff, strlen(buff), x, y); //03/09/21 in latest library version drawString1 was made into an overload of drawString
}

inline void CustomBox::Draw(ILI9341_t3n* disp, int newcolor)
{
	disp->fillRect(x, y, w, h, newcolor);
}

inline bool CustomBox::TouchHit(int in_x, int in_y)
{
	//Purpose:  Determine if input coordinates are within a specified box
	//Inputs:
	//  in_x, in_y = integers denoting mapped touch coordinates
	//  box location & dimensions
	//Outputs:
	//  true if (in_x, in_y) falls within box region.  Othewise, false

	//Serial.printf("TouchHit(%d,%d), box (x,y,w,h) = (%d,%d,%d,%d)\n",
	//	in_x, in_y, x, y, w, h);

	return (in_x > x && in_y > y && in_x < x + w && in_y < y + h);
}

I've posted an issue regarding the example incompatibility to KurtE's GitHub page for the ILI9341_t3n library, so maybe we'll see some pull requests to make them all compatible with the new library.

THANKS KurtE!

Frank
 
Status
Not open for further replies.
Back
Top