Trying to understand and optimize SRAM, featuring freeMemory() and local variables

Please forgive my english, I'm Italian and I'm still learning.
I've been informing myself a lot about how the Arduino manages its different memories, what's stored in them etcetera, because I want to save SRAM usage in any way possible. So, I've got an Arduino UNO rev3, connected to an OLED display (128x64) via SDA and SCL. I'm using the library 'Adafruit_SSD1306' to drive the screen, the 'Adafruit_GFX' and 'Wire'. When initialized, the SSD1306 library allocates about 1K of SRAM as the frame buffer. I've done some testing, and only initializing the screen during setup(), leaves me with 462 bytes of free SRAM, according to the FreeMemory function. Then, I declare an int variable, before setup(), and assign it a value of 0. In order to avoid that it gets 'cancelled out' by teh compiler during the compiling process, I also add 1 to it and (why not) print it on serial, every iteration. Now freeMemory is telling me that there's 492 bytes of free SRAM! I'll paste the code down here:

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//Declare variables
int v = 0;

Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
  // Initialize the serial port. (allocates serial buffer!)
  Serial.begin(9600);
  // Initialize the OLED display.
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // (allocates frame buffer!)
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  
}

void loop() {
    Serial.print(F("freeMemory:"));
    Serial.println(freeMemory());
    Serial.println(v);
    v++;
    delay(1000);
}

//FreeMemory Function, from the memoryFree.h library. Prints the available SRAM (the space between Stack and Heap).
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

From what I've read, the freeMemory() function is returning the space between the Stack and the Heap. So at first I thought it was heap fragmentation: maybe the space for the int is allocated in a hole. But this doesn't explain why the free memory is increasing.

[This second issue has been solved. Thanks to everyone who helped!]
There's also another issue I can't solve (still related to the memory management). I read that every local variable is held only in Stack, and when the function is returned the space it held is freed up completely. Well, I did some testing, and printing on serial the free SRAM, it looks like that space is not retrieved when the function returns. The space is occupied tho, as the freeMemory(); function now constantly returns 488. That's the code with the function:

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//Declare variables
int v = 0;

Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
  // Initialize the serial port. (allocates serial buffer!)
  Serial.begin(9600);
  // Initialize the OLED display.
  
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // (allocates frame buffer!)
    Serial.println(F("SSD1306 allocation failed"));
    for(;;);
  }
  
}

void loop() {
    Serial.print(F("freeMemory:"));
    Serial.println(freeMemory());
    Serial.println(v);
    v++;
    func();
    delay(1000);
}

void func() {
  int loc = random(0,15);
  Serial.print(F("(From function)freeMemory:"));
  Serial.println(freeMemory());
  Serial.println(loc);
  return; //Added just to try. Didn't seem to change things.
}

//FreeMemory Function, from the memoryFree.h library. Prints the available SRAM.
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

I'm really having some hard time understanding this. Any kind of help is widely appreciated! Thanks is advance :smiley:

A tiny note: what's my objective? I'm coding some games that run on the tiny OLED display. I want to make at least 2, but I don't want to reuse global variables to save SRAM, it just looks confusing. So what I'm trying to do is to declare as many variables as possible as local, inside a function with its own 'main' loop, which basically runs a game. I'll just need a little selection screen that calls a function with the chosen game. That way, I can declare only the variables I need each time for each game, and when the current game is exited, the space is freed up for the next game, which will declare its own local variables again.

The second problem may be compiler optimization. I suspect that it decided to inline func, rather than calling it, so the stack size hasn't changed and neither, consequently, has freeMemory.

I didn't consider this option! Is there a direct way to tell the compiler to treat it as a function and not just inline it? Just like volatile for variables, maybe.
Thank you so much for anwering!

I found this on stack exchange:

 __attribute__ ((noinline)) void func()

It does make a difference. I can't think of a good reason to use it other than experimentation though.

1 Like

If you pass a function pointer around and call the function using the function pointer, it shouldn't be inlined.

It works! If you want to know the reason why I want to use local variables that way, you can find everything in the 'tiny note:', at the end of the post. Thanks again for the help!
Now I just need to understand (and hopefully solve) the first problem :smiley:

The first one seems to be something to do with Serial.print. If you print the v variable, freemem goes up.

I'll guess the optimizer is playing tricks again and chose a more efficient way to print once it knew it was printing both F macro strings and an int, which seems a little strange. If you really want to know why, you can get at the generated code and compare them. Personally I wouldn't bother and if memory became an issue, I'd throw better hardware at the problem.

@wildbill
I doubt I understood that right.
freeMemory() is returning the right value, but the Serial.println(v) is causing problems due to compiler optimization?
Yeah, it doesn't make sense in my head either. Sorry for sounding stupid, I'll have to look harder into that one. Thanks for giving me an hint towards the right way!
[Edit]: I've tried commenting Serial.println(v); and freeMemory is printing a lower value than without the int v declaration. So yeah, that's definitely the problem here.

In reference to the end goal, you could use a union to define global variables for both games using the same area of memory.

Take a look at the u8g2 library, it has ways of using less buffer memory for the display.

1 Like

Try different libraries like 'GyverOLED' with 'microWire'.