How many functions deep

so im having this problem which i am assuming is a stack over flow, but im not sure, so i wanted to ask, on a pi pico how many functions can you call before it glitches out

for example:

void f1(){
f2();
}

void f2(){
f3();
}

void f3(){
f4();
}

void f4(){
f5();
}

void f5(){
f1();
}

i am building the most complicated program i have ever attempted and i run into an issue where i get sent back to the main part of the program about 6 functions in, im not sure if i coded an error or if it is a stack limitation, i also have no idea how to debug this on pico hardware.

any help is appreciated.

what i am attempting to do is design a gui on a tft, i am also trying to break the program down into digestible chunks based on what i want to display on the tft, i have a function for the main screen and the main menu, then from there you pick a function and it calls various other function routines.

another question i have is what happens when a function never returns?

so if function 1 calls function 2 and then function 2 calls function 3, and a point in function 3 calls function 1 as to skip going back to function 2.

This depends on the amount of free memory you have, and on the sizes of the stack frames of the functions.
It will also depend on whether the compiler can inline some functions for you.

how does one locate this information as the program is running? is there a library or utility that would allow me to see the free ram and the stack frame size ?

A thread calls function, it hangs until function returns, if it doesn’t return the thread hangs forever

It depends on if something keeps on adding functions that do not return. Eventually you will run out of memory no matter how much memory you have.

If you are using recursion it is vital that you climb out of the hole you dug for yourself.

If you use a debugger this is the sort of thing you can see happening. Normally this is a function of a jtag debug system.

if it hangs forever the memory consumed by it is never released correct? like:

void f1(){
int v1;
int v2;
f2();
}

those ints used or not would be spoken for space correct?

yes the ints will not be cleared until function returned

ugh, this means a total rewrite of 1700 lines of code. dang it .

what would be a good general control structure to do a menu based system

i have googled this intensively over the last 3 weeks but i cant quite wrap my head around what i did find and i am not sure im googling the right thing. heres the whole code so that you have an idea how it works now, and i am definately sure its not the best i was just trying to rough draft it and make improvements later

// Example sketch to demonstrate the drawing of X BitMap (XBM)
// format image onto the display.

// Information on the X BitMap (XBM) format can be found here:
// https://en.wikipedia.org/wiki/X_BitMap

// This example is part of the TFT_eSPI library:
// https://github.com/Bodmer/TFT_eSPI

// Created by Bodmer 23/04/18

////////////////////////////////////////////////////////////////////////Headers
#include "xbm.h"             // Sketch tab header for xbm images
#include "ico.h"
#include "var.h"
//////////////////////////////////////////////////////////////////////libraries
#include <TFT_eSPI.h>        // Hardware-specific library
#include <PsxControllerBitBangFastProc.h>

#include <avr/pgmspace.h>

#include <SPI.h>

#include "printf.h"
#include <nRF24L01.h>
#include <RF24.h>

#include "VirtuinoCM.h"

#include "RPi_Pico_TimerInterrupt.h"
#include <Streaming.h>
#include "SdFat.h"
///////////////////////////////////////////////////////////////////////////Pin Definitions
//PSX Controller Pins
const byte PIN_PS2_CLK = 20;
const byte PIN_PS2_DAT = 22;
const byte PIN_PS2_CMD = 21;
const byte PIN_PS2_ATT = 26;

//NRF24l01 Pins
const byte PIN_NRF_CE = 17;
const byte PIN_NRF_CSN = 15;
/////////////////////////////////////////////////////////////////////invocations
TFT_eSPI tft = TFT_eSPI();                                                          // Invoke library
PsxControllerBitBang<PIN_PS2_ATT, PIN_PS2_CMD, PIN_PS2_DAT, PIN_PS2_CLK> psx;
VirtuinoCM virtuino;
RF24 radio(PIN_NRF_CE, PIN_NRF_CSN);                                                // CE, CSN// nRF24L01(+) radio attached using Getting Started board <<<<<<<set to board
RPI_PICO_Timer ITimer0(0);                                                          // timer that Checks controller
//RPI_PICO_Timer ITimer1(2);                                                          // timer that runs virtuino

//------------------------------------------------------------------------------
// SdCardFactory constructs and initializes the appropriate card.
SdCardFactory cardFactory;
// Pointer to generic SD card.

SdCard* m_card = nullptr;
////////////////////////////////////////////////////////////////////////SD Card

// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h,
// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT.
#define SD_FAT_TYPE 1

#if SD_FAT_TYPE == 0
SdFat sd;
SdFile file;
#elif SD_FAT_TYPE == 1
SdFat32 sd;
File32 file;
#elif SD_FAT_TYPE == 2
SdExFat sd;
ExFile file;
#elif SD_FAT_TYPE == 3
SdFs sd;
FsFile file;
#else  // SD_FAT_TYPE
#error Invalid SD_FAT_TYPE
#endif  // SD_FAT_TYPE


//SdFat sd;
//SdFile file;
//////////////////////////////////////////////////////////////////////////////////////VARS
const char* const psxButtonNames[PSX_BUTTONS_NO] PROGMEM = {
  buttonSelectName,
  buttonL3Name,
  buttonR3Name,
  buttonStartName,
  buttonUpName,
  buttonRightName,
  buttonDownName,
  buttonLeftName,
  buttonL2Name,
  buttonR2Name,
  buttonL1Name,
  buttonR1Name,
  buttonTriangleName,
  buttonCircleName,
  buttonCrossName,
  buttonSquareName
};

const char* const controllerTypeStrings[PSCTRL_MAX + 1] PROGMEM = {
  ctrlTypeUnknown,
  ctrlTypeDualShock,
  ctrlTypeDsWireless,
  ctrlTypeGuitHero,
  ctrlTypeOutOfBounds
};

void setup()
{
  View_Intro();
  View_Main();
}

void loop()
{


}
//////////////////////////////////////////////////////////////////////////////////////////////////////LCDViews
void View_Intro() {
  int Step = 0;
  static byte slx, sly, srx, sry;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  int StatusY = 231;
  tft.begin();               // Initialise the display
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.setRotation(3);
  tft.drawXBitmap(x, y, frametest, frametest_width, frametest_height, TFT_CYAN);
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(320);
  drawGauge1(Step);

  ///controllerinit <<<<<<<<<<<<<<<<<<<<<<<<<<<
  InitController(true, StatusY);
  haveController = true;
  Step++;
  drawGauge1(Step);

  if (debug) {
    Serial.begin(115200);
    //while (!Serial) continue;
  }
  Step++;
  drawGauge1(Step);
  tft.drawString("Debug Terminal online", SCR_WD / 2, StatusY, 2);
  delay (300);

  Step++;
  drawGauge1(Step);
  String Stat1 = "System Init on " + String(PSTR_TO_F (BOARD_NAME));
  tft.drawString(Stat1, SCR_WD / 2, StatusY, 2);
  delay (300);
  Step++;
  drawGauge1(Step);
  tft.drawString(RPI_PICO_TIMER_INTERRUPT_VERSION, SCR_WD / 2, StatusY, 2);
  delay (300);
  Step++;
  drawGauge1(Step);
  String Stat2 = "CPU Frequency = " + String(PSTR_TO_F (F_CPU / 1000000)) + " MHz";
  tft.drawString(Stat2, SCR_WD / 2, StatusY, 2);
  delay (300);

  if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 300, TimerHandler0))
  {
    Step++;
    drawGauge1(Step);
    tft.drawString("Controller Timer Running", SCR_WD / 2, StatusY, 2);
    delay (300);
  }
  else
    tft.drawString("Cannot Start Timer", SCR_WD / 2, StatusY, 2);
  delay (300);

  if (!radio.begin()) {
    tft.drawString("Radio hardware is not responding!", SCR_WD / 2, StatusY, 2);
    delay (300);
  } else {
    Step++;
    drawGauge1(Step);
    tft.drawString("Radio hardware Online", SCR_WD / 2, StatusY, 2);
    delay (300);
  }

  radio.openWritingPipe(addresses[1]); // 00002
  Step++;
  drawGauge1(Step);
  tft.drawString("Radio Writing Pipe Open", SCR_WD / 2, StatusY, 2);
  delay (300);
  radio.openReadingPipe(1, addresses[0]); // 00001
  Step++;
  drawGauge1(Step);
  tft.drawString("Radio Reading Pipe Open", SCR_WD / 2, StatusY, 2);
  delay (300);
  radio.setAutoAck(false);
  Step++;
  drawGauge1(Step);
  tft.drawString("Radio Auto ACK off", SCR_WD / 2, StatusY, 2);
  delay (300);
  radio.setDataRate(RF24_1MBPS);
  Step++;
  drawGauge1(Step);
  tft.drawString("Radio Data Rate to 1MBp/s", SCR_WD / 2, StatusY, 2);
  delay (300);
  radio.setPALevel(RF24_PA_MIN);   // was RF24_PA_LOW
  Step++;
  drawGauge1(Step);
  tft.drawString("Radio Power AmpLifier Min", SCR_WD / 2, StatusY, 2);
  delay (300);

  Serial1.begin(115200);
  Serial1.setTimeout(100);
  Step++;
  drawGauge1(Step);
  tft.drawString("Virtuino Serial Online", SCR_WD / 2, StatusY, 2);
  delay (300);
  virtuino.begin(onReceived, onRequested, 256); //Start Virtuino. Set the buffer to 256. With this buffer Virtuino can control about 28 pins (1 command = 9bytes) The T(text) commands with 20 characters need 20+6 bytes
  //virtuino.key="1234";                       //This is the Virtuino password. Only requests the start with this key are accepted from the library
  Step++;
  drawGauge1(Step);
  tft.drawString("Virtuino ONLINE", SCR_WD / 2, StatusY, 2);
  delay (300);

  if (SDCardInit(50, false))
  {
    Step++;
    drawGauge1(Step);
    tft.setTextColor(TFT_GREEN , TFT_BLACK);
    tft.drawString("SD Card Initialized", SCR_WD / 2, StatusY, 2);
    tft.setTextColor(TFT_CYAN , TFT_BLACK);
    delay (300);
  }
  else
  {
    tft.setTextColor(TFT_RED , TFT_BLACK);
    tft.drawString("SD Card FAIL", SCR_WD / 2, StatusY, 2);
    tft.setTextColor(TFT_CYAN , TFT_BLACK);
  }


  Step = 100;
  drawGauge1(Step);
  tft.drawString("System Online", SCR_WD / 2, StatusY, 2);

  delay (3000);

}

void View_Main() {
  bool ok = false;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.drawXBitmap(x, y, frametest, frametest_width, frametest_height, TFT_CYAN);
  int StatusY = 231;
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(150);

  tft.setTextDatum(5);
  tft.setTextPadding(tft.textWidth("A") * 9);
  tft.drawString("Press ", ((SCR_WD / 2) - 1), tft.getViewportHeight() - (l3x32_height * 1.5), 1);
  tft.setTextDatum(3);
  tft.drawXBitmap((SCR_WD / 2) * 1, tft.getViewportHeight() - (l3x32_height * 2 + 5), l3x32, l3x32_width, l3x32_height, TFT_CYAN, TFT_BLACK );
  tft.drawString(" for Menu", ((SCR_WD / 2) * 1 + l3x32_width), tft.getViewportHeight() - l3x32_height * 1.5, 1);

  tft.setTextDatum(4);
  tft.setTextPadding(tft.textWidth("A") * 80);


  while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Main Control Structure
    if (psx.buttonPressed(PSB_L3)) {
      tft.drawXBitmap(15, 10, frametest2, frametest2_width, frametest2_height, TFT_CYAN, TFT_BLACK);
      tft.setTextSize(2);
      tft.setCursor(50, 25);
      tft.println("Main Menu");
      tft.setCursor(25, 60);
      tft.setTextSize(2);
      tft.println("X = Drive Controls");
      tft.setCursor(25, 80);
      tft.println("O = AHRS Information");
      tft.setCursor(25, 100);
      tft.println("S = Sub-System");
      tft.setCursor(25, 120);
      tft.println("T = Primary Weapon");
      tft.setCursor(25, 140);
      tft.println("L1 = Secondary Weapon");
      tft.setCursor(25, 160);
      tft.println("L2 = Power Subsystems");
      tft.setCursor(25, 180);
      tft.println("R1 = Sensor Subsystems");
      tft.setCursor(25, 200);
      tft.println("R2 = Diagnostic Menu");
      tft.setCursor(25, 220);
      tft.println("Start = Exit");
    }
    if (psx.buttonPressed(PSB_START)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_CROSS)) {
      View_ControllerDiag();
    }
    if (psx.buttonPressed(PSB_CIRCLE)) {
      View_NRFDiag();
    }
    if (psx.buttonPressed(PSB_SQUARE)) {
      View_SDDiag();
    }
    if (psx.buttonPressed(PSB_TRIANGLE)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_L1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_L2)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R2)) {
      tft.drawXBitmap(15, 10, frametest2, frametest2_width, frametest2_height, TFT_CYAN, TFT_BLACK);
      tft.setTextSize(2);
      tft.setCursor(50, 25);
      tft.println("Diagnostics");
      tft.setCursor(25, 60);
      tft.setTextSize(2);
      tft.println("X = Controller");
      tft.setCursor(25, 80);
      tft.println("O = NRF24L01");
      tft.setCursor(25, 100);
      tft.println("S = Virtuino");
      tft.setCursor(25, 120);
      tft.println("T = SD Card");
      tft.setCursor(25, 140);
      tft.println("L1 = Display");
      tft.setCursor(25, 160);
      tft.println("L2 = Settings");
      tft.setCursor(25, 180);
      tft.println("R1 = Terminal");
      tft.setCursor(25, 200);
      tft.println("R2 = Scope");
      tft.setCursor(25, 220);
      tft.println("Start = Exit");
      while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
        if (psx.buttonPressed(PSB_L3)) {

        }
        if (psx.buttonPressed(PSB_START)) {
          View_Main();
        }
        if (psx.buttonPressed(PSB_CROSS)) {
          View_ControllerDiag();
          ok = true;
        }
        if (psx.buttonPressed(PSB_CIRCLE)) {
          View_NRFDiag();
        }
        if (psx.buttonPressed(PSB_SQUARE)) {
          View_Main();
        }
        if (psx.buttonPressed(PSB_TRIANGLE)) {
          View_SDDiag();
        }
        if (psx.buttonPressed(PSB_L1)) {
          View_Main();
        }
        if (psx.buttonPressed(PSB_L2)) {
          View_Main();
        }
        if (psx.buttonPressed(PSB_R1)) {
          View_SerialTerminal();
        }
        if (psx.buttonPressed(PSB_R2)) {

        }
      }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end
    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end
}
void View_ControllerDiag() {
  bool ok = false;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.drawXBitmap(x, y, logo, logoWidth, logoHeight, TFT_WHITE);
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(320);
  while (!ok) {
    if (psx.buttonPressed(PSB_CROSS)) {
      // Location Circle For X BUtton
      tft.drawCircle(389, 184, 15, TFT_BLUE);
      tft.drawCircle(389, 184, 16, TFT_BLUE);
      tft.drawCircle(389, 184, 17, TFT_BLUE);
    } else {
      // Location Circle For X BUtton
      tft.drawCircle(389, 184, 15, TFT_WHITE);
      tft.drawCircle(389, 184, 16, TFT_WHITE);
      tft.drawCircle(389, 184, 17, TFT_WHITE);
    }

    if (psx.buttonPressed(PSB_CIRCLE)) {
      //Location Circle For Circle Button
      tft.drawCircle(430, 148, 15, TFT_RED);
      tft.drawCircle(430, 148, 16, TFT_RED);
      tft.drawCircle(430, 148, 17, TFT_RED);
    } else {
      //Location Circle For Circle Button
      tft.drawCircle(430, 148, 15, TFT_WHITE);
      tft.drawCircle(430, 148, 16, TFT_WHITE);
      tft.drawCircle(430, 148, 17, TFT_WHITE);
    }

    if (psx.buttonPressed(PSB_SQUARE)) {
      //Location Circle For Square Button
      tft.drawCircle(349, 148, 15, TFT_MAGENTA);
      tft.drawCircle(349, 148, 16, TFT_MAGENTA);
      tft.drawCircle(349, 148, 17, TFT_MAGENTA);
    } else {
      //Location Circle For Square Button
      tft.drawCircle(349, 148, 15, TFT_WHITE);
      tft.drawCircle(349, 148, 16, TFT_WHITE);
      tft.drawCircle(349, 148, 17, TFT_WHITE);
    }

    if (psx.buttonPressed(PSB_TRIANGLE)) {
      //Location Circle For triangle Button
      tft.drawCircle(389, 112, 15, TFT_GREEN);
      tft.drawCircle(389, 112, 16, TFT_GREEN);
      tft.drawCircle(389, 112, 17, TFT_GREEN);
    } else {
      //Location Circle For triangle Button
      tft.drawCircle(389, 112, 15, TFT_WHITE);
      tft.drawCircle(389, 112, 16, TFT_WHITE);
      tft.drawCircle(389, 112, 17, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_PAD_UP)) {
      //Location indicator For up Button
      tft.fillTriangle(93, 113, 112, 113, 103, 130, TFT_DARKCYAN);
    } else {
      //Location indicator For up Button
      tft.fillTriangle(93, 113, 112, 113, 103, 130, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_PAD_DOWN)) {
      //Location indicator For DOWN Button
      tft.fillTriangle(93, 185, 112, 185, 103, 167, TFT_DARKGREEN);
    } else {
      //Location indicator For DOWN Button
      tft.fillTriangle(93, 185, 112, 185, 103, 167, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_PAD_LEFT)) {
      //Location indicator For left Button
      tft.fillTriangle(67, 142, 67, 159, 85, 150, TFT_MAROON);
    } else {
      //Location indicator For left Button
      tft.fillTriangle(67, 142, 67, 159, 85, 150, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_PAD_RIGHT)) {
      //Location indicator For right Button
      tft.fillTriangle(138, 142, 138, 159, 124, 150, TFT_PURPLE);
    } else {
      //Location indicator For right Button
      tft.fillTriangle(138, 142, 138, 159, 124, 150, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_L1)) {
      tft.fillRect(95, 15, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(95, 15, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_L2)) {
      tft.fillRect(110, 15, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(110, 15, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_R1)) {
      tft.fillRect(375, 15, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(375, 15, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_R2)) {
      tft.fillRect(390, 15, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(390, 15, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_START)) {
      //Location indicator For Start Button
      tft.fillRect(280, 141, 10, 10, TFT_ORANGE);
    } else {
      //Location indicator For Start Button
      tft.fillRect(280, 141, 10, 10, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_SELECT)) {
      //Location indicator For select Button
      tft.fillRect(197, 141, 10, 10, TFT_CYAN);
    } else {
      //Location indicator For select Button
      tft.fillRect(197, 141, 10, 10, TFT_WHITE);
    }
    if (psx.buttonPressed(PSB_R3)) {
      tft.fillRect(320, 275, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(320, 275, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_L3)) {
      tft.fillRect(175, 275, 10, 10, TFT_CYAN);
    } else {
      tft.fillRect(175, 275, 10, 10, TFT_BLACK);
    }
    if (psx.buttonPressed(PSB_R3) && psx.buttonPressed(PSB_SELECT)) {
      View_Main();
    }

    //Left Joy
    tft.fillCircle(map(slx, 0, 255, 140, 210), map(sly, 0, 255, 185, 256), 4, TFT_GOLD);
    tft.drawCircle(map(slx, 0, 255, 140, 210), map(sly, 0, 255, 185, 256), 5, TFT_BLACK);
    tft.drawCircle(map(slx, 0, 255, 140, 210), map(sly, 0, 255, 185, 256), 6, TFT_BLACK);
    tft.drawCircle(map(slx, 0, 255, 140, 210), map(sly, 0, 255, 185, 256), 7, TFT_BLACK);
    //right Joy
    tft.fillCircle(map(srx, 0, 255, 285, 350), map(sry, 0, 255, 185, 256), 4, TFT_SILVER);
    tft.drawCircle(map(srx, 0, 255, 285, 350), map(sry, 0, 255, 185, 256), 5, TFT_BLACK);
    tft.drawCircle(map(srx, 0, 255, 285, 350), map(sry, 0, 255, 185, 256), 6, TFT_BLACK);
    tft.drawCircle(map(srx, 0, 255, 285, 350), map(sry, 0, 255, 185, 256), 7, TFT_BLACK);

  }


}

void View_NRFDiag() {
  bool ok = false;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.drawXBitmap(x, y, frametest, frametest_width, frametest_height, TFT_CYAN);
  int StatusY = 231;
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(320);
  tft.drawString("NRF24L01 Diagnostic", SCR_WD / 2, 15, 2);

  // For debugging info
  char *debug_info = new char[870];
  uint16_t str_len = radio.sprintfPrettyDetails(debug_info);
  tft.setViewport(40, 40, 390, 270 );
  //tft.frameViewport(TFT_CYAN, -4);
  tft.setCursor(0, 0);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextSize(1);
  tft.println(debug_info);
  tft.resetViewport();
}
void View_SDDiag() {
  bool ok = false;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.drawXBitmap(x, y, SDFrame, SDFrame_width, SDFrame_height, TFT_CYAN);
  int StatusY = 231;
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(tft.textWidth("SD Card Info"));
  tft.drawString("SD Card Info", SCR_WD / 2, 15, 2);
  tft.setTextPadding(tft.textWidth("A") * 80);
  tft.setViewport(9, 113, 318, 198 );                     //tft print terminal space for SD diag
  //tft.frameViewport(TFT_CYAN, -4);
  tft.setCursor(0, 0);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextSize(1);
  ////////////////////////////////////////////////////////////////////////////////////////////////core



  tft.print("SS = ");  tft.print(PIN_SD_SS);
  tft.print(" SCK = ");   tft.print(PIN_SD_SCK);
  tft.print(" MOSI = ");  tft.print(PIN_SD_MOSI);
  tft.print(" MISO = ");  tft.println(PIN_SD_MISO);


  if (!SDStarted) {
    SDCardInit(50, true);
  }
  tft.println("Determining Free Space...");
  //tft.print(F("Volume is FAT")); tft.print(int(sd.vol()->fatType()));
  tft.resetViewport();
  tft.setTextPadding(0);

  //------------------------information in Sd Card Icon
  tft.drawString(("FAT" + String(int(sd.vol()->fatType()))), 32, 94, 1);

  uint32_t size = sd.card()->sectorCount();
  uint32_t sizeMB = 0.000512 * size + 0.5;
  uint32_t freeSizeMB = 0.000512 * volFree * sd.vol()->sectorsPerCluster();
  uint32_t usedSizeMB = sizeMB - freeSizeMB;
  uint32_t sizeGB = sizeMB / 1024;

  uint32_t volumesize = sd.vol()->sectorsPerCluster();    // clusters are collections of blocks
  volumesize *= sd.vol()->clusterCount();        // we'll have a lot of clusters
  tft.drawString((String(sizeGB) + "GB"), 32, 71, 1);

  //57,50
  tft.setTextDatum(0);
  //43,11 ---------------- information location
  tft.drawString(String(BOARD_NAME), 43, 11, 1);
  tft.drawString(("SDFat Lib" + String(SD_FAT_VERSION_STR)), 43, 11 + tft.fontHeight() * 1, 1);
  tft.drawString(("Used Space: " + String(usedSizeMB) + "MB"), 57, 50, 1);
  tft.drawString(("Free Space: " + String(freeSizeMB) + "MB"), 57, 50 + tft.fontHeight(), 1);
  tft.drawString(("Sectors: " + String(volumesize)), 57, 50 + tft.fontHeight() * 2, 1);
  //tft.drawString(("Sectors: " + String(sd.maxSck)), 57, 50 + tft.fontHeight() * 2, 1);


  //335,285 top left corner
  //473,285 top right corner
  //473,311 bottom right corner
  //26H , 138W
  //map(value, fromLow, fromHigh, toLow, toHigh)

  for (int gridx = 0; gridx <= 138; gridx = gridx + 3) {
    for (int gridy = 0; gridy <= 193; gridy = gridy + 4) {
      tft.fillRect(335 + gridx, 80 + gridy, 2, 3, TFT_LIGHTGREY);
    }
  }

  //sd.vol()->blocksPerCluster();

  //bool Sd2Card::readBlock   (   uint32_t    blockNumber,    uint8_t *   dst  )

  int usedPercent = map(usedSizeMB, 0, sizeMB, 0, 100);
  int freePercent = map(freeSizeMB, 0, sizeMB, 0, 100);
  int freeWidth = (freePercent * 138) / 100;
  int usedWidth = (usedPercent * 138) / 100;

  tft.fillRectVGradient(335, 285, usedWidth, 26, TFT_RED, TFT_BLACK);
  tft.fillRectVGradient(335 + usedWidth, 285, freeWidth, 26, TFT_GREEN, TFT_BLACK);

  tft.setTextDatum(4);
  tft.setViewport(9, 113, 318, 198 );
  tft.setTextPadding(tft.textWidth("A") * 80);
  //tft.println("\nFiles found on the card (name, date and size in bytes): ");

  //root = SD.open("/");

  //printDirectory(root, 0); -----------------------------------------------------------------------------------reeval

  ////////////////////////////////////////////////////////////////////////////////////////////////
  tft.setTextDatum(3);
  tft.setTextPadding(tft.textWidth("A") * 9);
  tft.drawXBitmap((tft.getViewportWidth() / 7) * 1 - 25, tft.getViewportHeight() - (startb_height + 5), startb, cross_width, startb_height, TFT_CYAN, TFT_BLACK);
  tft.drawString("Exit", ((tft.getViewportWidth() / 7) * 1 + startb_width - 25), (tft.getViewportHeight() - (startb_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 7) * 2 - 20, tft.getViewportHeight() - (cross_height + 5), cross, cross_width, cross_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Init", ((tft.getViewportWidth() / 7) * 2 + cross_width - 20), (tft.getViewportHeight() - (cross_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 7) * 3 - 15, tft.getViewportHeight() - (square_height + 5), square, square_width, square_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Format", ((tft.getViewportWidth() / 7) * 3 + square_width - 15), (tft.getViewportHeight() - (square_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 7) * 4, tft.getViewportHeight() - (triangle_height + 5), triangle, triangle_width, triangle_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Test", ((tft.getViewportWidth() / 7) * 4 + triangle_width), (tft.getViewportHeight() - (triangle_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 7) * 5 + 5, tft.getViewportHeight() - (circle_height + 5), circle, circle_width, circle_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Block Map", ((tft.getViewportWidth() / 7) * 5 + circle_width + 5), (tft.getViewportHeight() - (circle_height / 2)), 1);
  tft.setTextDatum(4);
  tft.setTextPadding(tft.textWidth("A") * 80);

  //tft.println(debug_info);
  while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
    if (psx.buttonPressed(PSB_L3)) {

    }
    if (psx.buttonPressed(PSB_START)) {
      tft.resetViewport();
      delay(3000);
      View_Main();
    }
    if (psx.buttonPressed(PSB_CROSS)) {
      tft.fillScreen(TFT_BLACK);
      tft.setCursor(0, 0);
      tft.setViewport(9, 113, 318, 198 );
      SDCardInit(50, true);
      tft.resetViewport();
    }
    if (psx.buttonPressed(PSB_CIRCLE)) {
      tft.resetViewport();
      tft.setTextPadding(0);
      tft.setTextDatum(0);
      tft.drawString("Calculating Block Map...", 57, 50 + tft.fontHeight() * 5, 1);
      DoTimer = false;

      //----------------------------------------------------------------------------------make block grid
      //335,80        473,80      138W
      //335,80        473,273     193T
      //46 columns    48 rows     2208 visible  blocks


      uint8_t blk[512];
      bool blkEpty[2209] = {0};                           //each sector is 512 bytes
      uint8_t blkEptyLoop = 0;
      uint32_t blkPerBool = volumesize / 2208;      //how many sectors are represented by a single color block.
      uint32_t bitsPerBool = blkPerBool * 512;      //how many loops it takes to verify 1 color block on lcd
      uint32_t bitCount = 0;
      uint32_t blockCount = 0;

      //333,42 ------------- info space aboveblock map
      tft.drawString(("Block = " + String(blkPerBool) + " Sectors"), 333, 42, 1);
      tft.drawString(("Block = " + String(bitsPerBool) + " Bytes"), 333, 42 + tft.fontHeight() * 1, 1);
      //replace number to count to with size variable to do entire disk
      for (uint32_t tSector = 0; tSector <= 5000; tSector++) {
        if ( tSector % 100 == 0 ) {
          tft.drawString(("CYCLE: " + String(tSector) + " out of: " + String(size)), 57, 50 + tft.fontHeight() * 6, 1);
        }
        if (sd.card()->readSector(tSector, blk)) {
          for (uint32_t tByte = 0; tByte <= 512; tByte++) {
            if (blk[tByte] != 255 | blk[tByte] != 0) {
              tByte = 513;
              blkEpty[blockCount] = 1;
            } else {
              blkEpty[blockCount] = 0;
            }
          }
        }
        bitCount++;
        if (bitCount >= bitsPerBool) {
          blockCount++;
          bitCount = 0;
        }
      }

      tft.drawString("DONE!!! ", 57, 50 + tft.fontHeight() * 5, 1);

      for (int gridy = 0; gridy <= 193; gridy = gridy + 4) {
        for (int gridx = 0; gridx <= 138; gridx = gridx + 3) {
          if (blkEpty[blkEptyLoop]) {
            tft.fillRect(335 + gridx, 80 + gridy, 2, 3, TFT_RED);
          } else {
            tft.fillRect(335 + gridx, 80 + gridy, 2, 3, TFT_GREEN);
          }
          blkEptyLoop++;
        }
      }
      haveController = false;
      InitController(false, 0);
      DoTimer = true;
      tft.setTextDatum(4);
      tft.setViewport(9, 113, 318, 198 );
    }
    if (psx.buttonPressed(PSB_SQUARE)) {
      tft.setViewport(9, 113, 318, 158 );         //x,y,w,h
      tft.fillScreen(TFT_BLACK);
      tft.setCursor(0, 0);
      SDFormat(true);
      tft.resetViewport();
    }
    if (psx.buttonPressed(PSB_TRIANGLE)) {
      tft.setViewport(9, 113, 318, 158 );         //x,y,w,h
      tft.fillScreen(TFT_BLACK);
      tft.setCursor(0, 0);
      SDBenchMark(true);
      tft.resetViewport();
    }
    if (psx.buttonPressed(PSB_L1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_L2)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R2)) {

    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end
  tft.resetViewport();
}

void View_SerialTerminal() {
  bool ok = false;
  int x = 0;//random(tft.width()  - logoWidth);
  int y = 0;//random(tft.height() - logoHeight);
  tft.fillScreen(TFT_BLACK); // Black screen fill
  tft.drawXBitmap(x, y, frametest, frametest_width, frametest_height, TFT_CYAN);
  int StatusY = 231;
  tft.setTextDatum(4);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextPadding(tft.textWidth("A") * 80);
  tft.drawString("Serial Console", SCR_WD / 2, 15, 2);
  tft.setViewport(40, 40, 390, 270 );
  //tft.frameViewport(TFT_CYAN, -4);
  tft.setCursor(0, 0);
  tft.setTextColor(TFT_CYAN , TFT_BLACK);
  tft.setTextSize(1);

  tft.setTextDatum(3);
  tft.setTextPadding(tft.textWidth("A") * 9);
  tft.drawXBitmap((tft.getViewportWidth() / 6) * 1, tft.getViewportHeight() - (cross_height + 5), cross, cross_width, cross_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Exit", ((tft.getViewportWidth() / 6) * 1 + cross_width), (tft.getViewportHeight() - (cross_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 6) * 2, tft.getViewportHeight() - (circle_height + 5), circle, circle_width, circle_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Cfg", ((tft.getViewportWidth() / 6) * 2 + circle_width), (tft.getViewportHeight() - (circle_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 6) * 3, tft.getViewportHeight() - (square_height + 5), square, square_width, square_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("Clear", ((tft.getViewportWidth() / 6) * 3 + square_width), (tft.getViewportHeight() - (square_height / 2)), 1);

  tft.drawXBitmap((tft.getViewportWidth() / 6) * 4, tft.getViewportHeight() - (triangle_height + 5), triangle, triangle_width, triangle_height, TFT_BLACK, TFT_CYAN);
  tft.drawString("???", ((tft.getViewportWidth() / 6) * 4 + triangle_width), (tft.getViewportHeight() - (triangle_height / 2)), 1);
  tft.setTextDatum(4);
  tft.setTextPadding(tft.textWidth("A") * 80);

  //tft.println(debug_info);
  while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
    Do_Terminal();
    if (psx.buttonPressed(PSB_L3)) {

    }
    if (psx.buttonPressed(PSB_START)) {
      tft.resetViewport();
      View_Main();
    }
    if (psx.buttonPressed(PSB_CROSS)) {
      tft.resetViewport();

    }
    if (psx.buttonPressed(PSB_CIRCLE)) {
      View_NRFDiag();
    }
    if (psx.buttonPressed(PSB_SQUARE)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_TRIANGLE)) {
      View_SDDiag();
    }
    if (psx.buttonPressed(PSB_L1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_L2)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R2)) {

    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end
  tft.resetViewport();

}

//////////////////////////////////////////////////////////////////////////////////////////////////////LCDFunctions

void drawGauge1(int level)
{
  cx = SCR_WD / 2;
  cy = SCR_HT / 2;
  int rx0 = 40, ry0 = 40;
  int rx1 = 63, ry1 = 63;
  int mina = -75;
  int maxa = 180 + 75;
  for (int i = mina; i < maxa; i += 15) {
    sx = fastCos(i - 180);
    sy = fastSin(i - 180);
    xs0 = cx + sx * rx0 / MAXSIN;
    ys0 = cy + sy * ry0 / MAXSIN;
    xe0 = cx + sx * rx1 / MAXSIN;
    ye0 = cy + sy * ry1 / MAXSIN;
    sx = fastCos(i - 180 + 10);
    sy = fastSin(i - 180 + 10);
    xs1 = cx + sx * rx0 / MAXSIN;
    ys1 = cy + sy * ry0 / MAXSIN;
    xe1 = cx + sx * rx1 / MAXSIN;
    ye1 = cy + sy * ry1 / MAXSIN;
    int l = 100 * (i - mina) / (maxa - mina);
    if (l < level) {
      tft.fillTriangle(xs0, ys0, xe0, ye0, xe1, ye1, TFT_GREENYELLOW);
      tft.fillTriangle(xs1, ys1, xe1, ye1, xs0, ys0, TFT_GREENYELLOW);
    } else {
      tft.fillTriangle(xs0, ys0, xe0, ye0, xe1, ye1, TFT_LIGHTGREY);
      tft.fillTriangle(xs1, ys1, xe1, ye1, xs0, ys0, TFT_LIGHTGREY);
    }
  }
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////Math Functions
int fastSin(int i)
{
  while (i < 0) i += 360;
  while (i >= 360) i -= 360;
  if (i < 90)  return (pgm_read_byte(&sinTab[i])); else if (i < 180) return (pgm_read_byte(&sinTab[180 - i])); else if (i < 270) return (-pgm_read_byte(&sinTab[i - 180])); else
    return (-pgm_read_byte(&sinTab[360 - i]));
}

int fastCos(int i)
{
  return fastSin(i + 90);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////PSX Functions
byte psxButtonToIndex (PsxButtons psxButtons) {
  byte i;

  for (i = 0; i < PSX_BUTTONS_NO; ++i) {
    if (psxButtons & 0x01) {
      break;
    }

    psxButtons >>= 1U;
  }

  return i;
}

FlashStr getButtonName (PsxButtons psxButton) {
  FlashStr ret = F("");

  byte b = psxButtonToIndex (psxButton);
  if (b < PSX_BUTTONS_NO) {
    PGM_BYTES_P bName = reinterpret_cast<PGM_BYTES_P> (pgm_read_ptr (&(psxButtonNames[b])));
    ret = PSTR_TO_F (bName);
  }

  return ret;
}

void dumpButtons (PsxButtons psxButtons) {
  static PsxButtons lastB = 0;

  if (psxButtons != lastB) {
    lastB = psxButtons;     // Save it before we alter it

    Serial.print (F("Pressed: "));

    for (byte i = 0; i < PSX_BUTTONS_NO; ++i) {
      byte b = psxButtonToIndex (psxButtons);
      if (b < PSX_BUTTONS_NO) {
        PGM_BYTES_P bName = reinterpret_cast<PGM_BYTES_P> (pgm_read_ptr (&(psxButtonNames[b])));
        Serial.print (PSTR_TO_F (bName));
      }

      psxButtons &= ~(1 << b);

      if (psxButtons != 0) {
        Serial.print (F(", "));
      }
    }

    Serial.println ();
  }
}

void dumpAnalog (const char *str, const byte x, const byte y) {
  Serial.print (str);
  Serial.print (F(" analog: x = "));
  Serial.print (x);
  Serial.print (F(", y = "));
  Serial.println (y);
}

void ReadController() {
  if (psx.read()) {
    //Serial.println("9-1");
    byte lx, ly;
    psx.getLeftAnalog (lx, ly);
    if (lx != slx || ly != sly) {
      slx = lx;
      sly = ly;
    }
    byte rx, ry;
    psx.getRightAnalog (rx, ry);
    if (rx != srx || ry != sry) {
      srx = rx;
      sry = ry;
    }
    delay (1000 / 60);
  } else {
    haveController = false;
    InitController(false, 0);
  }
}


void InitController(bool debug, int StatusY) {
  if (!haveController) {
    if (psx.begin ()) {
      if (debug) {
        tft.drawString("Controller found!", SCR_WD / 2, StatusY, 2);
      }
      if (!psx.enterConfigMode ()) {
        if (debug) {
          tft.drawString("Cannot enter config mode", SCR_WD / 2, StatusY, 2);
        }
      } else {
        PsxControllerType ctype = psx.getControllerType ();
        PGM_BYTES_P cname = reinterpret_cast<PGM_BYTES_P> (pgm_read_ptr (&(controllerTypeStrings[ctype < PSCTRL_MAX ? static_cast<byte> (ctype) : PSCTRL_MAX])));
        if (debug) {
          tft.drawString(("Controller Type is: " + String(PSTR_TO_F (cname))), SCR_WD / 2, StatusY, 2);
        }
        //tft.drawString(PSTR_TO_F (cname), SCR_WD / 2, StatusY + 15, 2);
        if (!psx.enableAnalogSticks ()) {
          if (debug) {
            tft.drawString("Cannot enable analog sticks", SCR_WD / 2, StatusY, 2);
          }
        }

        //~ if (!psx.setAnalogMode (false)) {
        //~ Serial.println (F("Cannot disable analog mode"));
        //~ }
        //~ delay (10);

        if (!psx.enableAnalogButtons ()) {
          if (debug) {
            tft.drawString("Cannot enable analog buttons", SCR_WD / 2, StatusY, 2);
          }
        }

        if (!psx.exitConfigMode ()) {
          if (debug) {
            tft.drawString("Cannot enable analog sticks", SCR_WD / 2, StatusY, 2);
          }
        }
      }
    }
  }
  delay (1000 / 60);
  haveController = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VIRTUINO
//============================================================== onCommandReceived
//==============================================================
/* This function is called every time Virtuino app sends a request to server to change a Pin value
   The 'variableType' can be a character like V, T, O  V=Virtual pin  T=Text Pin    O=PWM Pin
   The 'variableIndex' is the pin number index of Virtuino app
   The 'valueAsText' is the value that has sent from the app   */
void onReceived(char variableType, uint8_t variableIndex, String valueAsText) {
  if (variableType == 'V') {
    float value = valueAsText.toFloat();        // convert the value to float. The valueAsText have to be numerical
    if (variableIndex < V_memory_count) V[variableIndex] = value;          // copy the received value to arduino V memory array
  }
}

//==============================================================
/* This function is called every time Virtuino app requests to read a pin value*/
String onRequested(char variableType, uint8_t variableIndex) {
  if (variableType == 'V') {
    if (variableIndex < V_memory_count) return  String(V[variableIndex]); // return the value of the arduino V memory array
  }
  return "";
}

//============================================================== virtuinoRun
void virtuinoRun() {
  while (Serial1.available()) {
    char tempChar = Serial1.read();
    if (tempChar == CM_START_CHAR) {             // a new command is starting...
      virtuino.readBuffer = CM_START_CHAR;   // copy the new command to the virtuino readBuffer
      virtuino.readBuffer += Serial1.readStringUntil(CM_END_CHAR);
      virtuino.readBuffer += CM_END_CHAR;
      if (debug) Serial.println("\nCommand= " + virtuino.readBuffer);
      String* response = virtuino.getResponse();   // get the text that has to be sent to Virtuino as reply. The library will check the inptuBuffer and it will create the response text
      if (debug) Serial.println("Response : " + *response);
      Serial1.print(*response);
      break;
    }
  }
}


//============================================================== vDelay
void vDelay(int delayInMillis) {
  long t = millis() + delayInMillis;
  while (millis() < t) virtuinoRun();
}

void makeVirtuinoPayload() {

  V[10] = 11;
  V[11] = 10;
  V[12] = slx;
  V[13] = sly;
  V[14] = psx.buttonPressed(PSB_L3);
  V[15] = srx;
  V[16] = sry;
  V[17] = psx.buttonPressed(PSB_R3);
  V[18] = psx.buttonPressed(PSB_SELECT);
  V[19] = psx.buttonPressed(PSB_START);
  V[20] = psx.buttonPressed(PSB_CROSS);
  V[21] = psx.buttonPressed(PSB_CIRCLE);
  V[22] = psx.buttonPressed(PSB_SQUARE);
  V[23] = psx.buttonPressed(PSB_TRIANGLE);
  V[24] = psx.buttonPressed(PSB_L1);
  V[25] = psx.buttonPressed(PSB_L2);
  V[26] = psx.buttonPressed(PSB_R1);
  V[27] = psx.buttonPressed(PSB_R2);
  V[28] = psx.buttonPressed(PSB_PAD_UP);
  V[29] = psx.buttonPressed(PSB_PAD_DOWN);
  V[30] = psx.buttonPressed(PSB_PAD_LEFT);
  V[31] = psx.buttonPressed(PSB_PAD_RIGHT);
  //V[32] = 0;                                      //controller connected status
  //V[33] = 0;                                      //controller in config mode error
  //V[34] = 0;                                       //radio initialized
  V[35] = 0;
  V[36] = 0;
  V[37] = 0;
  V[38] = 0;
  V[39] = 0;
  V[40] = 0;
  //Serial.println("99");
}


//////////////////////////////////////////////////////////////////////////////////////////////////////// timer

bool TimerHandler0(struct repeating_timer * t)
{
  if (DoTimer) {
    ReadController();
    makeVirtuinoPayload();
    virtuinoRun();
  }
  return true;
}
/*
  bool TimerHandler1(struct repeating_timer * t)
  {
  Serial.print("ITimer1: millis() = "); Serial.println(millis());
  makeVirtuinoPayload();
  // virtuinoRun();
  return true;
  }*/

////////////////////////////////////////////////////////////////////////////////////////////////////////////serial terminal

void Do_Terminal() {                                                                    // Heavily Modified Version of  >> TFT ILI9340 Serial Terminal Sketch by M. Ray Burnette ==> Public Domain 04/20/2014
  String lBlank = "";
  lDebug = true ;
  int fh = tft.fontHeight();
  int fw = tft.textWidth("A");
  int lChar = tft.getViewportWidth() / fw;
  int lNum = tft.getViewportHeight() / fh;
  int viewPortMaxChars = lNum * lChar;
  if ( ! lFlag )                         // new line roll-over cleanup
  {
    nCol = 0 ;                           // align character pointer to "x" home
    Tindex = int(nLCD / lChar) ;     // character pointer to current-line to graphic "y" lookup of home character
    //nRow = yCord[ Tindex ]  ;          // [0 - 14] ==> lines 1 - 15
    nRow = fh * Tindex;           // [0 - 14] ==> lines 1 - 15
    if (lDebug)
    {
      Serial << "Inside flag routine! \n \r" ;
      Serial << "nCol / nRow / nLCD = " << nCol << " / " << nRow << " / " << nLCD << "\n \r" ;
    }
    tft.setCursor (nCol, nRow) ;  // restore cursor to home character of current line
    //tft.print( sBlank ) ;         // clear line
    //tft.drawLine( x1,  y1,  x2,   y2, color);

    tft.fillRect( 0,  nRow,  475,    fh, TFT_BLACK);
    tft.drawLine(    0,  (nRow + fh),  390,    (nRow + fh), TFT_YELLOW);
    tft.setCursor (nCol, nRow) ;  // restore cursor to home character of current line
    lFlag = true ;                // reset flag for normal operation
  }

  // read data from serial port
  if (Serial.available())
  {
    char c = Serial.read() ;
    if (c)    // non-Null
    {
      if ( c > 31 && c < 127)
      {
        tft.print(c) ;  // keep non-printables off LCD
        ++nLCD ;        // increment character count
      }

      if ( c == 13 )           // carriage return?
      {
        c = 0 ;
        //tft.print( sBlank ) ;                // clear line from current position, may wrap
        nRow = (nLCD / lChar) + 1 ;            // next row (row == 0, 1, ... 14)
        if ( nRow > lNum - 5 ) nRow = 0 ;      // maintain boundary minus 3 lines at bottom for menu bar.
        Tindex = nRow ;                        // array index for graphic "Y" position of first character
        nCol = 0 ;                             // "X" Graphic coordinate
        nRow = fh * Tindex; ;                  // Graphic "Y" coordinate via lookup
        nLCD = Tindex * lChar  ;               // 26 characters per line (line = nRow +1)
        tft.setCursor (nCol, nRow) ;           // restore cursor to home character of current line
        //lcd.print( sBlank ) ;                // clear NEW line
        //tft.setCursor (nCol, nRow) ;         // restore cursor to home character of current line
        if (lDebug)
        {
          Serial << "Return Pressed!  " << "nCol / nRow / nLCD == " ;
          Serial << nCol << " / " << nRow << " / " << nLCD << " / " << lNum << "\n \r" ;
        }
      }

      if ( nLCD % lChar == 0 ) // just printed last character on line?
      {
        if (lDebug) Serial << "Last character of line printed!  " ;

        if (  nLCD == viewPortMaxChars) // last character of last line?
        {
          if (lDebug) Serial << "End of display! \n \r" ;
          nLCD = 0 ;
          c = 0 ;
          lFlag = false ;
        }
        else
        {
          lFlag = false ;  // activate clean-up routine
          //nLCD++  ;   // increment to 1st position next line

          for (int i = 0; i <= nLCD % termLineW; i++) {                 //create variable that contains white space for line remainder chars
            lBlank = lBlank + " ";                                      //add number of blanks to line end variable
          }
          tft.print( lBlank ) ; // clear end of line and some of next   //print the line blank
          if (lDebug) Serial << "nCol / nRow / nLCD == " << nCol << " / " << nRow << " / " << nRow << "\n \r";
          lBlank = "";                                                  //reset the line blank varibale to nothing
        }
      }
    }
  }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////SD Functions

// Initialize SD card
bool SDCardInit( uint32_t frequency, bool debug ) {
  bool init = false;
  SDStarted = false;
  if (!SDfirstTry) {
    if (debug) {
      tft.print(F("\nRestarting SD Init\n"));
    }
  }
  SDfirstTry = false;
  if (debug) {
    tft.print(F("SD_SSPIN: ")); tft.println(PIN_SD_SS);
  }
  if (DISABLE_CHIP_SELECT < 0) {
    if (debug) {
      tft.print("Assuming the SD is the only SPI device, Edit DISABLE_CHIP_SELECT to disable another device.\n");
    }
  } else {
    if (debug) {
      tft.print(F("Disabling SPI device on pin: ")); tft.println(DISABLE_CHIP_SELECT);
    }
    pinMode(DISABLE_CHIP_SELECT, OUTPUT);
    digitalWrite(DISABLE_CHIP_SELECT, HIGH);
  }
  if (!sd.begin(PIN_SD_SS, SD_SCK_MHZ(frequency))) {
    if (sd.card()->errorCode()) {
      if (debug) {
        tft.print(F(
                    "\nSD initialization failed.\n"
                    "Do not reformat the card!\n"
                    "Is the card correctly inserted?\n"
                    "Is chipSelect set to the correct value?\n"
                    "Does another SPI device need to be disabled?\n"
                    "Is there a wiring/soldering problem?\n"));
        tft.print(F("\nerrorCode: "));
        tft.print(int(sd.card()->errorCode()));
        tft.print(F(", errorData: ")); tft.println(sd.card()->errorData());
      }
      return init;
    }

    if (debug) {
      tft.println(F("Card successfully initialized."));
      tft.println(int(sd.card()->type()));
      switch (sd.card()->type()) {                                                     //0 - SD V1, 1 - SD V2, or 3 - SDHC.
        case 0:
          tft.println(F("Card Type is SD V1"));
          break;
        case 1:
          tft.println(F("Card Type is SD V2"));
          break;
        case 2:
          tft.println(F("Card Type is SDHC"));
          break;
        default:
          tft.println(F("Unable to determine card Type"));
          break;
      }
    }
    if (sd.vol()->fatType() == 0) {
      if (debug) {
        tft.print(F("Can't find a valid FAT16/FAT32 partition.\n"));
        tft.print(F("Try reformatting the card.  For best results use\n"));
        tft.print(F("the SdFormatter program in SdFat/examples or download\n"));
        tft.print(F("and use SDFormatter from www.sdcard.org/downloads.\n"));
      }
      return init;
    } else {
      if (debug) {
        switch (int(sd.vol()->fatType())) {                                                     //The FAT type of the volume. Values are 12, 16 or 32.
          case 12:
            tft.println(F("Volume Type is FAT12"));
            break;
          case 16:
            tft.println(F("Volume Type is FAT16"));
            break;
          case 32:
            tft.println(F("Volume Type is FAT32"));
            break;
          default:
            tft.println(F("Unable to determine Volume Type"));
            break;
        }
      }
    }
    if (debug) {
      tft.println(F("Can't determine error type"));
    }
    return init;
  }
  if (debug) {
    tft.println(F("Card successfully initialized."));
  }
  uint32_t size = sd.card()->sectorCount();
  if (size == 0) {
    if (debug) {
      tft.print(F("Can't determine the card size.\n"));
      tft.print(F("Try another SD card or reduce the SPI bus speed.\n"));
      tft.print(F("Edit SPI_SPEED in this program to change it.\n"));
    }
    return init;
  }
  uint32_t sizeMB = 0.000512 * size + 0.5;
  if (debug) {
    tft.print(F("Card size: ")); tft.print(sizeMB);
    tft.print(F(" MB (MB = 1,000,000 bytes)\n"));
    tft.print(F("Volume is FAT")); tft.print(int(sd.vol()->fatType()));
    tft.print(F(", Cluster size (bytes): "));  tft.println(sd.vol()->bytesPerCluster());
  }

  //cout << F("Files found (date time size name):\n");
  //sd.ls(&tft, LS_R | LS_DATE | LS_SIZE);

  //////////////  sd.ls(&Serial, LS_R); DOIT

  if ((sizeMB > 1100 && sd.vol()->sectorsPerCluster() < 64) || (sizeMB < 2200 && sd.vol()->fatType() == 32)) {
    if (debug) {
      tft.print(F("\nThis card should be reformatted for best performance.\n Use a cluster size of 32 KB for cards larger than 1 GB.\n Only cards larger than 2 GB should be formatted FAT32.\n"));
    }
    return init;
  }
  if (debug) {
    tft.println(F("Success! SD Initialized."));
  }
  volFree = sd.vol()->freeClusterCount();
  init = true;
  SDStarted = true;
  return init;
}

void SDFormat(bool debug) {
  bool ok = false;
  char c;
  String msg = "";
  file.close();
  sd.end();
  msg = (
          "\n"
          "This program can erase and/or format SD/SDHC/SDXC cards.\n"
          "\n"
          "Erase uses the card's fast flash erase command.\n"
          "Flash erase sets all data to 0X00 for most cards\n"
          "and 0XFF for a few vendor's cards.\n"
          "\n"
          "Cards up to 2 GiB (GiB = 2^30 bytes) will be formated FAT16.\n"
          "Cards larger than 2 GiB and up to 32 GiB will be formatted\n"
          "FAT32. Cards larger than 32 GiB will be formatted exFAT.\n"
          "\n"
          "Warning, all data on the card will be erased.\n"
          "Press 'Start' to continue, Press 'Select' to Abort");
  if (debug) {
    tft.println(msg);
  }
  while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
    if (psx.buttonPressed(PSB_START)) {
      ok = true;
      tft.fillScreen(TFT_BLACK);
      tft.setCursor(0, 0);
    }
    if (psx.buttonPressed(PSB_SELECT)) {
      ok = true;
      tft.fillScreen(TFT_BLACK);
      tft.setCursor(0, 0);
      msg = "Exitting Format utility";
      if (debug) {
        tft.println(msg);
      }
      return;
    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end

  // Select and initialize proper card driver.
  m_card = cardFactory.newCard(SD_CONFIG);
  if (!m_card || m_card->errorCode()) {
    msg = "card init failed.";
    if (debug) {
      tft.println(msg);
    }
    delay(10000);
    return;
  }

  cardSectorCount = m_card->sectorCount();
  if (!cardSectorCount) {
    msg = "Get sector count failed.";
    if (debug) {
      tft.println(msg);
    }
    delay(10000);
    return;
  }


  msg = "\nCard size: " + String(cardSectorCount * 5.12e-7) + " GB (GB = 1E9 bytes)\n";
  msg = msg + "Card size: " + String(cardSectorCount / 2097152.0) + " GiB (GiB = 2^30 bytes)\n";


  if (debug) {
    tft.println(msg);
  }

  msg =  F("Card will be formated ");
  if (cardSectorCount > 67108864) {
    msg = msg + F("exFAT\n");
  } else if (cardSectorCount > 4194304) {
    msg = msg + F("FAT32\n");
  } else {
    msg = msg + F("FAT16\n");
  }

  if (debug) {
    tft.println(msg);
  }

  msg = F(
          "\n"
          "Options are:\n"
          "SELECT - erase the card and skip formatting.\n"
          "START - erase and then format the card. (recommended)\n"
          "X - quick format the card without erase.\n"
          "TRIANGLE - EXIT utility\n"
          "\n"
          "Enter option: ");
  if (debug) {
    tft.println(msg);
  }

  while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
    if (psx.buttonPressed(PSB_SELECT)) {
      //ok = true;
      eraseCard(debug);
      //return;
    }
    if (psx.buttonPressed(PSB_START)) {
      //ok = true;
      eraseCard(debug);
      formatCard(debug);
      //return;
    }
    if (psx.buttonPressed(PSB_CROSS)) {
      
      formatCard(debug);
      //ok = true;
      //return;
    }
    if (psx.buttonPressed(PSB_TRIANGLE)) {
      msg = "Exitting Format utility";
      if (debug) {
        tft.println(msg);
      }
      return;
    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end
}


//------------------------------------------------------------------------------
// flash erase all data
void eraseCard(bool debug) {
  String msg = "";
  msg =  F("Erasing\n");
  if (debug) {
    tft.println(msg);
  }
  uint32_t firstBlock = 0;
  uint32_t lastBlock;
  uint16_t n = 0;

  do {
    lastBlock = firstBlock + ERASE_SIZE - 1;
    if (lastBlock >= cardSectorCount) {
      lastBlock = cardSectorCount - 1;
    }
    if (!m_card->erase(firstBlock, lastBlock)) {
      msg =  F("erase failed");
      if (debug) {
        tft.println(msg);
      }
      return;
    }
    tft.print(".");
    if ((n++) % 64 == 63) {
      tft.println(".");
    }
    firstBlock += ERASE_SIZE;
  } while (firstBlock < cardSectorCount);


  if (!m_card->readSector(0, sectorBuffer)) {
    msg =  F("readBlock");
    if (debug) {
      tft.println(msg);
    }
    return;
  }
  msg = "All data set to " + String(int(sectorBuffer[0])) + "\n";
  msg = msg + "Erase done\n";

  if (debug) {
    tft.println(msg);
  }
}
//------------------------------------------------------------------------------
void formatCard(bool debug) {
  String msg = "";
  ExFatFormatter exFatFormatter;
  FatFormatter fatFormatter;

  // Format exFAT if larger than 32GB.
  bool rtn = cardSectorCount > 67108864 ?
             exFatFormatter.format(m_card, sectorBuffer, &Serial) :
             fatFormatter.format(m_card, sectorBuffer, &Serial);

  if (!rtn) {
    msg = msg + F("Format Failed, Recommend use PC formatter.\n");

    if (debug) {
      tft.println(msg);
    }
    delay(10000);
    return;
  }
  msg = msg + F("Format Succeeded!!!.\n");

  if (debug) {
    tft.println(msg);
  }
  delay(10000);
  return;

}
//------------------------------------------------------------------------------






void SDInfo() {






}
// Unmount SD card
void SDBenchMark(bool debug) {

  float s;
  uint32_t t;
  uint32_t maxLatency;
  uint32_t minLatency;
  uint32_t totalLatency;
  bool skipLatency;
  String msg = "";

#if HAS_UNUSED_STACK
  if (debug) {
    tft.print(F("FreeStack: ")); tft.println(FreeStack());
  }
#endif  // HAS_UNUSED_STACK

  if (!SDStarted) {
    if (debug) {
      tft.println(F("Attempting to Init SD Card"));
    }
    if (!SDCardInit(50, debug )) {
      if (debug) {
        tft.println(F("FAILED to Init SD Card"));
      }
      return;
    } else {
      if (debug) {
        tft.println(F("Init SD Card SUCCESS"));
      }
    }
  }
  if (sd.fatType() == FAT_TYPE_EXFAT) {
    if (debug) {
      tft.println(F("Type is exFAT"));
    }
  } else {
    if (debug) {
      tft.print(F("Type is FAT")); tft.println(int(sd.fatType()));
    }
  }

  if (debug) {
    tft.print(F("Card size: ")); tft.print(sd.card()->sectorCount() * 512E-9); tft.println(F(" GB (GB = 1E9 bytes)"));
  }


  if (!chipIDDmp(debug)) {
    if (debug) {
      tft.print(F("Chip ID Dump FAILED "));
    }
  }

  // open or create file - truncate existing file.
  if (!file.open("bench.dat", O_RDWR | O_CREAT | O_TRUNC)) {
    if (debug) {
      tft.print(F("File open FAILED "));
    }
  }

  // fill buf with known data
  if (BUF_SIZE > 1) {
    for (size_t i = 0; i < (BUF_SIZE - 2); i++) {
      buf[i] = 'A' + (i % 26);
    }
    buf[BUF_SIZE - 2] = '\r';
  }
  buf[BUF_SIZE - 1] = '\n';

  msg = "\nFILE_SIZE_MB = " + String(FILE_SIZE_MB);
  msg = msg + F("\nBUF_SIZE = ") + String(BUF_SIZE) + F(" bytes");
  msg = msg + F("\nStarting write test, please wait.");

  if (debug) {
    tft.println(msg);
  }
  DoTimer = false;
  // do write test
  uint32_t n = FILE_SIZE / BUF_SIZE;

  msg = F("\nWrite speed and latency");
  msg = msg + F("\nSpeed,max,min,avg");
  msg = msg + F("\nKB/Sec,usec,usec,usec");

  if (debug) {
    tft.println(msg);
  }

  for (uint8_t nTest = 0; nTest < WRITE_COUNT; nTest++) {
    file.truncate(0);
    if (PRE_ALLOCATE) {
      if (!file.preAllocate(FILE_SIZE)) {
        if (debug) {
          tft.println("preAllocate failed");
        }
        return;
      }
    }
    maxLatency = 0;
    minLatency = 9999999;
    totalLatency = 0;
    skipLatency = SKIP_FIRST_LATENCY;
    t = millis();
    for (uint32_t i = 0; i < n; i++) {
      uint32_t m = micros();
      if (file.write(buf, BUF_SIZE) != BUF_SIZE) {
        if (debug) {
          tft.println("write failed");
        }
        return;
      }
      m = micros() - m;
      totalLatency += m;
      if (skipLatency) {
        // Wait until first write to SD, not just a copy to the cache.
        skipLatency = file.curPosition() < 512;
      } else {
        if (maxLatency < m) {
          maxLatency = m;
        }
        if (minLatency > m) {
          minLatency = m;
        }
      }
    }
    file.sync();
    t = millis() - t;
    s = file.fileSize();

    msg = String(s / t) + F(",") + String(maxLatency) + F(",") + String(minLatency) + F(",") + String(totalLatency) + F("\n");

    if (debug) {
      tft.println(msg);
    }

  }
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(0, 0);
  msg = F("\nStarting read test, please wait.");
  msg = msg + F("\nread speed and latency");
  msg = msg + F("\nSpeed,max,min,avg");
  msg = msg + F("\nKB/Sec,usec,usec,usec");

  if (debug) {
    tft.println(msg);
  }

  // do read test
  for (uint8_t nTest = 0; nTest < READ_COUNT; nTest++) {
    file.rewind();
    maxLatency = 0;
    minLatency = 9999999;
    totalLatency = 0;
    skipLatency = SKIP_FIRST_LATENCY;
    t = millis();
    for (uint32_t i = 0; i < n; i++) {
      buf[BUF_SIZE - 1] = 0;
      uint32_t m = micros();
      int32_t nr = file.read(buf, BUF_SIZE);
      if (nr != BUF_SIZE) {
        if (debug) {
          tft.println("read failed");
        }
        return;
      }
      m = micros() - m;
      totalLatency += m;
      if (buf[BUF_SIZE - 1] != '\n') {
        if (debug) {
          tft.println("data check error");
        }
        return;
      }
      if (skipLatency) {
        skipLatency = false;
      } else {
        if (maxLatency < m) {
          maxLatency = m;
        }
        if (minLatency > m) {
          minLatency = m;
        }
      }
    }
    s = file.fileSize();
    t = millis() - t;

    msg = String(s / t) + F(",") + String(maxLatency) + F(",") + String(minLatency) + F(",") + String(totalLatency) + F("\n");

    if (debug) {
      tft.println(msg);
    }
  }
  haveController = false;
  InitController(false, 0);
  DoTimer = true;
  if (debug) {
    tft.println(F("Done"));
  }
  file.close();

  return;
}

bool chipIDDmp(bool debug) {
  cid_t cid;
  bool init = false;
  String msg = "";
  if (!sd.card()->readCID(&cid)) {
    if (debug) {
      tft.println(F("readCID failed"));
      return init;
    }
  }
  if (debug) {
    msg = "\nManufacturer ID: " + String(cid.mid) + F("\nOEM ID: ") + String(cid.oid[0]) + String(cid.oid[1]) + F("\nProduct: ");
    for (uint8_t i = 0; i < 5; i++) {
      msg = msg + String(cid.pnm[i]);
    }
    msg = msg + "\nRevision: " + String(cid.prvN()) + F(".") + String(cid.prvM());
    msg = msg + "\nSerial number: " + String(cid.psn());
    msg = msg + "\nManufacturing date: " + String(cid.mdtMonth()) + F("/") + String(cid.mdtYear());
    tft.println(msg);

  }
  init = true;
  return init;
}


void SDCardUnmount() {
  Serial.print( "Unmounting SD card..." );
  file.close();
  sd.end();
  // Unmount SD card
  //card.end();
  // figure it ojut
  Serial.println( "done." );
}

For every call of a function a minimum saved on the stack will be the return address. This code really looks like fighting to the unavoidable death.

im sure most of my rough draft code is fighting the unavoidable death, some great novels are horrible first drafts lmao. i just dont have a foggy idea how to proceed with a better method.

I once made TFT menu for my bluetooth player, every menu was a class with own methods and variables contained within, so selecting menu item was loading appropriate class, while freeing previous menu item

being self taught the class thing is not something i have ever been good with so i will go google that and read for a while . you wouldn't happen to have the code for that or an example of how it works would you ?

In your initial example, f5 calls f1, which starts the cycle again. If it's as simple as that, it will crash any machine, regardless of how much memory it has.

explains why it dumps me half way in the middle of 2 functions. i guess that is what i messed up i wrote the menu items as their own functions . so if you went from the main menu to the diagnostic screen that was the menu function calling the diagnostic function, then if you pressed the button to leave the diagnostic function you would actually be calling the main function again. since its like a recursive nesting i am correct in assuming i have stack over flowed it.

Don’t think I have it, but even if I had, you wouldn’t want it, it was first attempt so needed a good rewrite, maybe also the reason why I don’t have it

bad working code can teach you alot more than good broken code. lol .

ok so from the stand point of the gui i should write the screen as a class and then just update it from the functions that return as they run? or i should write the interfaces that appear on the screen as individual classes and then execute them as i need to ?

I have. Just drop the last F1 call from inside F5. Let loop do the job!

if i release control at f5() and dont call f1() returning from f5() would place me after the call to f5() in f4() correct?

void f1(){
f2();
return;
}

void f2(){
f3();
return;
}

void f3(){
f4();
return;
}

void f4(){
f5();
return;
}

void f5(){
f1();
return;
}

if i did this it would take me all the way back to the first screen. then i would have to proceed with selecting my options again from the top.

i guess i could look at a button press in the control structure that issues the return, but then the control would take me back to the captive while loop that checks for button presses and would not redraw the menu without calling the funtion it was in again. so now i have the old information displayed but i am waiting for button presses from the previous options menu.

 while (!ok) {//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Diagnostic Control Structure
    Do_Terminal();
    if (psx.buttonPressed(PSB_L3)) {

    }
    if (psx.buttonPressed(PSB_START)) {
      tft.resetViewport();
      View_Main();
    }
    if (psx.buttonPressed(PSB_CROSS)) {
      tft.resetViewport();

    }
    if (psx.buttonPressed(PSB_CIRCLE)) {
      View_NRFDiag();
    }
    if (psx.buttonPressed(PSB_SQUARE)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_TRIANGLE)) {
      View_SDDiag();
    }
    if (psx.buttonPressed(PSB_L1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_L2)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R1)) {
      View_Main();
    }
    if (psx.buttonPressed(PSB_R2)) {

    }
  }//////////////////////////////////////////////////////////////////////////////////////////////////////////////////control struct end

above is the code that calls the functions returning to this traps me waiting on input and then if i return from that again i end up a menu higher in the same control structure, again and again until you reach the main menu.

so i have created a slug program every button you press makes your snail trail longer.

so then i take from my musing that my initial problem is that i need to rework the control structure, maybe something variable based, then use those variables to act on a switch case structure in the main loop then the switch case calls the funtions 1 time to which they return after drawing to the display into the loop that is monitoring for input then each funtion always returns to look at the input instead of a nested cascade of mess...

Look at this:

loop(){
void f1(){
f2();
return;
}

void f2(){
f3();
return;
}

void f3(){
f4();
return;
}

void f4(){
f5();
return;
}

void f5(){
//f1();
return;
}
}

if f1 is removed from f5 then it would cascade up to f5 and then back down to f1 over and over like a half sine wave.