Reducing SRAM usage by limiting scope of objects.

I'm trying to reduce SRAM usage in my project. One of the steps I took was to take a set of functions which are only run in setup() and turn them into an object which only exists in setup.

Before I had:

#include "console.h"
void setup(){
  startConsole(); // This called about 8 different functions with lots of strings. Even though I put the strings in PROGMEM, still big memory hog.
}

I changed it to this:

#include "console.h"
void setup(){
  if ( startConsole() ) { // now a simple function that returns a boolean
    ConsoleObj console; // instantiate console object with all those methods.
    console.start();
  }
}

So it still works and does save some SRAM according to the freeMem library:

int freeMemory() {
  int free_memory;
  if ((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__heap_start);
  } else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
    free_memory += freeListSize();
  }
  return free_memory;
}

void setup(){
  Serial.print(freeMemory());  
  if ( startConsole() ) { 
    ConsoleObj console;
    console.start();
    Serial.print(freeMemory());
  }
  Serial.print(freeMemory());
}

I get: 2582 2582 2582

Shouldn't the middle one be higher because I've instantiated a big object?

I've had major Issues with SRAM myself. if you are interested in increasing SRAM see post #8 of link, if you are interested in using a computer to process data to reduce SRAM usage see post #21.

From my experience, you do [u]not[/u] want to use Strings. They seemed like such a great way to do things at first that required very little code, but Jesus Christ if it doesn't eat up all your SRAM. Take my word for it, don't use Strings!

The second thing you can do is use F() macro. For example Change Serial.println("hi"); to Serial.println(F("hi")); to save SRAM.

The best thing you can do though is not use Strings. It may take a few more lines of Code to accomplish the same thing, but if you want to evolve your program in the future, you're going to want to avoid Strings at almost all cost. I can't emphasize that enough.

I agree with Strings. From what I've read, they're trouble and I've never used them in any project. Char arrays was a pain for a while, but I've got the hang of it. Using strtok() a lot.

I use F() everywhere. It's very annoying that it generates all those compiler warnings. I know this is fixed in 1.5x, but there are other issues there...

The device is a data-logger with six Serial devices (via MUX), three I2c devices, RTC and SD). Most of the code is for parsing and saving char arrays. I could probably trim down my buffers as well.

I was just surprised that no memory was freed up after the console object went out of scope. Perhaps this is a limitation of freeMemory();

I'm sure SDFat is a big hog. I know there are lighter alternatives.

Char arrays was a pain for a while, but I've got the hang of it. Using strtok() a lot.

I haven't learned how to use Char arrays yet. Right now i'm actually trying to figure out how to use atoi but from my reading, i'm not sure if strtok would be better. I have no idea. If you are trying to parse numbers, take a look at the link I mentioned, maybe we can help each other.

I was just surprised that no memory was freed up after the console object went out of scope. Perhaps this is a limitation of freeMemory();

Well depending on how you have it set up, the strings may or may not disappear after the console objective goes out of scope. I do know if you run out of SRAM like this, the it freezes everything up and the loop will not be able to free itself up. What was happening in the first post link I referenced was the strings were already eating up a lot of memory, then when I called upon a new string that combined multiple strings to save data to a sd card, it ran out completely, and froze the program. That was extremely difficult to debug.

I'm sure SDFat is a big hog. I know there are lighter alternatives.

I love the examples that comes with it, just to learn how sd cards work, but you're probably correct about the hog issue. Good for learning though!

Heres' an example where I use strtok(), atoi() and atol(). For printing floats, look at dtosrrf();

void setup(){
  Serial.begin(57600);
  char my_string[] = "FIRST,2,30.3,400,5000,60000,700000.7,8000000,90000000,100000000,LAST\r";
  char buf[50];
  char buf2[10];
  // we know it's eight comma separated values;
  char * pch;
  uint8_t i = 1;
  uint16_t myint;
  uint32_t mylong;
  float myfloat;
  uint8_t width = 8;
  uint8_t precision = 2;
  Serial.print("parsing "); Serial.println(my_string);
  pch = strtok(my_string,",\r");
  while ( pch != NULL) {
    myint = atoi(pch);
    mylong = atol(pch);
    myfloat = atof(pch);
    sprintf(buf,"Field %u is %s (int=%d,long=%lu float=%f)  ",i,pch,myint,mylong,myfloat); // sprintf doesn't support floats on Arduino
    Serial.print(buf);
    Serial.println(myfloat);
    pch = strtok(NULL, ",\r"); // puts next string in pch unitl there are no more when it will be NULL
    i++;
  }
}
void loop(){}

This is a great example! The most interesting thing is %u and that somehow automatically knows to look for i. Very interesting. The attachment shows the serial monitor printout. I am curious why about this line.

Field 7 is 700000.7 (int=-20896,long=700000 float=?) 700000.68

Up top in the string it is 700000.7 so why does it print 700000.68?

Question.jpg

Thomas499:
The most interesting thing is %u and that somehow automatically knows to look for i. Very interesting.

Not strange or interesting. It’s sprintf. A wonderful function. If you don’t know it, then you need to. It can do so many things.

Google sprintf and his older brother printf.

Thomas499: I am curious why about this line. Up top in the string it is 700000.7 so why does it print 700000.68?

Ahh, the wonder that is floating point numbers.

If you're having SRAM problems, why not simply buy a board with more SRAM? ProMinis can be bought for a few $, and have 1K of RAM, a Mega2560 has 8K, and a Due has 96K of RAM, and is almost 10X faster than any other Arduino.

Regards, Ray L.

I'm trying to reduce SRAM usage in my project.

It would help to post all your code. http://snippets-r-us.com/

We might spot other areas to save memory. Moving stuff into a function does not seem to me to be all that helpful, per se.

[quote author=Nick Gammon date=1423359884 link=msg=2081234] It would help to post all your code. http://snippets-r-us.com/

We might spot other areas to save memory. Moving stuff into a function does not seem to me to be all that helpful, per se. [/quote]

My code is almost 2000 lines long not including several libraries I wrote. I was trying to figure out some general theory of how SRAM works so I could apply it to my own project.

The way I understand it, if you instantiate an object at global scope, it's always there even if you don't use it anymore.

I thought that instantiating an object in a non-global scope would use SRAM, but you would get it back when that object went out of scope. I have an object I only need in setup() and not in loop(). I wasn't seeing this memory recovery and was wondering why.

  if ( startConsole() ) { // now a simple function that returns a boolean
    ConsoleObj console; // instantiate console object with all those methods.
    console.start();
  }

I don't understand the point of making an object (console) which immediately goes out of scope. What does this object do, that consumes RAM, that you save by doing it like this?

Having "methods" doesn't consume RAM per se. That consumes Program Memory (PROGMEM) which you don't save any by moving wherever-it-is that you call those functions from.

RayLivingston: If you're having SRAM problems, why not simply buy a board with more SRAM? ProMinis can be bought for a few $, and have 1K of RAM, a Mega2560 has 8K, and a Due has 96K of RAM, and is almost 10X faster than any other Arduino.

Regards, Ray L.

I'm using a Mega already. I've considered a Due, but my device is battery powered and power usage is a big concern. I've got a Due and Teensy 3.1 I should really test their power consumption.

[quote author=Nick Gammon date=1423371616 link=msg=2081332]

  if ( startConsole() ) { // now a simple function that returns a boolean
    ConsoleObj console; // instantiate console object with all those methods.
    console.start();
  }

I don't understand the point of making an object (console) which immediately goes out of scope. What does this object do, that consumes RAM, that you save by doing it like this?

Having "methods" doesn't consume RAM per se. That consumes Program Memory (PROGMEM) which you don't save any by moving wherever-it-is that you call those functions from. [/quote]

So when the program starts up, you have the option of going into an interactive console mode to set up a myriad of deployment parameters. The console.start() method is an interactive serial user interface. Once you are done setting up these values, you exit the console mode, unplug the USB cable and throw the device in the water.

My thought was that all the (substantial) code for the console mode is only necessary for the first few minutes of the several days I expect the program to run so no sense in keeping it in RAM.

Yes, you do know that there are different sorts of RAM? “Real” RAM (of which you have 2 kB) and program memory (PROGMEM) of which you have 32 kB.

By making an object like that you are merely moving things around, you aren’t saving RAM.

My code is almost 2000 lines long not including several libraries I wrote. I was trying to figure out some general theory of how SRAM works so I could apply it to my own project.

If you are not prepared to post your code you are asking hypothetical questions about undisclosed code. Perhaps I'll let other people answer such things.

Seeing that I have 16x as much PROGMEM as SRAM, I’ve been trying everything I can to move things from SRAM to PROGMEM using F() and PROGMEM. I’ve never thought of PROGMEM as RAM, but as program storage since it isn’t used as swap space and doesn’t change during execution.

Here’s an example which might illustrate what I’m getting at. Shouldn’t we recover the SRAM used for instantiating the object foo after it goes out of scope?

extern unsigned int __heap_start;
extern void *__brkval;

/*
 * The free list structure as maintained by the 
 * avr-libc memory allocation routines.
 */
struct __freelist {
  size_t sz;
  struct __freelist *nx;
};

/* The head of the free list structure */
extern struct __freelist *__flp;

/* Calculates the size of the free list */
int freeListSize() {
  struct __freelist* current;
  int total = 0;
  for (current = __flp; current; current = current->nx) {
    total += 2; /* Add two bytes for the memory block's header  */
    total += (int) current->sz;
  }
  return total;
}

int freeMemory() {
  int free_memory;
  if ((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__heap_start);
  } else {
    free_memory = ((int)&free_memory) - ((int)__brkval);
    free_memory += freeListSize();
  }
  return free_memory;
}

class memuser {
  public:
    int stuff[2000];
};


void setup(){
  uint16_t i = 0;
  pinMode(2,INPUT);
  digitalWrite(2,LOW);
  Serial.begin(57600);
  Serial.print(F("Memory before we instantiate foo: "));
  Serial.println(freeMemory());
  if ( digitalRead(2) == 0 ) {
    memuser foo;
    for ( i = 0; i < 2000 + 1 ; i++ ) foo.stuff[i] = 42;
    Serial.print("Memory after we instantiate foo: ");
    Serial.println(freeMemory());
    Serial.println(foo.stuff[10]); // So the compiler doesn't optimize this away
  }
  Serial.print(F("Memory after foo goes out of scope: "));
  Serial.println(freeMemory());
}

void loop(){
  Serial.print(F("Memory when loop begins(): "));
  Serial.println(freeMemory());
  while (true){};
}

One question is: What value, exactly, does freeMemory() return? Rather than being the total amount of "unused" RAM, it might be the size of the largest single contiguous block of available RAM. If this is the case, then it might well sometimes not change when objects are created and destroyed, as those objects will go into smaller blocks of (fragmented) RAM, and I doubt the Arduino memory manager does any garbage collection. If you allocate and destroy very large objects, you're more likely to see changes in freeMemory().

Regards, Ray L.

Does freeMemory() count automatic (stack) variables or does it count only heap allocations? All of your examples put the objects on the stack, so they might not count as “used” by freeMemory().

christop: Does freeMemory() count automatic (stack) variables or does it count only heap allocations? All of your examples put the objects on the stack, so they might not count as "used" by freeMemory().

freeMemory would certainly NOT count anything that lives on the stack. Maximum stack size is allocated at compile time, and does not change as the program runs.

Regards, Ray L.