/*************************************
Project:     OpAmp Tester
File:        Display
Version:     3.0 - May 2026
Written by:  diy-electronic.ru
**************************************
Version history
  1.0  Mar 2025 Initial version with
                1602 LCD display
  2.0  May 2025 With 2.4" TFT graphic
                display 240*320px
  2.1  Dec 2025 Calibration pages and
                Error pages added
  3.0  May 2026 Vos thermal drift 
                measurement result 
                display added
**************************************/

#include <TFT_eSPI.h> 

// Font sizes
#define FONT_SMALL  1
#define FONT_MIDDLE 2
#define FONT_LARGE  4

// Text magnification factor
#define X1 1  // Normal size
#define X2 2  // Double size

// Color themes. All colors are in 16-bit ("RGB565") format.
// Use https://rgbcolorpicker.com/565 for calculation.
const uint16_t themes[] = {
  // Theme GREEN  themeNum=0
  0x00c0, // Dark green background
  0x0740, // Light green borders and lines
  0xffff, // White text
  0xffe0, // Yellow highlighted text
  0xf800, // Red aux color
  // Theme BLUE  themeNum=1
  0x0010, // Dark blue background
  0x423f, // Light blue borders and lines
  0xffff, // White text
  0xffe0, // Yellow highlighted text
  0xf800, // Red aux color
  // Theme BROWN  themeNum=2
  0x2920, // Dark brown background
  0xc5c0, // Light brown borders and lines
  0xffff, // White text
  0xffe0, // Yellow highlighted text
  0xf800  // Red aux color
};

// Variables used to store current colors
uint16_t theme_BgColor,     //Background color
         theme_BorderColor, //Borders and lines
         theme_TextColor,   //Text color
         theme_HLTextColor, //Highlighted text color
         theme_AuxColor;    //Dark contour color

//------------------------------//
// Storage for error messages:  //
//------------------------------//
const String ErrMessages[] = {
  "Iq is too low.\nOpAmp is not\nproperly inserted\nor damaged.", // errID = 1
  "Iq is too high.\nIC is damaged.",                              // errID = 2
  "Calibration failed.\nCheck reference\nvoltages in PPS.",       // errID = 3
  "Vos is out of\nrange."                                         // errID = 4
};

// Progress bar data: position, size and number of steps
struct {
  uint16_t x;
  uint16_t y;
  uint16_t w;
  uint16_t h;
  uint16_t total_steps;
} progressBar;

TFT_eSPI tft = TFT_eSPI();       // Display object


//------------ Global functions ---------------

/**************************************/
/*           INIT DISPLAY             */
/* Is to be called from setup() only. */
/**************************************/
void displayInit() {
  tft.init();
  tft.setRotation(0);
  displayTheme();
}

/****************************************/
/*     CHANGE DISPLAY COLOR THEME       */
/* Is to be called on displayInit and   */
/* also if user has changed color theme */
/****************************************/
void displayTheme() {
  loadColorTheme();
  tft.fillScreen(theme_BorderColor);
  tft.setTextColor(theme_TextColor);
}

/***********************************/
/*           TITLE PAGE            */
/***********************************/
void showTitle() {
  cleanBackground();
  tft.setTextDatum(TC_DATUM);
  tft.setTextSize(X1);
  tft.drawString("diy-electronic.ru", 120, 20, FONT_LARGE);
  tft.setTextSize(X2);
  tft.setTextColor(theme_HLTextColor);
  tft.drawString("OpAmp",  120,  92, FONT_LARGE);
  tft.drawString("Tester", 120, 152, FONT_LARGE);
  tft.setTextSize(X1);
  tft.setTextColor(theme_TextColor);
  tft.drawString("v.3.0.0", 114, 234, FONT_LARGE);
  tft.drawString("2026",  120, 272, FONT_LARGE);
}

/***************************************/
/*          INSERT OpAmp PAGE          */
/***************************************/
void showInsertOpAmpPage() {
  preparePage("Start", "Mode", "Setup", "Insert OpAmp:");

  drawDoubleRoundRect(2, 40, 120, 38, theme_AuxColor); // Draw 14-pin socket
  drawDoubleRoundRect(2, 94, 212, 38, theme_AuxColor); // Draw 24-pin socket
  tft.drawFastVLine(72,  94, 38, theme_AuxColor);
  tft.drawFastVLine(73,  94, 38, theme_AuxColor);
  tft.drawFastVLine(142, 94, 38, theme_AuxColor);
  tft.drawFastVLine(143, 94, 38, theme_AuxColor);

  tft.setTextDatum(TC_DATUM);
  tft.setTextSize(X2);
  tft.drawString("SNGL",  45, 106, FONT_SMALL); // Draw socket labels
  tft.drawString("DBL1", 115, 106, FONT_SMALL);
  tft.drawString("DBL2", 185, 106, FONT_SMALL);
  tft.drawString("QUAD",  61,  52, FONT_SMALL);
  tft.fillCircle(12, 122, 3, theme_TextColor);  // Draw 1st pin markers
  tft.fillCircle(82, 122, 3, theme_TextColor);
  tft.fillCircle(152,122, 3, theme_TextColor);
  tft.fillCircle(12, 68, 3, theme_TextColor);

  tft.setTextSize(X1);
  tft.setCursor(152,37,FONT_MIDDLE);
  tft.setTextColor(theme_HLTextColor); // Highlighted text color
  tft.print("Voltage:");
  uint32_t pos_x = 150, pos_y = 61;
  tft.drawFastVLine(pos_x + 4, pos_y,  10, theme_HLTextColor); // Draw +/- sign
  tft.drawFastVLine(pos_x + 5, pos_y,  10, theme_HLTextColor);
  tft.drawFastHLine(pos_x, pos_y + 4,  10, theme_HLTextColor);
  tft.drawFastHLine(pos_x, pos_y + 5,  10, theme_HLTextColor);
  tft.drawFastHLine(pos_x, pos_y + 12, 10, theme_HLTextColor);
  tft.drawFastHLine(pos_x, pos_y + 13, 10, theme_HLTextColor);

  tft.setTextSize(X2);      // Draw voltage
  tft.setCursor(pos_x + 14, pos_y, FONT_SMALL);
  switch (Voltage) {
    case voltageStandard:
      tft.print("12V");
      break;
    case voltageLow:
      tft.print("2.5V");
      break;
  }
  tft.setTextColor(theme_TextColor); // Normal text color
  tft.setTextSize(X1);  // Draw legend/
  tft.setCursor(0,146,FONT_MIDDLE);
  tft.println(" SNGL - single OpAmp");
  tft.println(" DBL1 - dual OpAmp 1st half");
  tft.println(" DBL2 - dual OpAmp 2nd half");
  tft.println(" QUAD - quad OpAmp");
  tft.setCursor(0,226,FONT_MIDDLE);
  tft.print(" Insert only one OpAmp at a time!");
  tft.resetViewport();
}

/***************************************/
/*        TEST IN PROGRESS PAGE        */
/***************************************/
void showTestInProgressPage() {
  preparePage("", "", "", "Test in progress...");
  tft.resetViewport();
  drawDoubleRoundRect(12, 66, 216, 42, theme_AuxColor);
  progressBarInit(18,72,204,30,12);
}

/**************************************/
/*        ERROR MESSAGE PAGE          */
/*  errID - error number (index in    */
/*          ErrMessages array + 1)    */
/**************************************/
void showErrorPage(uint16_t errID) {
  preparePage("Exit", "", "", String("Error 0") + errID + ":");
  tft.setTextSize(X1);
  tft.setCursor(0,46,FONT_LARGE);
  tft.print(ErrMessages[errID - 1]);
  tft.resetViewport();
}

/*****************************************/
/*         OUT OF RANGE PAGE             */
/* ParamName - name of the param, which  */
/*             caused out of range error */
/*****************************************/
void showOutOfRangePage(String ParamName) {
  preparePage("Stop", "Skip", "", "Warning:");
  tft.setTextSize(X1);
  tft.setCursor(0,46,FONT_LARGE);
  tft.print(ParamName + " is out of range.");
  tft.resetViewport();
}

/*****************************/
/*       RESULTS PAGE        */
/*****************************/
void showResultsPage() {
  preparePage("Exit", "", "", " Test results:");
  tft.setTextSize(X1);
  tft.setTextColor(theme_TextColor);
  
  tft.setViewport(18, 4, 216, 254); // Set printable area
  tft.setCursor(0,46,FONT_LARGE);    // for the left column

  tft.println("Vos:");
  tft.println("dV/dT:");
  tft.println("Ib:");
  tft.println("Ios:");
  tft.println("Aol:");
  tft.println("CMRR:");
  tft.println("PSRR:");
  tft.println("Iq:");
  
  tft.setViewport(106, 4, 116, 254); // Set printable area
  tft.setCursor(0,46,FONT_LARGE);     // for the right column

  tft.print(Vos,2);
  tft.println("mV");

  tft.print(Drift,1);
  tft.print("uV/");
  
  int16_t CX = tft.getCursorX(); // print "degrees" symbol
  int16_t CY = tft.getCursorY();
  tft.setCursor(CX + 1,CY - 6,FONT_MIDDLE);
  tft.print("o");
  tft.setCursor(CX + 8,CY,FONT_LARGE);
  tft.println("C");
  
  selectColor(IbValid);
  tft.print(Ib,1);
  tft.println("nA");

  selectColor(IosValid);
  tft.print(Ios,1);
  tft.println("nA");

  selectColor(AolValid);
  tft.print(Aol,0);
  tft.println("");

  selectColor(CMRRValid);
  tft.print(CMRR,0);
  tft.println("dB");

  selectColor(PSRRValid);
  tft.print(PSRR,0);
  tft.println("dB");

  tft.setTextColor(theme_TextColor);
  tft.print(Iq,2);
  tft.println("mA");
  
  tft.resetViewport();
}

/*****************************/
/*  SELECT COLOR THEME PAGE  */
/*****************************/
void showSelectThemePage() {
  preparePage("Green", "Blue", "Brown", "Select theme:");
  tft.resetViewport();
}

/*************************/
/*  SELECT VOLTAGE PAGE  */
/*************************/
void showSelectVoltagePage() {
  preparePage("12V", "2.5V", "", "Select voltage:");
  tft.resetViewport();
}

/**********************************/
/*        CALIBRATION PAGE       */
/**********************************/
void showCalibrationPage(String title) {
  preparePage("-1", "+1", "OK", title + " tuning:");
  tft.resetViewport();
}

void showCalibrationStep(uint16_t step) {
  tft.setViewport(12, 16, 216, 254); // Set printable area
  tft.setTextSize(X1);
  tft.setCursor(0, calibrationSteps[step].posY, FONT_MIDDLE);
  tft.println(calibrationSteps[step].message);
  tft.resetViewport();
}

void showVoltmeter(float voltage) {
  int32_t x = 64, y = 176, w = 120, h = 48;
  drawDoubleRoundRect(x, y, w, h, theme_AuxColor); // border
  tft.fillSmoothRoundRect(x + 2, y + 2, w - 4, h - 4, 6, TFT_BLACK);
  tft.setTextSize(X1);
  tft.setViewport(x + 3, y + 4, w - 9, h - 8);
  tft.setCursor(20,8,FONT_LARGE);
  tft.print(voltage,3);
  tft.resetViewport();
}

/******************************/
/* CALIBRATION COMPLETED PAGE */
/******************************/
void showCalibrationCompletedPage() {
  preparePage("Exit", "", "", "Completed:");
  tft.setTextSize(X1);
  tft.setCursor(0,46,FONT_LARGE);
  tft.print("Press Exit to return.");
  tft.resetViewport();
}

/*********************************/
/*   SET PROGRESS BAR POSITION   */
/*********************************/
void progressBarSet(uint16_t Value) {
  uint16_t num = (Value <= progressBar.total_steps) ? Value : progressBar.total_steps;
  uint16_t elementWidth = (uint16_t) ((progressBar.w - 2) / progressBar.total_steps);
  for (int i = 1; i <= num; i++) {
    tft.fillRect((progressBar.x + 2 + (i - 1) * elementWidth), (progressBar.y + 2), (elementWidth - 2), (progressBar.h - 4), theme_AuxColor);
  }
}

//-------- Local functions ----------

void loadColorTheme() {
  theme_BgColor     = themes[themeNum * 5];
  theme_BorderColor = themes[themeNum * 5 + 1];
  theme_TextColor   = themes[themeNum * 5 + 2];
  theme_HLTextColor = themes[themeNum * 5 + 3];
  theme_AuxColor    = themes[themeNum * 5 + 4];
}

void cleanBackground() {
  tft.fillSmoothRoundRect(3, 3, 234, 314, 12,theme_BgColor,theme_BorderColor);
}

void drawDoubleRoundRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
  tft.drawRoundRect(x, y, w, h, 6, color);
  tft.drawRoundRect(x+1, y+1, w-2, h-2, 6, color);
}

// Draw main elements of a page - background, buttons and title
void preparePage(String button1, String button2, String button3, String title) {
  cleanBackground();
  if (button1.length() > 0)  {  // Do not draw buttons if 1st button name is empty
    tft.drawRoundRect(  9, 270, 71, 40, 8, theme_BorderColor);
    tft.drawRoundRect( 85, 270, 71, 40, 8, theme_BorderColor);
    tft.drawRoundRect(161, 270, 71, 40, 8, theme_BorderColor);
    tft.setTextDatum(TC_DATUM);
    tft.setTextSize(X1);
    tft.drawString(button1,  45, 280, FONT_LARGE);
    tft.drawString(button2, 120, 280, FONT_LARGE);
    tft.drawString(button3, 195, 280, FONT_LARGE);
  }
  tft.setViewport(12, 16, 216, 254);   // Set printable area
  //tft.frameViewport(TFT_WHITE, 1);   // Uncomment this line to see printable area border
  tft.setCursor(0,0,FONT_LARGE);       // Set cursor initial position and font
  tft.setTextColor(theme_HLTextColor); // Yellow title
  tft.println(title);
  tft.setTextColor(theme_TextColor);
}

void progressBarInit(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t total_steps) {
  tft.fillRect(x, y, w, h, theme_BorderColor);
  progressBar.x = x;
  progressBar.y = y;
  progressBar.w = w;
  progressBar.h = h;
  progressBar.total_steps = total_steps;
}

// Select highlighted text color for invalid parameters
void selectColor(bool ParamIsValid) {
  if (ParamIsValid) {
    tft.setTextColor(theme_TextColor);
  }
  else {
    tft.setTextColor(theme_AuxColor);
  }
}
