Out of memory, how to reduce this program's size

I'm a new arduino fan, and working on a project to record weather and GPS data to a SD card. I'm using the sparkfun weather shield and mini-SD shield, along with a GS407 gps receiver. I have modified the demo sketchs to get the below program. It works for a minute or two, but eventually locks. I believe I'm running out of memory on the arduino (compiled source is a little over 27K). I was hoping someone could take a look and recommend what I can do to shrink the size.

/* 
 Weather Shield with GPS
 By: Nathan Seidle
 modified to use with SD card shield
 
 License: This code is public domain
 Used with SparkFun GS407 GPS receiver
 */

#include <Wire.h> //I2C needed for sensors
#include "MPL3115A2.h" //Pressure sensor
#include "HTU21D.h" //Humidity sensor
#include <SoftwareSerial.h> //Needed for GPS
#include <TinyGPS++.h> //GPS parsing
#include <SD.h> //Micro SD card


TinyGPSPlus gps;

static const int RXPin = 5, TXPin = 4; //GPS is attached to pin 4(TX from GPS) and pin 5(RX into GPS)
SoftwareSerial ss(RXPin, TXPin); 

MPL3115A2 myPressure; //Create an instance of the pressure sensor
HTU21D myHumidity; //Create an instance of the humidity sensor

//Hardware pin definitions
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// digital I/O pins
const byte WSPEED = 3;
const byte STAT1 = 7;
const byte STAT2 = 8;
const byte GPS_PWRCTL = 6; //Pulling this pin low puts GPS to sleep but maintains RTC and RAM

const int chipSelect = 8; //for the SD card

// analog I/O pins
const byte REFERENCE_3V3 = A3;
const byte LIGHT = A1;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

//Global Variables
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
long lastSecond; //The millis counter to see when a second rolls by
byte seconds; //When it hits 60, increase the current minute
byte seconds_2m; //Keeps track of the "wind speed/dir avg" over last 2 minutes array of data
byte minutes; //Keeps track of where we are in various arrays of data
byte minutes_10m; //Keeps track of where we are in wind gust/dir over last 10 minutes array of data


//We need to keep track of the following variables:

float humidity = 0; // [%]
float tempf = 0; // [temperature F]
float pressure = 0;

float light_lvl = 455; //[analog value from 0 to 1023]

void setup()
{
  Serial.begin(9600);
  Serial.println("Weather Shield Example");

  ss.begin(9600); //Begin listening to GPS over software serial at 9600. This should be the default baud of the module.

  pinMode(STAT1, OUTPUT); //Status LED Blue
  pinMode(STAT2, OUTPUT); //Status LED Green
  
  pinMode(GPS_PWRCTL, OUTPUT);
  digitalWrite(GPS_PWRCTL, HIGH); //Pulling this pin low puts GPS to sleep but maintains RTC and RAM
  
  
  pinMode(REFERENCE_3V3, INPUT);
  pinMode(LIGHT, INPUT);

  //Configure the pressure sensor
  myPressure.begin(); // Get sensor online
  myPressure.setModeBarometer(); // Measure pressure in Pascals from 20 to 110 kPa
  myPressure.setOversampleRate(7); // Set Oversample to the recommended 128
  myPressure.enableEventFlags(); // Enable all three pressure and temp event flags 

  //Configure the humidity sensor
  myHumidity.begin();

  seconds = 0;
  lastSecond = millis();


  // turn on interrupts
  interrupts();
  
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(chipSelect, OUTPUT);

  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }


  Serial.println("Weather Shield online!");

}

void loop()
{
  //Keep track of which minute it is
  if(millis() - lastSecond >= 1000)
  {
    digitalWrite(STAT1, HIGH); //Blink stat LED
    
    lastSecond += 1000;

    //Take a speed and direction reading every second for 2 minute average
    if(++seconds_2m > 119) seconds_2m = 0;

    //if(seconds_2m % 10 == 0) displayArrays(); //For testing

    
    if(++seconds > 59)
    {
      seconds = 0;

      if(++minutes > 59) minutes = 0;
      if(++minutes_10m > 9) minutes_10m = 0;
    }

    //Report all readings every second
    printWeather();

    digitalWrite(STAT1, LOW); //Turn off stat LED
  }

  smartdelay(800); //Wait 1 second, and gather GPS data
}

//While we delay for a given amount of time, gather GPS data
static void smartdelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
      gps.encode(ss.read());
  } while (millis() - start < ms);
}


//Calculates each of the variables that wunderground is expecting
void calcWeather()
{
  //Calc humidity
  humidity = myHumidity.readHumidity();
 
  //Calc tempf from pressure sensor
  tempf = myPressure.readTempF();
 
  //Calc pressure
  pressure = myPressure.readPressure();

  //Calc light level
  light_lvl = get_light_level();
  
}

//Returns the voltage of the light sensor based on the 3.3V rail
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
float get_light_level()
{
  float operatingVoltage = analogRead(REFERENCE_3V3);

  float lightSensor = analogRead(LIGHT);
  
  operatingVoltage = 3.3 / operatingVoltage; //The reference voltage is 3.3V
  
  lightSensor = operatingVoltage * lightSensor;
  
  return(lightSensor);
}




//Prints the various variables directly to the port
//I don't like the way this function is written but Arduino doesn't support floats under sprintf
void printWeather()
{
  calcWeather(); //Go calc all the various sensors

  Serial.println();
  Serial.print("$");
  Serial.print("humidity=");
  Serial.print(humidity, 1);
  Serial.print(",tempf=");
  Serial.print(tempf, 1);
  Serial.print(",pressure=");
  Serial.print(pressure, 2);
  Serial.print(",light_lvl=");
  Serial.print(light_lvl, 2);

  Serial.print(",lat=");
  Serial.print(gps.location.lat(), 6);
  Serial.print(",lat=");
  Serial.print(gps.location.lng(), 6);
  Serial.print(",altitude=");
  Serial.print(gps.altitude.meters());
  Serial.print(",sats=");
  Serial.print(gps.satellites.value());

  char sz[32];
  Serial.print(",date=");
  sprintf(sz, "%02d/%02d/%02d", gps.date.month(), gps.date.day(), gps.date.year());
  Serial.print(sz);

  Serial.print(",time=");
  sprintf(sz, "%02d:%02d:%02d", gps.time.hour(), gps.time.minute(), gps.time.second());
  Serial.print(sz);

  Serial.print(",");
  Serial.println("#");

  File dataFile = SD.open("datalog2.txt", FILE_WRITE);
  
  //if the file is available, write to it:
  
  if (dataFile){
    dataFile.println();
    dataFile.print("humidity=");
    dataFile.print(humidity, 1);
    dataFile.print(",tempf=");
    dataFile.print(tempf, 1);
    dataFile.print(",pressure=");
    dataFile.print(pressure, 2);
    dataFile.print(",light_lvl=");
    dataFile.print(light_lvl, 2);

    dataFile.print(",lat=");
    dataFile.print(gps.location.lat(), 6);
    dataFile.print(",lat=");
    dataFile.print(gps.location.lng(), 6);
    dataFile.print(",altitude=");
    dataFile.print(gps.altitude.meters());
    dataFile.print(",sats=");
    dataFile.print(gps.satellites.value());

    dataFile.print(",date=");
    dataFile.print(gps.date.month());
    dataFile.print("/");
    dataFile.print(gps.date.day());
    dataFile.print("/");
    dataFile.print(gps.date.year());

    dataFile.print(",time=");
    dataFile.print(gps.time.hour());
    dataFile.print(":");
    dataFile.print(gps.time.minute());
    dataFile.print(":");
    dataFile.print(gps.time.second());


    dataFile.println("#");
    dataFile.close();
  }
  //if the file isn't open, print error
  else {
    Serial.println("SD card not present or error opening datalog.txt");
  }



}

Usual advice is better use of the F() macro, and see where that gets you.

Not sure what you mean..

Code is quite compact as is data, without running code looking for error which must be the cause..

You are not generating any additional data dynamically so RAM use is constant from compile time, the most likely cause is not setting a variable correctly then using a non initialized value in some form which causes the program loop to step outside its cvode parameters and apparently appear to stop in fact it just executing the old saying of garbage in - process = garbage out..

Remove bits of the system, test in reduced form till stable, then slowly add back pieces and identify what code causes the situation you are seeing..

Are you using an UNO ?

Is the function smartDelay() really smart or is it just me that's not smart.

It sounds like it could do an awful lot of gps.encode()s - whatever they do.

...R

Smart delay is part of tinygps+ examples

Not best solution bit it works

Not best solution bit it works

Well, except he's here because -something- isn't working.

Shouldn't lastsecond be an unsigned long? That's what millis() is. Probably not what is causing the problem you are having.

I'm sure the author of tinyGPS++ would disagree its just a mechanism to feed the encode routine whist in a delay

Looking at the code it not that routine, much more likely iin the SD card write...

tgsuperspec:
Not sure what you mean..

It consumes RAM a FLASH ROM:

 Serial.println("SD card not present or error opening datalog.txt");

It consumes only FLASH ROM:

 Serial.println(F("SD card not present or error opening datalog.txt"));

What does it show you for dynamic memory usage after a compile?

AWOL, the F() macro did help, thank you. It is much more stable now, but still is freezing at random points. I'll get output to serial and to the SD card for a while, then it appears that the program or one of shields has frozen and no more output. This is on an UNU R2. I may still be near the memory limits.

lar3ry:
What does it show you for dynamic memory usage after a compile?

How can I determine this?

You haven't shown us the code for those sensor libraries, it's difficult to know how compact they are.

One approach is to create your own minimised version of the sensor libraries, by taking out any crap that you don't need or use.

static const int RXPin = 5, TXPin = 4; //GPS is attached to pin 4(TX from GPS) and pin 5(RX into GPS)
SoftwareSerial ss(RXPin, TXPin);

This looks the wrong way round, to me. The first parameter of the SoftwareSerial constructor, is the pin which is the input signal into the Arduino. This would appear to be inconsistent with what the previous line in your code implies.

You have a lot of duplication of your output statements there.

One idea might be, to write all the outputs into a single char array, and then print that array to both the serial and the SD card.

lewisdw:

lar3ry:
What does it show you for dynamic memory usage after a compile?

How can I determine this?

After a compile, look in the bottom window of the IDE. It shows the Flash and RAM usage. My latest compile showed:

Sketch uses 1,838 bytes (5%) of program storage space. Maximum is 32,256 bytes.
Global variables use 201 bytes (9%) of dynamic memory, leaving 1,847 bytes for local variables. Maximum is 2,048 bytes.

I would have tried it myself, but I haven't taken the time to download the MPL3115A2 or HTU21D libraries.

  // turn on interrupts
  interrupts();

Why? Why are you diddling with interrupts?

lar3ry:

lewisdw:

lar3ry:
What does it show you for dynamic memory usage after a compile?

How can I determine this?

After a compile, look in the bottom window of the IDE. It shows the Flash and RAM usage. My latest compile showed:

Sketch uses 1,838 bytes (5%) of program storage space. Maximum is 32,256 bytes.
Global variables use 201 bytes (9%) of dynamic memory, leaving 1,847 bytes for local variables. Maximum is 2,048 bytes.

I would have tried it myself, but I haven't taken the time to download the MPL3115A2 or HTU21D libraries.

I'm using version 1.0.5 of the Arduino IDE, and mine does not show that information (I'm on a Mac if that matters). Mine only shows "Binary sketch size: 27,312 bytes (of a 32,256 byte maximum)"

PaulS:

  // turn on interrupts

interrupts();



Why? Why are you diddling with interrupts?

Oops. That was in the original source for the weather shield. I removed the interrupt routines, but missed that line. I'll take it out and try it without it.

Hello,

This code works but uses 27k once compilated, and when I try to add my own graphic outputs (text) ... it rruns out of memory at complilation!

What may I do to reduce the size?
thank you

#include <Wire.h>
#include "I2Cdev.h"
#include "RTIMUSettings.h"
#include "RTIMU.h"
#include "RTFusionRTQF.h" 
#include "CalLib.h"
#include <EEPROM.h>

RTIMU *imu;                                           // the IMU object
RTFusionRTQF fusion;                                  // the fusion object
RTIMUSettings settings;                               // the settings object

//  DISPLAY_INTERVAL sets the rate at which results are displayed

#define DISPLAY_INTERVAL  300                         // interval between pose displays

//  SERIAL_PORT_SPEED defines the speed to use for the debug serial port

#define  SERIAL_PORT_SPEED  115200

unsigned long lastDisplay;
unsigned long lastRate;
int sampleCount;

void setup()
{
  int errcode;
  
  Serial.begin(SERIAL_PORT_SPEED);
  Wire.begin();
  imu = RTIMU::createIMU(&settings);                        // create the imu object
  
  Serial.print("ArduinoIMU starting using device "); Serial.println(imu->IMUName());
  if ((errcode = imu->IMUInit()) < 0) {
    Serial.print("Failed to init IMU: "); Serial.println(errcode);
  }
  
  if (imu->getCalibrationValid())
    Serial.println("Using compass calibration");
  else
    Serial.println("No valid compass calibration data");

  lastDisplay = lastRate = millis();
  sampleCount = 0;
}

void loop()
{  
  unsigned long now = millis();
  unsigned long delta;
  
  if (imu->IMURead()) {                                // get the latest data if ready yet
    fusion.newIMUData(imu->getGyro(), imu->getAccel(), imu->getCompass(), imu->getTimestamp());
    sampleCount++;
    if ((delta = now - lastRate) >= 1000) {
      Serial.print("Sample rate: "); Serial.print(sampleCount);
      if (imu->IMUGyroBiasValid())
        Serial.println(", gyro bias valid");
      else
        Serial.println(", calculating gyro bias - don't move IMU!!");
        
      sampleCount = 0;
      lastRate = now;
    }
    if ((now - lastDisplay) >= DISPLAY_INTERVAL) {
      lastDisplay = now;
//      RTMath::display("Gyro:", (RTVector3&)imu->getGyro());                // gyro data
//      RTMath::display("Accel:", (RTVector3&)imu->getAccel());              // accel data
//      RTMath::display("Mag:", (RTVector3&)imu->getCompass());              // compass data
      RTMath::displayDegrees("Pose:", (RTVector3&)fusion.getFusionPose()); // fused output
      Serial.println();
    }
  }
}