Weather warning device project - saving RAM so I can hopefully add more features

Firstly thanks to those that have contributed help to getting this to this point - its been a learning process for sure!
This has been my first serious attempt at achieving something practical with Arduino.

Below is the code as it stands (sourced from various code examples and stitched together with my own bits of "glue" to make it work)

I'd like to add SD card logging to this project if possible, with a view to then transferring the logfile to the filesystem on the 3G module for sending as en email attachment every 24hours - but I am running out of RAM. I have used the F() macro where i can to save RAM but there are a lot of strings stored as #defines that I cant use that for. Majenko suggested STRUCT for these but I cant get me head around the examples I have read as they all seem to relay on fixed or known length strings whereas these strings may need to be changed to suit various email addresses or server configurations.

FYI - I am doing this with a Duemilanove. I guess I could go to a Mega but currently can't do that without a lot of rewiring to suit SoftwareSerial pin allocations on the Mega. I also don't like the idea of having to go to a Mega and having so many unused I/O sitting around either - bit like a sledgehammer to crack a peanut!

Am i just expecting too much from an Arduino Duemilanove??

First part........

#include <SoftwareSerial.h>          
#include <dht.h>   
#include <Average.h>
#include <Time.h>  


dht DHT;

#define txPin 12
#define DHT22_PIN 3 
#define PIN_ANEMOMETER  2

#define MSECS_CALC_WIND_SPEED 10000

#define uint  unsigned int
#define ulong unsigned long

// 3G definitions
#define unoTXPin (5)
#define unoRXPin (4) 
#define powerGSMPin (8)

// AT commands related
#define AT_CREG_QUERY ("AT+CREG?")
#define AT_NETWORK_SETUP ("AT+CGSOCKCONT=1,\"IP\",\"telstra.internet\",\"0.0.0.0\",0,0")
#define AT_SMTPSRV ("AT+SMTPSRV=\"mail.bigpond.com\",25")
#define AT_SMTPAUTH ("AT+SMTPAUTH=0") 
#define AT_SMTPFROM ("AT+SMTPFROM=\"user@server.com.au\"")
#define AT_SMTPRCPT ("AT+SMTPRCPT=0,0,\"recipient@server.com\"")
#define AT_PUSHOVER ("AT+SMTPRCPT=0,0,\"somebiglongalphanumerickey@api.pushover.net\"") 
#define AT_SMTPSEND ("AT+SMTPSEND")
#define AT_CTZU ("AT+CTZU=1")
#define AT_CCLK ("AT+CCLK?")

#define EOS ('\0')
#define ESC (0x1A)
#define OK ("OK")
#define CTRL_Z ("\x1a")

SoftwareSerial GSMSerial(unoRXPin,unoTXPin);
SoftwareSerial LCD(0, txPin);

int temp, rh, wind, iSpeed;
int TimeHours, TimeMinutes, TimeSeconds;
float tRa, rhRa, wRa;

Average<float> Taverage(6);
Average<float> RHaverage(6); 
Average<float> Waverage(60); 

volatile int numRevsAnemometer = 0; 
ulong nextCalcSpeed;
ulong time; 

boolean AlertStatus = false;
boolean UpdateStatus = false; 
boolean SendEnable = false; 

char response[100];
char* valPos;  


void setup() {
  Serial.begin(9600);
  delay(500);
  pinMode(txPin, OUTPUT);
  LCD.begin(9600); 
    clearLCD();
      delay(600); 
    selectLineOne(); 
      LCD.print(F("startup message")); 
    selectLineTwo();   
      LCD.print(F(" info and stuff"));  
    
    Serial.println(F("startup message"));
    Serial.println(""); 
    Serial.println(F("SYS : Initializing"));
    
delay (5000);  
      
pinMode(PIN_ANEMOMETER, INPUT); 
digitalWrite(PIN_ANEMOMETER, HIGH);   
attachInterrupt(0, countAnemometer, FALLING); 
nextCalcSpeed = millis() + MSECS_CALC_WIND_SPEED; 


GSMSerial.begin(9600);   
GSM_power_on();     
  Serial.println(F("3G : Ready"));    
  clearLCD();         
  selectLineOne();           
    LCD.print(F("  3G Power ON"));     
delay(2000);         
while( (sendATcommand(AT_CREG_QUERY, "+CREG:0,1", 500) ||   
    sendATcommand(AT_CREG_QUERY, "+CREG:0,5", 500)) == 0);  
  Serial.println(F("3G : Registered"));  
  selectLineOne();        
    LCD.print(F("  3G Registered"));       


sendATcommand(AT_CTZU, "OK", 500);      
delay(200);                         
SyncTime();               
  Serial.print(F("SYS : Time Set  "));           
    if (hour() < 10)                  
      Serial.print("0");         
  Serial.print(hour());         
  Serial.print(":");       
    if (minute() < 10)        
      Serial.print("0");       
  Serial.println(minute());    
        
delay(5000);

clearLCD();                  
  selectLineOne();                
    LCD.print(F("System Ready...."));    
    

pinMode (6, INPUT);         
digitalWrite(6, HIGH);         
readDIPswitch();          

sendATcommand(AT_NETWORK_SETUP,"OK",500);      
sendATcommand(AT_SMTPSRV,"OK",500);                    
sendATcommand(AT_SMTPAUTH,"OK",500);                
sendATcommand(AT_SMTPFROM,"OK",500);              



sendATcommand(AT_SMTPRCPT,"OK",500);         
GSMSerial.println(F("AT+SMTPSUB=\"Fire Weather Warning Station Startup OK\""));    
GSMSerial.println(F("AT+SMTPBODY=\"Fire Weather Warning Station has been restarted....\""));    
if (SendEnable == true){                                  
  sendATcommand(AT_SMTPSEND,"OK",500);               
  Serial.println(F("SYS : System Start Email Sent"));      
}                                          

delay(10000); 
}



void loop(){
  
readDIPswitch();          

if ((hour() == 0) && (minute() == 0)){                      
  SyncTime;                                                 
}                                                          

/
calcWindSpeed();              
calcTempHumd();              


tRa = Taverage.rolling(temp);        
rhRa = RHaverage.rolling(rh);       
wRa = Waverage.rolling(wind);    


if ((minute()== 0) && (UpdateStatus == false)){          
  SendHourlyUpdate();                                      
}                              
if ((minute() != 0) && (UpdateStatus == true)){            
  (UpdateStatus = false);                 
}                               


clearLCD();                                            
  selectLineOne();                              
    LCD.print(F("Tp="));                     
    LCD.print(temp);                         
    LCD.print(F(" Rh="));                
    LCD.print(rh);                           
    LCD.print(F(" W="));                           
    LCD.print(wind);                                    
  selectLineTwo();                                      
    DisplayTime();                                         
    DisplayAlertStatus();                                  
    DisplayEmailStatus();                                 
        

if (tRa >=15 && tRa <20){                                         
  int index = rhRa/5;                                               
  int8_t windvals[] = {-1,31,35,38,40,43,45,45,49,49,53,53,56,58};  
  if (index >12) index =12;                                         
  if (wRa >= windvals[index]){                                     
    Serial.println(F("Trigger in 15-20 Deg range"));               
    GFDI_ALERT();                                              
    }                                                           
  else AllClearMessage();                                          
}                                                                 

if (tRa >=20 && tRa <25){
  int index = rhRa/5;
  int8_t windvals[] = {-1,29,33,36,38,40,43,43,46,46,50,50,53,55};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in 20-25 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}
 
if (tRa >=25 && tRa <30){
  int index = rhRa/5;
  int8_t windvals[] = {-1,27,30,33,36,38,40,40,44,44,47,47,50,52};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in 25-30 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}  
  
if (tRa >=30 && tRa <35){
  int index = rhRa/5;
  int8_t windvals[] = {-1,25,28,31,33,35,37,37,41,41,44,44,47,49};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in 30-35 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}
 
if (tRa >=35 && tRa <40){
  int index = rhRa/5;
  int8_t windvals[] = {-1,23,26,28,31,33,35,35,38,38,41,41,44,46};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in 35-40 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}
 
if (tRa >=40 && tRa <45){
  int index = rhRa/5;
  int8_t windvals[] = {-1,21,24,26,28,30,32,32,35,35,39,39,41,43};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in 40-45 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}

if (tRa >=45){
  int index = rhRa/5;
  int8_t windvals[] = {-1,19,22,24,26,28,30,30,33,33,36,36,39,40};
  if (index >12) index =12;
  if (wRa >= windvals[index]){
    Serial.println(F("Trigger in over 45 Deg range"));
    GFDI_ALERT();
    }
  else AllClearMessage();
}
   
delay(MSECS_CALC_WIND_SPEED);
}

second part....

void countAnemometer() {                     // 
   numRevsAnemometer++;                      
}                                            //    


void calcWindSpeed() {
  long speed = 24000;                       
  speed *= numRevsAnemometer;                //
  speed /= MSECS_CALC_WIND_SPEED;            //
  iSpeed = speed;                            //
  numRevsAnemometer = 0;                   
  wind = iSpeed / 10;                        //
}                                            //


void calcTempHumd(){
  int chk = DHT.read22(DHT22_PIN);           
  temp = DHT.temperature,0;                  
  rh = DHT.humidity,0;                       
}                                            //

void GFDI_ALERT(){                                          
if (AlertStatus == false){                             
  sendATcommand(AT_PUSHOVER,"OK",500);                    
  sendATcommand("AT+SMTPSUB=\"warning alert message\"","OK",500);    
  GSMSerial.println(F("AT+SMTPBODY=\"warnign alert message body that may be a couple of lines of text\""));   
  sendATcommand(AT_SMTPSEND,"OK",500);                
  Serial.println(F("SYS : Alert Sent"));                 
  (AlertStatus = true);                                 
  DisplayAlertStatus();                                  
  delay(1000);                                              //    
  }                                                         // 
}                                                           // 


void AllClearMessage(){
if (AlertStatus == true){                                   
  sendATcommand(AT_PUSHOVER,"OK",500);                      
  sendATcommand("AT+SMTPSUB=\"warnign message all clear subject\"","OK",500);    
  GSMSerial.println(F("AT+SMTPBODY=\"body of all clear message thatr may be a couple of lines long\""));   
  sendATcommand(AT_SMTPSEND,"OK",500);                   
  Serial.println(F("SYS : All Clear Sent"));               
  (AlertStatus) = false;                                    
  DisplayAlertStatus();                                    
  delay(1000);                                              // 
  }                                                         //
}                                                           //          


void DisplayAlertStatus(){          
  if (AlertStatus == true){        
    selectAlertPos();              //
      LCD.print(F("ALERT!"));      //
  }                                //
  else{                            // 
    selectAlertPos();              // 
      LCD.print(F("**OK**"));      //
  }                                //
}                                  //

/
void DisplayEmailStatus(){
  if (SendEnable == true){         // 
     selectEmailPos();             //
       LCD.print(F("@"));          //
  }                                //   
  else{                            //
     selectEmailPos();             //
       LCD.print(F(" "));          //
  }                                //
}                                  // 


void SendHourlyUpdate(){
  if (UpdateStatus == false){                                                      
      sendATcommand(AT_SMTPRCPT,"OK",500);                                          
      sendATcommand("AT+SMTPSUB=\"Hourly Weather Update\"","OK",500);  
      GSMSerial.print(F("AT+SMTPBODY=\"Current Avg Temp = "));                      
      GSMSerial.print(tRa);                                                          //  
      GSMSerial.print(F(" DegC,  Current Avg RH = "));                               //
      GSMSerial.print(rhRa);                                                         //  
      GSMSerial.print(F(" %,  Current Avg Windspeed = "));                           // 
      GSMSerial.print(wRa);                                                          //  
      GSMSerial.println(F(" km/h\""));                                               //
      if (SendEnable == true){                                                       
        sendATcommand(AT_SMTPSEND,"OK",500);                                       
        Serial.print(F("SYS : Hourly Update Sent "));                               
        Serial.print(hour());                                                        // 
        Serial.print(":");                                                           //
        if (minute() < 10)                                                           //
          Serial.print("0");                                                         //
        Serial.println(minute());                                                    // 
      }                                                                              //
    (UpdateStatus) = true;                                                          
  }
}


void clearLCD(){    
  LCD.write(0xFE);   
  LCD.write(0x01);    
}

void selectLineOne(){  
  LCD.write(0xFE);     
  LCD.write(128);      
}

void selectLineTwo(){  
  LCD.write(0xFE);     
  LCD.write(192);      
}

void selectAlertPos(){ 
  LCD.write(0XFE);    
  LCD.write(199);      
}

void selectEmailPos(){ 
   LCD.write(0XFE);    
   LCD.write(207);     
}


void SyncTime(){
  Serial.println("Sync Time");
  sendATcommand(AT_CCLK, "+CCLK:  ", 500);               
    char* valPos = response +  19;                       
    TimeHours = atoi(valPos);                            
    TimeMinutes = atoi(valPos + 3);                      
    TimeSeconds = atoi(valPos + 3);                       
  setTime(TimeHours,TimeMinutes,TimeSeconds,0,0,0);      
}                                                         //


void DisplayTime(){
  if (hour() < 10){                                        
        LCD.print("0");                                    //
      }                                                    //
  LCD.print(hour());                                       
  LCD.print(":");                                          
  if (minute() < 10){                                      
      LCD.print("0");                                      //  
      }                                                    //  
    LCD.print(minute());                                   
}                                                          //


void readDIPswitch(){
  //Serial.println("Check DIP Switch");
  if (digitalRead(6) == 1){                                
  SendEnable = false;                                      //
  }                                                        //
else{                                                      //
  SendEnable = true;                                      
  }                                                        //
}                                                          //


int8_t sendATcommand(char* ATcommand, char* expected_answer1,
unsigned int timeout)
{
  uint8_t x=0, answer=0;
  unsigned long previous;                                     
  memset(response, EOS, 100);                                 
  delay(100);
  while( GSMSerial.available() > 0) GSMSerial.read();         
  GSMSerial.println(ATcommand);                               
  x = 0;
  previous = millis();
  do{                                                         
    if(GSMSerial.available() != 0){
      response[x] = GSMSerial.read();
      x++;
      
      if (strstr(response, expected_answer1) != NULL){        
        answer = 1;
      }
    }
  }
  while((answer == 0) && ((millis() - previous) < timeout));  
  return answer;
}


void GSM_power_on(){
  Serial.println("3G : Module Powering Up");                 
  uint8_t answer=0;                                          //   
  answer = sendATcommand("AT", "OK", 2000);                  
  if (answer == 0){                                          
    digitalWrite(powerGSMPin,HIGH);                          /
    delay(400);                                              //    
    digitalWrite(powerGSMPin,LOW);                           //
    delay(6000);                                             // 
    while(answer == 0){                                      
      answer = sendATcommand("AT", OK, 2000);                
      Serial.println(F("Wait..."));                         
    }                                                        //
  }                                                          //  
}

Hi Stocky

Yes you do use a lot of RAM. Good you use the F macro to store your print to serial and lcd in FLASH memory.
However you also have a lot of function calling like:

sendATcommand("AT+SMTPSUB=\"warning alert message\"","OK",500);

This also uses RAM.

I suggest you have a look at this page:

and try to store your char array in FLASH

Br
Lars

FYI - I am doing this with a Duemilanove. I guess I could go to a Mega but currently can't do that without a lot of rewiring to suit SoftwareSerial pin allocations on the Mega. I also don't like the idea of having to go to a Mega and having so many unused I/O sitting around either - bit like a sledgehammer to crack a peanut!

A Mega is not that much more expensive than a Duemilanove. The extra memory will be really handy. I think that it's a bit more like using a small ball peen hammer, instead of a tack hammer, to crack a walnut.

As for rewiring to suit SoftwareSerial pins, forget it. You'd have three more hardware serial ports that are more reliable, anyway. You'd still need to rewire, but you could get rid of SoftwareSerial.

void calcWindSpeed() {
  long speed = 24000;                       
  speed *= numRevsAnemometer;                //
  speed /= MSECS_CALC_WIND_SPEED;            //
  iSpeed = speed;                            //
  numRevsAnemometer = 0;                   
  wind = iSpeed / 10;                        //
}                                            //

For such a simple function, this code relies on a lot of magic numbers and global variables. It should take an argument - the number of times the anemometer has rotated - and it should return a value - the wind speed. The conversion of speed (a long int) to iSpeed (a useless global variable) is completely unnecessary. The Arduino is perfectly capable of dividing a long by 10.

And, those empty comments look pretty stupid. 8)

I have been fiddeling around with PROGMEM and have come up with a solution to save RAM. Please notice the the saving will increase with the number of times you call the sendATcommand function.

// Include macros and functions for  PROGMEM
#include <avr/pgmspace.h>

// Large enough to hold the longest AT command +1
static char ATcommandBuffer[100] = "";

// Large enough to hold the longest expected answer +1
static char expected_answerBuffer[100] = "";


void setup() {
  
  //
  Serial.begin( 9600 );
  Serial.println( F("Another way up'n running") );
  
  // Your way
  Serial.println( F("\nYour way" ) );
  sendATcommand("AT+SMTPSUB=\"warning alert message\"","OK",500);
  
  // Another way
  Serial.println( F("\nAnother way") );
  strcpy_P( ATcommandBuffer, PSTR("AT+SMTPSUB=\"warning alert message\"" ) );
  strcpy_P( expected_answerBuffer, PSTR("OK") );
  sendATcommand( ATcommandBuffer, expected_answerBuffer, 500);
 
  Serial.print( F("Free RAM") );
  Serial.println( freeRam() );
  
}

void loop() {
  // put your main code here, to run repeatedly: 
  
}

int8_t sendATcommand(char* ATcommand, char* expected_answer, unsigned int timeout){
  Serial.print( F("ATcommand: ") );
  Serial.println( ATcommand );
  Serial.print( F("expected_answer: ") );
  Serial.println( expected_answer );
}

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Also have a look here: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=38003‏

I got much wiser reading that.

Br
Lars

Sorry for all the empty comments.......I had to delete all my commenting to post the code without it taking 10 posts!

Stocky

Hi Lars , I tried to implement this in my code but lost 100bytes of free RAM instead of gaining some?

I'm studying that link you gave me to try and work it out!

EDIT
DERP! - which of course is the extra 100 char buffer I defined!

Still - it doesn't seem to make any different to the amount of RAM the code is using?
I read that page you pointed too and compared that with your code example and tried to implement it a few times in my code but doesn't seem to make a diff! I'll keep looking to see if I can fidn why

Tim

larsv:
I have been fiddeling around with PROGMEM and have come up with a solution to save RAM. Please notice the the saving will increase with the number of times you call the sendATcommand function.

// Include macros and functions for  PROGMEM

#include <avr/pgmspace.h>

// Large enough to hold the longest AT command +1
static char ATcommandBuffer[100] = "";

// Large enough to hold the longest expected answer +1
static char expected_answerBuffer[100] = "";

void setup() {
 
 //
 Serial.begin( 9600 );
 Serial.println( F("Another way up'n running") );
 
 // Your way
 Serial.println( F("\nYour way" ) );
 sendATcommand("AT+SMTPSUB="warning alert message"","OK",500);
 
 // Another way
 Serial.println( F("\nAnother way") );
 strcpy_P( ATcommandBuffer, PSTR("AT+SMTPSUB="warning alert message"" ) );
 strcpy_P( expected_answerBuffer, PSTR("OK") );
 sendATcommand( ATcommandBuffer, expected_answerBuffer, 500);

Serial.print( F("Free RAM") );
 Serial.println( freeRam() );
 
}

void loop() {
 // put your main code here, to run repeatedly:
 
}

int8_t sendATcommand(char* ATcommand, char* expected_answer, unsigned int timeout){
 Serial.print( F("ATcommand: ") );
 Serial.println( ATcommand );
 Serial.print( F("expected_answer: ") );
 Serial.println( expected_answer );
}

int freeRam () {
 extern int __heap_start, *__brkval;
 int v;
 return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}




Also have a look here: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=38003%E2%80%8F

I got much wiser reading that.

Br
Lars

OK - got it working - thanks Lars :slight_smile:
Free RAM now 898 vs 404 XD
Had to increase the send buffer to 150 to take a couple of really long strings but the overall gains far outweigh that loss.
Now to shuffle the code around to make it read nicely again as it looks terrible now!

Stocky