TeensyUserInterface update to support ST7796

defragster

Senior Member+
from ReadME: " Teensy User Interface
This library is a simple touchscreen user interface for building Teensy applications. Adding buttons and menus to your sketch is as-easy-as filling in a table. The library also includes many functions for displaying your own data." Designed around the ILI9341

Working again with the @KenHahn T_4.1 MINI using the ST7796 display (320x480 3.5" and great view angle with IPS)
The onboard touch is Adafruit_FT6206 and returns direct PIXEL of the touch!

Ken found this UI LIB only seen here noted on this thread: https://forum.pjrc.com/index.php?threads/teensyuserinterface-w-audio-design-tool.60372/

Not sure if 'Stan Reifel' is around? There is last updated 3 yr ago TeensyUI specific version, and a 2 yr ago updated UIArduino that added two styles and was more directed at RP2040/ESP32.
Started FORKS of both that works T_4.x with this display - and should also recognize when the original ILI9341 is specified (lower res and XPT2046_Touchscreen).

A WIP - Must check SPI and TOUCH pins and include explicit #includes in the INO to force proper inclusion in build.

Input/assist from @mjs513 and @KurtE before this post Note:
> current Beta 5 of TD 1.60 includes new support for the ST7796 in the ST7735 library
> does not include the Plain and Bold version of st7735_t3_font_Arial in 'SRC' but they are in thefonts.zip
> the library.properties file needs this change from true to: dot_a_linkage=false

Just did this to improve the look of the hardware DEMO - but seems functional and well developed. Is there anything Better or Easier?
Just did SRC edits and run included examples so far - is it of interest and worth more effort
1757905368354.png
1757905351985.png


Just saw this linked on the password thread: https://github.com/KrisKasprzak/ILI9341_t3_Menu
 
Last edited:
@KurtE - made this PR to change library.properties for the C .vs.CPP calling link issue
>> dot_a_linkage=false

As noted above the Arial plain and BOLD fonts would be handy (needed in above examples) in the 'src' folder versus the included .zip.
@KurtE - can you PR that? I'm not clear on adding files and it seems PJRC pulled from your repo?
Locally copies from .zip into src work - but enlistment here is to your repo?

@mjs513 - any progress on backporting SLIDER code from new to old library?
 
@KurtE - made this PR to change library.properties for the C .vs.CPP calling link issue
>> dot_a_linkage=false
Note there are trade offs with turning this off. This implies that every binary generated by it will be included in every sketch that uses anything in it. That includes every font in the library. And potentially if a sketch includes multiple libraries with same objects you could end up linking failures of duplicate objects.
 
@defragster

I have been using https://github.com/KrisKasprzak/ILI9341_t3_controls in my stuff that I modified: https://github.com/mjs513/ILI9341_t3_controls/tree/ILI9341_t3_controls_mods. I used in my vl53l5cx project that I posted on the forum.

As for sliders - havent really tried anything yet - been focused on revamping the screen calibration stuff. Not fan on how he did it. Which is the project for today. Attaching the calibration sketches I put together yesterday to do generic screen calibration that can be used for any screen.

@KurtE
Not sure how to get around the issue of not changing the linkage to false.
 

Attachments

  • ts_tft_test.zip
    4.9 KB · Views: 26
@defragster

I have been using https://github.com/KrisKasprzak/ILI9341_t3_controls in my stuff that I modified: https://github.com/mjs513/ILI9341_t3_controls/tree/ILI9341_t3_controls_mods. I used in my vl53l5cx project that I posted on the forum.

As for sliders - havent really tried anything yet - been focused on revamping the screen calibration stuff. Not fan on how he did it. Which is the project for today. Attaching the calibration sketches I put together yesterday to do generic screen calibration that can be used for any screen.

@KurtE
Not sure how to get around the issue of not changing the linkage to false.
Not sure. Although quick test and a simple sketch that does not include the comic font, does not appear to have it show up
in the binary. So maybe that problem does not exist any more... Linker smarter to not include a binary if nothing referenced? Even when the
file is linked in explicitly?

So maybe not an issue. But for example I believe earlier with your font library, without that being set, it was trying to bring in every
font.

Looks like, it is pruning down to each object...
For example with this sketch:
Code:
#define TFT_MISO  12
#define TFT_MOSI  11  //a12
#define TFT_SCK   13  //a13
#define TFT_DC   9
#define TFT_CS   10 
#define TFT_RST  8

#include <ST7735_t3.h> // Hardware-specific library
#include <ST7789_t3.h> // Hardware-specific library
#include <ST7796_t3.h> // Hardware-specific library
#include <st7735_t3_font_ComicSansMS.h>

#include <SPI.h>
ST7796_t3 tft = ST7796_t3(TFT_CS, TFT_DC, TFT_RST);



float p = 3.1415926;


void setup(void) {
  //pinMode(SD_CS, INPUT_PULLUP);  // don't touch the SD card
  Serial.begin(9600);
  Serial.print("hello!");
  tft.init(320, 480);
 
  Serial.printf("ComicSansMS_96 %p\n", &ComicSansMS_96);
  Serial.printf("ComicSansMS_72 %p\n", &ComicSansMS_72);
 
}

void loop() {
  tft.fillScreen(ST7735_RED);
  delay(500);
  tft.fillScreen(ST7735_GREEN);
  delay(500);
  tft.fillScreen(ST7735_BLUE);
  delay(500);
}
If I don't have the two printfs about the fonts.
Built for T41
Code:
FLASH: code:56436, data:8228, headers:9060   free for files:8052740
   RAM1: variables:10336, code:53912, padding:11624   free for local variables:448416
   RAM2: variables:12416  free for malloc/new:511872

If I just have the first one for size 96 it jumps:
Code:
Memory Usage on Teensy 4.1:
  FLASH: code:56500, data:59428, headers:8996   free for files:8001540
   RAM1: variables:61536, code:53976, padding:11560   free for local variables:397216
   RAM2: variables:12416  free for malloc/new:511872

And with both:
Code:
FLASH: code:56500, data:89124, headers:8996   free for files:7971844
   RAM1: variables:91232, code:53976, padding:11560   free for local variables:367520
   RAM2: variables:12416  free for malloc/new:511872

This was with the current BETA build... I know one point this was different and I know when I ported this code for GIGA
(or was it GIGA on Zephyr), I had to prune these out. In fact I edited the font files to not include all of the sizes.
As: from the above the size 96 takes: 51K of ram, and the 71 takes: almost 30K
With all of the fonts in that file referenced:
Code:
RAM1: variables:165984, code:54232, padding:11304   free for local variables:292768
It takes: 155K of ram...

And if you throw in references to all of Arial...
Code:
RAM1: variables:291936, code:54616, padding:10920   free for local variables:166816
We jump up to 281K of DTCM space!.

Sometimes I think maybe we should mark at least the larger ones to be PROGMEM.
 
@KurtE - @defragster

I converted the way display calibration was done to use the std way we have been doing it via the map function. Default calibration is setup for the ILI9431 and to update rotation3 calibration all you have to do is call:
```
ui.setTouchScreenCalibrationConstants(150, 3921, 206, 3934);
```

I am maintaining a branch with my edits at: https://github.com/mjs513/TeensyUserInterface/tree/my_edits if you all are interested. Now to try and figure out sliders.
 
Alcon
Just for reference here are my screen calibration sketches
 

Attachments

  • ts_tft_test.zip
    4.9 KB · Views: 25
As may have been viewed in <this> other thread (@KurtE posted there, so I know he's seen it), I created a button & slider template for the RA8875 screen. I was inspired by the work of @KrisKasprzak & a library that he created. I wanted additional functionality, so using Kris's work as a starting point, I created my own version. I did my initial work using the ILI9341 display to control an updated version of my TeensyMIDIPolySynth (TMPS) (using buttons & sliders to control the characteristics of the synthesizer components in real-time). Due to limited screen area, I eventually transitioned to using a 7" RA8875 based display (more usable area & very responsive to inputs, without adversely affecting other things that the controlling Teensy was managing, including MIDI, debug serial, inter-processor comms with a T4.0, etc.).

I don't know if any of these implementations and/or concepts might be applicable in your work with the ST7796, but I present them here for your review, in hopes that something might prove beneficial.

FEATURES:
- configurable button size & location
- configurable button colors (active, inactive, background/foreground for active/disabled, border)
- configurable slider size & location
- configurable slider tick marks (major & minor, background/foreground color for active/disabled)
- configurable slider handle (color for active/disabled, border color for active/disabled)
- configurable slider min/max values
- configurable slider value display (places before/after decimal, sign, location, background/foreground colors)
- configurable slider bump arrows (short press for moving one increment/decrement value, long press for continuous increment/decrement change, with configurable increment/decrement value, configurable repeat)
- configurable slider colors (active/disabled)
- configurable slider orientation (horizontal/vertical)

Mark J Culross
KD5RXT

Pictures of template example sketch output & a typical VFO control screen from my TMPS attached (sorry for the poor photography . . . TFT screens are very difficult subjects to take pictures of !!) . . . code included as well, of course.

Code:
//
//  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


#include <SPI.h>    // to avoid unnecessary compile errors, this *must* appear in the list of includes *before* RA8875
#include <RA8875.h>

// when used w/ Audio Adapter, must use an alternate CS pin for the display
const int RA8875_CHIP_SELECT     =  38;       // Teensy 38 (A14) -to- RA8875 05
const int RA8875_RESET           =   3;       // Teensy 03 (D03) -to -RA8875 11
const int RA8875_MISO            =  39;       // Teensy 39 (A15) -to- RA8875 06
const int RA8875_MOSI            =  26;       // Teensy 26 (A12) -to- RA8875 07
const int RA8875_SCLK            =  27;       // Teensy 27 (A13) -to- RA8875 08
const int RA8875_TS_INT          =   2;       // Teensy 02 (D02) -to- RA8875 33

const int RA8875_MAX_TOUCH_LIMIT =   1;

RA8875 tft = RA8875(RA8875_CHIP_SELECT, RA8875_RESET, RA8875_MOSI, RA8875_SCLK, RA8875_MISO);

//
// The following pins are used in this project:
//
// PIN D0       = (not used)
// PIN D1       = (not used)
// PIN D2       = RA8875 Touchscreen INT
// PIN D3       = RA8875 Touchscreen RESET
// PIN D4       = (not used)
// PIN D5       = (not used)
// PIN D6       = (not used)
// PIN D7       = (not used)
// PIN D8       = (not used)
// PIN D9       = (not used)
// PIN D10      = (not used)
// PIN D11      = (not used)
// PIN D12      = (not used)
// PIN D13      = (not used)
// PIN D14/A0   = (not used)
// PIN D15/A1   = (not used)
// PIN D16/A2   = (not used)
// PIN D17/A3   = (not used)
// PIN D18/A4   = (not used)
// PIN D19/A5   = (not used)
// PIN D20/A6   = (not used)
// PIN D21/A7   = (not used)
// PIN D22/A8   = (not used)
// PIN D23/A9   = (not used)
// PIN D24/A10  = (not used)
// PIN D25/A11  = (not used)
// PIN D26/A12  = RA8875 Touchscreen MOSI (MOSI1)
// PIN D27/A13  = RA8875 Touchscreen SCLK (SCK1)
// PIN D28      = (not used)
// PIN D29      = (not used)
// PIN D30      = (not used)
// PIN D31      = (not used)
// PIN D32      = (not used)
// PIN D33      = (not used)
// PIN D34      = (not used)
// PIN D35      = (not used)
// PIN D36      = (not used)
// PIN D37      = (not used)
// PIN D38/A14  = RA8875 Touchscreen CS (CS1)
// PIN D39/A15  = RA8875 Touchscreen MISO (MISO1)
// PIN D40/A16  = (not used)
// PIN D41/A17  = (not used)

// onboard LED on pin 13
#define LED_PIN 13
#define LED_PIN_ON 16
#define LED_PIN_OFF 0

// 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 RA8875_DIMGREY     0x6B4D
#define RA8875_MDGREY      0x8410
#define RA8875_ASHGREY     0xB5F6
#define RA8875_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              = {400, 270, 800, 420,     &uniqueScreenAreaText,  RA8875_BLACK,  RA8875_BLACK,  RA8875_BLACK,  true};

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


// PRIMARY BUTTONS
const String menu1ButtonText              = "MENU 1";
BUTTON_TYPE menu1Button                   = {120,  48, 70, 36,            &menu1ButtonText,  RA8875_BLACK,  RA8875_GREEN, RA8875_ORANGE, false};

const String menu2ButtonText              = "MENU 2";
BUTTON_TYPE menu2Button                   = {195,  48, 70, 36,            &menu2ButtonText,  RA8875_BLACK,  RA8875_GREEN, RA8875_ORANGE, false};

const String menu3ButtonText              = "MENU 3";
BUTTON_TYPE menu3Button                   = {270,  48, 70, 36,            &menu3ButtonText,  RA8875_BLACK,  RA8875_GREEN, RA8875_ORANGE, false};

const String menu4ButtonText              = "MENU 4";
BUTTON_TYPE menu4Button                   = {345,  48, 70, 36,            &menu4ButtonText,  RA8875_BLACK,  RA8875_GREEN, RA8875_ORANGE, false};



// MENU 1 BUTTONS
const String menu1BacklightButtonTextV    = "BACKLIGHT";
BUTTON_TYPE menu1BacklightButtonV         = {750, 91, 90, 30,  &menu1BacklightButtonTextV,  RA8875_GREEN,  RA8875_BLACK,    RA8875_RED,  true};

const String menu1BacklightButtonTextH    = "BACKLIGHT";
BUTTON_TYPE menu1BacklightButtonH         = {400, 240, 90, 30,  &menu1BacklightButtonTextH,  RA8875_GREEN,  RA8875_BLACK,    RA8875_RED,  true};

const String menu1Button1Text             = "BUTTON 1-1";
BUTTON_TYPE menu1Button1                  = {100, 200, 90, 30,           &menu1Button1Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu1Button2Text             = "BUTTON 1-2";
BUTTON_TYPE menu1Button2                  = {100, 250, 90, 30,           &menu1Button2Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu1Button3Text             = "BUTTON 1-3";
BUTTON_TYPE menu1Button3                  = {100, 300, 90, 30,           &menu1Button3Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu1Button4Text             = "BUTTON 1-4";
BUTTON_TYPE menu1Button4                  = {100, 350, 90, 30,           &menu1Button4Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};


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

const String menu2Button2Text             = "BUTTON 2-2";
BUTTON_TYPE menu2Button2                  = {100, 250, 90, 30,           &menu2Button2Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu2Button3Text             = "BUTTON 2-3";
BUTTON_TYPE menu2Button3                  = {100, 300, 90, 30,           &menu2Button3Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu2Button4Text             = "BUTTON 2-4";
BUTTON_TYPE menu2Button4                  = {100, 350, 90, 30,           &menu2Button4Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};


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

const String menu3Button2Text             = "BUTTON 3-2";
BUTTON_TYPE menu3Button2                  = {100, 250, 90, 30,           &menu3Button2Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu3Button3Text             = "BUTTON 3-3";
BUTTON_TYPE menu3Button3                  = {100, 300, 90, 30,           &menu3Button3Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu3Button4Text             = "BUTTON 3-4";
BUTTON_TYPE menu3Button4                  = {100, 350, 90, 30,           &menu3Button4Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};


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

const String menu4Button2Text             = "BUTTON 4-2";
BUTTON_TYPE menu4Button2                  = {100, 250, 90, 30,           &menu4Button2Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu4Button3Text             = "BUTTON 4-3";
BUTTON_TYPE menu4Button3                  = {100, 300, 90, 30,           &menu4Button3Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_RED,  true};

const String menu4Button4Text             = "BUTTON 4-4";
BUTTON_TYPE menu4Button4                  = {100, 350, 90, 30,           &menu4Button4Text,  RA8875_BLACK,  RA8875_GREEN,    RA8875_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    = { 750,  305,  30, 255, 127.00, 16, 2, 3, 0, false,     1.0,    255.0,  true, true, 1.00, 750, 115, RA8875_WHITE, RA8875_MDGREY, RA8875_GREEN, RA8875_BLACK,   RA8875_GREEN, RA8875_BLACK,   RA8875_BLACK, RA8875_MDGREY, RA8875_MDGREY, RA8875_MDGREY, RA8875_BLACK, RA8875_BLACK, true,  true, false, 0, 250, SLIDER_MODE_VERTICAL };
SLIDER_TYPE  menu1BacklightSliderH    = { 400,  305, 255,  30, 127.00, 16, 2, 3, 0, false,     1.0,    255.0,  true, true, 1.00, 400, 265, RA8875_WHITE, RA8875_MDGREY, RA8875_GREEN, RA8875_BLACK,   RA8875_GREEN, RA8875_BLACK,   RA8875_BLACK, RA8875_MDGREY, RA8875_MDGREY, RA8875_MDGREY, RA8875_BLACK, RA8875_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
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.clearScreen(RA8875_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, RA8875_GREEN, RA8875_BLACK);

            centerDrawText(VERSION1, tft.width() / 2, tft.height() / 2 - 50, RA8875_GREEN, RA8875_BLACK);
            centerDrawText(VERSION2, tft.width() / 2, tft.height() / 2, RA8875_YELLOW, RA8875_BLACK);
            centerDrawText(VERSION3, tft.width() / 2, tft.height() / 2 + 50, RA8875_RED, RA8875_BLACK);
         }
         break;

      case CONFIG_MODE_INIT_SCREEN:
         {
            tft.clearScreen(RA8875_BLACK);

            tft.setTextColor(RA8875_GREEN, RA8875_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(RA8875_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, RA8875_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, RA8875_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, RA8875_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, RA8875_BLACK);

   centerDrawText(outString, thisSlider.xValueCenterLoc + 1, thisSlider.yValueCenterLoc, thisSlider.valueColor, RA8875_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[RA8875_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 = tft.getTouches();

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

   if (currently_touched)
   {
      tft.getTScoordinates(coordinates);

      // 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];
      }

      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;

   pinMode(RA8875_CHIP_SELECT, OUTPUT);
   digitalWrite(RA8875_CHIP_SELECT, HIGH);

   pinMode(RA8875_RESET, OUTPUT);
   digitalWrite(RA8875_RESET, LOW);

   check_time = millis();
   while ((millis() - check_time) <= 100);

   digitalWrite(RA8875_RESET, HIGH);

   Serial.begin(57600);

   check_time = millis();
   while (!Serial && ((millis() - check_time) <= 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.begin(RA8875_800x480);

   tft.DMA_enable();

   tft.setRotation(2);

   tft.clearScreen(RA8875_BLACK);

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

   tft.brightness(menu1BacklightSliderV.value);

   tft.useCapINT(RA8875_TS_INT);   // use the FT5206 chip interrupt

   tft.setTouchLimit(RA8875_MAX_TOUCH_LIMIT);

   tft.enableCapISR(true);         // capacitive touch screen interrupt it's armed

   tft.writeTo(L1);                // write to layer 1

   check_splash_time = millis();

   drawScreen();

   pinMode(LED_PIN, OUTPUT);
}  // setup()


// EOF placeholder
 

Attachments

  • 20250916_113805[1].jpg
    20250916_113805[1].jpg
    194.3 KB · Views: 41
  • 20250916_114328[1].jpg
    20250916_114328[1].jpg
    293.1 KB · Views: 36
Last edited:
@kd5rxt-mark
Looks like you are using Kris's ILI9341 control library?
@mjs513: This is an extended capability (not actually separated into a library) that I created, but which used Kris's work as inspiration (I gave credit & attribution to @KrisKasprzak in my previous post). I needed/wanted more capabilities than Kris's library actually provided e.g. BUMP UP/DOWN buttons, different actions based upon short press vs. long press, ability to enable/disable components, etc., which evolved with the enhancements to my TMPS.

I created this template to make it easy for me to start developing future apps using the RA8875 (which has so far been my Teensy-RV-Leveling-Helper, my Teensy-CAN-Monitor, & most recently my Teensy-SI4375-all-band-radio-receiver).

Mark J Culross
KD5RXT

(edited to add explanations & examples)
 
Last edited:
work of @KrisKasprzak & a library that he created
That looks much more colorful and complete.

Have not looked at that or usability beyond running the examples in the TeensyUI and updating it to use the i2c Adafruit_FT6206.h in use as noted in p#1.

Apparently that FT6206 i2c Touch is unique to the 3.5" IPS version of the ST7796 display used here - most other variants like the ILI9341 use the XPT2046_Touchscreen.
 
Apparently that FT6206 i2c Touch is unique to the 3.5" IPS version of the ST7796 display used here - most other variants like the ILI9341 use the XPT2046_Touchscreen.

The RA8875 from buydisplay.com uses the FT5206 touch controller . . . maybe that is similar to the FT6206 ?? I don't personally have any ST7796 displays, so I can't investigate from here.

In some of my older projects, I started out using one of the ILI9341 touchscreen displays available from Adafruit. I was advised recently by someone attempting to reproduce one of my older projects (the <EnigmaVisual> Enigma simulator) that the newer ILI9341 displays available from Adafruit have now transitioned to using the TSC2007 touch controller in place of the STMPE610. These two controller behave very differently. I definitely much prefer the XPT2046 as used in the PJRC ILI9341 display.

Mark J Culross
KD5RXT
 
The FT5xxx type capacitive controller have more features like up to 5 finger detection and gesture recognition and so generally used on the larger LCDs like the 7" RA8875 as used on the Project System or Mark's setup. Smaller LCDs like the 3.5" used on the Mini Platform use the FT6xxx type capacitive controller which only supports 1 or 2 fingers and is less expensive.
 
@mjs513 - not sure if you'll get sources from defragster github? If so you'll probably find :

lcdInitialize() >> LINE 3633 :: lcd->invertDisplay(true);

May not work for TFT and maybe needed just for IPS - suppose that is BLOCKING versus EMISSIVE light generation.
It is under "#if __has_include("ST7796_t3.h")" ... but that won't be enough.
>

Might be fun to get the best features of TeensyUI and the KrisKasprzak/ILI9341_t3_Menu in one and then adapted to the various displays in an actively supported library.
 
@defragster
Was testing your version of the lib which I am going to synch in github later but want to give it a test.

A couple of things
1. There is no st7735_font_bold in the st7735 library only st7735_font_arial. Personally would rather just use the ILI9341_font library
2. That lcdInitialize() >> LINE 3633 :: lcd->invertDisplay(true); setting is wrecking havoc with my display. Think you might be better off just creating another function to do the invertDisplay and have user call it. Colors are all off if its enabled on my display.

EDIT
@defragster just pushed a change to your lib to add in the invertDisplay method.
 
Last edited:
I saw one ref noted in p#1 to the @KrisKasprzak library from another thread - seems it was not the right one?
But yes, whichever was the correct one would be good to see and get a robust Button/Control/Windowing library for various Teensy Displays that is actively supported. Not sure where the "just did that" work is?

One big question I had about the p#1 libs was - given the "Stan Reifel" UI libs are 2-3 years Stale: what's the best way to Fork/Publish to a 'New Owner'. They are MIT licensed - which according to PJRC is the best acceptable type.

Better to build on a fork of that or just BUILD on @KrisKasprzak?

synch in github
I see the Sync exposing the 'Invert' - that seems a usable way given it will take some overt action for 'some' displays. Along with that.
I'll remove the invertDisplay() in lcdInitialize() and have to add it to each INO as needed.

Seems something Similar must be done for the TOUCH_MAP versus TOUCH_PIXEL #IFDEF switch. As the hardcoding of ALL 7796's will NOT use the _PIXEL version of mapping. See:: getTouchScreenCoords(), etc in the .cpp. Safe to assume there will only be those two options with no variation of a third type of TOUCH?
 
What other variables are there between the displays that might be used?

For IPS version needed (where the UI lib owns the display): invertDisplay()
Hardcoded there is also _MAP [analog math] versus _PIXEL [reads Pixel]: Any other variants within the same TOUCH library?
Resolution across families?
Unique Display or Touch init params - already have those.

These changes to update from just ILI9341 look like this where INO does device #includes:
Code:
#if __has_include("ILI9341_t3.h")
#include <ILI9341_t3.h>
#elif __has_include("ST7796_t3.h")
#include <ST7796_t3.h>
#define ILI9341_t3 ST7796_t3
#elif __has_include("ILI9488_t3.h")
#define ILI9341_t3 ILI9488_t3
#endif

#if __has_include("XPT2046_Touchscreen.h")
#include <XPT2046_Touchscreen.h>
#define TOUCH_MAP
#elif __has_include("Adafruit_FT6206.h")
#include <Adafruit_FT6206.h>
#define XPT2046_Touchscreen Adafruit_FT6206
#define TOUCH_PIXEL
#endif
 
Back
Top