Go Down

Topic: Lack of SRAM memory (Read 1 time) previous topic - next topic

blastboot

Apr 22, 2013, 04:54 pm Last Edit: Apr 22, 2013, 04:56 pm by blastboot Reason: 1
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).

Code: [Select]
#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...

blastboot

Code: [Select]
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!

AWOL

Code: [Select]
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;


Code: [Select]

            sprintf(ficheiroLer,"%s%s", noMes[carmes - '0' + 1], ".csv");
or similar.
Looks like a few of your GETs could live in PROGMEM too.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

blastboot

Thanks for answering!
Quote
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.

AWOL

Assume "carmes" is an ASCII decimal digit.
Subtracting '0' turns it into its actual decimal value.
Use that to form your array subscript.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

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?
If yes, and to free SRAM a little bit memory i should use it as the way i'm using it , like:
Code: [Select]
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?

dc42

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:

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

If you use this instead:

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

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

3. 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.

4. 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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

AWOL

Code: [Select]
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.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

blastboot

dc42 thank you very much, i will look to do what you have recommended.
Quote
3. 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
Code: [Select]
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:
Code: [Select]
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!

blastboot

I realize that i have a problem using
Quote
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:
Quote
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?

AWOL

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.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.

blastboot

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

liudr

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:
Code: [Select]
char ficheiroIndex[] = "i.htm";
char ficheiroTanque[] = "t.jpg";
char ficheiroBorder[] = "b.jpg";
char ficheiroFavicon[] = "f.ico";

blastboot

#13
Apr 24, 2013, 01:03 am Last Edit: Apr 24, 2013, 01:30 am by blastboot Reason: 1
I've done this:
Code: [Select]
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:
Code: [Select]
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...

GoForSmoke


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
  • as the first element.
  • , [1], [2] ... [2] is the 3rd, not 2nd.
Nick Gammon on multitasking Arduinos:
1) http://gammon.com.au/blink
2) http://gammon.com.au/serial
3) http://gammon.com.au/interrupts

Go Up