TeensyUserInterface update to support ST7796

This: https://github.com/PaulStoffregen/ILI9341_fonts
... confusing to name fonts ... with or without display name - and just found a 2022 abandoned github copy from old computer, but not in current libraries.
@defragster

As I mentioned a couple times before you should use this version https://github.com/mjs513/ILI9341_fonts of Paul's lib. @KurtE spent time adding in support for multiple displays:

C++:
#if __has_include(<RA8875.h>)
    #include "RA8875.h"
#elif __has_include(<ILI9488_t3.h>)
    #include "ILI9488_t3.h"
#elif __has_include(<ILI9341_t3n.h>)
    #include "ILI9341_t3n.h"
#elif __has_include(<ILI9341_t3.h>)
    #include "ILI9341_t3.h"
#elif __has_include(<ST7735_t3.h>)
    #include "ST7735_t3.h"
#elif __has_include(<HX8357_t3n.h>)
    #include "HX8357_t3n.h"
#elif __has_include(<GC9A01A_t3n.h>)
    #include "GC9A01A_t3n.h"
#elif __has_include(<ILI9341_GIGA_n.h>)
    #include "ILI9341_GIGA_n.h"
#else
    #include "def_ili9341_fonts.h"
#endif
 
@defragster

As I mentioned a couple times before you should use this version https://github.com/mjs513/ILI9341_fonts of Paul's lib. @KurtE spent time adding in support for multiple displays:
Actually I think you did most of the work 😆

I really wish we could have (maybe still can) abstract, such that we don't have to muck up each of the font files.

Where maybe we should had them all depend on a header file within the font library and instead update the display libraries
to know about that header and use it if defined... Might have had to cast to a different structure name....
 
mentioned a couple times before
Missed any mention(s) : not seeing in "this thread" - so that was to be a question ... though the "?" wasn't there I see.

So, cool they work and are there
maybe we should
If only there was a font DROPDOWN :) Each sketch/display Lib (and AdaFruit) having unique names/approach has been a distraction. PJRC did the MEGA list years back and @KurtE / @mjs513 have incorporated and expanded the utility ... Is there a TeensyUser Wiki entry for 'typesetting your sketch'? :) (y) :ninja:
 
For the heck of it, I added a remote to @mjs513 branch and switched to his updated branch.
I brought up the simple menu example with the new 3.5" IPS display and it comes up :D

But noticed touch did not work. That is when I checked and saw that this display does not use a CHIP select pin
but instead has I2C interface... Guess I can not use it with my existing boards... Probably just breadboard for now.
1758310700817.png
 
Most capacitive touch controllers use I2C for the touch interface including the RA8875. This is apparently due to the fact that capacitive touch controllers tend to have fairly powerful 16-bit microcontrollers built in that reduces the amount of communications needed, so SPI would be overkill.
 
Quick note: the next thing I ran into is the display was not starting up. Pretty sure I know why. My boards that have displays are typically setup
with RESET signals setup, as I have had better luck with displays that get reset...

The current APIs don't address this this. They assume you hard wire it to 3.3v

Code:
Hacked up example:
  const int lCD_RST_PIN = 8;
  const int TOUCH_CS_PIN = 6; // 8;
  //
  // setup the LCD orientation, the default font and initialize the user interface
  //
  pinMode(lCD_RST_PIN, OUTPUT);
  digitalWriteFast(lCD_RST_PIN, HIGH);
  ui.begin(LCD_CS_PIN, LCD_DC_PIN, TOUCH_CS_PIN, LCD_ORIENTATION_LANDSCAPE_4PIN_RIGHT, Arial_9_Bold);
...
Most capacitive touch controllers use I2C for the touch interface including the RA8875. This is apparently due to the fact that capacitive touch controllers tend to have fairly powerful 16-bit microcontrollers built in that reduces the amount of communications needed, so SPI would be overkill.
I understand. My main comment is at first glance it looks like these displays are a simple drop in replacement for the ILI9241, ILI9488,
other ST7796 and their not. hopefully not different enough to cause issues. As for example on my boards, the pins for SCL, MISO, MOSI
for the display are routed over for those pins for the touch controller.

Will jumper it breadboard, which will limit it's use for me. But looks nice
 
Last edited:
@kd5rxt-mark
Spent a little time to see what your controls sketch looks like on the 7796:
1758372231883.png


I like the your sliders a lot better - mind if I try to incorporate them into our menu library?

Thanks
Mike
C++:
//
//  Teensy 4.1 RA8875 template - version 1.1 dated 20250916-1200
//
//    - designed & written by Mark J Culross (KD5RXT)
//
//    - controlled via buttons/sliders displayed on a 7" RA8875 800x480 display w/ touchscreen from here:
//
//         https://www.buydisplay.com/7-inch-lcd-module-capacitive-touch-screen-panel-i2c-spi-serial
//
//         Display options (ER-TFTM070-5):
//            Interface:               Pin Header Connection-4-wire SPI
//            VDD:                     5.0V (can always change the jumper later to power from 3.3VDC)
//            Touch Panel:             7" Capacitive Touch Panel with Controller
//            MicroSD Card Interface:  Pin Header Connection (not useable - see display docs)
//            Font Chip:               (none required / none selected)
//
//         Make sure to edit C:\Program Files (x86)\Arduino\hardware\teensy\avr\libraries\RA8875\_settings\RA8875UserSettings.h
//            - uncomment the following line: #define USE_FT5206_TOUCH//capacitive touch screen
//
//  Arduino IDE Configuration (last built with Arduino 1.8.19 + Teensyduino 1.60b4):
//     Tools/Board:           "Teensy 4.1"
//     Tools/USB Type:        "Serial"
//     Tools/CPU Speed:       "600MHz"
//     Tools/Optimize:        "Faster"
//     Tools/Keyboard Layout: "US English"
//     Tools/Port:            "COMx Serial (Teensy 4.1)"
//
//

const String VERSION0  = "1.1";
const String VERSION1  = ">> Teensy RA8875 template <<";
const String VERSION2  = "version " + VERSION0 + " dated 09/16/2025 @1200";
const String VERSION3  = "designed & written by Mark J Culross (KD5RXT)";

//#define DISABLE_BACKLIGHT_CONTROL       // uncomment to disable backlight control (NOTE: enabling may induce 230Hz tone in LINE OUT audio)
//#define DEBUG_TOUCHSCREEN               // uncomment to print touchscreen coordinates & touch state to SerialMonitor
//#define DEBUG_SLIDER_BUMP               // uncomment to print slider bump up/down calculations & intermediate values to SerialMonitor

const int ST7796_MAX_TOUCH_LIMIT =   1;

#include <ST7796_t3.h>
#include <st7735_t3_font_Arial.h>
#include <font_ArialBold.h>
#include <XPT2046_Touchscreen.h>

 const int LCD_CS_PIN = 10;
  const int LCD_DC_PIN = 9;

  const int TOUCH_CS_PIN = 27; // 8;
 
ST7796_t3 tft = ST7796_t3(LCD_CS_PIN, LCD_DC_PIN, 8);

XPT2046_Touchscreen ts(TOUCH_CS_PIN, 255);

// keep track of splash screen delay
const int CHECK_SPLASH_MILLIS = 4000;
unsigned long check_splash_time;

// keep track of when the screen needs to be updated
boolean screen_update_required = false;

// keep track of how often to check touchscreen
const int CHECK_TOUCHSCREEN_MILLIS = 20;
unsigned long check_touchscreen_time = millis();

// custom RA8875 TFT colors (5-bit RED, 6-bit GREEN, 5-bit BLUE)
#define ST7735_DIMGREY     0x6B4D
#define ST7735_MDGREY      0x8410
#define ST7735_ASHGREY     0xB5F6
#define ST7735_ORANGE      0xFB00


typedef enum
{
   CONFIG_MODE_SPLASH = 0, CONFIG_MODE_INIT_SCREEN,
   CONFIG_MODE_MENU1, CONFIG_MODE_MENU2, CONFIG_MODE_MENU3, CONFIG_MODE_MENU4,
}  CONFIG_MODE;

CONFIG_MODE config_mode = CONFIG_MODE_SPLASH;

boolean previously_touched = false;
boolean touch_triggered = false;

// global touchscreen coordinates (in pixels) where touched
int16_t BtnX = -1, BtnY = -1;

struct BUTTON_TYPE
{
   unsigned int   xCenterLoc;
   unsigned int   yCenterLoc;
   unsigned int   xSize;
   unsigned int   ySize;
   const String*  textPtr;
   uint16_t       textColor;
   uint16_t       buttonColor;
   uint16_t       borderColor;
   boolean        activated;
};

// create button objects, passing in the display object

// UNIQUE PORTION OF THE DISPLAY SCREEN (only used for clearing the bottom portion of the screen & detecting touches)
const String uniqueScreenAreaText         = "";
BUTTON_TYPE uniqueScreenArea              = {240, 160, 480, 320,     &uniqueScreenAreaText,  ST7735_BLACK,  ST7735_BLACK,  ST7735_BLACK,  true};

// CHANGING BUTTON PORTION OF THE DISPLAY SCREEN (only used for clearing/redrawing buttons when changing menus)
const String buttonScreenAreaText         = "";
BUTTON_TYPE buttonScreenArea              = {240,  81, 400,  50,     &buttonScreenAreaText,  ST7735_BLACK,  ST7735_BLACK,  ST7735_BLACK,  true};


// PRIMARY BUTTONS
const String menu1ButtonText              = "MENU 1";
BUTTON_TYPE menu1Button                   = {120,  24, 70, 36,            &menu1ButtonText,  ST7735_BLACK,  ST7735_GREEN, ST7735_ORANGE, false};

const String menu2ButtonText              = "MENU 2";
BUTTON_TYPE menu2Button                   = {195,  24, 70, 36,            &menu2ButtonText,  ST7735_BLACK,  ST7735_GREEN, ST7735_ORANGE, false};

const String menu3ButtonText              = "MENU 3";
BUTTON_TYPE menu3Button                   = {270,  24, 70, 36,            &menu3ButtonText,  ST7735_BLACK,  ST7735_GREEN, ST7735_ORANGE, false};

const String menu4ButtonText              = "MENU 4";
BUTTON_TYPE menu4Button                   = {345,  24, 70, 36,            &menu4ButtonText,  ST7735_BLACK,  ST7735_GREEN, ST7735_ORANGE, false};



// MENU 1 BUTTONS
const String menu1BacklightButtonTextV    = "BACKLIGHT";
BUTTON_TYPE menu1BacklightButtonV         = {350, 85, 90, 30,  &menu1BacklightButtonTextV,  ST7735_GREEN,  ST7735_BLACK,    ST7735_RED,  true};

const String menu1BacklightButtonTextH    = "BACKLIGHT";
BUTTON_TYPE menu1BacklightButtonH         = {240, 130, 90, 30,  &menu1BacklightButtonTextH,  ST7735_GREEN,  ST7735_BLACK,    ST7735_RED,  true};

const String menu1Button1Text             = "BUTTON 1-1";
BUTTON_TYPE menu1Button1                  = {50, 100, 90, 30,           &menu1Button1Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu1Button2Text             = "BUTTON 1-2";
BUTTON_TYPE menu1Button2                  = {50, 150, 90, 30,           &menu1Button2Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu1Button3Text             = "BUTTON 1-3";
BUTTON_TYPE menu1Button3                  = {50, 200, 90, 30,           &menu1Button3Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu1Button4Text             = "BUTTON 1-4";
BUTTON_TYPE menu1Button4                  = {50, 250, 90, 30,           &menu1Button4Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};


// MENU 2 BUTTONS
const String menu2Button1Text             = "BUTTON 2-1";
BUTTON_TYPE menu2Button1                  = {100, 200, 90, 30,           &menu2Button1Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu2Button2Text             = "BUTTON 2-2";
BUTTON_TYPE menu2Button2                  = {100, 250, 90, 30,           &menu2Button2Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu2Button3Text             = "BUTTON 2-3";
BUTTON_TYPE menu2Button3                  = {100, 300, 90, 30,           &menu2Button3Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu2Button4Text             = "BUTTON 2-4";
BUTTON_TYPE menu2Button4                  = {100, 350, 90, 30,           &menu2Button4Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};


// MENU 3 BUTTONS
const String menu3Button1Text             = "BUTTON 3-1";
BUTTON_TYPE menu3Button1                  = {100, 200, 90, 30,           &menu3Button1Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu3Button2Text             = "BUTTON 3-2";
BUTTON_TYPE menu3Button2                  = {100, 250, 90, 30,           &menu3Button2Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu3Button3Text             = "BUTTON 3-3";
BUTTON_TYPE menu3Button3                  = {100, 300, 90, 30,           &menu3Button3Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu3Button4Text             = "BUTTON 3-4";
BUTTON_TYPE menu3Button4                  = {100, 350, 90, 30,           &menu3Button4Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};


// MENU 4 BUTTONS
const String menu4Button1Text             = "BUTTON 4-1";
BUTTON_TYPE menu4Button1                  = {100, 200, 90, 30,           &menu4Button1Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu4Button2Text             = "BUTTON 4-2";
BUTTON_TYPE menu4Button2                  = {100, 250, 90, 30,           &menu4Button2Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu4Button3Text             = "BUTTON 4-3";
BUTTON_TYPE menu4Button3                  = {100, 300, 90, 30,           &menu4Button3Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};

const String menu4Button4Text             = "BUTTON 4-4";
BUTTON_TYPE menu4Button4                  = {100, 350, 90, 30,           &menu4Button4Text,  ST7735_BLACK,  ST7735_GREEN,    ST7735_RED,  true};


#define BUMP_REPEAT_START_DELAY_MILLISECONDS 750

typedef enum
{
   SLIDER_MODE_HORIZONTAL = 0, SLIDER_MODE_VERTICAL
}  SLIDER_MODE;

struct SLIDER_TYPE
{
   unsigned int   xCenterLoc;
   unsigned int   yCenterLoc;
   unsigned int   xSize;
   unsigned int   ySize;
   float          value;
   unsigned int   minorTickSections;  // normally = 10
   unsigned int   majorTickSections;  // normally =  2
   unsigned int   placesBeforeTheDecimal;
   unsigned int   placesAfterTheDecimal;
   boolean        showPlusMinusSign;
   float          minValue;  // for HORIZONTAL = all the way to the left, for VERTICAL, all the way up
   float          maxValue;  // for HORIZONTAL = all the way to the right, for VERTICAL, all the way down
   boolean        withBumpUpArrow;
   boolean        withBumpDownArrow;
   float          bumpValue;
   unsigned int   xValueCenterLoc;
   unsigned int   yValueCenterLoc;
   uint16_t       valueColor;
   uint16_t       backgroundColor;
   uint16_t       borderColor;
   uint16_t       scaleColor;
   uint16_t       handleColor;
   uint16_t       handleBorderColor;
   uint16_t       backgroundColorDisabled;
   uint16_t       borderColorDisabled;
   uint16_t       scaleColorDisabled;
   uint16_t       handleColorDisabled;
   uint16_t       handleBorderColorDisabled;
   uint16_t       bumpBackgroundColor;
   boolean        activated;
   boolean        repeatEnabled;
   boolean        previouslyTouched;
   unsigned long  touchStartMillis;
   unsigned int   repeatMilliseconds;
   SLIDER_MODE    orientation;
};

SLIDER_TYPE  menu1BacklightSliderV    = { 420,  180,  20, 150, 127.00, 16, 2, 3, 0, false,     1.0,    255.0,  true, true, 1.00, 360, 115, ST7735_WHITE, ST7735_MDGREY, ST7735_GREEN, ST7735_BLACK,   ST7735_GREEN, ST7735_BLACK,   ST7735_BLACK, ST7735_MDGREY, ST7735_MDGREY, ST7735_MDGREY, ST7735_BLACK, ST7735_BLACK, true,  true, false, 0, 250, SLIDER_MODE_VERTICAL };
SLIDER_TYPE  menu1BacklightSliderH    = { 244,  180, 150,  20, 127.00, 16, 2, 3, 0, false,     1.0,    255.0,  true, true, 1.00, 240, 160, ST7735_WHITE, ST7735_MDGREY, ST7735_GREEN, ST7735_BLACK,   ST7735_GREEN, ST7735_BLACK,   ST7735_BLACK, ST7735_MDGREY, ST7735_MDGREY, ST7735_MDGREY, ST7735_BLACK, ST7735_BLACK, true,  true, false, 0, 250, SLIDER_MODE_HORIZONTAL };




// function headers
void centerDrawText(const String text, unsigned int xCenterLoc, unsigned int yCenterLoc, uint16_t textColor, uint16_t textBackground);
boolean checkButton(BUTTON_TYPE thisButton);
boolean checkSlider(SLIDER_TYPE* thisSlider);
boolean checkSliderBumpDown(SLIDER_TYPE* thisSlider);
boolean checkSliderBumpUp(SLIDER_TYPE* thisSlider);
void drawButton(BUTTON_TYPE thisButton);
void drawScreen(void);
void drawSlider(SLIDER_TYPE thisSlider);
void loop();
boolean processTouchscreen(void);
void setup();



// draw text, centered around xLoc & yLoc
#line 562 "C:\\Users\\Merli\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2025819-58196-1tl48s7.quon\\sketch_sep19b\\sketch_sep19b.ino"
boolean checkSliderBumpDown(SLIDER_TYPE * thisSlider);
#line 646 "C:\\Users\\Merli\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2025819-58196-1tl48s7.quon\\sketch_sep19b\\sketch_sep19b.ino"
boolean checkSliderBumpUp(SLIDER_TYPE * thisSlider);
#line 262 "C:\\Users\\Merli\\AppData\\Local\\Temp\\.arduinoIDE-unsaved2025819-58196-1tl48s7.quon\\sketch_sep19b\\sketch_sep19b.ino"
void centerDrawText(const String text, unsigned int xCenterLoc, unsigned int yCenterLoc, uint16_t textColor, uint16_t textBackground)
{
   unsigned int xOffset = (text.length() * 8) / 2;
   unsigned int yOffset = 8;

   tft.setTextColor(textColor, textBackground);
   tft.setTextSize(1);

   tft.setCursor(xCenterLoc - xOffset, yCenterLoc - yOffset);
   tft.print(text);
}  // centerDrawText


// check if a button was pressed
boolean checkButton(BUTTON_TYPE thisButton)
{
   boolean retVal = false;

   // if touched most recently in thisButton
   if ((BtnX >= (uint16_t)(thisButton.xCenterLoc - (thisButton.xSize / 2))) &&
         (BtnX <= (uint16_t)(thisButton.xCenterLoc + (thisButton.xSize / 2))) &&
         (BtnY >= (uint16_t)(thisButton.yCenterLoc - (thisButton.ySize / 2))) &&
         (BtnY <= (uint16_t)(thisButton.yCenterLoc + (thisButton.ySize / 2))))
   {
      retVal = true;
   }

   return (retVal);
}  // checkButton()


// check if a slider has changed
boolean checkSlider(SLIDER_TYPE* thisSlider)
{
   boolean retVal = false;
   float newValue = 0.0f;

   // if thisSlider is active & touched most recently in thisSlider
   if (thisSlider->activated)
   {
      if (thisSlider->orientation == SLIDER_MODE_VERTICAL)
      {
         // if touched most recently in thisSlider
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2) + 15))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2) + 15))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
         {
            if (BtnX < (uint16_t)(thisSlider->xCenterLoc - (thisSlider->xSize / 2)))
            {
               BtnX = (uint16_t)(thisSlider->xCenterLoc - (thisSlider->xSize / 2));
            }

            if (BtnX > (uint16_t)(thisSlider->xCenterLoc + (thisSlider->xSize / 2)))
            {
               BtnX = (uint16_t)(thisSlider->xCenterLoc + (thisSlider->xSize / 2));
            }

            if (BtnY < (uint16_t)(thisSlider->yCenterLoc - (thisSlider->ySize / 2)))
            {
               BtnY = (uint16_t)(thisSlider->yCenterLoc - (thisSlider->ySize / 2));
            }

            if (BtnY > (uint16_t)(thisSlider->yCenterLoc + (thisSlider->ySize / 2)))
            {
               BtnY = (uint16_t)(thisSlider->yCenterLoc + (thisSlider->ySize / 2));
            }

            newValue = (float)map((float)BtnY, (float)(thisSlider->yCenterLoc - (thisSlider->ySize / 2)), (float)(thisSlider->yCenterLoc + (thisSlider->ySize / 2)), thisSlider->maxValue, thisSlider->minValue);

            if (newValue != thisSlider->value)
            {
               thisSlider->value = newValue;

               retVal = true;
            }
         } else {
            // if thisSlider is active & has either bump arrow & has been held & was touched most recently in thisSlider's bump arrow area
            if ((thisSlider->withBumpDownArrow || thisSlider->withBumpUpArrow) && (thisSlider->repeatEnabled))
            {
               // if touched most recently in thisSlider's bump up arrow
               if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
                     (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
                     (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2) + 50))) &&
                     (BtnY <= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))))
               {

#ifdef DEBUG_SLIDER_BUMP
                  Serial.print("initial slider value:   ");
                  Serial.println(thisSlider->value);
                  Serial.print("slider bump up value:   ");
                  Serial.println(thisSlider->bumpValue);
#endif

                  if (!(thisSlider->previouslyTouched))
                  {
                     thisSlider->previouslyTouched = true;
                     thisSlider->touchStartMillis = millis();
                  } else {
                     if ((millis() - thisSlider->touchStartMillis) > BUMP_REPEAT_START_DELAY_MILLISECONDS)
                     {
                        if ((thisSlider->value + thisSlider->bumpValue) < thisSlider->maxValue)
                        {
                           thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
                           thisSlider->value += thisSlider->bumpValue;
                        } else {
                           thisSlider->value = thisSlider->maxValue;
                        }

#ifdef DEBUG_SLIDER_BUMP
                        Serial.print("resulting slider value: ");
                        Serial.println(thisSlider->value);
                        Serial.println("");
#endif

                        retVal = true;
                     }
                  }
               } else {
                  // if touched most recently in thisSlider's bump down arrow
                  if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
                        (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
                        (BtnY >= (uint16_t)(thisSlider->yCenterLoc + (thisSlider->ySize / 2))) &&
                        (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2) + 50))))
                  {

#ifdef DEBUG_SLIDER_BUMP
                     Serial.print("initial slider value:   ");
                     Serial.println(thisSlider->value);
                     Serial.print("slider bump down value:   ");
                     Serial.println(thisSlider->bumpValue);
#endif

                     if (!(thisSlider->previouslyTouched))
                     {
                        thisSlider->previouslyTouched = true;
                        thisSlider->touchStartMillis = millis();
                     } else {
                        if ((millis() - thisSlider->touchStartMillis) > BUMP_REPEAT_START_DELAY_MILLISECONDS)
                        {
                           if ((thisSlider->value - thisSlider->bumpValue) > thisSlider->minValue)
                           {
                              thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
                              thisSlider->value -= thisSlider->bumpValue;
                           } else {
                              thisSlider->value = thisSlider->minValue;
                           }

#ifdef DEBUG_SLIDER_BUMP
                           Serial.print("resulting slider value: ");
                           Serial.println(thisSlider->value);
                           Serial.println("");
#endif

                           retVal = true;
                        }
                     }
                  } else {
                     // force another wait delay before repeat
                     thisSlider->previouslyTouched = false;
                  }
               }
            } else {
               // force another wait delay before repeat
               thisSlider->previouslyTouched = false;
            }
         }
      } else {  // SLIDER_MODE_HORIZONTAL
         // if touched most recently in thisSlider
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2) + 15))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2) + 15))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
         {
            if (BtnX < (uint16_t)(thisSlider->xCenterLoc - (thisSlider->xSize / 2)))
            {
               BtnX = (uint16_t)(thisSlider->xCenterLoc - (thisSlider->xSize / 2));
            }

            if (BtnX > (uint16_t)(thisSlider->xCenterLoc + (thisSlider->xSize / 2)))
            {
               BtnX = (uint16_t)(thisSlider->xCenterLoc + (thisSlider->xSize / 2));
            }

            if (BtnY < (uint16_t)(thisSlider->yCenterLoc - (thisSlider->ySize / 2)))
            {
               BtnY = (uint16_t)(thisSlider->yCenterLoc - (thisSlider->ySize / 2));
            }

            if (BtnY > (uint16_t)(thisSlider->yCenterLoc + (thisSlider->ySize / 2)))
            {
               BtnY = (uint16_t)(thisSlider->yCenterLoc + (thisSlider->ySize / 2));
            }

            newValue = (float)map((float)BtnX, (float)(thisSlider->xCenterLoc - (thisSlider->xSize / 2)), (float)(thisSlider->xCenterLoc + (thisSlider->xSize / 2)), thisSlider->minValue, thisSlider->maxValue);

            if (newValue != thisSlider->value)
            {
               thisSlider->value = newValue;

               retVal = true;
            }
         } else {
            // if thisSlider is active & has either bump arrow & has been held & was touched most recently in thisSlider's bump arrow area
            if ((thisSlider->withBumpDownArrow || thisSlider->withBumpUpArrow) && (thisSlider->repeatEnabled))
            {
               // if touched most recently in thisSlider's bump up arrow
               if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
                     (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2) + 50))) &&
                     (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))) &&
                     (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
               {

#ifdef DEBUG_SLIDER_BUMP
                  Serial.print("initial slider value:   ");
                  Serial.println(thisSlider->value);
                  Serial.print("slider bump up value:   ");
                  Serial.println(thisSlider->bumpValue);
#endif

                  if (!(thisSlider->previouslyTouched))
                  {
                     thisSlider->previouslyTouched = true;
                     thisSlider->touchStartMillis = millis();
                  } else {
                     if ((millis() - thisSlider->touchStartMillis) > BUMP_REPEAT_START_DELAY_MILLISECONDS)
                     {
                        if ((thisSlider->value + thisSlider->bumpValue) < thisSlider->maxValue)
                        {
                           thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
                           thisSlider->value += thisSlider->bumpValue;
                        } else {
                           thisSlider->value = thisSlider->maxValue;
                        }

#ifdef DEBUG_SLIDER_BUMP
                        Serial.print("resulting slider value: ");
                        Serial.println(thisSlider->value);
                        Serial.println("");
#endif

                        retVal = true;
                     }
                  }
               } else {
                  // if touched most recently in thisSlider's bump down arrow
                  if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2) + 50))) &&
                        (BtnX <= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
                        (BtnY >= (uint16_t)(thisSlider->yCenterLoc - (thisSlider->ySize / 2))) &&
                        (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
                  {
#ifdef DEBUG_SLIDER_BUMP
                     Serial.print("initial slider value:   ");
                     Serial.println(thisSlider->value);
                     Serial.print("slider bump down value:   ");
                     Serial.println(thisSlider->bumpValue);
#endif

                     if (!(thisSlider->previouslyTouched))
                     {
                        thisSlider->previouslyTouched = true;
                        thisSlider->touchStartMillis = millis();
                     } else {
                        if ((millis() - thisSlider->touchStartMillis) > BUMP_REPEAT_START_DELAY_MILLISECONDS)
                        {
                           if ((thisSlider->value - thisSlider->bumpValue) > thisSlider->minValue)
                           {
                              thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
                              thisSlider->value -= thisSlider->bumpValue;
                           } else {
                              thisSlider->value = thisSlider->minValue;
                           }

#ifdef DEBUG_SLIDER_BUMP
                           Serial.print("resulting slider value: ");
                           Serial.println(thisSlider->value);
                           Serial.println("");
#endif

                           retVal = true;
                        }
                     }
                  } else {
                     // force another wait delay before repeat
                     thisSlider->previouslyTouched = false;
                  }
               }
            } else {
               // force another wait delay before repeat
               thisSlider->previouslyTouched = false;
            }
         }
      }
   }

   return (retVal);
}  // checkSlider()


// check if a slider bump down arrow has been pressed
boolean checkSliderBumpDown(SLIDER_TYPE * thisSlider)
{
   boolean retVal = false;

   // if thisSlider is active & has a bump down arrow & was touched most recently in thisSlider's bump down arrow area
   if ((thisSlider->activated) && (thisSlider->withBumpDownArrow))
   {
      if (thisSlider->orientation == SLIDER_MODE_VERTICAL)
      {
         // if touched most recently in thisSlider's bump down arrow
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2) + 50))))
         {

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("initial slider value:   ");
            Serial.println(thisSlider->value);
            Serial.print("slider bump down value:   ");
            Serial.println(thisSlider->bumpValue);
#endif

            if ((thisSlider->value - thisSlider->bumpValue) > (thisSlider->minValue))
            {
               thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
               thisSlider->value -= thisSlider->bumpValue;
            } else {
               thisSlider->value = thisSlider->minValue;
            }

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("resulting slider value: ");
            Serial.println(thisSlider->value);
            Serial.println("");
#endif

            retVal = true;

            // force another wait delay before repeat
            thisSlider->previouslyTouched = false;
         }
      } else {   // SLIDER_MODE_HORIZONTAL
         // if touched most recently in thisSlider's bump down arrow
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2) + 50))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
         {

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("initial slider value:   ");
            Serial.println(thisSlider->value);
            Serial.print("slider bump down value:   ");
            Serial.println(thisSlider->bumpValue);
#endif

            if ((thisSlider->value - thisSlider->bumpValue) > (thisSlider->minValue))
            {
               thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
               thisSlider->value -= thisSlider->bumpValue;
            } else {
               thisSlider->value = thisSlider->minValue;
            }

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("resulting slider value: ");
            Serial.println(thisSlider->value);
            Serial.println("");
#endif

            retVal = true;

            // force another wait delay before repeat
            thisSlider->previouslyTouched = false;
         }
      }
   }

   return (retVal);
}  // checkSliderBumpDown()


// check if a slider bump up arrow has been pressed
boolean checkSliderBumpUp(SLIDER_TYPE * thisSlider)
{
   boolean retVal = false;

   // if thisSlider is active & has a bump up arrow & was touched most recently in thisSlider's bump up arrow area
   if ((thisSlider->activated) && (thisSlider->withBumpUpArrow))
   {
      if (thisSlider->orientation == SLIDER_MODE_VERTICAL)
      {
         // if touched most recently in thisSlider's bump up arrow
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc - ((thisSlider->xSize / 2)))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2) + 50))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))))
         {

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("initial slider value:   ");
            Serial.println(thisSlider->value);
            Serial.print("slider bump up value:   ");
            Serial.println(thisSlider->bumpValue);
#endif

            if ((thisSlider->value + thisSlider->bumpValue) < thisSlider->maxValue)
            {
               thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
               thisSlider->value += thisSlider->bumpValue;
            } else {
               thisSlider->value = thisSlider->maxValue;
            }

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("resulting slider value: ");
            Serial.println(thisSlider->value);
            Serial.println("");
#endif

            retVal = true;

            // force another wait delay before repeat
            thisSlider->previouslyTouched = false;
         }
      } else {
         // if touched most recently in thisSlider's bump up arrow
         if ((BtnX >= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2)))) &&
               (BtnX <= (uint16_t)(thisSlider->xCenterLoc + ((thisSlider->xSize / 2) + 50))) &&
               (BtnY >= (uint16_t)(thisSlider->yCenterLoc - ((thisSlider->ySize / 2)))) &&
               (BtnY <= (uint16_t)(thisSlider->yCenterLoc + ((thisSlider->ySize / 2)))))
         {

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("initial slider value:   ");
            Serial.println(thisSlider->value);
            Serial.print("slider bump up value:   ");
            Serial.println(thisSlider->bumpValue);
#endif

            if ((thisSlider->value + thisSlider->bumpValue) < thisSlider->maxValue)
            {
               thisSlider->value = (float)((round)(thisSlider->value / thisSlider->bumpValue)) * thisSlider->bumpValue;
               thisSlider->value += thisSlider->bumpValue;
            } else {
               thisSlider->value = thisSlider->maxValue;
            }

#ifdef DEBUG_SLIDER_BUMP
            Serial.print("resulting slider value: ");
            Serial.println(thisSlider->value);
            Serial.println("");
#endif

            retVal = true;

            // force another wait delay before repeat
            thisSlider->previouslyTouched = false;
         }
      }
   }

   return (retVal);
}  // checkSliderBumpUp()


// draw a button
void drawButton(BUTTON_TYPE thisButton)
{
   if (thisButton.activated)
   {
      tft.fillRect(thisButton.xCenterLoc - (thisButton.xSize / 2) + 1, thisButton.yCenterLoc - (thisButton.ySize / 2) + 1, thisButton.xSize - 2, thisButton.ySize - 2, thisButton.buttonColor);
      tft.drawRect(thisButton.xCenterLoc - (thisButton.xSize / 2), thisButton.yCenterLoc - (thisButton.ySize / 2), thisButton.xSize, thisButton.ySize, thisButton.borderColor);

      centerDrawText(*(thisButton.textPtr), thisButton.xCenterLoc, thisButton.yCenterLoc, thisButton.textColor, thisButton.buttonColor);
   } else {
      tft.fillRect(thisButton.xCenterLoc - (thisButton.xSize / 2) + 1, thisButton.yCenterLoc - (thisButton.ySize / 2) + 1, thisButton.xSize - 2, thisButton.ySize - 2, thisButton.borderColor);
      tft.drawRect(thisButton.xCenterLoc - (thisButton.xSize / 2), thisButton.yCenterLoc - (thisButton.ySize / 2), thisButton.xSize, thisButton.ySize, thisButton.buttonColor);

      centerDrawText(*(thisButton.textPtr), thisButton.xCenterLoc, thisButton.yCenterLoc, thisButton.textColor, thisButton.borderColor);
   }
}  // drawButton()


// update the screen based upon the current mode
void drawScreen(void)
{
   if (config_mode != CONFIG_MODE_SPLASH)
   {
      // clear the unique portion of the display screen
      drawButton(uniqueScreenArea);

      menu1Button.activated       = false;
      menu2Button.activated       = false;
      menu3Button.activated       = false;
      menu4Button.activated       = false;
   }

   switch (config_mode)
   {
      case CONFIG_MODE_SPLASH:
         {
            tft.fillScreen(ST7735_BLACK);

            // NOTE: first character printed to TFT after restart is lost (so print a "don't care" space character)
            centerDrawText(" ", tft.width() / 2, tft.height() / 2 - 100, ST7735_GREEN, ST7735_BLACK);

            centerDrawText(VERSION1, tft.width() / 2, tft.height() / 2 - 50, ST7735_GREEN, ST7735_BLACK);
            centerDrawText(VERSION2, tft.width() / 2, tft.height() / 2, ST7735_YELLOW, ST7735_BLACK);
            centerDrawText(VERSION3, tft.width() / 2, tft.height() / 2 + 50, ST7735_RED, ST7735_BLACK);
         }
         break;

      case CONFIG_MODE_INIT_SCREEN:
         {
            tft.fillScreen(ST7735_BLACK);

            tft.setTextColor(ST7735_GREEN, ST7735_BLACK);
            tft.setCursor(95, 6);
            tft.print(VERSION1);
            tft.setCursor(405, 6);
            tft.print(" [");
            tft.print(VERSION2);
            tft.print("]");
         }
         break;

      case CONFIG_MODE_MENU1:
         {
            menu1Button.activated    = true;
         }
         break;

      case CONFIG_MODE_MENU2:
         {
            menu2Button.activated   = true;
         }
         break;

      case CONFIG_MODE_MENU3:
         {
            menu3Button.activated    = true;
         }
         break;

      case CONFIG_MODE_MENU4:
         {
            menu4Button.activated   = true;
         }
         break;
   }


   if (config_mode > CONFIG_MODE_INIT_SCREEN)
   {
      // clear the button portion of the display screen
      drawButton(buttonScreenArea);


      drawButton(menu1Button);
      drawButton(menu2Button);
      drawButton(menu3Button);
      drawButton(menu4Button);
   }

   // update screen using the current config mode
   switch (config_mode)
   {
      case CONFIG_MODE_SPLASH:
      case CONFIG_MODE_INIT_SCREEN:
         {
         }
         break;

      case CONFIG_MODE_MENU1:
         {
            tft.setTextSize(1);
            tft.setTextColor(ST7735_WHITE);

#ifndef DISABLE_BACKLIGHT_CONTROL
            drawSlider(menu1BacklightSliderV);
            drawButton(menu1BacklightButtonV);
            drawSlider(menu1BacklightSliderH);
            drawButton(menu1BacklightButtonH);

            drawButton(menu1Button1);
            drawButton(menu1Button2);
            drawButton(menu1Button3);
            drawButton(menu1Button4);
#endif
         }
         break;

      case CONFIG_MODE_MENU2:
         {
            drawButton(menu2Button1);
            drawButton(menu2Button2);
            drawButton(menu2Button3);
            drawButton(menu2Button4);
         }
         break;

      case CONFIG_MODE_MENU3:
         {
            drawButton(menu3Button1);
            drawButton(menu3Button2);
            drawButton(menu3Button3);
            drawButton(menu3Button4);
         }
         break;

      case CONFIG_MODE_MENU4:
         {
            drawButton(menu4Button1);
            drawButton(menu4Button2);
            drawButton(menu4Button3);
            drawButton(menu4Button4);
         }
         break;
   }
}  // drawScreen()


// draw a slider
void drawSlider(SLIDER_TYPE thisSlider)
{
   int characterCount = 0;
   String outString = "";

   if (thisSlider.value < thisSlider.minValue)
   {
      thisSlider.value = thisSlider.minValue;
   }

   if (thisSlider.value > thisSlider.maxValue)
   {
      thisSlider.value = thisSlider.maxValue;
   }

   if (thisSlider.orientation == SLIDER_MODE_HORIZONTAL)
   {
      if (thisSlider.activated)
      {
         // clear the bump down arrow area if enabled
         if (thisSlider.withBumpDownArrow)
         {
            // clear the slider bump down arrow area
            tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 42, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, 35, thisSlider.ySize + 6, thisSlider.bumpBackgroundColor);

            // draw the slider bump down arrow outline
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 42, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, 35, thisSlider.ySize + 6, thisSlider.borderColor);
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 40, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 1, 31, thisSlider.ySize + 2, thisSlider.borderColor);

            // draw the slider bump down arrow
            for (int i = 0; i < 7; i++)
            {
               tft.drawLine(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 23 - i, thisSlider.yCenterLoc - (7 - i), thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 23 - i, thisSlider.yCenterLoc + (7 - i), thisSlider.handleColor);
            }
         }

         // clear the bump up arrow area if enabled
         if (thisSlider.withBumpUpArrow)
         {
            // clear the slider bump up arrow area
            tft.fillRect(thisSlider.xCenterLoc + (thisSlider.xSize / 2) + 9, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, 35, thisSlider.ySize + 6, thisSlider.bumpBackgroundColor);

            // draw the slider bump up arrow outline
            tft.drawRect(thisSlider.xCenterLoc + (thisSlider.xSize / 2) + 9, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, 35, thisSlider.ySize + 6, thisSlider.borderColor);
            tft.drawRect(thisSlider.xCenterLoc + (thisSlider.xSize / 2) + 11, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 1, 31, thisSlider.ySize + 2, thisSlider.borderColor);

            // draw the slider bump up arrow
            for (int i = 0; i < 7; i++)
            {
               tft.drawLine(thisSlider.xCenterLoc + (thisSlider.xSize / 2) + 30 - i, thisSlider.yCenterLoc - i - 1, thisSlider.xCenterLoc + (thisSlider.xSize / 2) + 30 - i, thisSlider.yCenterLoc + i + 1, thisSlider.handleColor);
            }
         }

         // clear the entire slider
         tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, thisSlider.xSize + 15, thisSlider.ySize + 6, thisSlider.backgroundColor);

         // draw the slider outline
         tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, thisSlider.xSize + 15, thisSlider.ySize + 6, thisSlider.borderColor);

         // draw the slider handle
         tft.fillRect((int)map(thisSlider.value, thisSlider.minValue, thisSlider.maxValue, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.xCenterLoc + (thisSlider.xSize / 2)) - 6, thisSlider.yCenterLoc - (2 + thisSlider.ySize * 4 / 10), 13, (thisSlider.ySize * 8 / 10) + 4, ST7735_BLACK);
         tft.fillRect((int)map(thisSlider.value, thisSlider.minValue, thisSlider.maxValue, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.xCenterLoc + (thisSlider.xSize / 2)) - 4, thisSlider.yCenterLoc - thisSlider.ySize * 4 / 10, 9, thisSlider.ySize * 8 / 10, thisSlider.handleColor);

         // draw the slider guide line
         tft.drawLine(thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc, thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc, thisSlider.scaleColor);          // guide line

         tft.drawLine(thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc - 7, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc + 7, thisSlider.scaleColor);  // left end
         tft.drawLine(thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc - 7, thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc + 7, thisSlider.scaleColor);  // right end

         // draw the slider minor tick lines
         for (unsigned int i = 1; i < thisSlider.minorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.minorTickSections), thisSlider.yCenterLoc - 2, thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.minorTickSections), thisSlider.yCenterLoc + 2, thisSlider.scaleColor);      // minor tick lines
         }

         // draw the slider major tick lines
         for (unsigned int i = 1; i < thisSlider.majorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.majorTickSections), thisSlider.yCenterLoc - 4, thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.majorTickSections), thisSlider.yCenterLoc + 4, thisSlider.scaleColor);      // major tick lines
         }
      } else {
         // clear the entire slider
         tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, thisSlider.xSize + 15, thisSlider.ySize + 6, thisSlider.backgroundColorDisabled);

         // draw the slider outline
         tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 3, thisSlider.xSize + 15, thisSlider.ySize + 6, thisSlider.borderColorDisabled);

         // draw the slider handle
         tft.fillRect((int)map(thisSlider.value, thisSlider.minValue, thisSlider.maxValue, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.xCenterLoc + (thisSlider.xSize / 2)) - 6, thisSlider.yCenterLoc - (2 + thisSlider.ySize * 4 / 10), 13, (thisSlider.ySize * 8 / 10) + 4, ST7735_BLACK);
         tft.fillRect((int)map(thisSlider.value, thisSlider.minValue, thisSlider.maxValue, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.xCenterLoc + (thisSlider.xSize / 2)) - 4, thisSlider.yCenterLoc - thisSlider.ySize * 4 / 10, 9, thisSlider.ySize * 8 / 10, thisSlider.handleColorDisabled);

         // draw the slider guide line
         tft.drawLine(thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc, thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc, thisSlider.scaleColorDisabled);          // guide line

         tft.drawLine(thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc - 7, thisSlider.xCenterLoc - (thisSlider.xSize / 2), thisSlider.yCenterLoc + 7, thisSlider.scaleColorDisabled);  // left end
         tft.drawLine(thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc - 7, thisSlider.xCenterLoc + (thisSlider.xSize / 2), thisSlider.yCenterLoc + 7, thisSlider.scaleColorDisabled);  // right end

         // draw the slider minor tick lines
         for (unsigned int i = 1; i < thisSlider.minorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.minorTickSections), thisSlider.yCenterLoc - 2, thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.minorTickSections), thisSlider.yCenterLoc + 2, thisSlider.scaleColorDisabled);      // minor tick lines
         }

         // draw the slider major tick lines
         for (unsigned int i = 1; i < thisSlider.majorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.majorTickSections), thisSlider.yCenterLoc - 4, thisSlider.xCenterLoc - thisSlider.xSize / 2 + (thisSlider.xSize * i / thisSlider.majorTickSections), thisSlider.yCenterLoc + 4, thisSlider.scaleColorDisabled);      // major tick lines
         }
      }
   } else {  // SLIDER_MODE_VERTICAL
      if (thisSlider.activated)
      {
         // clear the bump down arrow area if enabled
         if (thisSlider.withBumpDownArrow)
         {
            // clear the slider bump down arrow area
            tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc + (thisSlider.ySize / 2) + 8, thisSlider.xSize + 6, 40, thisSlider.bumpBackgroundColor);

            // draw the slider bump down arrow outline
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc + (thisSlider.ySize / 2) + 8, thisSlider.xSize + 6, 40, thisSlider.borderColor);
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 1, thisSlider.yCenterLoc + (thisSlider.ySize / 2) + 10, thisSlider.xSize + 2, 36, thisSlider.borderColor);

            // draw the slider bump down arrow
            for (int i = 0; i < 7; i++)
            {
               tft.drawLine(thisSlider.xCenterLoc - (7 - i), thisSlider.yCenterLoc + (thisSlider.ySize / 2) + i + 24, thisSlider.xCenterLoc + (7 - i), thisSlider.yCenterLoc + (thisSlider.ySize / 2) + i + 24, thisSlider.handleColor);
            }
         }

         // clear the bump up arrow area if enabled
         if (thisSlider.withBumpUpArrow)
         {
            // clear the slider bump up arrow area
            tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 46, thisSlider.xSize + 6, 40, thisSlider.bumpBackgroundColor);

            // draw the slider bump up arrow outline
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 46, thisSlider.xSize + 6, 40, thisSlider.borderColor);
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 1, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 44, thisSlider.xSize + 2, 36, thisSlider.borderColor);

            // draw the slider bump up arrow
            for (int i = 0; i < 7; i++)
            {
               tft.drawLine(thisSlider.xCenterLoc - (7 - i), thisSlider.yCenterLoc - (thisSlider.ySize / 2) - i - 24, thisSlider.xCenterLoc + (7 - i), thisSlider.yCenterLoc - (thisSlider.ySize / 2) - i - 24, thisSlider.handleColor);
            }
         }

         // clear the entire slider
         tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 7, thisSlider.xSize + 6, thisSlider.ySize + 15, thisSlider.backgroundColor);

         // draw the slider outline
         tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 7, thisSlider.xSize + 6, thisSlider.ySize + 15, thisSlider.borderColor);

         // draw the slider handle
         tft.fillRect(thisSlider.xCenterLoc - (2 + thisSlider.xSize * 4 / 10), (int)map(thisSlider.value, thisSlider.maxValue, thisSlider.minValue, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.yCenterLoc + (thisSlider.ySize / 2)) - 6, (thisSlider.xSize * 8 / 10) + 4, 13, ST7735_BLACK);
         tft.fillRect(thisSlider.xCenterLoc - thisSlider.xSize * 4 / 10, (int)map(thisSlider.value, thisSlider.maxValue, thisSlider.minValue, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.yCenterLoc + (thisSlider.ySize / 2)) - 4, thisSlider.xSize * 8 / 10, 9, thisSlider.handleColor);

         // draw the slider guide line
         tft.drawLine(thisSlider.xCenterLoc, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.xCenterLoc, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.scaleColor);          // guide line

         tft.drawLine(thisSlider.xCenterLoc - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.xCenterLoc + 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.scaleColor);  // top end
         tft.drawLine(thisSlider.xCenterLoc - 7, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.xCenterLoc + 7, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.scaleColor);  // bottom end

         // draw the slider minor tick lines
         for (unsigned int i = 1; i < thisSlider.minorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - 2, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.minorTickSections), thisSlider.xCenterLoc + 2, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.minorTickSections), thisSlider.scaleColor);      // minor tick lines
         }

         // draw the slider major tick lines
         for (unsigned int i = 1; i < thisSlider.majorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - 4, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.majorTickSections), thisSlider.xCenterLoc + 4, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.majorTickSections), thisSlider.scaleColor);      // major tick lines
         }
      } else {
         // clear the bump uparrow area if enabled
         if (thisSlider.withBumpUpArrow)
         {
            // clear the slider bump up arrow area
            tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 46, thisSlider.xSize + 6, 40, thisSlider.backgroundColorDisabled);

            // draw the slider bump up arrow outline
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 46, thisSlider.xSize + 6, 40, thisSlider.borderColorDisabled);
            tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 1, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 44, thisSlider.xSize + 2, 36, thisSlider.borderColorDisabled);

            // draw the slider bump up arrow
            for (int i = 0; i < 7; i++)
            {
               tft.drawLine(thisSlider.xCenterLoc - (7 - i), thisSlider.yCenterLoc - (thisSlider.ySize / 2) - i - 24, thisSlider.xCenterLoc + (7 - i), thisSlider.yCenterLoc - (thisSlider.ySize / 2) - i - 24, thisSlider.borderColorDisabled);
            }
         }

         // clear the entire slider
         tft.fillRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 7, thisSlider.xSize + 6, thisSlider.ySize + 15, thisSlider.backgroundColorDisabled);

         // draw the slider outline
         tft.drawRect(thisSlider.xCenterLoc - (thisSlider.xSize / 2) - 3, thisSlider.yCenterLoc - (thisSlider.ySize / 2) - 7, thisSlider.xSize + 6, thisSlider.ySize + 15, thisSlider.borderColorDisabled);

         // draw the slider handle
         tft.drawRect(thisSlider.xCenterLoc - (2 + thisSlider.xSize * 4 / 10), (int)map(thisSlider.value, thisSlider.maxValue, thisSlider.minValue, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.yCenterLoc + (thisSlider.ySize / 2)) - 4, (thisSlider.xSize * 8 / 10) + 4, 9, thisSlider.borderColorDisabled);

         // draw the slider guide line
         tft.drawLine(thisSlider.xCenterLoc, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.xCenterLoc, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.scaleColorDisabled);          // guide line

         tft.drawLine(thisSlider.xCenterLoc - 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.xCenterLoc + 7, thisSlider.yCenterLoc - (thisSlider.ySize / 2), thisSlider.scaleColorDisabled);  // top end
         tft.drawLine(thisSlider.xCenterLoc - 7, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.xCenterLoc + 7, thisSlider.yCenterLoc + (thisSlider.ySize / 2), thisSlider.scaleColorDisabled);  // bottom end

         // draw the slider minor tick lines
         for (unsigned int i = 1; i < thisSlider.minorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - 2, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.minorTickSections), thisSlider.xCenterLoc + 2, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.minorTickSections), thisSlider.scaleColorDisabled);      // minor tick lines
         }

         // draw the slider major tick lines
         for (unsigned int i = 1; i < thisSlider.majorTickSections; i++)
         {
            tft.drawLine(thisSlider.xCenterLoc - 4, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.majorTickSections), thisSlider.xCenterLoc + 4, thisSlider.yCenterLoc - thisSlider.ySize / 2 + (thisSlider.ySize * i / thisSlider.majorTickSections), thisSlider.scaleColorDisabled);      // major tick lines
         }
      }
   }

   characterCount = thisSlider.placesBeforeTheDecimal + thisSlider.placesAfterTheDecimal;
   if (thisSlider.placesAfterTheDecimal != 0)
   {
      // add one character for the decimal point
      characterCount++;
   }
   if (thisSlider.showPlusMinusSign)
   {
      // add one for the +/- sign
      characterCount++;

      if (thisSlider.value >= 0.0)
      {
         outString = outString + "+";
      } else {
         outString = outString + "-";
      }
   }

   switch (thisSlider.placesBeforeTheDecimal)
   {
      case 5:
         {
            if (abs(thisSlider.value) >= 10000.0)
            {
               outString = outString + (char)(((int)(abs(thisSlider.value)) / 10000) + 0x30);
            }
         }
      // no break, so fall-thru

      case 4:
         {
            if (abs(thisSlider.value) >= 1000)
            {
               outString = outString + (char)((((int)(abs(thisSlider.value)) % 10000) / 1000) + 0x30);
            }
         }
      // no break, so fall-thru

      case 3:
         {
            if (abs(thisSlider.value) >= 100)
            {
               outString = outString + (char)((((int)(abs(thisSlider.value)) % 1000) / 100) + 0x30);
            }
         }
      // no break, so fall-thru

      case 2:
         {
            if (abs(thisSlider.value) >= 10)
            {
               outString = outString + (char)((((int)(abs(thisSlider.value)) % 100) / 10) + 0x30);
            }
         }
         // no break, so fall-thru
   }

   outString = outString + (char)(((int)(abs(thisSlider.value)) % 10) + 0x30);

   if (thisSlider.placesAfterTheDecimal != 0)
   {
      outString = outString + ".";
   }

   switch (thisSlider.placesAfterTheDecimal)
   {
      case 1:
         {
            outString = outString + (char)(((int)((abs(thisSlider.value) * 10.0f)) % 10) + 0x30);
         }
         break;

      case 2:
         {
            outString = outString + (char)(((int)((abs(thisSlider.value) * 10.0f)) % 10) + 0x30);
            outString = outString + (char)(((int)((abs(thisSlider.value) * 100.0f)) % 10) + 0x30);
         }
         break;
   }

   tft.fillRect(thisSlider.xValueCenterLoc - ((thisSlider.placesBeforeTheDecimal + thisSlider.placesAfterTheDecimal + 2) * 4), thisSlider.yValueCenterLoc - 4, (thisSlider.placesBeforeTheDecimal + thisSlider.placesAfterTheDecimal + 2) * 8, 10, ST7735_BLACK);

   centerDrawText(outString, thisSlider.xValueCenterLoc + 1, thisSlider.yValueCenterLoc, thisSlider.valueColor, ST7735_BLACK);
}  // drawSlider()


// main loop
void loop()
{
   if ((config_mode == CONFIG_MODE_SPLASH) && (millis() > (check_splash_time + CHECK_SPLASH_MILLIS)))
   {
      config_mode = CONFIG_MODE_INIT_SCREEN;

      drawScreen();

      config_mode = CONFIG_MODE_MENU1;

      drawScreen();
   }

   // if the touchscreen is being touched anywhere
   if ((config_mode > CONFIG_MODE_INIT_SCREEN) && ((millis() - check_touchscreen_time) > CHECK_TOUCHSCREEN_MILLIS) && processTouchscreen())
   {
      check_touchscreen_time = millis();

      switch (config_mode)
      {
         case CONFIG_MODE_SPLASH:
         case CONFIG_MODE_INIT_SCREEN:
            {
            }
            break;

         case CONFIG_MODE_MENU1:
            {
#ifndef DISABLE_BACKLIGHT_CONTROL
               if (checkSlider(&menu1BacklightSliderV))
               {
                  menu1BacklightSliderH.value = menu1BacklightSliderV.value;

                  drawSlider(menu1BacklightSliderV);
                  drawSlider(menu1BacklightSliderH);

                  //tft.brightness(menu1BacklightSliderV.value);
               }

               if (checkSlider(&menu1BacklightSliderH))
               {
                  menu1BacklightSliderV.value = menu1BacklightSliderH.value;

                  drawSlider(menu1BacklightSliderV);
                  drawSlider(menu1BacklightSliderH);

                  //tft.brightness(menu1BacklightSliderV.value);
               }
#endif
            }
            break;

         case CONFIG_MODE_MENU2:
            {
            }
            break;

         case CONFIG_MODE_MENU3:
            {
            }
            break;

         case CONFIG_MODE_MENU4:
            {
            }
            break;
      }
   } else {
      // if the touchscreen was touched, but is no longer being touched
      if (touch_triggered)
      {
         touch_triggered = false;


         // if the touchscreen was last touched within menu1Button
         if (checkButton(menu1Button))
         {
            config_mode = CONFIG_MODE_MENU1;

            screen_update_required = true;
         }

         // if the touchscreen was last touched within menu2Button
         if (checkButton(menu2Button))
         {
            config_mode = CONFIG_MODE_MENU2;

            screen_update_required = true;
         }

         // if the touchscreen was last touched within menu3Button
         if (checkButton(menu3Button))
         {
            config_mode = CONFIG_MODE_MENU3;

            screen_update_required = true;
         }

         // if the touchscreen was last touched within menu4Button
         if (checkButton(menu4Button))
         {
            config_mode = CONFIG_MODE_MENU4;

            screen_update_required = true;
         }


         switch (config_mode)
         {
            case CONFIG_MODE_SPLASH:
            case CONFIG_MODE_INIT_SCREEN:
               {
               }
               break;

            case CONFIG_MODE_MENU1:
               {
#ifndef DISABLE_BACKLIGHT_CONTROL
                  if (checkButton(menu1BacklightButtonV) || checkButton(menu1BacklightButtonH))
                  {
                     if (menu1BacklightSliderV.value != 127)
                     {
                        menu1BacklightSliderV.value = 127;
                        menu1BacklightSliderH.value = 127;
                     } else {
                        menu1BacklightSliderV.value = 255;
                        menu1BacklightSliderH.value = 255;
                     }

                     drawSlider(menu1BacklightSliderV);
                     drawSlider(menu1BacklightSliderH);

                     //tft.brightness(menu1BacklightSliderV.value);
                  }
#endif

                  if (checkSliderBumpDown(&menu1BacklightSliderV))
                  {
                     menu1BacklightSliderH.value = menu1BacklightSliderV.value;

                     drawSlider(menu1BacklightSliderV);
                     drawSlider(menu1BacklightSliderH);
                  }

                  if (checkSliderBumpUp(&menu1BacklightSliderV))
                  {
                     menu1BacklightSliderH.value = menu1BacklightSliderV.value;

                     drawSlider(menu1BacklightSliderV);
                     drawSlider(menu1BacklightSliderH);
                  }

                  if (checkSliderBumpDown(&menu1BacklightSliderH))
                  {
                     menu1BacklightSliderV.value = menu1BacklightSliderH.value;

                     drawSlider(menu1BacklightSliderV);
                     drawSlider(menu1BacklightSliderH);
                  }

                  if (checkSliderBumpUp(&menu1BacklightSliderH))
                  {
                     menu1BacklightSliderV.value = menu1BacklightSliderH.value;

                     drawSlider(menu1BacklightSliderV);
                     drawSlider(menu1BacklightSliderH);
                  }


                  if (checkButton(menu1Button1))
                  {
                     menu1Button1.activated = !menu1Button1.activated;
                     drawButton(menu1Button1);

                     if (menu1Button1.activated)
                     {
                        Serial.println("...activated BUTTON 1 in MENU 1...");
                     } else {
                        Serial.println("...deactivated BUTTON 1 in MENU 1...");
                     }
                  }

                  if (checkButton(menu1Button2))
                  {
                     menu1Button2.activated = !menu1Button2.activated;
                     drawButton(menu1Button2);

                     if (menu1Button2.activated)
                     {
                        Serial.println("...activated BUTTON 2 in MENU 1...");
                     } else {
                        Serial.println("...deactivated BUTTON 2 in MENU 1...");
                     }
                  }

                  if (checkButton(menu1Button3))
                  {
                     menu1Button3.activated = !menu1Button3.activated;
                     drawButton(menu1Button3);

                     if (menu1Button3.activated)
                     {
                        Serial.println("...activated BUTTON 3 in MENU 1...");
                     } else {
                        Serial.println("...deactivated BUTTON 3 in MENU 1...");
                     }
                  }

                  if (checkButton(menu1Button4))
                  {
                     menu1Button4.activated = !menu1Button4.activated;
                     drawButton(menu1Button4);

                     if (menu1Button4.activated)
                     {
                        Serial.println("...activated BUTTON 4 in MENU 1...");
                     } else {
                        Serial.println("...deactivated BUTTON 4 in MENU 1...");
                     }
                  }
               }
               break;

            case CONFIG_MODE_MENU2:
               {
                  if (checkButton(menu2Button1))
                  {
                     menu2Button1.activated = !menu2Button1.activated;
                     drawButton(menu2Button1);

                     if (menu2Button1.activated)
                     {
                        Serial.println("...activated BUTTON 1 in MENU 2...");
                     } else {
                        Serial.println("...deactivated BUTTON 1 in MENU 2...");
                     }
                  }

                  if (checkButton(menu2Button2))
                  {
                     menu2Button2.activated = !menu2Button2.activated;
                     drawButton(menu2Button2);

                     if (menu2Button2.activated)
                     {
                        Serial.println("...activated BUTTON 2 in MENU 2...");
                     } else {
                        Serial.println("...deactivated BUTTON 2 in MENU 2...");
                     }
                  }

                  if (checkButton(menu2Button3))
                  {
                     menu2Button3.activated = !menu2Button3.activated;
                     drawButton(menu2Button3);

                     if (menu2Button3.activated)
                     {
                        Serial.println("...activated BUTTON 3 in MENU 2...");
                     } else {
                        Serial.println("...deactivated BUTTON 3 in MENU 2...");
                     }
                  }

                  if (checkButton(menu2Button4))
                  {
                     menu2Button4.activated = !menu2Button4.activated;
                     drawButton(menu2Button4);

                     if (menu2Button4.activated)
                     {
                        Serial.println("...activated BUTTON 4 in MENU 2...");
                     } else {
                        Serial.println("...deactivated BUTTON 4 in MENU 2...");
                     }
                  }
               }
               break;

            case CONFIG_MODE_MENU3:
               {
                  if (checkButton(menu3Button1))
                  {
                     menu3Button1.activated = !menu3Button1.activated;
                     drawButton(menu3Button1);

                     if (menu3Button1.activated)
                     {
                        Serial.println("...activated BUTTON 1 in MENU 3...");
                     } else {
                        Serial.println("...deactivated BUTTON 1 in MENU 3...");
                     }
                  }

                  if (checkButton(menu3Button2))
                  {
                     menu3Button2.activated = !menu3Button2.activated;
                     drawButton(menu3Button2);

                     if (menu3Button2.activated)
                     {
                        Serial.println("...activated BUTTON 2 in MENU 3...");
                     } else {
                        Serial.println("...deactivated BUTTON 2 in MENU 3...");
                     }
                  }

                  if (checkButton(menu3Button3))
                  {
                     menu3Button3.activated = !menu3Button3.activated;
                     drawButton(menu3Button3);

                     if (menu3Button3.activated)
                     {
                        Serial.println("...activated BUTTON 3 in MENU 3...");
                     } else {
                        Serial.println("...deactivated BUTTON 3 in MENU 3...");
                     }
                  }

                  if (checkButton(menu3Button4))
                  {
                     menu3Button4.activated = !menu3Button4.activated;
                     drawButton(menu3Button4);

                     if (menu3Button4.activated)
                     {
                        Serial.println("...activated BUTTON 4 in MENU 3...");
                     } else {
                        Serial.println("...deactivated BUTTON 4 in MENU 3...");
                     }
                  }
               }
               break;

            case CONFIG_MODE_MENU4:
               {
                  if (checkButton(menu4Button1))
                  {
                     menu4Button1.activated = !menu4Button1.activated;
                     drawButton(menu4Button1);

                     if (menu4Button1.activated)
                     {
                        Serial.println("...activated BUTTON 1 in MENU 4...");
                     } else {
                        Serial.println("...deactivated BUTTON 1 in MENU 4...");
                     }
                  }

                  if (checkButton(menu4Button2))
                  {
                     menu4Button2.activated = !menu4Button2.activated;
                     drawButton(menu4Button2);

                     if (menu4Button2.activated)
                     {
                        Serial.println("...activated BUTTON 2 in MENU 4...");
                     } else {
                        Serial.println("...deactivated BUTTON 2 in MENU 4...");
                     }
                  }

                  if (checkButton(menu4Button3))
                  {
                     menu4Button3.activated = !menu4Button3.activated;
                     drawButton(menu4Button3);

                     if (menu4Button3.activated)
                     {
                        Serial.println("...activated BUTTON 3 in MENU 4...");
                     } else {
                        Serial.println("...deactivated BUTTON 3 in MENU 4...");
                     }
                  }

                  if (checkButton(menu4Button4))
                  {
                     menu4Button4.activated = !menu4Button4.activated;
                     drawButton(menu4Button4);

                     if (menu4Button4.activated)
                     {
                        Serial.println("...activated BUTTON 4 in MENU 4...");
                     } else {
                        Serial.println("...deactivated BUTTON 4 in MENU 4...");
                     }
                  }
               }
               break;
         }

         if (screen_update_required)
         {
            drawScreen();

            screen_update_required = false;
         }
      }
   }
}  // loop()


// process any touchscreen activity
boolean processTouchscreen(void)
{
   uint16_t coordinates[ST7796_MAX_TOUCH_LIMIT][2];   // array to hold the touch coordinates
   boolean currently_touched = false;

   // fill the FT5206 registers to get access to the data inside the library...
   //tft.updateTS();

   currently_touched = ts.touched();

   if (!currently_touched && previously_touched)
   {
      touch_triggered = true;
   }

   if (currently_touched)
   {
      TS_Point p = ts.getPoint();
      coordinates[0][0] = p.x;
      coordinates[0][1] = p.y;
#ifdef DEBUG_TOUCHSCREEN
      Serial.print("coordinates[0][0] : ");
      Serial.print(p.x);
      Serial.print("     coordinates[0][1] : ");
      Serial.println(p.y);
#endif
      // range-check the values before using them
      //if ((coordinates[0][0] > 0) && (coordinates[0][0] < tft.width()) && (coordinates[0][1] > 0) && (coordinates[0][1] < tft.height()))
      //{
      //   BtnX = coordinates[0][0];
      //   BtnY = coordinates[0][1];
       
      BtnX = map(coordinates[0][0], 4007, 226, 480, 0);
      BtnY = map(coordinates[0][1], 3951, 192, 320, 0);
      //}

      previously_touched = true;

#ifdef DEBUG_TOUCHSCREEN
      Serial.print("X : ");
      Serial.print(BtnX);
      Serial.print("     Y : ");
      Serial.println(BtnY);
#endif
   } else {
      previously_touched = false;
   }

   return (currently_touched);  // whether the touchscreen is being touched or not
}  // processTouchscreen()


// one-time setup
void setup()
{
   unsigned long check_time;

   Serial.begin(57600);

   while (!Serial && (millis() <= 2000));

   Serial.println("===================================================");
   Serial.print("           ");
   Serial.println(VERSION1);
   Serial.print("        ");
   Serial.println(VERSION2);
   Serial.print("   ");
   Serial.println(VERSION3);
   Serial.println("===================================================");
   Serial.println("");
   Serial.println("");

   if (CrashReport) {
      Serial.print(CrashReport);
   }

   tft.init(320, 480);
   tft.setRotation(3);
   tft.fillScreen(ST7735_BLACK);

#ifdef DISABLE_BACKLIGHT_CONTROL
   menu1BacklightSliderV.value = 127;
   menu1BacklightSliderH.value = 127;
#endif

   //tft.brightness(menu1BacklightSliderV.value);
  ts.begin();
  ts.setRotation(1);

   check_splash_time = millis();

   drawScreen();

}  // setup()


// EOF placeholder
 
@mjs513 - looks good.

@all - It will be interesting to see where this ends up and how it compares to trying to use LVGL.

I don't have any real experience with LVGL, Other than I have run the example sketch for the Arduino Giga with display shield (on MBED):
1758384220407.png

And that LVGL appears to be display library that is built into Zephyr. I have not tried it yet on Zephyr.

Kurt
 
@kd5rxt-mark
Spent a little time to see what your controls sketch looks like on the 7796:
View attachment 38204

I like the your sliders a lot better - mind if I try to incorporate them into our menu library?

Thanks
Mike

Mike (@mjs513):

Most certainly, don't mind at all !! As I mentioned earlier, the original inspiration came from @KrisKasprzak. I added more capabilities to my implementation as needed for my TeensyMIDIPolySynth (TMPS). Thrilled that these may prove useful to others as well !!

Mark J Culross
 
@kd5rxt-mark - @defragster - @KurtE - @KennyM

Finally got some time and frankly energy to convert the slider code into a class that can eventually be included in controls. As a draft I am attaching a standalone sketch currently using FT driver. Implemented only one slider as a test. Sorry for the image quality here


IMG_1650.png


going to tweak more but any thoughts.
 

Attachments

  • sliderM_class-251006a.zip
    7.3 KB · Views: 20
@mjs513 just tried running your sliderM_class software. I played around a little bit with resizing the slider larger to get a better feel for the touch control on the small screen. Overall, it seems to work well.

Looks like the slider value text size is fixed at '1'? That ends up being pretty small on the smaller screen, but not sure if there is a better option as '2' is pretty large.

Took a pic compared to the original RA8875 display it was written for.

1759791063686.jpeg
 
Looks like the slider value text size is fixed at '1'? That ends up being pretty small on the smaller screen, but not sure if there is a better option as '2' is pretty large.
@KenHahn

Thanks for checking it. A bit late now but will check hiw we can improve that.

Mike
 
Good Morning all
Added in the ability now to use a ILI9341 font plus set the font fore and background color:
```
void setValueFont(Slider& s, const ILI9341_t3_font_t& f, uint16_t color, uint16_t bgColor);
```
also added
```
void setValue(Slider& s, float value);
float getValue(Slider& s);
```
Have something weird going on now that the bump arrows arent working not sure why??? Let me know if you are expeiencing this as well

NOTE: Still have to add in inactivated color setup and activate option. One step at a time

EDIT: Fixed the bump buttons. and replaced the attached.
 

Attachments

  • sliderM_class_v2-251007a.zip
    7.5 KB · Views: 11
Last edited:
Ok just finished adding in the last piece - setEnable and setDisabledColors.

Guess now its time for tweaking

Cheers
Mike
 

Attachments

  • sliderM_class_v2-251007a.zip
    7.7 KB · Views: 24
With this latest version, I am getting quite a few compiler errors. Using the ST7735_t3 & ILI9341_t3 libraries in Beta 5 and latest 1.12.3 GFX library.

Code:
In file included from C:\Users\kenha\AppData\Local\Arduino15\packages\teensy\hardware\avr\0.60.5\libraries\ST7735_t3\src/ST7796_t3.h:21,
                 from C:\Users\kenha\Documents\Arduino\sliderM_class\sliderM_class.ino:4:
C:\Users\kenha\AppData\Local\Arduino15\packages\teensy\hardware\avr\0.60.5\libraries\ST7735_t3\src/ST7735_t3.h:907:29: error: redefinition of 'class ST7735_Button'
  907 | #define Adafruit_GFX_Button ST7735_Button
      |                             ^~~~~~~~~~~~~
C:\Users\kenha\AppData\Local\Arduino15\packages\teensy\hardware\avr\0.60.5\libraries\ILI9341_t3/ILI9341_t3.h:562:7: note: in expansion of macro 'Adafruit_GFX_Button'
  562 | class Adafruit_GFX_Button {
      |       ^~~~~~~~~~~~~~~~~~~
C:\Users\kenha\AppData\Local\Arduino15\packages\teensy\hardware\avr\0.60.5\libraries\ST7735_t3\src/ST7735_t3.h:908:7: note: previous definition of 'class ST7735_Button'
  908 | class ST7735_Button {
      |       ^~~~~~~~~~~~~
C:\Users\kenha\Documents\Arduino\sliderM_class\sliderM_class.ino:22:20: error: no matching function for call to 'sliderM::sliderM(ST7796_t3*)'
   22 | sliderM slider(&tft);
      |                    ^
In file included from C:\Users\kenha\Documents\Arduino\sliderM_class\sliderM_class.ino:21:
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:121:5: note: candidate: 'sliderM::sliderM(ILI9341_t3*)'
  121 |     sliderM(ILI9341_t3* display);
      |     ^~~~~~~
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:121:25: note:   no known conversion for argument 1 from 'ST7796_t3*' to 'ILI9341_t3*'
  121 |     sliderM(ILI9341_t3* display);
      |             ~~~~~~~~~~~~^~~~~~~
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:61:7: note: candidate: 'constexpr sliderM::sliderM(const sliderM&)'
   61 | class sliderM
      |       ^~~~~~~
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:61:7: note:   no known conversion for argument 1 from 'ST7796_t3*' to 'const sliderM&'
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:61:7: note: candidate: 'constexpr sliderM::sliderM(sliderM&&)'
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.h:61:7: note:   no known conversion for argument 1 from 'ST7796_t3*' to 'sliderM&&'
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.cpp: In member function 'void sliderM::drawSlider(sliderM::Slider&)':
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.cpp:238:196: error: 'ST7735_BLACK' was not declared in this scope
  238 |          d->fillRect((int)map(s.value, s.minValue, s.maxValue, s.xCenterLoc - (s.xSize / 2), s.xCenterLoc + (s.xSize / 2)) - 6, s.yCenterLoc - (2 + s.ySize * 4 / 10), 13, (s.ySize * 8 / 10) + 4, ST7735_BLACK);
      |                                                                                                                                                                                                    ^~~~~~~~~~~~
C:\Users\kenha\Documents\Arduino\sliderM_class\classM.cpp:266:196: error: 'ST7735_BLACK' was not declared in this scope
  266 |          d->fillRect((int)map(s.value, s.minValue, s.maxValue, s.xCenterLoc - (s.xSize / 2), s.xCenterLoc + (s.xSize / 2)) - 6, s.yCenterLoc - (2 + s.ySize * 4 / 10), 13, (s.ySize * 8 / 10) + 4, ST7735_BLACK);
      |                                                                                                                                                                                                    ^~~~~~~~~~~~
exit status 1

Compilation error: no matching function for call to 'sliderM::sliderM(ST7796_t3*)'
 
@KenHahn

With this latest version, I am getting quite a few compiler errors. Using the ST7735_t3 & ILI9341_t3 libraries in Beta 5 and latest 1.12.3 GFX library.
I think I am at a loss here. I just update GFX lib to the latest just in case (1.12.3) and not seeing any errors. Compiling and loading fine on the mini-platform. Are you compiling for the ST7796 display - its the only one I tested so far.

What sketch are you using? The one posted in message #43? Think @KurtE was playing with it this morning and don't believe he saw any of this issues?

EDIT:
Did try and compile for the ILI9341 and am receiving undefined references for all the draw command so am going to have investigate the other boards once finished tweaking this.

OK now compiling - simple fix. After the include for the ILI9341_t3.h in the sliderM .h and .cpp files have to actually define it:
so it should now read in both the .h and the .cpp
C++:
#if __has_include("ILI9341_t3.h")
#include <ILI9341_t3.h>
#define ILI9341_t3 ILI9341_t3
#elif __has_include("NT35510_t4x_p.h")
#include <Teensy_Parallel_GFX.h>
#include <NT35510_t4x_p.h>
#define ILI9341_t3 NT35510_t4x_p
#elif __has_include("RA8876_t41_p.h")
#include <RA8876_common.h>
#include <RA8876_t41_p.h>
#define ILI9341_t3 RA8876_t41_p
#elif __has_include("ST7796_t3.h")
#include <ST7796_t3.h>
#define ILI9341_t3 ST7796_t3
#endif
 
Last edited:
@mjs513 I just downloaded everything from #43 and tried to compile the .ino for the ST7796 with no changes. Also downloaded to my laptop and get the same compiler errors on that system as well. The #37 version still compiles fine.

Since you and KurtE aren't seeing the same issue, might be something with my build environment. I'll poke around some more.
 
@KenHahn

I did a clean compile of the sketch and still compiled no issues. Just so you have something for comparison I am attaching my compile verbose output. Also I usually compile with optimization of FASTER. Good luck. Wish I could be of more help.
 

Attachments

  • compile.txt
    246.8 KB · Views: 13
I only get this error:
Code:
Alternatives for HX8357_t3n.h: []
In file included from T:\T_Drive\tCode\libraries\ILI9341_fonts\src\font_ArialBlack.c:1:ResolveLibrary(HX8357_t3n.h)

  -> candidates: []
T:\T_Drive\tCode\libraries\ILI9341_fonts\src\font_ArialBlack.h:13:18: fatal error: HX8357_t3n.h: No such file or directory
Multiple libraries were found for "font_Arial.h"
   13 |         #include "HX8357_t3n.h"
 Used: T:\T_Drive\tCode\libraries\ILI9341_fonts
      |                  ^~~~~~~~~~~~~~
 Not used: T:\T_Drive\arduino-1.8.19\hardware\teensy\avr\libraries\ILI9341_t3
compilation terminated.
 Not used: T:\T_Drive\arduino-1.8.19\hardware\teensy\avr\libraries\ILI9341_fonts
 
Ok all here is a version that is only configured for the st7796 maybe that will resolve some issues
 

Attachments

  • sliderM_class_v2-251007a.zip
    7.5 KB · Views: 16
I tried the files in #49. Still had a number of compiler errors.

Looking at your compiler log in #47, I added the ILI9341_fonts-master to my local library and then the only compiler error was related to HX8357_t3n missing like @defragster got in #48. Downloaded that library from your GitHub and now #49 compiles OK.

When first run, it draws the main bar as shown below and then there is about a 4 second delay before it draws the buttons and adds color. Not sure what that is about.

1759878021332.jpeg
 
Back
Top