Two channel interrupt driven datalogger - Need to reduce code size

Hi Everyone,

I hope someone can help me out, I am pretty new to the Arduino and am making a two input pulse datalogger for an experiment. So far everything has been going well except that I have run out of program storage space. I have been trying my best to reduce the code required but I am still just over at 31,946 bytes for the Arduino Nano.

I am using the Freetronics RTC and Temperature/Humidity modules (and accelerometer but this isn't as critical so has already been removed from the code) as well as the Arduino TFT screen / SD card module. The inputs are going into the interrupt pins D2 & D3 and everything works well on the LCD display.

I need to have both the LCD display working (so someone can check on the experiment from time to time) as well as logging to the SD card for analysis at the end of the experiment.

Can someone help me reduce my code size please?

Apologies for the poor coding style but I have spent most of today hacking away at it in frustration trying to reduce the sketch size.

/*
Two channel interrupt driven datalogger
 */
#include <Time.h> // Time library for timekeeping on the Arduino
#include <TFT.h>  // Arduino TFT LCD library
#include <SPI.h> // SPI driver library
#include <SD.h> // SD card reading and writing library
#include <SoftI2C.h> // I2C interface library
#include <DS3232RTC.h> // Library for the RTC
#include "DHT.h" // Library for the Temperature and Humidity sensor
//#include <stdlib.h>

// Pin definition for the temperature and humidity sensor
const int DHTPIN = 8;
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// Pin definition for the TFT screen
const int TFTcs = 4; // Chip select pin for the screen
const int dc = 6; // Pin for Data/Command selection for screen
const int rst = 7; // Reset pin for the screen
const int BL = 5; // Backlight dimming pin LOW = dimmed, HIGH = bright

// Pin definition for the Accelerometer
//const int XaxisAcc = A0; // X axis for the accelerometer
//const int YaxisAcc = A1; // Y axis for the accelerometer
//const int ZaxisAcc = A2; // Z axis for the accelerometer

// Pin definition for the SD card Serial Select
const int SDcs = 9; // 

volatile int Input1 = LOW; // Set input for interrupt on D2
volatile int Input2 = LOW; // Set input for interrupt on D3

SoftI2C i2c(A4, A5); // Use pins A4 and A5 as software I2C for RTC
DS3232RTC rtc(i2c); // Attach the RTC to the I2C pins

// Create an instance of the TFTscreen library
TFT TFTscreen = TFT(TFTcs, dc, rst); // Pass the chip select pin, data/command pin and reset pin

// char array to print variables to the screen
char Time[9];
char Date[9];
char currentTemp[5];
char currentHumid[5];
char currentCount1[11];
char currentCount2[11];

String sHour;
String sMinute;
String sSecond;

String sDay;
String sMonth;

String currentTimeString;
String currentDateString;

char Filename[] = "Temp.txt";
String Datastring;

unsigned long Count1 = 0;
unsigned long Count2 = 0;

word currentMillis = 0;
word previousMillis = 0;
word previousTimeMillis = 0;
word previousDHTMillis = 0;
word previousDateMillis = 0;

const int Timeinterval = 1000;
const int DHTinterval = 10000;
const int Dateinterval = 60000;
const int interval = 1000;

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

  dht.begin(); // Initialise the temperature and humidity module

  pinMode(BL, OUTPUT); // set the backlight pin as an output
  pinMode(10, OUTPUT); // Pin 10 needs to be kept as an output (even if not used) or SD library won't work
  digitalWrite(BL, HIGH); // Set default brightness of backlight

  attachInterrupt(0, Increment1, RISING);
  attachInterrupt(1, Increment2, RISING);

  TFTscreen.begin(); // Initialise the TFT LCD
  TFTscreen.background(0, 0, 0); // clear the screen with a black background
  
  TFTscreen.stroke(255, 255, 255);// Write static text to the screen with a white font
  TFTscreen.setTextSize(2);  // set the font size as 2 from here on
  
  if (!SD.begin(SDcs)) {
    //TFTscreen.text("Card failure", 0, 0);// Display SD card initialisation failure on TFT
    //TFTscreen.text("check card", 0, 20);
    //TFTscreen.text("is present", 0, 40);
    //TFTscreen.text("and correctly", 0, 60);
    //TFTscreen.text("formatted as", 0, 80);
    //TFTscreen.text("FAT16 / FAT32", 0, 100);
    //delay(86400000);
    return; //nothing else to do
  }else{
    TFTscreen.text("Time:", 0, 0); // write the text to the top left corner
    TFTscreen.text("Date:", 0, 20);
    TFTscreen.text("Temp:", 0, 40);
    TFTscreen.text("Humid:", 0, 60);
    TFTscreen.text("Count1:", 0, 80);
    TFTscreen.text("Count2:", 0, 100);
  }
  WriteDHTtoTFT();
  WriteDatetoTFT();
}

void loop() {
  currentMillis = millis();

  if(currentMillis - previousDHTMillis >= DHTinterval){
    previousDHTMillis = currentMillis;   
    WriteDHTtoTFT();
  }
  
  if(currentMillis - previousTimeMillis >= Timeinterval){
    previousTimeMillis = currentMillis;   
    WriteTimetoTFT();
  }
  
  if(currentMillis - previousDateMillis >= Dateinterval){
    previousDateMillis = currentMillis;   
    WriteDatetoTFT();
  }
  
  if(currentMillis - previousMillis >= interval){
    previousMillis = currentMillis;   
    WriteCountstoTFT();
  }
}

void WriteCountstoTFT(){
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(80, 80, 82, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  dtostrf(Count1, 1, 0, currentCount1);
  TFTscreen.text(currentCount1, 80, 80);
    
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(80, 100, 82, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  dtostrf(Count2, 1, 0, currentCount2);
  TFTscreen.text(currentCount2, 80, 100);  
}

void WriteDHTtoTFT(){
  dtostrf(dht.readTemperature(),0,1,currentTemp);
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(60, 40, 70, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  strcat(currentTemp,"c");
  strcat(currentTemp,'\0');
  TFTscreen.text(currentTemp, 60, 40);

  dtostrf(dht.readHumidity(),0,1,currentHumid);
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(70, 60, 70, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  strcat(currentHumid,"%");
  strcat(currentHumid,'\0');
  TFTscreen.text(currentHumid, 70, 60);
}

void WriteTimetoTFT(){
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(60, 0, 94, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  currentTimeString = printTime();
  currentTimeString.toCharArray(Time, 9);
  TFTscreen.text(Time, 60, 0);  
}

void WriteDatetoTFT(){
  TFTscreen.fill(0, 0, 0); // Set the shape fill to black
  TFTscreen.stroke(0, 0, 0); // Set the outline colour for the shape to black
  TFTscreen.rect(60, 20, 94, 14);
  TFTscreen.stroke(255, 255, 255); // set the font color to white
  currentDateString = printDate();
  currentDateString.toCharArray(Date, 9);
  TFTscreen.text(Date, 60, 20);
}

String printTime()
{
  RTCTime time;
  rtc.readTime(&time);
  byte hour = time.hour;
  byte minute = time.minute;
  byte second = time.second;

  //if(hour<10){
    //sHour = (String)('0')+(String)hour;
  //}else{
    sHour = (String)hour;
  //}

  //if(minute<10){
    //sMinute = (String)('0')+(String)minute;
  //}else{
    sMinute = (String)minute;
  //}

  //if(second<10){
    //sSecond = (String)('0')+(String)second;
  //}else{
    sSecond = (String)second;
  //}
  
  String CurrentTime = sHour + ':' + sMinute + ':' + sSecond;
  return CurrentTime;
}

String printDate()
{
  RTCDate date;
  rtc.readDate(&date);
  byte day = date.day;
  byte month = date.month;
  int year = date.year;

  //if(day<10){
    //sDay = (String)('0')+(String)day;
  //}else{
    sDay = (String)day;
  //}

  //if(month<10){
    //sMonth = (String)('0')+(String)month;
  //}else{
    sMonth = (String)month;
  //}

  String CurrentDate = sDay + '/' + sMonth + '/' + (String)(year-2000);
  return CurrentDate;
}

void Increment1(){
  Count1++;
  Datastring="Counter1 "+printDate()+" "+printTime()+" "+currentHumid+" "+currentTemp;

  File dataFile = SD.open(Filename, FILE_WRITE);
  if (dataFile) {
    dataFile.println(Datastring);
    dataFile.close();
  }
}

void Increment2(){
  Count2++;
  Datastring="Counter2 "+printDate()+" "+printTime()+" "+currentHumid+" "+currentTemp;
    
  File dataFile = SD.open(Filename, FILE_WRITE);
  if (dataFile) {
    dataFile.println(Datastring);
    dataFile.close();
  }
}
/*
void ResetCounters(){
  neutronCount1=0;
  neutronCount2=0;
}
*/

OK, here is the bottom line first. Disconnect the Nano from various sensors, put in in the bottom drawer, and get a Mega.

Just counting the number of #includes is enough to reveal the problem, without worrying about the rest of the code. And the problem is that your project is too big for a Nano. What's more, the problem is likely to get worse and, if it wasn't too big right now, it probably will be next week.

Further, while 31946 might be "just over" to load into Nano, trimming it down a few bytes here and there to "just under" isn't necessarily going to save you. Managing to load a programme is not a guarantee that it will run, and the time to get confident is when it is below about 30k. Now you have gotten this far, you will be amazed how difficult it is do that.

None of the following is a solution to your problem but:-
Maybe my paranoia is showing, but I would be vary wary of that datastring stuff. It is probably unwarranted, and something you may well later regret later. Further, I have never seen SoftI2C before and wonder why you would use it when nobody else does.

No need to apologise for the code style. It is pretty well presented, and I imagine it is pretty kosher. There is just too much of it,.

You have commented out most of the String class functions, but you could remove all the String classes. I think that the libraries don't use it, and the String class is avoided by some of use because of memory usage.

I didn't know that <Time.h> is a default library. Did I miss something ?

In the end you have included too many libraries and you need an Arduino Mega 2560 as Nick_Pyner wrote.

I didn't know that <Time.h> is a default library. Did I miss something ?

maybe from the playground, or some RTC?
There are a lot of libs you can use e.g. - avr-libc: Modules -

Some hints:
Reduce all variables to their smallest form. You have time strings at the beginning, then later you have bytes.
Use bytes for all time operations, and values under 256. Use int for values under 65536. Avoid floating point math. Although many programmers are against global variables, they save memory if reused after they are needed, but you need to watch it! Have a look at Bits and Bytes in Reference area. Don't spell out words in strings, use abbreviations for shorter strings.

Thanks for the advice everyone.

Unfortunately moving to the Mega is not an option, due to the limited space requirements for the experiment I need a small form factor for the controller, plus it needs to be able to be soldered into place as the the system will be moved around a bit and we can't risk wires coming loose.

I have noticed that every string operation that I have removed gives me back approximately 300 or so bytes. Would somebody be able to help me remove the string operations in the printTime() and printDate() methods so they just return a char array?

I have also noticed that just opening a file on the SD card for writing uses up a hefty 4,326 bytes (approx 14% of available code limit), are there any other streamlined SD card libraries that anyone knows of that are available for datalogging. I have already tried to cut code out of the TFT library but it doesn't make any difference due to preprocessor commands for efficiency that don't include code if it isn't used.

Also Steinie44, what do you mean by "Don't spell out words, use abbreviations"? do you mean reduce the length of my variable names? I was under the impression that the compiler just replaces any variable names with an address or pointer during pre-compilation.

OK, in that event you might find that, since most of the size of a Mega is taken up by pins and tracks that are of no interest, building up a custom Mega equivalent from scratch might fix the problem, and you develop the job on a plain-vanilla Mega.

I note you have two time-related libraries. That is not necessarily a problem in itself but I can't see why you are doing that. I'm not familiar with the DS3232 but I understand all the DSxxxx clocks are coded the same way. I have found that the most code-efficient way to get the time is here

http://bildr.org/2011/03/ds1307-arduino/

This approach uses no libraries and no strings either

I use libraries for stuff like datestamping the SD, but am still in front by using the above to get the time. I found this while vainly struggling to use the Uno as a datalogger. No promises of salvation, but it might help.

IF the real job of your display is this:

    TFTscreen.text("Time:", 0, 0); // write the text to the top left corner
    TFTscreen.text("Date:", 0, 20);
    TFTscreen.text("Temp:", 0, 40);
    TFTscreen.text("Humid:", 0, 60);
    TFTscreen.text("Count1:", 0, 80);
    TFTscreen.text("Count2:", 0, 100);

I submit the LCD you are using is a bad choice. It is grossly underutilised, a waste of money, and probably a waste of space as well. If you want the fancy stuff, you pay a high price for it in memory, and speed as well for that matter. If you can do without it, a Nokia 5110 will deliver the above data with about 90% less code, and cost you $5.

The pictures below show a 5110 and a TFT. They are interchangeable, and the fundamental information they display is the same but don't even think about putting a UNO under the TFT, even if it did fit physically.

I have already commented on that string stuff. In short, any code with the letters str in it should be deemed unwarranted, and the kiss of death. There is also a lot of stuff involving const ints, unsigned longs, bytes, and god only knows what all else that suggests that your code is either very efficient or verbose junk, but it will take a better man than me to check. It needs checking nonetheless.

This code is just a simple clock with Nokia 5110. It shows the efficiency of both.

#include <PCD8544.h>
#include "Wire.h"
#define DS1307_ADDRESS 0x68
static PCD8544 lcd;

void setup(){
  Wire.begin();
  Serial.begin(9600);
  lcd.begin(84, 48);
  
  // Print a message to the LCD.
    lcd.setCursor(0,0);
  lcd.print("Today it is");
}

void loop(){
  printDate();
  delay(1000);

}

byte bcdToDec(byte val)  {
// Convert binary coded decimal to normal decimal numbers
  return ( (val/16*10) + (val%16) );
}

void printDate(){

  // Reset the register pointer
  Wire.beginTransmission(DS1307_ADDRESS);

  byte zero = 0x00;
  Wire.write(zero);
  Wire.endTransmission();

  Wire.requestFrom(DS1307_ADDRESS, 7);

  int second = bcdToDec(Wire.read());
  int minute = bcdToDec(Wire.read());
  int hour = bcdToDec(Wire.read() & 0b111111); //24 hour time
  int weekDay = bcdToDec(Wire.read()); //0-6 -> sunday - Saturday
  int monthDay = bcdToDec(Wire.read());
  int month = bcdToDec(Wire.read());
  int year = bcdToDec(Wire.read());


  lcd.setCursor(21,1);
    switch (weekDay)               // Friendly printout the weekday
  {
    case 1:
      lcd.print("MONDAY  ");
      Serial.print("MON  ");
      break;
    case 2:
      lcd.print("TUESDAY  ");
      Serial.print("TUE  ");
      break;
    case 3:
      lcd.print("WEDNESDAY  ");
      Serial.print("WED  ");
      break;
    case 4:
      lcd.print("THURSDAY  ");
      Serial.print("THU  ");
      break;
    case 5:
      lcd.print("FRIDAY  ");
      Serial.print("FRI  ");
      break;
    case 6:
      lcd.print("SATURDAY  ");
      Serial.print("SAT  ");
      break;
    case 7:
      lcd.print("SUNDAY  ");
       Serial.print("SUN  ");
      break;
  }

  Serial.print(monthDay);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.print(year);
  Serial.print(" ");
  Serial.print(hour);
  Serial.print(":");
  Serial.print(minute);
  Serial.print(":");
  Serial.println(second);
 
   lcd.setCursor(0,4); 
  lcd.print(monthDay);
  lcd.print("/");
  lcd.print(month);
  lcd.print("/");
  lcd.print(year);
  
  lcd.setCursor(0,5);
  
   if( second==0)
  {
   lcd.print("         ");
   lcd.setCursor(0,5);
   } 
  
  lcd.print(hour);
  lcd.print(":");
  lcd.print(minute);
  lcd.print(":");
  lcd.print(second);  
}

5110.jpg

steinie44:
Use int for all values under 65537 . . . Don't spell out words, use abbreviations.

Utter nonsense - on the eight bit Arduinos, "int" stores values -32768 to + 32767 and "unsigned int" 0 to +65535, so 65536 ("under 65537") would require a "long".
You save nothing on your code size by using abbreviated variable names.

Use int for all values under 65537 . . . Don't spell out words, use abbreviations.
Utter nonsense - on the eight bit Arduinos, "int" stores values -32768 to + 32767 and "unsigned int" 0 to +65535, so 65536 ("under 65537") would require a "long".
You save nothing on your code size by using abbreviated variable names.

Gee, Last time I checked, 65535 is less then 65537. Is this not so?????????????????? I mean '<65536'

Who said anything about abbreviated variable names?????????????????????????????
Serial.print("Temperature"); takes more memory then
Serial.print("Tmp");

I am here to help. I don't flame peoples posts as a few of the "so called experts" do.
Brattain Member does not mean God.

Now, try putting 65535 into an "int", as you suggested.
Oh dear.

You're right - I left mere god status behind a long time ago. :wink: