Measuring and displaying temperature and pH [seeking constructive critizism on code]

Hello,
I have as a project creating a measurement of temperature and pH and displaying it without having a computer connected to the arduino for a time spam of 5 days.
for this project I am using the following hardware:

currently haven't done the pH section (as I am waiting for the hardware). Once that has been said, my current sketch is working properly and doing what I am looking for:

  • store the data in the SD card by averaging the value over a time laps
  • displaying the measured values (averages) over the display during the last hour in a XY axis
  • being able to change the display between the main and secondary measurement
    so right now, from my tests, it works, but it is one of my first projects in arduino (for example I had so many headaches over the overflow of variables in sums/substractions...)
    in any case, I would love from more experienced users to give an insight on what could be improved over my code, to make it more robust and reliable.
    here is the current code (I am not uploading the image of the wiring as there is nothing particular, outside that I am using a software SPI to communicate with the MAX6675 as the hardware SPI of the mega is already used for the shield (which controls the display AND the SD reader/writer with it).
//##################//
///// libraries //////
//##################//
// -SD reader
#include <SPI.h>
#include <SD.h>

// -Adafruid shield
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_seesaw.h>
#include <Adafruit_TFTShield18.h>


// -Temp measurement
#include "max6675.h"


//##################//
/// end libraries ////
//##################//

//##################//
///// variables //////
//##################//

//shield variables
Adafruit_TFTShield18 ss;
uint32_t buttons;
#define SD_CS    4                                                // Chip select line for SD card on Shield
#define TFT_CS  10                                                // Chip select line for TFT display on Shield
#define TFT_DC   8                                                // Data/command line for TFT on Shield
#define TFT_RST  -1                                               // Reset line for TFT is handled by seesaw!
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);   // Using hardware SPI


//temp read
#define pinSCK 36                                                 // SHOULD NOT use the SPI hardware! so not pin 53 https://www.arduino.cc/reference/en/language/functions/communication/spi/
#define pinCS  37                                                 // SHOULD NOT use the SPI hardware! so not pin 52 https://www.arduino.cc/reference/en/language/functions/communication/spi/
#define pinSO  38                                                 // randomly chosen
MAX6675 thermocouple(pinSCK, pinCS, pinSO);
const uint32_t minTimeMax6675=220;                                // min time in miliseconds for correct (temperature) measurement with the max6675


//pH read
const uint32_t minTimePh=0;                                       // min time in miliseconds for correct (pH) measurement


//file definition values
String fileName="run";    // file name for storing data
int fileNumber=0;         // file index for fileName
File myFile;              // file object
bool writeFile=1;              // file object


// time variables
uint32_t previousTime=0;  // variable to save previous time to ensure that the measurement takes enought time
uint32_t initialTime=0;   // variable to save original time when the measurement began


  
//display configuration
const int numberOfMeasurements=2;                                                 // number of different displays
String titles[numberOfMeasurements]={"Temp","pH"};                                // titles for each measurement
uint32_t minTimes[numberOfMeasurements]={minTimeMax6675,minTimePh};               // min times list for each measurement
uint32_t minTime;                                                                 // max(minTimes)

int currrentDisplayPosition=0;                                                    // current position for display
int NotcurrrentDisplayPosition=1;                                                 // the position that is not displayed
uint16_t backGroundColor[numberOfMeasurements]={ST77XX_RED,ST77XX_GREEN};         // background color for display depending on chosen display 
uint16_t fontColor[numberOfMeasurements]={ST77XX_WHITE,ST77XX_BLACK};             // font color for display depending on chosen display 
uint16_t pointDispColor[numberOfMeasurements]={ST77XX_BLUE,ST77XX_BLUE};          // point color for display depending on chosen display


//display values

  //Y axis temp
int AxisMax[numberOfMeasurements]={35,8};                                         // maximum value of the Y axis for the measurement that is displayed (temperature or pH)
int AxisMin[numberOfMeasurements]={22,5};                                         // minumum value of the Y axis for the measurement that is displayed (temperature or pH)

  //X axis tile
float AxisTimeMin=0;                                                              // maximum value of the X axis (time)
float AxisTimeMax=1;                                                              // minimum value of the X axis (time)
float timeRange=AxisTimeMax-AxisTimeMin;                                          // range of time before needing to update the display 
int numberOfPathsOverAxis=0;                                                      // number of times that the display went thought the X axis

  //X axis pixel definition
const int minPixelX=18;                                                           // position of the pixel in X for the begining of the axis
const int maxPixelX=139;                                                          // position of the pixel in X for the end of the axis

  //y axis pixel definition
const int maxPixelY=95;                                                           // position of the pixel in Y for the begining of the axis
const int minPixelY=23;                                                           // position of the pixel in Y for the end of the axis



//measurement calculation
float averageTimeForValue=1000 ;                                                  // time interval to return a measurement value in miliseconds
uint32_t lastTimeForValue = 0;                                                    // time counter since last returned measurement value

float sumTime=0;                                                                  // sum of time values in time interval of averageTimeForValue
float sumMeasures[numberOfMeasurements];                                          // sum of measurements values in time interval of averageTimeForValue


int counterPoints=0;                                                              // counter of numbers of points measured in a time interval of averageTimeForValue
int counterStoredPoints=0;                                                        // counter of numbers of stored measurements points
int counterOfPixel=0;                                                             // counter of numbers of pixels used (to check if display should be updated)

const int maxNumberOfPoints = maxPixelX - minPixelX;                              // max numbers of possible pixels in the X axis before going out of the drawn axis
float timeStepForDrawing=((AxisTimeMax-AxisTimeMin)*3600*1000)/maxNumberOfPoints; // time step for drawing next pixel
uint32_t previousDrawPoint=0;                                                     // value of the last Y (in pixels) for the last measured point displayed

float storedMeasures[numberOfMeasurements][maxNumberOfPoints];                    // saving values for each measurement
float storedTime[maxNumberOfPoints];                                              // saving time values of each measurement done (ie., storedMeasures)
float storedPixelMeasures[numberOfMeasurements][maxNumberOfPoints];               // saving a pixel positions of the measurements for cases when changing the displayed measurement
float storedPixelTime[maxNumberOfPoints];                                         // saving a pixel position of the time where the measurement was done (ie., storedPixelMeasures)

//##################//
/// end variables ////
//##################//








//##################//
///// functions //////
//##################//

//display functions
void checkIfFileExist( ) {
  /*
  In case that the file already exist the display will ask to overwrite the file or create a new one.
  */
  if (SD.exists(fileName+".csv")) {
    tft.fillScreen(ST77XX_BLACK);
    tft.setTextSize(1);
    tft.setTextColor(ST77XX_WHITE);
    tft.setCursor(0, 0);
    tft.print("The file "+fileName+".csv already exists, overwrite it?\n (no)<=      =>(yes)");
    while (true) {
        buttons = ss.readButtons();
        if(! (buttons & TFTSHIELD_BUTTON_RIGHT)) {  // Assuming LOW means pressed
          tft.setCursor(10, 50);
          SD.remove(fileName + ".csv");
          tft.fillScreen(ST77XX_GREEN);  // Clear screen
          tft.setCursor(10, 50);
          tft.print("File overwrited.");
          //Serial.println(fileName+" overwrited.");
          break;
        } else if(! (buttons & TFTSHIELD_BUTTON_LEFT)) { // Assuming LOW means pressed
          while (true) {
            if (SD.exists(fileName+"_"+String(fileNumber)+".csv")) {
              fileNumber+=1;
            } else {
              fileName=fileName+"_"+String(fileNumber);
              tft.setCursor(10, 50);
              tft.fillScreen(ST77XX_GREEN);  // Clear screen
              tft.setCursor(10, 50);
              tft.print("File with name "+fileName+" created.");
              break;
            }
          }
            break;
        }
      }
  } else {
    tft.fillScreen(ST77XX_BLACK);
    tft.print("loading...");
  }
  delay(2500); // Short delay to allow the message to be seen
}

void displayCurrentValue(  ) {
  /*
  display the value of the measurement that is not the main display in right top corner, if main display is temperature it will display pH, if main display is pH it will display temperature
  */
  int temp1, temp2;
  String unitMeasure,extraSpaces,extraSpacesTitle;
  temp1              = (storedMeasures[NotcurrrentDisplayPosition][counterStoredPoints-1] / 10)*10;                                          // Integer component
  temp2              = ((storedMeasures[NotcurrrentDisplayPosition][counterStoredPoints-1] / 10)*10 - (float) temp1 ) * 100;                 // 2 decimal places
  
  if (currrentDisplayPosition==0){
    unitMeasure        = "";
    extraSpaces        = "  ";
    extraSpacesTitle        = "    ";
  } else {
    unitMeasure        = " C";
    extraSpaces        = "";
    extraSpacesTitle        = "  ";
  }
  tft.setTextSize(1);                                                   //
  tft.setTextColor(fontColor[currrentDisplayPosition]);
  tft.setCursor(50, 1);  tft.print(fileName);
  tft.setCursor(115, 1);  tft.print(extraSpacesTitle+titles[NotcurrrentDisplayPosition]);
  tft.fillRect(115, 10, 45, 8, backGroundColor[currrentDisplayPosition]);
  tft.setCursor(115, 10);
  tft.print(extraSpaces+String(temp1)+"."+String(temp2)+unitMeasure);
}

void drawLegends(  ) {
  /*
  it draws the axis and axis legends over the display
  */
  String centens,decens,units;
  String minAxis,maxAxis;
  tft.fillScreen(backGroundColor[currrentDisplayPosition]);
  if (numberOfPathsOverAxis>=100) {
    centens = String((numberOfPathsOverAxis / 100) % 10); // Extract hundreds place
  } else {
    centens = " "; // Extract hundreds place
  }
  if (numberOfPathsOverAxis>=10) {
    decens = String((numberOfPathsOverAxis / 10) % 10);
  } else {
    decens = " ";
  }
  units = String(numberOfPathsOverAxis % 10);
  tft.setTextSize(0.5);
  if (currrentDisplayPosition==0){
    minAxis=String(AxisMin[currrentDisplayPosition]);
    maxAxis=String(AxisMax[currrentDisplayPosition]);
  } else {
    minAxis=" "+String(AxisMin[currrentDisplayPosition]);
    maxAxis=" "+String(AxisMax[currrentDisplayPosition]);
  }
  tft.setTextColor(fontColor[currrentDisplayPosition]);
  tft.setCursor(0, minPixelY-4);    tft.print(maxAxis+">");
  tft.setCursor(0, maxPixelY-4);    tft.print(minAxis+">");

  tft.setCursor(0, minPixelY-14);    tft.print(titles[currrentDisplayPosition]);
  tft.setCursor(minPixelX-2, maxPixelY+2);    tft.print("^    ^    ^    ^    ^");
  if (centens!=" "){
    tft.setCursor(minPixelX-2, maxPixelY+7+2);  tft.print(centens+"   +1   +3   +4   +6 m");
    tft.setCursor(minPixelX-2, maxPixelY+15+2); tft.print(decens+"    5    0    5    0 i");
    tft.setCursor(minPixelX-2, maxPixelY+22+2); tft.print(units+"h                    n");
  }
  else if (decens!=" "){
    tft.setCursor(minPixelX-2, maxPixelY+7+2);  tft.print(decens+"   +1   +3   +4   +6 m");
    tft.setCursor(minPixelX-2, maxPixelY+15+2); tft.print(units+"h   5    0    5    0 i");
    tft.setCursor(minPixelX-2, maxPixelY+22+2); tft.print("                      n");
  } else {
    tft.setCursor(minPixelX-2, maxPixelY+7+2);  tft.print(units+"h  +1   +3   +4   +6 m");
    tft.setCursor(minPixelX-2, maxPixelY+15+2); tft.print("     5    0    5    0 i");
    tft.setCursor(minPixelX-2, maxPixelY+22+2); tft.print("                      n");
  }
    tft.drawFastVLine(minPixelX, minPixelY, maxPixelY-minPixelY, ST77XX_BLACK);                 // setup Y axis
    tft.drawFastHLine(minPixelX, maxPixelY, maxPixelX-minPixelX, ST77XX_BLACK);                 // setup X axis
}

void drawPoint(int nOfPoint) {
  /*
  draws in the display a new measurement point and stores the pixel position
  */
  for (int i = 0; i < numberOfMeasurements; i++) {
    storedPixelMeasures[i][counterOfPixel] = map(storedMeasures[i][nOfPoint], AxisMin[i], AxisMax[i], maxPixelY, minPixelY);
  }
  storedPixelTime[counterOfPixel]=map(storedTime[nOfPoint], AxisTimeMin*3600*1000, AxisTimeMax*3600*1000, minPixelX, maxPixelX);
  if (storedPixelTime[counterOfPixel]>=minPixelX) {
    tft.drawPixel(storedPixelTime[counterOfPixel], storedPixelMeasures[currrentDisplayPosition][counterOfPixel], pointDispColor[currrentDisplayPosition]);
  }
  if (storedPixelTime[counterOfPixel]>=maxPixelX) {
    counterOfPixel=maxPixelX-minPixelX;
  } else {
    counterOfPixel+=1;
  }
}

void reDrawPoints( ) {
  /*
  re draws all the stored points when changing between displays
  */
  for (int i = 0; i < counterOfPixel; i++) {
    tft.drawPixel(storedPixelTime[i], storedPixelMeasures[currrentDisplayPosition][i], pointDispColor[currrentDisplayPosition]);
  }
}

void setup () {
  Serial.begin(9600);
  while (!Serial);

  // start by disabling both SD and TFT
  pinMode(TFT_CS, OUTPUT);
  digitalWrite(TFT_CS, HIGH);
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);

  // Start seesaw helper chip
  if (!ss.begin()){
    Serial.println("seesaw could not be initialized!");
    while(1);
  }
  //Serial.println("seesaw started");
  Serial.print("Version: "); Serial.println(ss.getVersion(), HEX);

  // Start set the backlight off
  ss.setBacklight(TFTSHIELD_BACKLIGHT_OFF);
  // Reset the TFT
  ss.tftReset();
  
  // Initialize 1.8" TFT
  tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
  ss.setBacklight(TFTSHIELD_BACKLIGHT_ON);



  Serial.print("Initializing SD card...");

  if (!SD.begin(SD_CS)) {
    Serial.println("failed!");
  } else {
    Serial.println("OK!");  
  }

  // set the screen display to landscape
  tft.setRotation(1);
  if (writeFile) {
  checkIfFileExist( );

    myFile=SD.open(fileName+".csv",FILE_WRITE);
    myFile.print("time");
    myFile.print(";");
    myFile.print("temp");
    myFile.println(";");
    myFile.close();
  }

  drawLegends();
  displayCurrentValue();
  // setting up variables
  for (int i = 0; i < numberOfMeasurements; i++) {
    sumMeasures[i]=0;
  }
  for (int i = 0; i < numberOfMeasurements; i++) {
    for (int j = 0; j < maxNumberOfPoints; j++) {
      storedPixelMeasures[i][j]=maxPixelY;
    }
  }


  for (int i = 0; i < numberOfMeasurements; i++) {  // Start loop from the second element
    if (minTimes[i] >= minTime) {
      minTime = minTimes[i];  // Update minValue if current element is smaller
    }
  }
  
  initialTime=millis();
  previousTime=initialTime;
}

void loop () {
  // section to change screen in case of button pressing
  buttons = ss.readButtons();
  if(! (buttons & TFTSHIELD_BUTTON_1)) {
    if (currrentDisplayPosition!=(numberOfMeasurements-1)) {
      currrentDisplayPosition++;
      NotcurrrentDisplayPosition=0;
    } else {
      currrentDisplayPosition=0;
      NotcurrrentDisplayPosition++;
    }
    
    drawLegends();
    displayCurrentValue();
    reDrawPoints();
    // delay(90);
  }
  if(! (buttons & TFTSHIELD_BUTTON_3)) {
    if (currrentDisplayPosition!=0) {
      currrentDisplayPosition--;
      NotcurrrentDisplayPosition=numberOfMeasurements-1;
    } else {
      currrentDisplayPosition=numberOfMeasurements-1;
      NotcurrrentDisplayPosition--;
    }
    drawLegends();
    displayCurrentValue();
    reDrawPoints();
    // delay(90);
  }

  
  if (millis() - lastTimeForValue >= averageTimeForValue) {
    //  update last display moment, once per second = once per 1000 millis
    lastTimeForValue += averageTimeForValue;
    
    
    // storing a new average value
    for (int i = 0; i < numberOfMeasurements; i++) {
      storedMeasures[i][counterStoredPoints] = sumMeasures[i] / counterPoints;
    }
    storedTime[counterStoredPoints]=sumTime / counterPoints;


    if (millis() - previousDrawPoint >= timeStepForDrawing) {
      previousDrawPoint+=timeStepForDrawing;
      //display of new point
      drawPoint(counterStoredPoints);
      displayCurrentValue(  );
    }
    if (writeFile) {
    myFile=SD.open(fileName+".csv",FILE_WRITE);
    if (myFile) {
      myFile.print(storedTime[counterStoredPoints]);
      myFile.print(";");
      myFile.print(storedMeasures[0][counterStoredPoints]);
      myFile.println(";");
      myFile.close();
    } else {
      Serial.print(fileName+".csv");
      Serial.println("error while opening SD file");
    }
    }

    if (counterOfPixel >= maxPixelX-minPixelX) {
      numberOfPathsOverAxis+=1;
      AxisTimeMin=AxisTimeMax;
      AxisTimeMax=AxisTimeMax+timeRange;
      drawLegends();
      counterOfPixel=0;
    }

    if (counterStoredPoints >= maxNumberOfPoints - 1) {
        // Reset the values of storedMeasures to the minimum value of the axis and also the sum of measurements
        for (int i = 0; i < numberOfMeasurements; i++) {
            for (int j = 0; j < maxNumberOfPoints; j++) {
                storedMeasures[i][j] = AxisMin[numberOfMeasurements];
            }
        }
        for (int j = 0; j < maxNumberOfPoints; j++) {
            storedTime[j] = 0;
        }
        counterStoredPoints = 0;  // Reset counter
    } else {
        counterStoredPoints++;  // Increment counter
    }
    
    for (int i = 0; i < numberOfMeasurements; i++) {
      sumMeasures[i] = 0;
    }
    sumTime = 0;
    counterPoints = 0;
  }
 

  sumTime += millis()-initialTime;
  counterPoints += 1;

  //wait enought for the sensor
  if (millis()-previousTime<minTime) {
    delay(minTime-(millis()-previousTime));
  }

  previousTime=millis();

  //  section to modify manually
  sumMeasures[0] += thermocouple.readCelsius();
  sumMeasures[1] += 6.8;
}


//##################//
/// end functions ///
//#################//

I would appreciate on the "what you can improve is...." (with a little explanation if possible). please before lighting the torches and coming after me, keep in mind that I am not too familiar with C++ nor with arduino, and it is one of my first projects.

Have you tried your project in its intended environment? Powering from USB and powering from battery should have the same result, so ensure the power source is right-sized. If you have power issues, consider just collecting data without a display.

sorry? I am not going to powered from a battery but from the barrel jack of the arduino, so this will not be a problem. I was looking more for insight on the code and what to improve on it.

What do you consider "needs improvement?" Does it perform as you intended?

at least for the moment, I will run it for 5 days which I haven't tested yet, and that I had had issues with timings and if conditionals, also a LOT of the code I implemented from scratch with my own knowledge and I imagine that there are some 'current' techniques/approaches that I have not applied and wanted to learn about but instead of randomly ask how to do something, hope that by posting at least something that in logic works, that people could see better what I am looking to do.