Lack of SRAM memory

Hello, I've finished to write and compile my program, but when it start running the arduino runs out of memory and it gives strange characters to serial.
I need some help to optimize my code... to make more SRAM available (Flash memory is good: 31.030 bytes).

#include <SPI.h>
#include <Ethernet.h>
#include <VirtualWire.h>
#include <Wire.h>
#include "SdFatUtil.h"

//VARIÁVEIS PARA O RTC (DS1307)
//SCL      <--> Analog Input 5
//SDA      <--> Analog Input 4
#define DS1307_I2C_Endereco 0x68  // This is the I2C address
#define I2C_Write Wire.write 
#define I2C_Read Wire.read
byte segundo, minuto, hora, diaSemana, dia, mes, ano;

//CONVERSÃO DE BCD PARA DECIMAL
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

char* noMes[] = {
  "","Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"};

//DECLARAÇÃO DOS PINOS
const byte ledInfo = 2;
const byte ledRececao = 3;
const byte pinoRececao = 8;
const byte BUFSIZ = 128;

//DECLARAÇÃO DAS VARIÁVEIS GLOBAIS
char mensagem[6];
unsigned int dadosAgua = 0;
float dadosBat = 0.0;
float percentagem = 0;
unsigned long litros = 0;
boolean Log = false;
int altAgua = 0;

//CONFIG ETHERNET SHIELD
byte mac[] = { 
  0x80, 0xA2, 0xDA, 0x00, 0xEA, 0x8C };
byte ip[] = { 
  10, 50, 196, 80 };
char ficheiroIndex[] = "i.htm";
char ficheiroTanque[] = "t.jpg";
char ficheiroBorder[] = "b.jpg";
char ficheiroFavicon[] = "f.ico";
char ficheiroLog[8];
char* ficheiroLer;
EthernetServer server = EthernetServer(80);
EthernetClient client;

//PROGRAMAÇÃO DO CARTÃO SD
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

//ARMAZENA SEQUÊNCIAS DE ERRO NA MEMÓRIA FLASH PARA ECONOMIZAR RAM
#define error(s) error_P(PSTR(s))
void error_P(const char* str) {
  PgmPrint("Erro: ");
  SerialPrintln_P(str);
  if (card.errorCode()) {
    PgmPrint("Erro cart.SD: ");
    Serial.print(card.errorCode(), HEX);
    Serial.print(',');
    Serial.println(card.errorData(), HEX);
  }
  digitalWrite(ledInfo, HIGH);
  while(1);
}

void setup() {

  Serial.begin(9600);
  delay(100);

  pinMode(ledInfo, OUTPUT);
  pinMode(ledRececao, OUTPUT);
  pinMode(10, OUTPUT);

  //INICIA A COMUNICAÇÃO I2C
  Wire.begin();

  //LIGA E DESLIGA OS 2 LEDS PARA INDICAR QUE ESTÃO OPERACIONAIS
  digitalWrite(ledInfo, HIGH);
  digitalWrite(ledRececao, HIGH);
  digitalWrite(10, HIGH);
  delay(1000);
  digitalWrite(ledInfo, LOW);
  digitalWrite(ledRececao, LOW);

  PgmPrint("RAM: ");
  Serial.println(FreeRam());

  //INICIALIZA O CARTÃO SD EM "FULL SPEED", MÁXIMO DESEMPENHO. (PARA EVITAR ERROS COM LIGAÇÃO À BREADBOARD DEVE ESTAR EM "HALF SPEED")
  if (!card.init(SPI_FULL_SPEED, 4)) error("Erro Init cart.SD!");

  //INICIALIZA O VOLUME FAT
  if (!volume.init(&card)) error("Erro vol cart.SD!");

  //LISTA NA SÉRIE OS FICHEIROS QUE ESTÃO NA RAIZ DO CARTÃO SD, COM DATA E TAMANHO
  if (!root.openRoot(&volume)) error("Erro root cart.SD!");

  //INICIA O SERVIDOR
  Ethernet.begin(mac, ip);
  server.begin();
  delay(100);

  //CONFIGURA E INICIA O RECEPTOR
  vw_set_rx_pin(pinoRececao);
  vw_setup(2000);
  vw_rx_start();

  //LED PISCA 3 VEZES A INDICAR TODA A CONFIGURAÇÂO CONCLUÍDA COM SUCESSO
  for (int i = 0; i<3; i++) {
    digitalWrite(ledInfo, HIGH);
    delay(250);
    digitalWrite(ledInfo, LOW);
    delay(250);
  }
  Serial.println(F("Config concl"));
}

void loop() {

  altAgua++;
  rececaoRF();
  clienteLigado();
  delay(500);
}

//RECEPÇÃO DOS DADOS POR RF 433MHz
void rececaoRF() {

  byte buf[VW_MAX_MESSAGE_LEN];
  byte buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) {
    digitalWrite(ledRececao, HIGH);
    for (int j = 0; j<buflen; j++) {
      mensagem[j] = buf[j];
    }
    if (mensagem[0] == 'R') {
      dadosAgua = atoi(&mensagem[1]);
    } 
    else if (mensagem[0] == 'B') {
      dadosBat = atof(&mensagem[1]);
      Serial.print(F("Volt Bat: "));
      Serial.println(dadosBat);
    }
    Serial.print(F("Msg Receb: "));
    Serial.println(mensagem);
    memset(&buf, 0, sizeof(buf));
    memset(&mensagem, 0, sizeof(mensagem));
    calcAgua();
    gravarDados();
    digitalWrite(ledRececao, LOW);
  }
}

CONTINUES...

void calcAgua() {
  percentagem = ((465-dadosAgua)*100)/465;
  litros = ((465-dadosAgua)*600000)/465;
}

void gravarDados() {

  //FAZ O STACK POINTER IR PARA O INÍCIO
  Wire.beginTransmission(DS1307_I2C_Endereco);
  I2C_Write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_I2C_Endereco, 7);

  segundo = bcdToDec(I2C_Read() & 0x7f); //MASCARA PQ É BIT DE CONTROLE
  minuto  = bcdToDec(I2C_Read());
  hora    = bcdToDec(I2C_Read() & 0x3f); //MÁSCARA PQ É BIT DE CONTROLE
  dia     = bcdToDec(I2C_Read());
  mes     = bcdToDec(I2C_Read());
  ano     = bcdToDec(I2C_Read());

  sprintf(ficheiroLog,"%s%s", noMes[mes], ".csv");

  Log = file.open(&root, ficheiroLog, O_WRITE | O_APPEND);
  if (Log){
    //HORA
    file.print(dia, DEC);
    file.print(" ");
    file.print(noMes[mes]);
    file.print(" ");
    file.print("20");
    if (ano < 10) {
      file.print("0");
    }
    file.print(ano, DEC);
    file.print(" , ");
    if (hora < 10) {
      file.print("0");
    }
    file.print(hora, DEC);
    file.print(":");
    if (minuto < 10) {
      file.print("0");
    }
    file.print(minuto, DEC);
    file.print(":");
    if (segundo < 10) {
      file.print("0");
    }
    file.print(segundo, DEC);
    //DADOS    
    file.print(" , ");
    file.print(percentagem, 1);
    file.print(" , ");
    file.println(litros);
    file.close();
    Log = false;
  } 
  else {
    Serial.println(F("ERRO grav LOG!"));
  }
}

void clienteLigado() {
  char clientline[BUFSIZ];
  int index = 0;
  client = server.available();
  if (client) {
    digitalWrite(ledInfo, HIGH);
    delay(1);
    digitalWrite(ledInfo, LOW);
    index = 0;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          if (index >= BUFSIZ) 
            index = BUFSIZ - 1;
          continue;

        }
        clientline[index] = 0;
        Serial.println(clientline);
        digitalWrite(ledInfo, HIGH);
        delay(1);
        digitalWrite(ledInfo, LOW);        

        if (strstr(clientline, "GET / ") != 0) {
          headerRespostaHttp();
          conTypeTxt();
          ficheiroLer = ficheiroIndex;
          lerFicheiroCartao();

        }
        else if (strstr(clientline, "GET /tqe") != 0) {
          headerRespostaHttp();
          conTypeImg();
          ficheiroLer = ficheiroTanque;
          lerFicheiroCartao();
        }
        else if (strstr(clientline, "GET /bdr") != 0) {
          headerRespostaHttp();
          conTypeImg();
          ficheiroLer = ficheiroBorder;
          lerFicheiroCartao();
        }
        else if (strstr(clientline, "GET /fin") != 0) {
          headerRespostaHttp();
          client.println(F("Content-Type: image/x-icon"));
          client.println();
          ficheiroLer = ficheiroFavicon;
          lerFicheiroCartao();
        }
        else if (strstr(clientline, "GET /&dadosagua") != 0) {
          headerRespostaHttp();
          conTypeTxt();
          client.print(altAgua);
          client.println(F("px"));
          Serial.println(altAgua);
        }
        else if (strstr(clientline, "GET /&logg") != 0) {
          headerRespostaHttp();
          char carMes = clientline[10];
          switch (carMes) {
          case '1': 
            sprintf(ficheiroLer,"%s%s", noMes[1], ".csv");
            //ficheiroLer = "Jan.csv";
            break;
          case '2': 
            sprintf(ficheiroLer,"%s%s", noMes[2], ".csv");
            //ficheiroLer = "Fev.csv";
            break;
          case '3': 
            sprintf(ficheiroLer,"%s%s", noMes[3], ".csv");
            //ficheiroLer = "Mar.csv";
            break;
          case '4': 
            sprintf(ficheiroLer,"%s%s", noMes[4], ".csv");
            //ficheiroLer = "Abr.csv";
            break;
          case '5': 
            sprintf(ficheiroLer,"%s%s", noMes[5], ".csv");
            //ficheiroLer = "Mai.csv";
            break;
          case '6': 
            sprintf(ficheiroLer,"%s%s", noMes[6], ".csv");
            //ficheiroLer = "Jun.csv";
            break;
          case '7': 
            sprintf(ficheiroLer,"%s%s", noMes[7], ".csv");
            //ficheiroLer = "Jul.csv";
            break;
          case '8': 
            sprintf(ficheiroLer,"%s%s", noMes[8], ".csv");
            //ficheiroLer = "Ago.csv";
            break;
          case '9': 
            sprintf(ficheiroLer,"%s%s", noMes[9], ".csv");
            //ficheiroLer = "Set.csv";
            break;
          case 'o': 
            sprintf(ficheiroLer,"%s%s", noMes[10], ".csv");
            //ficheiroLer = "Out.csv";
            break;
          case 'n': 
            sprintf(ficheiroLer,"%s%s", noMes[11], ".csv");
            //ficheiroLer = "Nov.csv";
            break;
          case 'd': 
            sprintf(ficheiroLer,"%s%s", noMes[12], ".csv");
            //ficheiroLer = "Dez.csv";
            break;
          }
          client.print(F("Content-Disposition: attachment; filename=\"LOG_"));
          client.print(ficheiroLer);
          client.println(F("\""));
          client.println();
          lerFicheiroCartao();
        }
        else {
          client.println(F("HTTP/1.1 404 Not Found"));
          conTypeTxt();
          client.println(F("<h2>Erro 404</h2>"));
          client.println(F("<s2>Caminho/ficheiro nao existe.<s2>"));
          client.println(F(""));
        }
        break;
      }
    }
    delay(1);
    client.stop();
  }
}

void headerRespostaHttp() {
  client.println(F("HTTP/1.1 200 OK"));
}

void conTypeTxt() {
  client.println(F("Content-Type: text/html"));
  client.println();
}

void conTypeImg() {
  client.println(F("Content-Type: image/jpeg"));
  client.println();
}

void lerFicheiroCartao() {
  boolean ficheiro = false;
  ficheiro = file.open(&root, ficheiroLer, O_READ);
  if (ficheiro) {
    byte clientBuf[64];
    int clientCount = 0;

    while(file.available()) {
      clientBuf[clientCount] = file.read();
      clientCount++;
      if (clientCount > 63) {
        client.write(clientBuf, 64);
        clientCount = 0;
      }
    }
    if (clientCount > 0) {
      client.write(clientBuf, clientCount);
    }
    file.close();
    memset(&ficheiroLer, 0, sizeof(ficheiroLer));
  }
}

Thanks in advance!

case '1': 
            sprintf(ficheiroLer,"%s%s", noMes[1], ".csv");
            //ficheiroLer = "Jan.csv";
            break;
          case '2': 
            sprintf(ficheiroLer,"%s%s", noMes[2], ".csv");
            //ficheiroLer = "Fev.csv";
            break;
            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");

or similar.
Looks like a few of your GETs could live in PROGMEM too.

Thanks for answering!

sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");

I couldn't understand what does this do in this format. Can you give me a simple explanation?

The progmem is a good idea. I will look for it to know how to use it.

Assume "carmes" is an ASCII decimal digit.
Subtracting '0' turns it into its actual decimal value.
Use that to form your array subscript.

Ok, so let's say that carMes is char '1'... the function as you said, subtracts '0' to '1' which gives '1' and then adds '1' which gives '2', so it will look at position 2 of char array, is this?
If yes, and to free SRAM a little bit memory i should use it as the way i'm using it , like:

switch (carMes) {
          case '1': 
            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");
            //ficheiroLer = "Jan.csv";
            break;
          case '2': 
            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");
            //ficheiroLer = "Fev.csv";
            break;

Or another form?

  1. You have a number of variables such as mensagem, segundo etc. that are used in only one function, and whose values do not (as far as i can see) need to be preserved between calls to that function. Declare those as local variables inside the function, so that they do not occupy storage all the time.

  2. You have a number of instances where you are passing a literal string as a parameter (not the format parameter) to sprintf, for example:

sprintf(ficheiroLer,"%s%s", noMes[2], ".csv");

If you use this instead:

sprintf(ficheiroLer,"%s.csv", noMes[2]);

then you will save one of the string literals, and hence a few bytes.

  1. You use the same string literal several times, for example ".csv", "%s%s" and " , ". Depending on whether gcc is performing string pooling or not, you might save memory by naming string literals that you use more than once.

  2. Other than those, it's mostly a case of moving string literals into progmem. There are versions of some of the string handling functions that use progmem directly, for example strstr_P.

switch (carMes) {
          case '1': 
            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");
            //ficheiroLer = "Jan.csv";
            break;
          case '2': 
            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");
            //ficheiroLer = "Fev.csv";
            break;

No, the switch is now unnecessary, because you're doing the subscript calculation the same for every case.
You may want to use the switch for your non-consecutive months at the end of the year.

dc42 thank you very much, i will look to do what you have recommended.

  1. You use the same string literal several times, for example ".csv", "%s%s" and " , ". Depending on whether gcc is performing string pooling or not, you might save memory by naming string literals that you use more than once.

You're telling to use like char name[]=".csv" and everytime i need to "write" that i point to "name" char. Is this?

AWOL ok i've understanding it now. So... looking to dc42 comment i suppose that i can put your ideas in the function, and it will look like:

sprintf(ficheiroLer,"%s.csv", noMes[carmes - '0' + 1]);

It's good like that?

(sorry for my english but it is not my native language)

Thanks for helping me!

I realize that i have a problem using

sprintf(ficheiroLer,"%s.csv", noMes[carmes - '0' + 1]);

because i have 12 log files -> one for each month <- and i'm expecting to receive &logg1 (for Jan), &logg2 (for Feb) ... until &logg9 (for Sep) and because i'm looking only to position 10:

char carMes = clientline[10];

i've put &loggo (for Oct), &loggn (for Nov) and &loggd (for December). So that function will not work for the last 3 months.
It's possible to do something like char carMes be equal to position 10 and 11 of clientline and then naming &logg from 01 to 12 for this function to work?

I think I already pointed that out.
Lots of ways to handle it - look-up table, multiple ternary operators, switch/case for the last three months.

Wich is the best way to save RAM? I've putted the month char array to progmem already

To save SRAM you can save the entire month string as one string in the PROGMEM and extract only 3 characters at time (could be hard on syntax).
You can also use F() on these:

char ficheiroIndex[] = "i.htm";
char ficheiroTanque[] = "t.jpg";
char ficheiroBorder[] = "b.jpg";
char ficheiroFavicon[] = "f.ico";

I've done this:

prog_char ficheiroIndex[] PROGMEM = "i.htm";
prog_char ficheiroTanque[] PROGMEM = "t.jpg";
prog_char ficheiroBorder[] PROGMEM = "b.jpg";
prog_char ficheiroFavicon[] PROGMEM = "f.ico";
PROGMEM const char* fichPag[] = {
  ficheiroIndex, ficheiroTanque, ficheiroBorder, ficheiroFavicon};
char* ficheiroLer;

So the char array will be stored in progmem.

And for log file i've done this:

else if (strstr(clientline, "GET /&logg") != 0) {
          
          char carMes = clientline[10];
          byte num = atoi(&carMes);
          if (num > 0 && num<10) { 
            strcpy_P(nomeMes, (char*)pgm_read_word(&(noMes[carMes - '0'])));
            sprintf(ficheiroLer,"%s.csv", nomeMes);  <------- printing nomeMes before this line returns Jan as expected 
            Serial.println(ficheiroLer);  <-------- This isn't printed and arduino restarts...
          }
          else {
          switch (carMes) {
          case 'o': 
            strcpy_P(nomeMes, (char*)pgm_read_word(&(noMes[10])));
            sprintf(ficheiroLer,"%s.csv", nomeMes); 
            break;
          case 'n': 
            strcpy_P(nomeMes, (char*)pgm_read_word(&(noMes[11])));
            sprintf(ficheiroLer,"%s.csv", nomeMes); 
            break;
          case 'd': 
            strcpy_P(nomeMes, (char*)pgm_read_word(&(noMes[12])));
            sprintf(ficheiroLer,"%s.csv", nomeMes); 
            break;
          }
          }
          headerRespostaHttp();
          client.print(F("Content-Disposition: attachment; filename=\"LOG_\""));
          //client.print(ficheiroLer);
          //client.println(F("\""));
          client.println();
          lerFicheiroCartao();
        }

But when it receives GET /&logg1 (for example) arduino restarts. FreeRam() tells me that it has 925 bytes of memory, because i've comment almost the entire program to see if this part works, but it restarts...

blastboot:
Ok, so let's say that carMes is char '1'... the function as you said, subtracts '0' to '1' which gives '1' and then adds '1' which gives '2', so it will look at position 2 of char array, is this?

Arrays start with [0] as the first element. [0], [1], [2] ... [2] is the 3rd, not 2nd.

you're right i know that but write it wrong... :blush:

I'm sorry that I am not answering your question, but I have found a library that tells you how much SRAM you have left. It could be useful in the future for diagnosing problems causeb by the lack of SRAM: Arduino Playground - AvailableMemory.

blastboot:
you're right i know that but write it wrong... :blush:

I speak logic. The code is

 noMes[carmes - '0' + 1]

If carmes is '0' then the index will be 1 because of the + 1. You waste noMes[0] and must have an extra noMes[] to hold the last because the + 1 moves all up by 1, you see?

int Arry[ 4 ] = { 1, 2, 3, 4 };
Arry[0] == 1
Arry[3] == 4

for ( byte i = 0; i < 4; i++ )
{
Serial.println( Arry[ i ] );
}

will print
1
2
3
4

Just a couple of thoughts. First off, this doesn't look good to me, as it indicates
almost all of the Flash in the chip [I assume it's a UNO] has been used up, unless
I'm reading it wrong.

(Flash memory is good: 31.030 bytes).

Secondly, personally I don't see much in the code posted originally to indicate the
strings use up very much RAM. I would think that more likely the libraries being
called are the bad boys. Do you know what size RAM buffers each of them uses?

#include <SPI.h>
#include <Ethernet.h>
#include <VirtualWire.h>
#include <Wire.h>
#include "SdFatUtil.h"

blastboot:
dc42 thank you very much, i will look to do what you have recommended.

  1. You use the same string literal several times, for example ".csv", "%s%s" and " , ". Depending on whether gcc is performing string pooling or not, you might save memory by naming string literals that you use more than once.

You're telling to use like char name[]=".csv" and everytime i need to "write" that i point to "name" char. Is this?

I'm suggesting that may save memory - it depends on whether gcc is using string pooling both in flash and in RAM when it is invoked from the Arduino IDE. Try it on a few strings to see whether it saves any memory before you spend time applying it on the whole program. You should always use "const char*" instead of "char*" when naming string literals.