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:
- arduino mega 2560 R3
- MAX6675 breakout board (https://www.analog.com/media/en/technical-documentation/data-sheets/MAX6675.pdf)
- thermocouple type K
- Adafruit 1.8" Color TFT Shield w/microSD and Joystick - v 2 (Adafruit 1.8 Color TFT Shield w/microSD and Joystick [v 2] : ID 802 : Adafruit Industries, Unique & fun DIY electronics and kits)
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.