Program optimisation [SOLVED]

I'm trying to optimise my program so that it saves as much memory as possible but performs the same functions as I want to use an IR receiver and remote to remotely control it, however, I don't have enough dynamic memory. I'm new to Arduino, so any tips on optimising the code in any way to save dynamic memory, decrease compiler times etc, please let me know. Thank you!

CODE:

/*    _______________________________________________________________________________________________________________________________________________________________________
ㅤㅤ /\             \                                                                                                                                          /            /\
ㅤ ㅤ|_\             \                                                               DATALOGGER                                                               /            /_|
ㅤㅤ |__\             \________________________________  16x2 LCD, SD ADAPTER, DS1307, DS18B20 (5.1kPU), 10k NTC THERMISTOR  ________________________________/            /__| 
ㅤㅤ |___\                                             \____________________________________________________________________/                                            /___|  
ㅤㅤ |____\                                                                                                                                                             /____|
ㅤ ㅤ|_____\                  _______________________________________________________________________________________________________________________                  /_____|
ㅤㅤ \______\                /                                                                                                                       \                /______/                                            
ㅤㅤ  \_____/\______________/  THIS PROGRAM MAY ALSO BE USED AS A GENERAL DATA LOGGER BY MODIFYING THE SENSOR CODE AND INPUTTING THE NECESSARY CODE   \______________/\_____/
ㅤ ㅤ                        \________________________________________________________________________________________________________________________/                     

___________________________________________________________________________________________________________________________________________________________________________________
ㅤ  ㅤ   ㅤ           ㅤ/                                                   \                           /
HARDWARE WIRING LIST: /                                                     \   PROGRAM DESCRIPTION:  /   
_____________________/                                                       \_______________________/
ㅤ                                                      
_________________________________                        _________________________________________________________________
FOR 16X2 LIQUID CRYSTAL DISPLAY: \                      /                                                                 \
............_____________________/                     /  This program is designed to log data from a DS18B20 temperature  \
-VSS GND   /                                           |  sensor   and   a  10K  NTC  thermistor.  Once the  SD  card  is  | 
-VDD 5V    \                                           |  initialised and the start button is pressed, the  program  will  | 
-V0 10K POT \                                          |  create a file in the SD card and name it according to the  date  | 
-RS PIN D2  |                                          |  (the date is retrieved using a DS1307 RTC). once the  file  and  | 
-RW GND    /                                           |  file name is created, 8 data samples from  the  DS18B20  sensor  | 
-E PIN D3  |                                           |  and thermistor are  averaged  and  logged  within  the  logging  | 
-D0 X     /                                            |  interval into the created file, as well as the time in  seconds  | 
-D1 X    /                                             |  that the program started  and  the  real  time  clock.  If  the  | 
-D2 X    \                                             |  program is ran  until  the  next  day,  data  logging  will  be  | 
-D3 X     \                                            |  stopped, and another file will be created and  named  according  | 
-D4 PIN D4 \                                           |  to the date. The  liquid  crystal  display  or  serial  monitor  | 
-D5 PIN D5  \                                          |  (dependant on the DEBUG state) can be used to ensure  that  the  | 
-D6 PIN D6  |                                          |  program runs correctly, displaying SD card initialisation,  RTC  | 
-D7 PIN D7  /                                          |  initialisation, the filename the data is writing to,  the  time  | 
-A 5V      /                                           |  the data started logging in seconds, and the  current  time  of  | 
-K GND    /                                            \  the RTC. If any errors occur, the LCD or  serial  monitor  will  / 
_________/                                              \__________________  display an error message.  __________________/ 
ㅤ                                                                         \___________________________/
____________________________
FOR SD CARD ADAPTER MODULE: \
..............______________/
-CS PIN D10   \
-MOSI PIN D11  |
-MISO PIN D12 /
-SCK PIN D13 /
____________/

_________________
FOR DS1307 RTC:  \
.............____/
-SCL PIN SCL \
-SDA PIN SDA  |
-SQW X  _____/
_______/

________________________________
FOR DS18B20 TEMPERATURE SENSOR: \
........._______________________/
-POS 5V  \
-NEG GND  \__
-DQ 5.1kPU   \________________
-5.1kPU LINKING PIN D9 AND 5V \______________
-104pF CERAMIC CAPACITOR LIKNING POS AND NEG \
_____________________________________________/

________________________
FOR 10k NTC THERMISTOR: \
..........._____________/
-POS 5V    \
-NEG PIN A1 \___________________________
-10k RESISTOR LIKNING NEG AND A1 TO GND \
________________________________________/

// INCLUDE LIBRARIES AND DEFINED CONSTANTS:                                                                                      
/********************************************************************************************************************************************************************************/

#include <OneWire.h> //one wire communication
#include <DallasTemperature.h> //library for DS18B20 temperature sensor
#include <SPI.h> //library for SPI pins of the SD card module adapter
#include <SD.h> //library for the SD card 
#include <LiquidCrystal.h> //library for the LCD
#include <TimeLib.h> //time elements library
#include <DS1307RTC.h> //library for the DS1307 RTC

#define totalTime() millis() / 1000 //gives the total time in seconds
#define ONE_WIRE_BUS 9 //define the communication pin of the DS18B20 temperature sensor to digital pin 9
#define T2Pin A0 //define the pin of the thermistor to analog pin 1
#define year() tmYearToCalendar(tm.Year) - 2000 //gives the year as a 2 digit integer

// DEBUG STATE: 
/********************************************************************************************************************************************************************************/
// with this define constant, the user has the ability to either send debugging information to the serial monitor or to the LCD. The serial monitor sends more detailed debugging
// information, at a cost of decreased storage; the LCD prints vague error messages, but allows the debugging info to be viewed remotely and increases program storage

#define DEBUG 0 //current DEBUG state, spaced out for ease of navigation. Equal to 0 or 1: 0 for LCD debugging data, 1 for serial monitor debugging data

// IF DEBUG STATE
/********************************************************************************************************************************************************************************/

#if DEBUG == 1 //if DEBUG == 1 then serial debugging data will be printed, lcd debugging data will not. x in all of these arguments are placeholders

#define debug(x) Serial.print(x) //define debug as serial print
#define debugln(x) Serial.println(x) //define debugln as serial println
#define debugLCD(x) //lcd print undefined, therefore it will not be compiled
#define clearLCD() //lcd clear undefined, therefore it will not be compiled
#define SCLCD() //set cursor undefined, therefore it will not be compiled

#else //if DEBUG == 0 then lcd debugging data will be printed, serial debugging data will not

#define debug(x) //serial print undefined, therefore it will not be compiled
#define debugln(x) //serial println undefined, therefore it will not be compiled
#define debugLCD(x) lcd.print(x) //define debugLCD as lcd prin
#define clearLCD() lcd.clear() //define lcd clear as clearLCD
#define SCLCD() lcd.setCursor(0, 0) //define set cursor LCD as lcd set cursor to 0, 0 on the display

#endif //end if definitions

// GENERAL DATALOGGER GLOBAL VARIABLES:                    
/********************************************************************************************************************************************************************************/

File dataFile; //the file we will be using to create the filename and write to

LiquidCrystal lcd(2, 3, 4, 5, 6, 7); //set pins on the lcd

tmElements_t tm; //time elements
const short CS = 10; //chip select pin for the micro SD card adapter module
const short logInterval = 10; //the interval of which the data is written to the file
const float sampleInterval = 1.25; //the interval of which the samples will be take
static unsigned long logTime = totalTime() + logInterval; //the total log time of the data logger
static float sampleTime = totalTime() + sampleInterval; //the total sample time of the data logger
const short userDelay = 2000; //delay for user opimisation
static short numSamples = 0; //the number of samples taken

// TEMPERATURE DATALOGGER GLOBAL VARIABLES:                    
/********************************************************************************************************************************************************************************/

OneWire oneWire(ONE_WIRE_BUS); //data communication 
DallasTemperature sensors(&oneWire); //used for the DS18B20 sensor

static float averageT1; //the average of the sum of samples of temperature sensor 1, divided by the number of samples
static float averageT2; //the average of the sum of samples of temperature sensor 2, divided by the number of samples
static float totalT1 = 0; //the total of the sum of samples of temperature sensor 1
static float totalT2 = 0; //the total of the sum of samples of temperature sensor 2

// INITIALISATION FUNCTION:
/********************************************************************************************************************************************************************************/

void initialisation() { //used in setup to initialise the RTC and SD card

  delay(userDelay); //delay for user optimisation
  Serial.begin(115200); //begins serial communication
  lcd.begin(16, 2); //begins LCD communication
  SCLCD(); //sets the cursor of the LCD to 0, 0
  debugln(); //prints a new line to format serial monitor data in case of zero bytes printed during serial connection
  debugln("Initialising RTC & SD card..."); //prints that the RTC and SD are being initialised to the serial monitor
  debugLCD("Initialising..."); //prints that the RTC and SD are being initialised to the LCD 
  delay(userDelay); //delay for user optimisation
  clearLCD(); //clears the LCD

  if (!SD.begin(CS)) { //checks if SD card is available and prints error message if not

    debugln("SD card failed. Check hardware"); //prints that the SD card has failed to the serial monitor
    debugLCD("SD card failed"); //prints that the SD card has failed to the LCD 
    while (1) {
      //create an endless loop with nothing to do if file does not open
    } //end while (1) loop
  } //end if (!SD.begin(CS))

  else {  //prints message that the SD has been initialised

    debugln("SD initialised"); //prints message that the SD card has been initialised to the serial monitor
    debugLCD("SD initialised"); //prints message that the SD card has been initialised to the LCD
    delay(userDelay); //delay for user optimisation
  }  //end else if (!SD.begin(CS))

  clearLCD(); //clears the LCD

  if (RTC.read(tm)) { //prints message that the RTC has been initialised

    debugln("RTC initialised"); //prints that the RTC has been initialised to the serial monitor
    debugLCD("RTC initialised"); //prints that the RTC has been initialised to the LCD 
    delay(userDelay); //delay for user optimisation
  } //end if (RTC.read(tm))

  else { //prints debugging message if RTC fails to initialise 

    debugln("RTC failed. Check hardware"); //prints RTC failed message to the serial monitor
    debugLCD("RTC failed"); //prints RTC failed message to the LCD 
    while (1) {
      //create an endless loop with nothing to do if file does not open
    } //end while (1) loop
  } //end else if (RTC.read(tm))

  clearLCD(); //clears the LCD
} //end initialisation()

// CREATE FILE AND NAME() FUNCTION:
/*******************************************************************************************************************************************************************************/

void createFileAndName() { //creates file and names the file according to the date

  char fileName[13]; //buffer to store file name
  sprintf(fileName, "%02d-%02d-%d.txt", tm.Day, tm.Month, year());  //formats and concatenates the RTC data to create the file name

  if (!SD.exists(fileName)) { //creates file if the file is not already created

    debug("Creating file..."); //prints that the file is being created to the serial monitor
    debug("File name: "); //prints the beginning of the sentence to the serial monitor
    debugln(fileName); //prints the file name to the serial monitor
  } //end if (!SD.exists(fileName))

  else { //appends data to already existing file

    debug("The file: "); //prints the beginning of the sentence to the file name
    debug(fileName); //prints file name to the serial monitor
    debugln(" already exists. Opening file..."); //prints message to the serial monitor
  } //end else if (!SD.exists(fileName))

  debugLCD(fileName); //prints the file name to the top left of the LCD

// SD CARD FIRST WRITE- HEADER:
/********************************************************************************************************************************************************************************/
 
  dataFile = SD.open(fileName, FILE_WRITE); //opens the dataFile named after the current date on the RTC

  if (dataFile) { //if the dataFile opens it will print the datalogging header

    dataFile.println(); // Helps format the data if more than one logging process has been utilised that day.
    dataFile.println();
    dataFile.println("TEMPERATURE DATA LOGGER"); //prints the datalogging header to the dataFile
    dataFile.println();
    dataFile.close(); //closes the data file to avoid data corruption
    debugln("TEMPERATURE DATA LOGGER"); //prints datalogging header to the serial monitor
  } //end if (dataFile)

  else { //if the file does not open 

    debugln("Error opening and writing to file"); //prints error message to the serial monitor
    clearLCD(); //clears the CLCD to print the error message
    debugLCD("File write error"); //prints error message to the LCD
    while (1) {
      //create an endless loop with nothing to do if file does not open
    } //end while (1) loop
  }  //end else if (dataFile)
} //end createFileAndName()

// GET AVERAGE READING() FUNCTION:
/********************************************************************************************************************************************************************************/

void getAverageReading() { //prints and logs average temperature data to the file 

  lcd.setCursor(0, 1); //sets the cursor below the file name printed at the lop left of the LCD 

// RTC TIME INFO PRINT TO LCD:
/********************************************************************************************************************************************************************************/
 
  char displayTime[9]; //placeholder for displaying real time
  RTC.read(tm); //reads the time from the RTC
  sprintf(displayTime, "%02d:%02d:%02d", tm.Hour, tm.Minute, tm.Second); //concatenates and formats the data of the real time clock
  debugLCD(displayTime); //prints the real time to the LCD

// CALCULATION FOR T2: 
/********************************************************************************************************************************************************************************/
  
  int tempReading = analogRead(T2Pin); //request to read the T2 pin (analog pin A1)
  double tempK = log(10000.0 * ((1024.0 / tempReading - 1))); //temperature in Kelvin from analog pin reading
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK)) * tempK); //redeclaration of tempK to convert pin reading to Kelvin
  double T2 = tempK - 273.15; //T2 calculation result in degrees celsius from Kelvin

/********************************************************************************************************************************************************************************/

  sensors.getTempCByIndex(0); //gets the temperature from the request within setup
  sensors.requestTemperatures(); //requests temperature in the loop, which will be used in the next loop iteration

// CHARACTER ARRAYS: 
/********************************************************************************************************************************************************************************/
  
  char averageData[65]; //character buffer for sprintf()
  char charLogTime[8]; //placeholder for log time character
  char charAverageT1[9]; //placeholder for averageT1 character
  char charAverageT2[9]; //placeholder for averageT2 character

// DTOSTRF FUNCTIONS: 
/********************************************************************************************************************************************************************************/ 

  dtostrf(logTime, 7, 0, charLogTime); //converts integer to character array and used to format the logTime data within the lcd screen and the average data character buffer
  dtostrf(averageT1, 8, 2, charAverageT1); //converts floating point number to a character array as sprintf cannot use float values
  dtostrf(averageT2, 8, 2, charAverageT2); //converts floating point number to a character array as sprintf cannot use float values

// SPRINTF TO CONCATENATE/FORMAT LOGGED DATA:
/********************************************************************************************************************************************************************************/

  sprintf(averageData, "TIME:  %02d:%02d:%02d  LOG:%ss  T1:%sºC  T2:%sºC", tm.Hour, tm.Minute, tm.Second, charLogTime, charAverageT1, charAverageT2);

// OPEN FILE:
/********************************************************************************************************************************************************************************/

  char fileName[13]; //buffer to store file name
  sprintf(fileName, "%02d-%02d-%d.txt", tm.Day, tm.Month, year());  //formats and concatenates the RTC data to create the file name
  dataFile = SD.open(fileName, FILE_WRITE); //opens file to write to

  if (!SD.exists(fileName)) { //creates file if the file is not already created

    createFileAndName(); //re-runs the createFileDirAndName() user defined function 
  } //end if (!SD.exists(fileName))

  else {
    //do nothing
  } //end else if (!SD.exists(fileName))

// TAKE SAMPLE:
/********************************************************************************************************************************************************************************/

  if (totalTime() > sampleTime) { //increments the following every 1.25 seconds

    numSamples += 1; //increments the number of samples by 1
    totalT1 += sensors.getTempCByIndex(0); //increments the total of T1 by the sample reading
    totalT2 += T2; //increments the total of T2 by the sample reading
    sampleTime += sampleInterval; //increments the sample time by the sample interval
    averageT1 = totalT1 / numSamples;  //calculate total of T1
    averageT2 = totalT2 / numSamples;  //calculate total of T2

    if (sensors.getTempCByIndex(0) <= -10 || sensors.getTempCByIndex(0) >= 70 || T2 <= -10 || T2 >= 70) { //highlights faulty readings so that they can be discarded

      dataFile.print("*"); //prints to the file
      debug("*"); //prints to the serial monitor
      lcd.setCursor(15, 0); //sets LCD sursor 
      debugLCD("*"); //prints to the LCD
    } //end if (sensors.getTempCByIndex(0) || T2 <= -10 || sensors.getTempCByIndex(0) || T2 >= 70)
  }  //end if (totalTime() > sampleTime)

// AVERAGE TEMPERATURE READINGS:
/********************************************************************************************************************************************************************************/

  averageT1 = totalT1 / numSamples;  //calculate total of T1
  averageT2 = totalT2 / numSamples;  //calculate total of T2

// WRITE AND PRINT DATA TO SD & LCD:
/********************************************************************************************************************************************************************************/

  if (dataFile) {  //checks if the file is available

    if (totalTime() > logTime) {  //prints the data within the logging interval

      dataFile.println(averageData);  //prints the average data to the dataFile
      debugln(averageData); //prints the average data to the serial monitor
      lcd.setCursor(8, 1); //sets the cursor so the LCD displays the time at the bottom right of the LCD 
      debugLCD(charLogTime); //prints the log time to the LCD 
      debugLCD("s"); //prints s to format the printed log time on the LCD 
      logTime += logInterval; //increments the log time by the log interva
    }  //end if (totalTime() > logTime)

    if (numSamples > 7) {  //resets the number of samples and the total temperature readings

      numSamples = 0; //resets the number of samples back to 0
      totalT1 = 0; //resets the total of T1 back to 0
      totalT2 = 0; //resets the total of T2 back to 0
    }  //end if (numSamples > 7)
  }  //end if (dataFile)

  else {  //if the file does not open

    debugln("Error opening and writing to file"); //prints error message to the serial monitor
    clearLCD(); //clears the LCD to print the error message
    debugLCD("File write error"); //prints error message to the LCD
    while (1) {
      //create an endless loop with nothing to do if file does not open
    }  //end while (1) loop
  }  //end else if (dataFile)

  dataFile.close(); //closes the data file at the end to avoid file corruption 
}  //end getAverageReading()

// SETUP() FUNCTION
/********************************************************************************************************************************************************************************/

void setup() {  //runs once at the start of the program

  initialisation(); //runs initialisation() user defined function in setup
  createFileAndName();  //runs createFileDirAndName() user defined function in setup
  sensors.requestTemperatures();  //requests data before first loop iteration to print data at the log interval
  sensors.setWaitForConversion(false);
}  //end setup()

// LOOP() FUNCTION
/********************************************************************************************************************************************************************************/

void loop() {  //loops getAverageReading() user defined function and displays info on the 16x2 LCD screen

  getAverageReading(); //runs the getAverageReading() user defined function
}  //end loop()

// END OF PROGRAM
/********************************************************************************************************************************************************************************/

Nice ASCII-art, but due to the vast numbers of empty lines quite unreadable.

Anyway, don't use dynamic memory, use static memory or global variables. As you don't use reentrant code it's a nobrainer.

May I ask what Arduino board you have for this?

Thanks, I got bored waiting on a response from someone one day so I spent that time making it look cool lol

I've removed a lot of the empty lines and edited the original post, is that any better?

Also, I'm not not sure what reentrant code is, I'll look into it.

It is an ELEGOO UNO board.

You can save some space by using sprintf_P, which expects the format string I’m pgmspace.
That and using a common, global, sprintf output buffer should help.

More when I can look at it on a big screen.

would there be a way to reduce dynamic memory usage instead of increasing it? I'm pretty sure the issue I'm having (not being able to run the program with an IR receiver code implemented) is that I don't have enough dynamic memory space.. as soon as it goes to around 75%, the SD card can no longer be wrote to.

you don't use that much dynamic memory beside using the stack

you should use the F() macro for storing text in flash, or use indeed sprintf_P()

you could also make your stack buffers global - like those

  char averageData[65]; //character buffer for sprintf()
  char charLogTime[8]; //placeholder for log time character
  char charAverageT1[9]; //placeholder for averageT1 character
  char charAverageT2[9]; //placeholder for averageT2 character

to ensure you know ahead of time how much RAM is required

Using the F macro does save you over 100 bytes of dynamic memory (1386 vs 1268).

Or just eliminate most if not all of those buffers and instead use multiple print statements.

yes, where applicable (I've not checked just noticed those)

Create one debug function, that sends the same text for both LCD and Serial.
Given that the LCD message has to be compact, it might save 50% or more for these
messages.

debug(char * txt)
{
  debugln(txt);
  debugLCD(txt);
}

I tried to use #define for both the lcd and serial monitor:

// DEBUG STATE:
/********************************************************************************************************************************************************************************/
// with this define constant, the user has the ability to send debugging information to the serial monitor and the LCD.

#define DEBUG 1  //current DEBUG state, spaced out for ease of navigation. Equal to 0 or 1: 0 for LCD debugging data, 1 for serial monitor debugging data

// IF DEBUG STATE
/********************************************************************************************************************************************************************************/




#if DEBUG == 1  //if DEBUG == 1 then debugging data will be printed. txt in all of these arguments are placeholders

#define debug(txt) Serial.println(txt)   //define debug as serial print
#define debug(txt) lcd.print(txt)
#define clearLCD() lcd.clear()           //lcd clear undefined, therefore it will not be compiled
#define SCLCD() lcd.setCursor(0, 0)      //set cursor undefined, therefore it will not be compiled

#else //if DEBUG == 0 then lcd debugging data will be printed, serial debugging data will not

#define debug(txt)                       //serial print undefined, therefore it will not be compiled                    //serial println undefined, therefore it will not be compiled
#define debug(txt)
#define clearLCD()                       //define lcd clear as clearLCD
#define SCLCD()                          //define set cursor LCD as lcd set cursor to 0, 0 on the display
#endif    

However, for some reason, I get the error:

Compilation error: no matching function for call to 'LiquidCrystal::print()'

If I move lcd.print to else if debug == 0, it works fine. I don't think I should have to create a user defined function for this, rather just use a define statement- do I?

EDIT: I'm just not going to touch the debug statements anymore, I've got enough program memory now, thank you though!

you can't have two macros with the same name. You could possibly do something like

#define debug(...)  {Serial.println(__VA_ARGS__); lcd.print(__VA_ARGS__);}

that would let you support all the variations of print (with or without parameters) such as

  debug(10);
  debug(someVariable);
  debug(F("Hello"));
  debug(10, HEX);
  debug(123.456, 3);