Output variable at time intervals

The basic part of the question is simple, right?

millis() passed >= intervalTime
something=variable

But this needs to create from 20-40 different variables so I am trying to make this into a struct and increase the variable every time the interval comes up. This might be the wrong approach, but I'm trying something. What this is doing is measuring the voltage of a battery during discharge every 15 seconds. I need to print these values to a display. I'll figure out the printing to a display after I get this part done. But when just trying to print this to Serial I am not getting anything.

I'm putting in the tags the relevant code I think. There is a lot of code not in the tags. I don't have an issue posting the entire code, but it is about 1000 lines
Again, right now I am just trying to get this to Serial. Eventually it will end up on my display, I'll have to increment the coordinates on the display with the variable.


unsigned const int numChanT = 40;
unsigned int chanT;
struct voltageTime {
  float vTime;
};
voltageTime ChannelsT[numChanT];
unsigned long intervalTime;      //used for vTime data
unsigned long previntervalTime;  //used for vTime data

setup(){
}
loop(){
getCalculations();
getVTime();
SerialData();
if button pressed // Not really the code, but a button press starts the time
previntervalTime=millis();

}

void getCalculations() {

  //getAccurateVoltage();
  getCurrentAverage();
  voltageI = Icorrected * (VCC / 1023);
  Icurrent = abs((voltageI - QOV) / sens);
  getPVoltageAverage();
  getFVoltageAverage();
  Pvoltage = vpavg * (VCC / 1023);
  Fvoltage = vfavg * (VCC / 1023);

  if (isrunning == true || irRunning == true) {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P) + (Icurrent / 250);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F) + (Icurrent / 250);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  } else {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  }
  if (voltageFull >= 5) {
    numberCells = '2';
  } else if (voltageFull < 5) {
    numberCells = '1';
  }
  //VCC = VCCC / 1000;

  if (isrunning == true) {
    CurrentTime = (millis() - StartTime) / 1000;
    mahTime = (millis() - prevmahTime) / 1000;
  }
  if (CurrentTime > runTime) {
    runTime = CurrentTime;
  } else if (isrunning == false) {
    CurrentTime = 0;  // Need to change to keep the previous runtime around, but have a reset data button to zero time
  }
  if (mahTime - prevmahTime >= 1) {
    mAH += (Icurrent / 3.6);
    prevmahTime = mahTime;
  }
  if (mAH > rTmAH) {
    rTmAH = mAH;
  }
}  // end Calculations

void getVTime() {
  intervalTime = millis() - previntervalTime / 1000;
  if (intervalTime >= 15) {
    ChannelsT[chanT].vTime = voltageFull;
    previntervalTime = intervalTime;
    chanT++;
  }
}

void SerialData() {
  //getCalculations();
  Serial.println("----------------------------------------");
  Serial.println();
  Serial.print(VCC);
  Serial.print("V VCC ");
  Serial.print(QOV);
  Serial.print(" QOV ");
  Serial.println();
  Serial.print(analogRead(Isens));
  Serial.print(" RAW Read ");
  Serial.print(Icorrected);
  Serial.print(" Corrected ");
  Serial.print(voltageI);
  Serial.print("V ");
  Serial.print(Icurrent);
  Serial.print(" Amps ");
  Serial.print(iavg);
  Serial.print(" AVG ");
  Serial.print(Output);
  Serial.println();
  Serial.print(voltageFull);
  Serial.print(" V Full  ");
  Serial.print(vfavg);
  Serial.print("  ACT  ");
  Serial.print(analogRead(VFsens));
  Serial.print(" ");
  Serial.println();
  Serial.print(voltagePartial);
  Serial.print("   ");
  Serial.print(" V Partial   ");
  Serial.print(vpavg);
  Serial.print("   ACT  ");
  Serial.print(analogRead(VPsens));
  Serial.println();
  Serial.print("Cell 1 ");
  Serial.print(cell1, 3);
  Serial.print("V   Cell 2 ");
  Serial.print(cell2, 3);
  Serial.print("V");
  Serial.println();
  Serial.println(ChannelsT[chanT].vTime);
}

not sure what your problem is
I think you over complicate things by having an array of structures

struct voltageTime {
  float vTime;
};
voltageTime ChannelsT[numChanT];

why not just an array of floats

float vTime[numChanT];

one usually uses structures to hold a number of user-defined data types which group together

Hello trilerian

Consider:

constexpr uint32_t Interval {15000};
uint32_t now;
uint32_t currentMillis;
float vTime[40];
constexpr uint8_t SizeOfArray {sizeof(vTime) / sizeof(vTime[0])};
uint8_t measureCounter = 0;
uint8_t PotPin {A8};
void setup()
{
  Serial.begin(9600);
  Serial.println ("Start");
}
void loop()
{
  currentMillis =  millis();
  if ( currentMillis - now >= Interval)
  {
    now = currentMillis;
    Serial.print ("measure: ");
    float potRead = analogRead(PotPin);
    Serial.println(potRead);
    vTime[measureCounter++] = potRead;;

    if ((measureCounter % SizeOfArray) == 0)
    {
      Serial.println("\narray is filled");
      for (auto vTime_ : vTime) Serial.println(vTime_);
      while (1);
    }
  }
}

There is no Serial.begin() in the code you posted.

If you tell us the code you are using does have that, understand that you have wasted the forum members time by posting code which is not the code you are working on.

That's because you are a beginner programmer, not because what you are attempting to do needs 1000 lines of code. With help it can probably be reduced to 100~300 lines with no loss of features.

Since someone always says it...

/*
    Code Version 1.0
    WMH Racing Battery Wizard
    Written by Andrew Sarratore
    Date: 10/28/2023
    Code Version 1.1
    Add getCurrentAverage()
    add getFVoltageAverage()
    add getCalculations()
    add Program()
    Changes to the "Run Program" Menu
    Date:12/11/2023
    Code Version 1.2
    add getPVoltageAverage
    add more readings to the dishcharge display
    Code Version 1.3
    Date:12/27/2023
    add self calibrate VCC
    add mAH reading, untested
    add PID_v1.h library, still need to write PID code for the PWM value and replace current feedback loop
    Code Version 1.4
    Date:12/30/2023
    Add IR struct - Untested
    Add getIR function - still need to write the menu and page function
    Code Version 1.4.2
    Date:1/4/2024
    Calibrate initial QOV based on VCC during setup
    Fixed averages to be floats instead of int...
    Adding IR Reading menus
    Code Version 1.5
    PID added, Kp=0.25, Ki=10, Kd=0
    Added page 6 for displaying voltage data
Date:1/15/24
Code Version 1.6
Changed where the code calls the calculations and true VCC functions from.  This made other parts of the loop faster and vcc more accurate
Added in a multiplier to voltage under load.  (Icurrent/1000)*4.  I can simplify to Icurrent/250.  
This fixes voltage under load readings at various discharge rates.  May change for each unit
Date:1/16/24
Code Version 1.7
Adding 1S support
Date:1/17/24
Code Version 1.8
Adding Struct for Time/Voltage variable
Filling the page for the Time/Voltage variable  vTime
   
    Voltage values every 15 seconds - look into graphing this, would be cool.  
   
   
*/

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_Touchscreen.h"
#include "Math.h"
#include "PID_v1.h"
#include "movingAvg.h"

// Define SPI pins for both display and touch
#define TFT_CS 10
#define TFT_DC 9
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
#define TS_CS 7
#define ROTATION 1
#define Isens A0
#define VFsens A1
#define VPsens A2
#define PWM 3
#define K (1.0 / 30)

char currentPage;
char numberCells;

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC/RST
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
XPT2046_Touchscreen ts(TS_CS);

// calibration values
float xCalM = -0.09, yCalM = -0.06;    // gradients
float xCalC = 329.36, yCalC = 248.13;  // y axis crossing points

int8_t blockWidth = 20;  // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0;  // block position (pixels)

class ScreenPoint {
public:
  int16_t x;
  int16_t y;

  // default constructor
  ScreenPoint() {
  }

  ScreenPoint(int16_t xIn, int16_t yIn) {
    x = xIn;
    y = yIn;
  }
};

class Button {
public:

  int x;
  int y;
  int width;
  int height;
  char *text;

  Button() {
  }

  void initButtonG(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderG();
  }
  void initButtonR(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderR();
  }
  void initButtonB(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderB();
  }
  void initButtonY(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderY();
  }
  void renderG() {
    tft.fillRect(x, y, width, height, ILI9341_GREEN);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderR() {
    tft.fillRect(x, y, width, height, ILI9341_RED);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderB() {
    tft.fillRect(x, y, width, height, ILI9341_BLUE);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderY() {
    tft.fillRect(x, y, width, height, ILI9341_YELLOW);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  bool isClicked(ScreenPoint sp) {
    if ((sp.x >= x) && (sp.x <= (x + width)) && (sp.y >= y) && (sp.y <= (y + height))) {
      return true;
    } else {
      return false;
    }
  }
};

unsigned const int numChan = 5;
unsigned int chan;
struct ChannelData {
  float startVoltageP;
  float startVoltageF;
  float endVoltageP;
  float endVoltageF;
  float voltageDropP;
  float voltageDropF;
  float internalResistanceP;
  float internalResistanceF;
};
ChannelData Channels[numChan];

unsigned const int numChanT = 40;
unsigned int chanT;
struct voltageTime {
  float vTime;
};
voltageTime ChannelsT[numChanT];

//Touch Screen
ScreenPoint getScreenCoords(int16_t x, int16_t y) {
  int16_t xCoord = round((x * xCalM) + xCalC);
  int16_t yCoord = round((y * yCalM) + yCalC);
  if (xCoord < 0) xCoord = 0;
  if (xCoord >= tft.width()) xCoord = tft.width() - 1;
  if (yCoord < 0) yCoord = 0;
  if (yCoord >= tft.height()) yCoord = tft.height() - 1;
  return (ScreenPoint(xCoord, yCoord));
}

// I can probably make an array of the following button variables...
Button btnIP;  // Current Selection +
Button btnIM;  // Current Selection -
Button btnVP;  // Cutoff V +
Button btnVM;  // Cutoff V -
Button btnMI;  // Main - Discharge Rate
Button btnMV;  // Main - Voltage Cutoff
Button btnMM;  // Main Menu on other pages
Button btnCT;  // Main Calibrate Touch
Button btnRP;  // Main Menu - Run Program
Button btnPN;  // Run/Running
Button btnMN;  // Discharge - Main Menu
Button btnRN;  // STOP
Button btnIR;  // Main - IR Reading
Button btnRC;  // IR Reading - Check IR
Button btnDD;  // Discharge - dischargeData
Button btnRPD;

double IV = 30;                  // Initial rate of discharge in ampres.  User changable in code
float VV = 350.00;               // Cutoff voltage*100 so ++ works to change the value
unsigned long StartTime;         // Start time to measure time of discharge
unsigned long CurrentTime;       // Will give how long the discharge is running
unsigned long mahTime;           // used for mAH
unsigned long prevmahTime;       // used for mAH
unsigned long intervalTime;      //used for vTime data
unsigned long previntervalTime;  //used for vTime data
unsigned long runTime;  // runTime of the discharge function
//unsigned long lastFrame = millis();

//PID
double Input;
double Output;
//Specify the links and initial tuning parameters
double Kp = .01, Ki = 15, Kd = 0;
PID myPID(&Input, &Output, &IV, Kp, Ki, Kd, DIRECT);

// Current Variables
float VCC = 0;          // 4.967  measure the value(Measure value not needed with the VCC calibration code)
float VCCC = 0;         // Used for Accurate Voltage
float QOV = 0;          // Calibration happens in setup
float sens = 0.04;      //sensitivity of the current sensor
float iavg;             // analogRead(Isens)/IN
float Icurrent = 0;     // Current Reading in Amps
float Icorrected = 0;   //iavg-IOFF
float voltageI = 0;     // iavg translated to voltage
float IOFF = 0.6;       // Offset to the raw read to zero current
int IN = 1000;          // Sample number to average current
float mAH;              // mAH used
float rTmAH;            // Runtime mAH used, highest value

// Battery Voltage Variables (may be able to make a struct or class here as well)
int ValueR1F = 5094;       //Measured value
int ValueR2F = 5104;       //Measured value
int ValueR1P = 5106;       //Measured value
int ValueR2P = 5082;       //Measured value
int VFN = 1000;            // Sample number for average of analogRead(VFsens) Full voltage
int VPN = 1000;            // Sample number for average of analogRead(VPsens) Partial voltage
float vfavg;               // Average of the analogRead(VFsens)/VFN      -Full Voltage
float vpavg;               // Average of the analogRead(VPsens)/VPN      -Partial Voltage
float Fvoltage = 0;        // Calculation of FV seen by MCU
float Pvoltage = 0;        // Calculation of PV seen by MCU
float voltageFull = 0;     // Calculation of Full Voltage
float voltagePartial = 0;  // Calculation of Partial Voltage
float cell1 = 0;           // Voltage of Cell 1
float cell2 = 0;           // Voltage of Cell 2
float internalResistanceAVGP = 0;
float internalResistanceAVGF = 0;
boolean isrunning = false;
boolean irRunning = false;
int zz = 0;  // variable used for analogWrite(PWM,zz)

//===================================================================SETUP=========================================================================================
void setup() {
  Serial.begin(115200);
  // avoid chip select contention
  pinMode(TS_CS, OUTPUT);
  digitalWrite(TS_CS, HIGH);
  pinMode(TFT_CS, OUTPUT);
  pinMode(Isens, INPUT);
  pinMode(VFsens, INPUT);
  pinMode(VPsens, INPUT);
  pinMode(PWM, OUTPUT);
  digitalWrite(TFT_CS, HIGH);
  tft.begin();
  tft.setRotation(ROTATION);
  tft.fillScreen(ILI9341_BLACK);
  ts.begin();
  ts.setRotation(ROTATION);
  //calibrateTouchScreen(); Leaving here for first run of the dispaly to get coordinates of touch screen
  getAccurateVoltage();
  getCurrentAverage();
  getFVoltageAverage();
  getPVoltageAverage();
  getCalculations();
  calibrateQOV();

  //initialize the variables we're linked to
  Input = Icurrent;
  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  currentPage = '0';  //  Indicates that we are at Home Screen
  Home();
}//==================================================================END SETUP=====================================================================

//======================================================================LOOP======================================================================
void loop() {
  ScreenPoint sp;
  // limit frame rate
  //while((millis() - lastFrame) < 20); //(caused issues with millis function to get mAH, may need to revisit as this helped some flickering)
  //lastFrame = millis();

  // Home Screen
  if (currentPage == '0') {

    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMV.isClicked(sp)) {
        currentPage = '1';  // Go to Discharge Current
        tft.fillScreen(ILI9341_BLACK);
        discharge();
      } else if (btnMI.isClicked(sp)) {
        currentPage = '2';  // Go to Cutoff Voltage
        tft.fillScreen(ILI9341_BLACK);
        cutoff();
      }

      else if (btnCT.isClicked(sp)) {
        currentPage = '3';  // Go to Calibrate Screen
        tft.fillScreen(ILI9341_BLACK);
        calibrateTouchScreen();
      }

      else if (btnRP.isClicked(sp)) {
        currentPage = '4';  // Go to Run Program
        getCalculations();
        tft.fillScreen(ILI9341_BLACK);
        program();
      }

      else if (btnIR.isClicked(sp)) {
        currentPage = '5';  // Go to irReading
        tft.fillScreen(ILI9341_BLACK);
        irReading();
      }
    }
  }

  // Discharge Current
  if (currentPage == '1') {
    tft.setCursor(100, 150);
    tft.print(IV, 0);
    tft.setCursor(190, 150);
    tft.print("A");
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnIP.isClicked(sp)) {
        if (IV < 45) {
          IV++;
        }
        tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnIM.isClicked(sp)) {
        IV--;
        tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
        delay(100);

      } else if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }
    }
  }  // End Page 1

  // Cutoff Voltage
  if (currentPage == '2') {
    tft.setCursor(100, 150);
    tft.print(VV / 100);
    tft.setCursor(210, 150);
    tft.print("V");
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnIP.isClicked(sp)) {
        VV++;
        tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnIM.isClicked(sp)) {
        if (VV > 280) {
          VV--;
        }
        tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }
    }
  }  // End Page 2

  // Run Discharge (Surely I can move some of this to the page function, but when moved it caused the screen to refresh every loop)
  if (currentPage == '4') {
    getAccurateVoltage();
    getCalculations();
    getVTime();
    SerialData();
    tft.setCursor(0, 50);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.print("Battery:");
    tft.setCursor(0, 75);
    tft.print("Cutoff ");
    tft.print(VV / 100);
    tft.print("V");
    tft.setTextSize(2);
    tft.setCursor(0, 100);
    tft.print("Actual ");
    tft.print(voltageFull, 2);
    tft.setCursor(145, 100);
    tft.print("V");
    tft.setCursor(160, 50);
    tft.print("Current:");
    tft.setCursor(160, 75);
    tft.print("Rate ");
    tft.print(IV, 0);
    tft.print("A");
    tft.setCursor(160, 100);
    tft.print("Actual ");
    tft.print(Icurrent, 1);
    tft.setCursor(305, 100);
    tft.print("A");
    tft.setTextSize(1);
    tft.setCursor(160, 120);
    tft.print("Time");
    tft.setCursor(160, 130);
    tft.fillRect(160, 130, 50, 10, ILI9341_BLACK);
    tft.print(runTime);
    tft.setCursor(240, 120);
    tft.print("mAH");
    tft.setCursor(240, 130);
    tft.print(rTmAH, 0);
    tft.setCursor(0, 150);
    tft.print("VCC");
    tft.setCursor(0, 160);
    tft.print(VCC, 3);

    if (voltageFull > 5) {
      tft.setTextSize(1);
      tft.setCursor(0, 120);
      tft.print("Cell 1  ");
      tft.setCursor(45, 120);
      tft.print(cell1, 3);
      tft.setCursor(80, 120);
      tft.print("V");
      tft.setCursor(0, 130);
      tft.print("Cell 2  ");
      tft.setCursor(45, 130);
      tft.print(cell2, 3);
      tft.setCursor(80, 130);
      tft.print("V");
    }  //

    else if (voltageFull < 5) {
      tft.setTextSize(1);
      tft.setCursor(0, 120);
      tft.print("Cell 1  ");
      tft.setCursor(45, 120);
      tft.print(cell1, 3);
      tft.setCursor(80, 120);
      tft.print("V");
      tft.setCursor(0, 130);
    }  //end if

    if (isrunning == true) {
      btnPN.initButtonR(0, 15, 140, 25, "DISCHARGE");
      RunDischarge();
    } else {
      tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
      btnPN.initButtonG(0, 15, 140, 25, "    RUN");
    }
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMN.isClicked(sp)) {
        currentPage = '0';
        Home();
      } else if (btnRN.isClicked(sp)) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      } else if (btnPN.isClicked(sp)) {
        tft.fillRect(160, 130, 320, 10, ILI9341_BLACK);
        rTmAH = 0;
        runTime = 0;
        StartTime = millis();
        prevmahTime = millis();
        previntervalTime = millis();
        isrunning = true;

      } else if (btnDD.isClicked(sp)) {
        tft.fillScreen(ILI9341_BLACK);
        currentPage = '6';
        dischargeData();
      }
    }  //end if

  }  // End Page 4

  // IR Reading
  if (currentPage == '5') {
    tft.setCursor(0, 60);
    tft.print("Cell 1");
    tft.setCursor(0, 80);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    if (numberCells == '2') {
      tft.print(internalResistanceAVGF - internalResistanceAVGP);
    } else if (numberCells == '1') {
      tft.print(internalResistanceAVGF);
    }
    tft.setCursor(80, 80);
    tft.print("m");
    tft.drawChar(90, 80, 233, ILI9341_WHITE, 2, 2);
    if (numberCells == '2') {
      tft.setCursor(0, 100);
      tft.print("Cell 2");
      tft.setCursor(0, 120);
      tft.print(internalResistanceAVGP);
      tft.setCursor(80, 120);
      tft.print("m");
      tft.drawChar(90, 120, 233, ILI9341_WHITE, 2, 2);
    }

    if (irRunning == true) {
      btnRC.initButtonR(90, 15, 140, 25, "Reading IR");
      tft.setCursor(0, 80);
      tft.fillRect(0, 40, 100, 100, ILI9341_BLACK);

      getIR();
    } else {
      tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
      btnRC.initButtonG(90, 15, 140, 25, "  Check IR  ");
    }
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }

      else if (btnRC.isClicked(sp)) {
        irRunning = true;
      }
    }
  }  // End Page 5

  if (currentPage == '6') {
    getCalculations();
    getVTime();
    tft.setTextSize(1);
    tft.setTextColor(ILI9341_ORANGE);
    tft.setCursor(25, 45);
    tft.print(voltageFull);
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      } else if (btnRPD.isClicked(sp)) {
        currentPage = '4';  // Go to Run Program
        getCalculations();
        tft.fillScreen(ILI9341_BLACK);
        program();
      }
    }
  }  // End Page 6

  delay(0);
}
//===========================================END LOOP=============================================
// Home Screen CurrentPage=0
void Home() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(50, 0);
  tft.setTextColor(0x07FF);
  tft.setTextSize(4);
  tft.print("MAIN MENU");
  tft.setTextColor(0x07FF);
  tft.setTextSize(3);
  tft.setCursor(0, 40);
  tft.print("1.");
  tft.setCursor(0, 80);
  tft.print("2.");
  tft.setCursor(0, 120);
  tft.print("3.");
  tft.setCursor(0, 160);
  tft.print("4.");
  tft.setCursor(0, 200);
  tft.print("5.");
  btnMV.initButtonG(50, 40, 200, 25, "Discharge Rate");
  btnMI.initButtonG(50, 80, 200, 25, "Voltage Cutoff");
  btnCT.initButtonG(50, 120, 200, 25, "Calibrate Screen");
  btnRP.initButtonG(50, 160, 200, 25, "Run Discharge");
  btnIR.initButtonG(50, 200, 200, 25, "IR Reading");
  // add button for getIR function page, adjust button heights to fit, or make 2 colums and adjust width...
}
// Discharge Current Selection CurrentPage=1
void discharge() {
  btnIM.initButtonR(80, 75, 80, 60, "-");
  btnIP.initButtonG(160, 75, 80, 60, "+");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
  tft.setCursor(50, 0);
  tft.setTextSize(4);
  tft.setTextColor(ILI9341_YELLOW);
  tft.print("DISCHARGE");
  tft.setCursor(70, 40);
  tft.print("CURRENT");
  tft.setCursor(100, 150);
  tft.print(IV, 0);
  tft.setCursor(190, 150);
  tft.print("A");
}  // End discharge

// Cutoff Voltage Selection CurrentPage=2
void cutoff() {

  btnIM.initButtonR(80, 75, 80, 60, "-");
  btnIP.initButtonG(160, 75, 80, 60, "+");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
  tft.setCursor(80, 0);
  tft.setTextSize(4);
  tft.setTextColor(ILI9341_YELLOW);
  tft.print("VOLTAGE");
  tft.setCursor(95, 40);
  tft.print("CUTOFF");
}  // End Cutoff

// Run Program CurrentPage=4
void program() {
  btnPN.initButtonG(0, 15, 140, 25, "    RUN");
  btnMN.initButtonB(90, 200, 140, 25, " Main Menu");
  btnRN.initButtonR(160, 15, 140, 25, "   STOP   ");
  btnDD.initButtonY(90, 150, 140, 25, "   DATA   ");
}  // End Program

// IR Reading CurrentPage=5
void irReading() {
  btnRC.initButtonG(90, 15, 140, 25, "  Check IR  ");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
}  // End IR Reading

void dischargeData() {
  tft.setTextSize(1);
  tft.setTextColor(ILI9341_BLUE);
  tft.setCursor(0, 45);
  tft.print("000:");
  tft.setCursor(55, 45);
  tft.print("015:");

  btnRPD.initButtonG(90, 15, 140, 25, " Discharge");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
}  // End dischargeData

void calibrateTouchScreen() {
  TS_Point p;
  int16_t x1, y1, x2, y2;

  tft.fillScreen(ILI9341_BLACK);
  // wait for no touch
  while (ts.touched())
    ;
  tft.drawFastHLine(10, 20, 20, ILI9341_RED);
  tft.drawFastVLine(20, 10, 20, ILI9341_RED);
  while (!ts.touched())
    ;
  delay(50);
  p = ts.getPoint();
  x1 = p.x;
  y1 = p.y;
  tft.drawFastHLine(10, 20, 20, ILI9341_BLACK);
  tft.drawFastVLine(20, 10, 20, ILI9341_BLACK);
  delay(500);
  while (ts.touched())
    ;
  tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_RED);
  tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_RED);
  while (!ts.touched())
    ;
  delay(50);
  p = ts.getPoint();
  x2 = p.x;
  y2 = p.y;
  tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_BLACK);
  tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_BLACK);

  int16_t xDist = tft.width() - 40;
  int16_t yDist = tft.height() - 40;

  // translate in form pos = m x val + c
  // x
  xCalM = (float)xDist / (float)(x2 - x1);
  xCalC = 20.0 - ((float)x1 * xCalM);
  // y
  yCalM = (float)yDist / (float)(y2 - y1);
  yCalC = 20.0 - ((float)y1 * yCalM);

  currentPage = '0';
  Home();
  /* // Serial print the actual coordinates from the touch calibrate, enter into the global variables, for first run of the screen
Serial.print("x1 = ");Serial.print(x1);
Serial.print(", y1 = ");Serial.print(y1);
Serial.print("x2 = ");Serial.print(x2);
Serial.print(", y2 = ");Serial.println(y2);
Serial.print("xCalM = ");Serial.print(xCalM);
Serial.print(", xCalC = ");Serial.print(xCalC);
Serial.print("yCalM = ");Serial.print(yCalM);
Serial.print(", yCalC = ");Serial.println(yCalC);
*/
}  // END Calibrate

//  Current Average
void getCurrentAverage() {
  int ii;
  double rawIRead;
  for (int ii = 0; ii < IN; ii++) {
    //analogRead(Isens);
    rawIRead += analogRead(Isens);
  }
  iavg = rawIRead / IN;
  Icorrected = (iavg - IOFF);
}  // end getCurrent

//   Full voltage average (2s voltage if a 2s battery or 1s voltage for a 1s battery)
void getFVoltageAverage() {
  int jj;
  float rawVFRead;
  for (int jj = 0; jj < VFN; jj++) {
    //analogRead(VFsens);
    rawVFRead += analogRead(VFsens);
  }
  vfavg = rawVFRead / VFN;
}  // end getFVoltage

//  Partial Voltage average (only applies for 2s, this is cell 2 voltage)
void getPVoltageAverage() {
  int kk;
  float rawVPRead;
  for (int kk = 0; kk < VPN; kk++) {
    // analogRead(VPsens);
    rawVPRead += analogRead(VPsens) + 1;
  }
  vpavg = rawVPRead / VPN;
}  //  end Partial Voltage

// Runs the main discharge function
void RunDischarge() {
  if (isrunning == true) {
    if (numberCells == '2') {
      if (cell1 > VV / 100 && cell2 > VV / 100) {
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
      }

      else if (cell1 < VV / 100 || cell2 < VV / 100) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      }
    }

    else if (numberCells == '1') {
      if (cell1 > VV / 100) {
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
      }

      else if (cell1 < VV / 100) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      }
    }
  }

}  // end runDischarge

void getCalculations() {
  getCurrentAverage();
  voltageI = Icorrected * (VCC / 1023);
  Icurrent = abs((voltageI - QOV) / sens);
  getPVoltageAverage();
  getFVoltageAverage();
  Pvoltage = vpavg * (VCC / 1023);
  Fvoltage = vfavg * (VCC / 1023);

  if (isrunning == true || irRunning == true) {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P) + (Icurrent / 250);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F) + (Icurrent / 250);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  } else {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  }
  if (voltageFull >= 5) {
    numberCells = '2';
  } else if (voltageFull < 5) {
    numberCells = '1';
  }
  VCC = VCCC / 1000;

  if (isrunning == true) {
    CurrentTime = (millis() - StartTime) / 1000;
    mahTime = (millis() - prevmahTime) / 1000;
  }
  if (CurrentTime > runTime) {
    runTime = CurrentTime;
  } else if (isrunning == false) {
    CurrentTime = 0;  
  }
  if (mahTime - prevmahTime >= 1) {
    mAH += (Icurrent / 3.6);
    prevmahTime = mahTime;
  }
  if (mAH > rTmAH) {
    rTmAH = mAH;
  }
}  // end Calculations

// calibrates the VCC using the bandgap ref.  This works and doesn't work.  Probably be better to add an external vref to the design
void getAccurateVoltage() {
  int vv;
  float rawgetVoltage = 0;
  for (vv = 0; vv < 100; vv++) {
    getVoltage();
    rawgetVoltage += getVoltage();
  }
  VCCC = rawgetVoltage / 100;
}
// Read the voltage of the battery the Arduino is currently running on (in millivolts)
float getVoltage() {
  const long InternalReferenceVoltage = 1080;  // Adjust this value to your boards specific internal BG voltage x1000
  ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
  ADCSRA |= _BV(ADSC);  // Start a conversion
  while (((ADCSRA & (1 << ADSC)) != 0))
    ;                                                               // Wait for it to complete
  float results = (((InternalReferenceVoltage * 1024) / ADC) + 5);  // Scale the value; calculates for straight line value
  return results;
}  // end VCC calibration

//Internal Resistance Measurement
void getIR() {
  if (isrunning == false) {
    irRunning = true;
    for (int chan = 0; chan < (numChan - 1); chan++) {
      tft.fillRect(0, 120, 130, 40, ILI9341_BLACK);
      getCalculations();
      SerialData();
      Channels[chan].startVoltageF = voltageFull - (Icurrent / 250);     // record unloaded voltage of battery
      Channels[chan].startVoltageP = voltagePartial - (Icurrent / 250);  // record unloaded voltage of battery
      zz = 75;                                                           // Will be appx 20A with 2s, may split this into if statements based on current battery voltage
      analogWrite(PWM, zz);                                              //Turn Mosfets on
      delay(250);                                                        //delay to stabalize - may need to fine tune this delay, but the shorter the better
      getCalculations();
      SerialData();
      Channels[chan].endVoltageF = voltageFull;                                                 // record voltage of battery under load
      Channels[chan].endVoltageP = voltagePartial;                                              // record voltage of battery under load
      zz = 0;                                                                                   //
      analogWrite(PWM, zz);                                                                     //Turn off the mosfets
      Channels[chan].voltageDropF = Channels[chan].startVoltageF - Channels[chan].endVoltageF;  // Voltage drop from the load
      Channels[chan].voltageDropP = Channels[chan].startVoltageP - Channels[chan].endVoltageP;  // Voltage drop from the load
      Channels[chan].internalResistanceF = (Channels[chan].voltageDropF / Icurrent) * 1000;     //Ohms Law V=IR, R=V/I, Readings in mΩ
      Channels[chan].internalResistanceP = (Channels[chan].voltageDropP / Icurrent) * 1000;     //Ohms Law V=IR, R=V/I, Readings in mΩ
      delay(500);                                                                               // Allow for stablization between readings - may need to fine tune this delay, but start on the high side
    }
    for (int chan = 0; chan < (numChan - 1); chan++) {
      internalResistanceAVGF = +Channels[chan].internalResistanceF;  // run the readings numChan times and average the IR
      internalResistanceAVGP = +Channels[chan].internalResistanceP;  // run the readings numChan times and average the IR
    }
  }  //End if
  irRunning = false;
}  //end getIR

void getVTime() {
  intervalTime = millis() - previntervalTime / 1000;
  if (intervalTime >= 15) {
    ChannelsT[chanT].vTime = voltageFull;
    previntervalTime = intervalTime;
    chanT++;
  }
}

void calibrateQOV() {
  analogRead(Isens);
  QOV = (analogRead(Isens) * VCC) / 1023;
}

void SerialData() {
  Serial.println("----------------------------------------");
  Serial.println();
  Serial.print(VCC);
  Serial.print("V VCC ");
  Serial.print(QOV);
  Serial.print(" QOV ");
  Serial.println();
  Serial.print(analogRead(Isens));
  Serial.print(" RAW Read ");
  Serial.print(Icorrected);
  Serial.print(" Corrected ");
  Serial.print(voltageI);
  Serial.print("V ");
  Serial.print(Icurrent);
  Serial.print(" Amps ");
  Serial.print(iavg);
  Serial.print(" AVG ");
  Serial.print(Output);
  Serial.println();
  Serial.print(voltageFull);
  Serial.print(" V Full  ");
  Serial.print(vfavg);
  Serial.print("  ACT  ");
  Serial.print(analogRead(VFsens));
  Serial.print(" ");
  Serial.println();
  Serial.print(voltagePartial);
  Serial.print("   ");
  Serial.print(" V Partial   ");
  Serial.print(vpavg);
  Serial.print("   ACT  ");
  Serial.print(analogRead(VPsens));
  Serial.println();
  Serial.print("Cell 1 ");
  Serial.print(cell1, 3);
  Serial.print("V   Cell 2 ");
  Serial.print(cell2, 3);
  Serial.print("V");
  Serial.println();
  Serial.println(ChannelsT[chanT].vTime);
}

Thank you, I will revise my code to use an array for vTime and not a struct. I wasn't sure if I would be adding another variable to go in the struct or not.

Thank you, I will consider and work out how to implement into my code.

Just criticizing beginner programmers only makes them defensive. I specifically said I was trying not to make forum members sift through my beginner code.

But go ahead, put your money where your mouth is. My code is posted now. Make it 300 lines without losing any features.

1 Like

I tried to implement the suggested code.
This reported that the array was full after about 30 seconds and froze the rest of the code..
It output the variables once the array was full.

/*
    Code Version 1.0
    WMH Racing Battery Wizard
    Written by Andrew Sarratore
    Date: 10/28/2023
    Code Version 1.1
    Add getCurrentAverage()
    add getFVoltageAverage()
    add getCalculations()
    add Program()
    Changes to the "Run Program" Menu
    Date:12/11/2023
    Code Version 1.2
    add getPVoltageAverage
    add more readings to the dishcharge display
    Code Version 1.3
    Date:12/27/2023
    add self calibrate VCC
    add mAH reading, untested
    add PID_v1.h library, still need to write PID code for the PWM value and replace current feedback loop
    Code Version 1.4
    Date:12/30/2023
    Add IR struct - Untested
    Add getIR function - still need to write the menu and page function
    Code Version 1.4.2
    Date:1/4/2024
    Calibrate initial QOV based on VCC during setup
    Fixed averages to be floats instead of int...
    Adding IR Reading menus
    Code Version 1.5
    PID added, Kp=0.25, Ki=10, Kd=0
    Added page 6 for displaying voltage data
Date:1/15/24
Code Version 1.6
Changed where the code calls the calculations and true VCC functions from.  This made other parts of the loop faster and vcc more accurate
Added in a multiplier to voltage under load.  (Icurrent/1000)*4.  I can simplify to Icurrent/250.  
This fixes voltage under load readings at various discharge rates.  May change for each unit
Date:1/16/24
Code Version 1.7
Adding 1S support
Date:1/17/24
Code Version 1.8
Adding Struct for Time/Voltage variable
Filling the page for the Time/Voltage variable  vTime
   
    Voltage values every 15 seconds - look into graphing this, would be cool.  
   
   
*/

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_Touchscreen.h"
#include "Math.h"
#include "PID_v1.h"
#include "movingAvg.h"

// Define SPI pins for both display and touch
#define TFT_CS 10
#define TFT_DC 9
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
#define TS_CS 7
#define ROTATION 1
#define Isens A0
#define VFsens A1
#define VPsens A2
#define PWM 3
#define K (1.0 / 30)

char currentPage;
char numberCells;

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC/RST
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
XPT2046_Touchscreen ts(TS_CS);

// calibration values
float xCalM = -0.09, yCalM = -0.06;    // gradients
float xCalC = 329.36, yCalC = 248.13;  // y axis crossing points

int8_t blockWidth = 20;  // block size
int8_t blockHeight = 20;
int16_t blockX = 0, blockY = 0;  // block position (pixels)

class ScreenPoint {
public:
  int16_t x;
  int16_t y;

  // default constructor
  ScreenPoint() {
  }

  ScreenPoint(int16_t xIn, int16_t yIn) {
    x = xIn;
    y = yIn;
  }
};

class Button {
public:

  int x;
  int y;
  int width;
  int height;
  char *text;

  Button() {
  }

  void initButtonG(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderG();
  }
  void initButtonR(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderR();
  }
  void initButtonB(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderB();
  }
  void initButtonY(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderY();
  }
  void renderG() {
    tft.fillRect(x, y, width, height, ILI9341_GREEN);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderR() {
    tft.fillRect(x, y, width, height, ILI9341_RED);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderB() {
    tft.fillRect(x, y, width, height, ILI9341_BLUE);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderY() {
    tft.fillRect(x, y, width, height, ILI9341_YELLOW);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  bool isClicked(ScreenPoint sp) {
    if ((sp.x >= x) && (sp.x <= (x + width)) && (sp.y >= y) && (sp.y <= (y + height))) {
      return true;
    } else {
      return false;
    }
  }
};

// Struct for getIR
unsigned const int numChan = 5;
unsigned int chan;
struct ChannelData {
  float startVoltageP;
  float startVoltageF;
  float endVoltageP;
  float endVoltageF;
  float voltageDropP;
  float voltageDropF;
  float internalResistanceP;
  float internalResistanceF;
};
ChannelData Channels[numChan];

/*
unsigned const int numChanT = 40;
unsigned int chanT;
struct voltageTime {
  float vTime;
};
voltageTime ChannelsT[numChanT];
*/
constexpr long Interval {15000};
float vTime[40];
constexpr int SizeOfArray {sizeof(vTime) / sizeof(vTime[0])};
int measureCounter = 0;

//Touch Screen
ScreenPoint getScreenCoords(int16_t x, int16_t y) {
  int16_t xCoord = round((x * xCalM) + xCalC);
  int16_t yCoord = round((y * yCalM) + yCalC);
  if (xCoord < 0) xCoord = 0;
  if (xCoord >= tft.width()) xCoord = tft.width() - 1;
  if (yCoord < 0) yCoord = 0;
  if (yCoord >= tft.height()) yCoord = tft.height() - 1;
  return (ScreenPoint(xCoord, yCoord));
}

// I can probably make an array of the following button variables...
Button btnIP;  // Current Selection +
Button btnIM;  // Current Selection -
Button btnVP;  // Cutoff V +
Button btnVM;  // Cutoff V -
Button btnMI;  // Main - Discharge Rate
Button btnMV;  // Main - Voltage Cutoff
Button btnMM;  // Main Menu on other pages
Button btnCT;  // Main Calibrate Touch
Button btnRP;  // Main Menu - Run Program
Button btnPN;  // Run/Running
Button btnMN;  // Discharge - Main Menu
Button btnRN;  // STOP
Button btnIR;  // Main - IR Reading
Button btnRC;  // IR Reading - Check IR
Button btnDD;  // Discharge - dischargeData
Button btnRPD;

double IV = 30;                  // Initial rate of discharge in ampres.  User changable in code
float VV = 350.00;               // Cutoff voltage*100 so ++ works to change the value
unsigned long StartTime;         // Start time to measure time of discharge
unsigned long CurrentTime;       // Will give how long the discharge is running
unsigned long mahTime;           // used for mAH
unsigned long prevmahTime;       // used for mAH
unsigned long intervalTime;      //used for vTime data
unsigned long previntervalTime;  //used for vTime data
unsigned long runTime;           // runTime of the discharge function
unsigned long lastFrame;         // used for screen flickering

//PID
double Input;
double Output;
//Specify the links and initial tuning parameters
double Kp = .01, Ki = 15, Kd = 0;
PID myPID(&Input, &Output, &IV, Kp, Ki, Kd, DIRECT);

// Current Variables
float VCC = 0;          // 4.967  measure the value(Measure value not needed with the VCC calibration code)
float VCCC = 0;         // Used for Accurate Voltage
float QOV = 0;          // Calibration happens in setup
float sens = 0.04;      //sensitivity of the current sensor
float iavg;             // analogRead(Isens)/IN
float Icurrent = 0;     // Current Reading in Amps
float Icorrected = 0;   //iavg-IOFF
float voltageI = 0;     // iavg translated to voltage
float IOFF = 0.6;       // Offset to the raw read to zero current
int IN = 1000;          // Sample number to average current
float mAH;              // mAH used
float rTmAH;            // Runtime mAH used, highest value

// Battery Voltage Variables (may be able to make a struct or class here as well)
int ValueR1F = 5094;       //Measured value
int ValueR2F = 5104;       //Measured value
int ValueR1P = 5106;       //Measured value
int ValueR2P = 5082;       //Measured value
int VFN = 1000;            // Sample number for average of analogRead(VFsens) Full voltage
int VPN = 1000;            // Sample number for average of analogRead(VPsens) Partial voltage
float vfavg;               // Average of the analogRead(VFsens)/VFN      -Full Voltage
float vpavg;               // Average of the analogRead(VPsens)/VPN      -Partial Voltage
float Fvoltage = 0;        // Calculation of FV seen by MCU
float Pvoltage = 0;        // Calculation of PV seen by MCU
float voltageFull = 0;     // Calculation of Full Voltage
float voltagePartial = 0;  // Calculation of Partial Voltage
float cell1 = 0;           // Voltage of Cell 1
float cell2 = 0;           // Voltage of Cell 2
float internalResistanceAVGP = 0;
float internalResistanceAVGF = 0;
boolean isrunning = false;
boolean irRunning = false;
int zz = 0;  // variable used for analogWrite(PWM,zz)

//===================================================================SETUP=========================================================================================
void setup() {
  Serial.begin(115200);
  // avoid chip select contention
  pinMode(TS_CS, OUTPUT);
  digitalWrite(TS_CS, HIGH);
  pinMode(TFT_CS, OUTPUT);
  pinMode(Isens, INPUT);
  pinMode(VFsens, INPUT);
  pinMode(VPsens, INPUT);
  pinMode(PWM, OUTPUT);
  digitalWrite(TFT_CS, HIGH);
  tft.begin();
  tft.setRotation(ROTATION);
  tft.fillScreen(ILI9341_BLACK);
  ts.begin();
  ts.setRotation(ROTATION);
  //calibrateTouchScreen(); Leaving here for first run of the dispaly to get coordinates of touch screen
  getAccurateVoltage();
  getCurrentAverage();
  getFVoltageAverage();
  getPVoltageAverage();
  getCalculations();
  calibrateQOV();
  lastFrame=millis();
  //initialize the variables we're linked to
  Input = Icurrent;
  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  currentPage = '0';  //  Indicates that we are at Home Screen
  Home();
}//==================================================================END SETUP=====================================================================

//======================================================================LOOP======================================================================
void loop() {
  ScreenPoint sp;
  // limit frame rate
  //while((millis() - lastFrame) < 20); //(caused issues with millis function to get mAH, may need to revisit as this helped some flickering)
  //lastFrame = millis();

  // Home Screen
  if (currentPage == '0') {

    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMV.isClicked(sp)) {
        currentPage = '1';  // Go to Discharge Current
        tft.fillScreen(ILI9341_BLACK);
        discharge();
      } else if (btnMI.isClicked(sp)) {
        currentPage = '2';  // Go to Cutoff Voltage
        tft.fillScreen(ILI9341_BLACK);
        cutoff();
      }

      else if (btnCT.isClicked(sp)) {
        currentPage = '3';  // Go to Calibrate Screen
        tft.fillScreen(ILI9341_BLACK);
        calibrateTouchScreen();
      }

      else if (btnRP.isClicked(sp)) {
        currentPage = '4';  // Go to Run Program
        getCalculations();
        tft.fillScreen(ILI9341_BLACK);
        program();
      }

      else if (btnIR.isClicked(sp)) {
        currentPage = '5';  // Go to irReading
        tft.fillScreen(ILI9341_BLACK);
        irReading();
      }
    }
  }

  // Discharge Current
  if (currentPage == '1') {
    tft.setCursor(100, 150);
    tft.print(IV, 0);
    tft.setCursor(190, 150);
    tft.print("A");
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnIP.isClicked(sp)) {
        if (IV < 45) {
          IV++;
        }
        tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnIM.isClicked(sp)) {
        IV--;
        tft.fillRect(100, 150, 125, 50, ILI9341_BLACK);
        delay(100);

      } else if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }
    }
  }  // End Page 1

  // Cutoff Voltage
  if (currentPage == '2') {
    tft.setCursor(100, 150);
    tft.print(VV / 100);
    tft.setCursor(210, 150);
    tft.print("V");
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnIP.isClicked(sp)) {
        VV++;
        tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnIM.isClicked(sp)) {
        if (VV > 280) {
          VV--;
        }
        tft.fillRect(100, 150, 100, 50, ILI9341_BLACK);
        delay(100);
      } else if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }
    }
  }  // End Page 2

  // Run Discharge (Surely I can move some of this to the page function, but when moved it caused the screen to refresh every loop)
  if (currentPage == '4') {
    getAccurateVoltage();
    getCalculations();
    getVTime();
    SerialData();
    tft.setCursor(0, 50);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    tft.print("Battery:");
    tft.setCursor(0, 75);
    tft.print("Cutoff ");
    tft.print(VV / 100);
    tft.print("V");
    tft.setTextSize(2);
    tft.setCursor(0, 100);
    tft.print("Actual ");
    tft.print(voltageFull, 2);
    tft.setCursor(145, 100);
    tft.print("V");
    tft.setCursor(160, 50);
    tft.print("Current:");
    tft.setCursor(160, 75);
    tft.print("Rate ");
    tft.print(IV, 0);
    tft.print("A");
    tft.setCursor(160, 100);
    tft.print("Actual ");
    tft.print(Icurrent, 1);
    tft.setCursor(305, 100);
    tft.print("A");
    tft.setTextSize(1);
    tft.setCursor(160, 120);
    tft.print("Time");
    tft.setCursor(160, 130);
    tft.fillRect(160, 130, 50, 10, ILI9341_BLACK);
    tft.print(runTime);
    tft.setCursor(240, 120);
    tft.print("mAH");
    tft.setCursor(240, 130);
    tft.print(rTmAH, 0);
    tft.setCursor(0, 150);
    tft.print("VCC");
    tft.setCursor(0, 160);
    tft.print(VCC, 3);

    if (voltageFull > 5) {
      tft.setTextSize(1);
      tft.setCursor(0, 120);
      tft.print("Cell 1  ");
      tft.setCursor(45, 120);
      tft.print(cell1, 3);
      tft.setCursor(80, 120);
      tft.print("V");
      tft.setCursor(0, 130);
      tft.print("Cell 2  ");
      tft.setCursor(45, 130);
      tft.print(cell2, 3);
      tft.setCursor(80, 130);
      tft.print("V");
    }  //

    else if (voltageFull < 5) {
      tft.setTextSize(1);
      tft.setCursor(0, 120);
      tft.print("Cell 1  ");
      tft.setCursor(45, 120);
      tft.print(cell1, 3);
      tft.setCursor(80, 120);
      tft.print("V");
      tft.setCursor(0, 130);
    }  //end if

    if (isrunning == true) {
      btnPN.initButtonR(0, 15, 140, 25, "DISCHARGE");
      RunDischarge();
    } else {
      tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
      btnPN.initButtonG(0, 15, 140, 25, "    RUN");
    }
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMN.isClicked(sp)) {
        currentPage = '0';
        Home();
      } else if (btnRN.isClicked(sp)) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      } else if (btnPN.isClicked(sp)) {
        tft.fillRect(160, 130, 320, 10, ILI9341_BLACK);
        rTmAH = 0;
        runTime = 0;
        StartTime = millis();
        prevmahTime = millis();
        previntervalTime = millis();
        isrunning = true;

      } else if (btnDD.isClicked(sp)) {
        tft.fillScreen(ILI9341_BLACK);
        currentPage = '6';
        dischargeData();
      }
    }  //end if

  }  // End Page 4

  // IR Reading
  if (currentPage == '5') {
    tft.setCursor(0, 60);
    tft.print("Cell 1");
    tft.setCursor(0, 80);
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
    if (numberCells == '2') {
      tft.print(internalResistanceAVGF - internalResistanceAVGP);
    } else if (numberCells == '1') {
      tft.print(internalResistanceAVGF);
    }
    tft.setCursor(80, 80);
    tft.print("m");
    tft.drawChar(90, 80, 233, ILI9341_WHITE, 2, 2);
    if (numberCells == '2') {
      tft.setCursor(0, 100);
      tft.print("Cell 2");
      tft.setCursor(0, 120);
      tft.print(internalResistanceAVGP);
      tft.setCursor(80, 120);
      tft.print("m");
      tft.drawChar(90, 120, 233, ILI9341_WHITE, 2, 2);
    }

    if (irRunning == true) {
      btnRC.initButtonR(90, 15, 140, 25, "Reading IR");
      tft.setCursor(0, 80);
      tft.fillRect(0, 40, 100, 100, ILI9341_BLACK);

      getIR();
    } else {
      tft.setTextColor(ILI9341_WHITE, ILI9341_GREEN);
      btnRC.initButtonG(90, 15, 140, 25, "  Check IR  ");
    }
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      }

      else if (btnRC.isClicked(sp)) {
        irRunning = true;
      }
    }
  }  // End Page 5

  if (currentPage == '6') {
    getVTime();
    tft.setTextSize(1);
    tft.setTextColor(ILI9341_ORANGE);
    tft.setCursor(25, 45);
    tft.print(voltageFull);
    if (ts.touched()) {
      TS_Point p = ts.getPoint();
      sp = getScreenCoords(p.x, p.y);
      if (btnMM.isClicked(sp)) {
        currentPage = '0';
        Home();
      } else if (btnRPD.isClicked(sp)) {
        currentPage = '4';  // Go to Run Program
        getCalculations();
        tft.fillScreen(ILI9341_BLACK);
        program();
      }
    }
  }  // End Page 6

  delay(0);
}
//===========================================END LOOP=============================================
// Home Screen CurrentPage=0
void Home() {
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(50, 0);
  tft.setTextColor(0x07FF);
  tft.setTextSize(4);
  tft.print("MAIN MENU");
  tft.setTextColor(0x07FF);
  tft.setTextSize(3);
  tft.setCursor(0, 40);
  tft.print("1.");
  tft.setCursor(0, 80);
  tft.print("2.");
  tft.setCursor(0, 120);
  tft.print("3.");
  tft.setCursor(0, 160);
  tft.print("4.");
  tft.setCursor(0, 200);
  tft.print("5.");
  btnMV.initButtonG(50, 40, 200, 25, "Discharge Rate");
  btnMI.initButtonG(50, 80, 200, 25, "Voltage Cutoff");
  btnCT.initButtonG(50, 120, 200, 25, "Calibrate Screen");
  btnRP.initButtonG(50, 160, 200, 25, "Run Discharge");
  btnIR.initButtonG(50, 200, 200, 25, "IR Reading");
}
// Discharge Current Selection CurrentPage=1
void discharge() {
  btnIM.initButtonR(80, 75, 80, 60, "-");
  btnIP.initButtonG(160, 75, 80, 60, "+");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
  tft.setCursor(50, 0);
  tft.setTextSize(4);
  tft.setTextColor(ILI9341_YELLOW);
  tft.print("DISCHARGE");
  tft.setCursor(70, 40);
  tft.print("CURRENT");
  tft.setCursor(100, 150);
  tft.print(IV, 0);
  tft.setCursor(190, 150);
  tft.print("A");
}  // End discharge

// Cutoff Voltage Selection CurrentPage=2
void cutoff() {

  btnIM.initButtonR(80, 75, 80, 60, "-");
  btnIP.initButtonG(160, 75, 80, 60, "+");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
  tft.setCursor(80, 0);
  tft.setTextSize(4);
  tft.setTextColor(ILI9341_YELLOW);
  tft.print("VOLTAGE");
  tft.setCursor(95, 40);
  tft.print("CUTOFF");
}  // End Cutoff

// Run Program CurrentPage=4
void program() {
  btnPN.initButtonG(0, 15, 140, 25, "    RUN");
  btnMN.initButtonB(90, 200, 140, 25, " Main Menu");
  btnRN.initButtonR(160, 15, 140, 25, "   STOP   ");
  btnDD.initButtonY(90, 150, 140, 25, "   DATA   ");
}  // End Program

// IR Reading CurrentPage=5
void irReading() {
  btnRC.initButtonG(90, 15, 140, 25, "  Check IR  ");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
}  // End IR Reading

void dischargeData() {
  tft.setTextSize(1);
  tft.setTextColor(ILI9341_BLUE);
  tft.setCursor(0, 45);
  tft.print("000:");
  tft.setCursor(55, 45);
  tft.print("015:");

  btnRPD.initButtonG(90, 15, 140, 25, " Discharge");
  btnMM.initButtonB(90, 200, 140, 25, " Main Menu");
}  // End dischargeData

void calibrateTouchScreen() {
  TS_Point p;
  int16_t x1, y1, x2, y2;

  tft.fillScreen(ILI9341_BLACK);
  // wait for no touch
  while (ts.touched())
    ;
  tft.drawFastHLine(10, 20, 20, ILI9341_RED);
  tft.drawFastVLine(20, 10, 20, ILI9341_RED);
  while (!ts.touched())
    ;
  delay(50);
  p = ts.getPoint();
  x1 = p.x;
  y1 = p.y;
  tft.drawFastHLine(10, 20, 20, ILI9341_BLACK);
  tft.drawFastVLine(20, 10, 20, ILI9341_BLACK);
  delay(500);
  while (ts.touched())
    ;
  tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_RED);
  tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_RED);
  while (!ts.touched())
    ;
  delay(50);
  p = ts.getPoint();
  x2 = p.x;
  y2 = p.y;
  tft.drawFastHLine(tft.width() - 30, tft.height() - 20, 20, ILI9341_BLACK);
  tft.drawFastVLine(tft.width() - 20, tft.height() - 30, 20, ILI9341_BLACK);

  int16_t xDist = tft.width() - 40;
  int16_t yDist = tft.height() - 40;

  // translate in form pos = m x val + c
  // x
  xCalM = (float)xDist / (float)(x2 - x1);
  xCalC = 20.0 - ((float)x1 * xCalM);
  // y
  yCalM = (float)yDist / (float)(y2 - y1);
  yCalC = 20.0 - ((float)y1 * yCalM);

  currentPage = '0';
  Home();
  /* // Serial print the actual coordinates from the touch calibrate, enter into the global variables, for first run of the screen
Serial.print("x1 = ");Serial.print(x1);
Serial.print(", y1 = ");Serial.print(y1);
Serial.print("x2 = ");Serial.print(x2);
Serial.print(", y2 = ");Serial.println(y2);
Serial.print("xCalM = ");Serial.print(xCalM);
Serial.print(", xCalC = ");Serial.print(xCalC);
Serial.print("yCalM = ");Serial.print(yCalM);
Serial.print(", yCalC = ");Serial.println(yCalC);
*/
}  // END Calibrate

//  Current Average
void getCurrentAverage() {
  int ii;
  double rawIRead;
  for (int ii = 0; ii < IN; ii++) {
    //analogRead(Isens);
    rawIRead += analogRead(Isens);
  }
  iavg = rawIRead / IN;
  Icorrected = (iavg - IOFF);
}  // end getCurrent

//   Full voltage average (2s voltage if a 2s battery or 1s voltage for a 1s battery)
void getFVoltageAverage() {
  int jj;
  float rawVFRead;
  for (int jj = 0; jj < VFN; jj++) {
    //analogRead(VFsens);
    rawVFRead += analogRead(VFsens);
  }
  vfavg = rawVFRead / VFN;
}  // end getFVoltage

//  Partial Voltage average (only applies for 2s, this is cell 2 voltage)
void getPVoltageAverage() {
  int kk;
  float rawVPRead;
  for (int kk = 0; kk < VPN; kk++) {
    // analogRead(VPsens);
    rawVPRead += analogRead(VPsens) + 1;
  }
  vpavg = rawVPRead / VPN;
}  //  end Partial Voltage

// Runs the main discharge function
void RunDischarge() {
  if (isrunning == true) {
    if (numberCells == '2') {
      if (cell1 > VV / 100 && cell2 > VV / 100) {
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
      }

      else if (cell1 < VV / 100 || cell2 < VV / 100) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      }
    }

    else if (numberCells == '1') {
      if (cell1 > VV / 100) {
        Input = Icurrent;
        myPID.Compute();
        analogWrite(PWM, Output);
      }

      else if (cell1 < VV / 100) {
        zz = 0;
        analogWrite(PWM, zz);
        isrunning = false;
      }
    }
  }

}  // end runDischarge

void getCalculations() {
  getCurrentAverage();
  voltageI = Icorrected * (VCC / 1023);
  Icurrent = abs((voltageI - QOV) / sens);
  getPVoltageAverage();
  getFVoltageAverage();
  Pvoltage = vpavg * (VCC / 1023);
  Fvoltage = vfavg * (VCC / 1023);

  if (isrunning == true || irRunning == true) {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P) + (Icurrent / 250);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F) + (Icurrent / 250);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  } else {
    voltagePartial = ((Pvoltage * (ValueR1P + ValueR2P)) / ValueR2P);
    voltageFull = ((Fvoltage * (ValueR1F + ValueR2F)) / ValueR2F);
    if (numberCells == '2') {
      cell1 = voltageFull - voltagePartial;
      cell2 = voltagePartial;
    } else if (numberCells == '1') {
      cell1 = voltageFull;
    }
  }
  if (voltageFull >= 5) {
    numberCells = '2';
  } else if (voltageFull < 5) {
    numberCells = '1';
  }
  VCC = VCCC / 1000;

  if (isrunning == true) {
    CurrentTime = (millis() - StartTime) / 1000;
    mahTime = (millis() - prevmahTime) / 1000;
  }
  if (CurrentTime > runTime) {
    runTime = CurrentTime;
  } else if (isrunning == false) {
    CurrentTime = 0;  
  }
  if (mahTime - prevmahTime >= 1) {
    mAH += (Icurrent / 3.6);
    prevmahTime = mahTime;
  }
  if (mAH > rTmAH) {
    rTmAH = mAH;
  }
}  // end Calculations

// calibrates the VCC using the bandgap ref.  This works and doesn't work.  Probably be better to add an external vref to the design
void getAccurateVoltage() {
  int vv;
  float rawgetVoltage = 0;
  for (vv = 0; vv < 100; vv++) {
    getVoltage();
    rawgetVoltage += getVoltage();
  }
  VCCC = rawgetVoltage / 100;
}
// Read the voltage of the battery the Arduino is currently running on (in millivolts)
float getVoltage() {
  const long InternalReferenceVoltage = 1080;  // Adjust this value to your boards specific internal BG voltage x1000
  ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
  ADCSRA |= _BV(ADSC);  // Start a conversion
  while (((ADCSRA & (1 << ADSC)) != 0))
    ;                                                               // Wait for it to complete
  float results = (((InternalReferenceVoltage * 1024) / ADC) + 5);  // Scale the value; calculates for straight line value
  return results;
}  // end VCC calibration

//Internal Resistance Measurement
void getIR() {
  if (isrunning == false) {
    irRunning = true;
    for (int chan = 0; chan < (numChan - 1); chan++) {
      tft.fillRect(0, 120, 130, 40, ILI9341_BLACK);
      getCalculations();
      SerialData();
      Channels[chan].startVoltageF = voltageFull - (Icurrent / 250);     // record unloaded voltage of battery
      Channels[chan].startVoltageP = voltagePartial - (Icurrent / 250);  // record unloaded voltage of battery
      zz = 75;                                                           // Will be appx 20A with 2s
      analogWrite(PWM, zz);                                              //Turn Mosfets on
      delay(250);                                                        //delay to stabalize - may need to fine tune this delay, but the shorter the better
      getCalculations();
      SerialData();
      Channels[chan].endVoltageF = voltageFull;                                                 // record voltage of battery under load
      Channels[chan].endVoltageP = voltagePartial;                                              // record voltage of battery under load
      zz = 0;                                                                                   //
      analogWrite(PWM, zz);                                                                     //Turn off the mosfets
      Channels[chan].voltageDropF = Channels[chan].startVoltageF - Channels[chan].endVoltageF;  // Voltage drop from the load
      Channels[chan].voltageDropP = Channels[chan].startVoltageP - Channels[chan].endVoltageP;  // Voltage drop from the load
      Channels[chan].internalResistanceF = (Channels[chan].voltageDropF / Icurrent) * 1000;     //Ohms Law V=IR, R=V/I, Readings in mΩ
      Channels[chan].internalResistanceP = (Channels[chan].voltageDropP / Icurrent) * 1000;     //Ohms Law V=IR, R=V/I, Readings in mΩ
      delay(500);                                                                               // Allow for stablization between readings - may need to fine tune this delay, but start on the high side
    }
    for (int chan = 0; chan < (numChan - 1); chan++) {
      internalResistanceAVGF = +Channels[chan].internalResistanceF;  // run the readings numChan times and average the IR
      internalResistanceAVGP = +Channels[chan].internalResistanceP;  // run the readings numChan times and average the IR
    }
  }  //End if
  irRunning = false;
}  //end getIR

void getVTime() {
  //getCalculations();
  intervalTime = millis() - previntervalTime;
  if (intervalTime >= Interval) {
    previntervalTime = intervalTime;
    Serial.print ("measure: ");
Serial.print(voltageFull);
vTime[measureCounter++] = voltageFull;;
if ((measureCounter % SizeOfArray) == 0) {
Serial.println("\narray is filled");
for (auto vTime_ : vTime) Serial.println(vTime_);
while(1);
    }
  }
}
void calibrateQOV() {
  analogRead(Isens);
  QOV = (analogRead(Isens) * VCC) / 1023;
}

void SerialData() {
  Serial.println("----------------------------------------");
  Serial.println();
  Serial.print(VCC);
  Serial.print("V VCC ");
  Serial.print(QOV);
  Serial.print(" QOV ");
  Serial.println();
  Serial.print(analogRead(Isens));
  Serial.print(" RAW Read ");
  Serial.print(Icorrected);
  Serial.print(" Corrected ");
  Serial.print(voltageI);
  Serial.print("V ");
  Serial.print(Icurrent);
  Serial.print(" Amps ");
  Serial.print(iavg);
  Serial.print(" AVG ");
  Serial.print(Output);
  Serial.println();
  Serial.print(voltageFull);
  Serial.print(" V Full  ");
  Serial.print(vfavg);
  Serial.print("  ACT  ");
  Serial.print(analogRead(VFsens));
  Serial.print(" ");
  Serial.println();
  Serial.print(voltagePartial);
  Serial.print("   ");
  Serial.print(" V Partial   ");
  Serial.print(vpavg);
  Serial.print("   ACT  ");
  Serial.print(analogRead(VPsens));
  Serial.println();
  Serial.print("Cell 1 ");
  Serial.print(cell1, 3);
  Serial.print("V   Cell 2 ");
  Serial.print(cell2, 3);
  Serial.print("V");
  Serial.println();
}

I didn't offer to do that, I said the forum can help you to do that.

Seeing the code in full, it is less repetitive than I expected, so probably reducing it to 300 lines was overoptimistic of me. But there is certainly plenty of repetition that could be removed. Here are some that I spotted. I'm sure other forum members can chip in with other ideas.

These 2 methods, initButtonG() and initButtonR(), and several more, are almost exact duplicates:

  void initButtonG(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderG();
  }
  void initButtonR(int xPos, int yPos, int butWidth, int butHeight, char *butText) {
    x = xPos;
    y = yPos;
    width = butWidth;
    height = butHeight;
    text = butText;
    renderR();
  }

The only difference is that one calls renderG() and the other calls renderR(). If we look at those functions, we see they are also almost exact duplicates:

void renderG() {
    tft.fillRect(x, y, width, height, ILI9341_GREEN);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }
  void renderR() {
    tft.fillRect(x, y, width, height, ILI9341_RED);
    tft.setCursor(x + 5, y + 5);
    tft.setTextSize(2);
    tft.setTextColor(ILI9341_WHITE);
    tft.print(text);
  }

The only difference is that one uses the constant ILI9341_GREEN and the other uses ILI9341_RED.

If you made the colour an additional parameter of a single initButton() function and a single render() function, that would remove a lot of duplication.

There is a constrain() function you could use here:

  if (xCoord < 0) xCoord = 0;
  if (xCoord >= tft.width()) xCoord = tft.width() - 1;

Probably above all, there are many, many similar groups of code lines to this throughout the code:

      tft.setTextSize(1);
      tft.setCursor(0, 120);
      tft.print("Cell 1  ");
      tft.setCursor(45, 120);
      tft.print(cell1, 3);
      tft.setCursor(80, 120);
      tft.print("V");

which could reduce to:

      tftPrintValue(0, "Cell 1  ", 45, cell1, 80, "V", 1, 120);

by defining a function like:

void tftPrintValue(int nameX, char *name, int valueX, float value, int decimals, int unitsX, char *units, int textSize, int Y) { 
      tft.setTextSize(textSize);
      tft.setCursor(nameX, Y);
      tft.print(name);
      tft.setCursor(valueX, Y);
      tft.print(value, decimals);
      tft.setCursor(unitsX, Y);
      tft.print(units);
}

There are several near duplicate functions like these:

//   Full voltage average (2s voltage if a 2s battery or 1s voltage for a 1s battery)
void getFVoltageAverage() {
  int jj;
  float rawVFRead;
  for (int jj = 0; jj < VFN; jj++) {
    //analogRead(VFsens);
    rawVFRead += analogRead(VFsens);
  }
  vfavg = rawVFRead / VFN;
}  // end getFVoltage

//  Partial Voltage average (only applies for 2s, this is cell 2 voltage)
void getPVoltageAverage() {
  int kk;
  float rawVPRead;
  for (int kk = 0; kk < VPN; kk++) {
    // analogRead(VPsens);
    rawVPRead += analogRead(VPsens) + 1;
  }
  vpavg = rawVPRead / VPN;
}  //  end Partial Voltage

that could be replaced with a single function like:

float getAverage(int analogPin, int samples) {
  float raw = 0.0;
  for (int jj = 0; jj < samples; jj++) {
    raw += analogRead(analogPin);
  }
  return raw / samples;
}

I hope these suggestions are helpful, I think they might reduce the total code by 300 lines, if not to 300 lines!

This is why I didn't post my full code to begin with. While I do want to be able to clean up my code, this always redirects from the thing I want to fix.

In this case, trying to print out the voltage at 15 second intervals.

Then why not just do that? Every time you have a new voltage value, decide whether it's been 15s since the last time you printed it, and if so, print it again. You know your code far better than any of us, so it should be easy to decide where to call a function with a value. You can even move the timing decision within the function, to reduce clutter/change in your other code. It's nothing more than a call to millis(), a stored value, a threshold, and a print statement.
something like:

void setup() {
  Serial.begin(115200);
}

void loop() {
  float voltage = 0;
  myfunc(voltage); //somewhere in your code; substitute your variable name, please.
}
void myfunc(float v) {
  static uint32_t previous = 0;
  if (millis() - previous > 15000) {
    Serial.println(v);
    previous = millis();
  }
}

Yes, printing the voltage to serial every 15 seconds is easy. But I need to print this to a display in different spots and keep the old values.

Basically have 40 spots on my display, and incrementing voltage vs time. My thought was to do this with a stuct of voltage, but it was suggested to use an array, which makes sense. But when I try to do this I can't even get it to print to serial, let alone trying to figure out how to print it to my display.

Of course I can do this the manual way of creating 40 voltage variables and 40 different timers and printing to those 40 different spots. Just thought there would be a better way.

Well then, first step, get it to print to serial in one place, because if you can't do that, the rest is immaterial.
Second, rethink your display locations.

myfunc(voltage, location) isn't much harder to envisage.

where the same display routine is capable of putting the output in different locations, instead of pfaffing around with display code in 40 locations.
YMMV, just trying to help.

Something like the following? The display thing is going to take me a bit.

int vt;
unsigned long currentTime;
unsigned long prevTime=0;
float vTime[vt];

setup(){
Serial.begin (115200);

}

loop(){
 for (int vt = 0; vt<39; vt++)
currentTime=millis();
 if (currentTime-prevTime>=15000){
 prevTime=currentTime();
 Serial.println(vTime[vt]);
 }
}

"I will clean up this haystack, but right now I need to find that needle"

1 Like

Well yeah, then you can leave the haystack alone...

And hope that was the only needle...

Some folks have such narrow vision that they just don't want to hear any advice that doesn't get them the immediate fix they want. They just don't want to know how much easier the whole thing could be. They don't realize that we've been through this before.

If someone gave me that many ways to improve my program that much, I'd be thankful. But I realize the value of good advice.

Some folks just want answers to questions they ask, not to questions they didn't ask. Some folks are extremely busy and have massive time commitments. Some folks use resources outside themselves to help solve problems.

The list of what some folks are, think, want, have, etcetera, goes on and on.