0

As I'm experiencing weird bugs in a rather lengthy project, I started analyzing my RAM usage on my ATMEGA328P with Arduino bootloader. At least I tried to and wrote the following piece of code. Consider the following written in Arduino IDE 1.8.5:

extern int __heap_start, *__brkval;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(500);
  f1();
  delay(500);
  freeRAM();
}

void f1() {
  int local = 0;
  local = 6;
  freeRAM();

}

void freeRAM() {
  int local = 0;
  local = 5;
  if (*__brkval == 0) {
    Serial.println("The break point is 0,...");
    Serial.println((int)&local - __heap_start);
  }
  else {
    Serial.println("The break point is not 0,...");
    Serial.println((int)&local - *__brkval);
  }
}

And the rather unexpected output:

The break point is not 0,...
2048
The break point is 0,...
2294
The break point is 0,...
2294
The break point is 0,...
2294
The break point is 0,...
2294
The break point is 0,...
2294
The break point is 0,...
2294
The break point is 0,...
2294

I was expecting 2 different values of free RAM because on the second call of freeFram(); the function call and local variables of f1() should already be popped from the stack. Perhaps i misused the external variables (__heap_start, __brkval) or I misinterpreted one of the links I read:

memories-of-an-arduino

Lecture 08: Memory Allocation and Program Memory Layout

Perhaps someone could elaborate on this.

EDIT: @Gerben: Thx for the hint, didn't change anything though!

@Edgar:

Looking at a snippet of the objdump:

 76e:   0e 94 8e 01     call    0x31c   ; 0x31c <delay.constprop.3>
 772:   1a 82           std     Y+2, r1 ; 0x02
 774:   19 82           std     Y+1, r1 ; 0x01
 776:   1a 83           std     Y+2, r17        ; 0x02
 778:   09 83           std     Y+1, r16        ; 0x01
 77a:   0e 94 5a 02     call    0x4b4   ; 0x4b4 <_Z7freeRAMv>
 77e:   0e 94 8e 01     call    0x31c   ; 0x31c <delay.constprop.3>
 782:   0e 94 5a 02     call    0x4b4   ; 0x4b4 <_Z7freeRAMv>
 786:   0e 94 5f 01     call    0x2be   ; 0x2be <_Z14serialEventRunv>
 78a:   f1 cf           rjmp    .-30            ; 0x76e <main+0xfe>

I realized the compiler was indeed inlining the call to f1(). Changing f1() to:

__attribute__((noinline)) void f1() {
  volatile int local = 0;
  local = 6;
  freeRAM();

}

and results in the following objdump:

 784:   0e 94 8e 01     call    0x31c   ; 0x31c <delay.constprop.3>
 788:   0e 94 94 02     call    0x528   ; 0x528 <_Z2f1v>
 78c:   0e 94 8e 01     call    0x31c   ; 0x31c <delay.constprop.3>
 790:   0e 94 5a 02     call    0x4b4   ; 0x4b4 <_Z7freeRAMv>
 794:   0e 94 5f 01     call    0x2be   ; 0x2be <_Z14serialEventRunv>
 798:   f5 cf           rjmp    .-22            ; 0x784 <main+0xf0>

which therefore results in the desired behaviour:

The break point is 0,...
2294
The break point is 0,...
2288
The break point is 0,...
2294
The break point is 0,...
2288
The break point is 0,...
2294
The break point is 0,...
2288
The break point is 0,...
2294

Thanks a lot!

A question that's on my mind now: Looking at the values of freeRam, shouldn't the value be smaller than 2048? Am I calculating wrong?

EDIT #2: In the meantime it turned out that my "memory"-problem was actually some self induced pointer stupidity :/

But for the sake of completeness, and if someone is on the search for a method for memory analysis like I was, here are my 2 cents.

@Gerben: I'm sry, I realised that I have to declare the function noinline as well as declare some volatile variable in it to stop the compiler from inlining the call. So Edgar and you were both right at the beginning!!

@Edgar you were completly right again, with you and some reading from here:

Official freeRam() command #5289

I came up with the following code:

extern int __heap_start, *__brkval;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:
  reportFreeRAM();
  delay(500);
  f1();
  delay(500);

}

__attribute__((noinline)) void f1() {
  //String s = "foobar";
  volatile char arr[7] = "foobar";
  reportFreeRAM();

}

void reportFreeRAM () {
  int v;
  Serial.println("-------RAM Report---------");
  Serial.print("__brkval = ");
  Serial.println((int)__brkval);
  Serial.print("FreeRAM: ");
  Serial.println((int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval));
}

which results in the following output:

-------RAM Report---------
__brkval = 0
FreeRAM: 1792
-------RAM Report---------
__brkval = 0
FreeRAM: 1781
-------RAM Report---------
__brkval = 0
FreeRAM: 1792
-------RAM Report---------
__brkval = 0
FreeRAM: 1781
-------RAM Report---------
__brkval = 0
FreeRAM: 1792

and if f1() is changed to:

__attribute__((noinline)) void f1() {
  String s = "foobar";
  //volatile char arr[7] = "foobar";
  reportFreeRAM();
}

we see the following result:

-------RAM Report---------
__brkval = 0
FreeRAM: 1786
-------RAM Report---------
__brkval = 519
FreeRAM: 1773
-------RAM Report---------
__brkval = 510
FreeRAM: 1786
-------RAM Report---------
__brkval = 519
FreeRAM: 1773
-------RAM Report---------
__brkval = 510
FreeRAM: 1786

which is rather interesting, because you see the break-point-pointer getting initialised with the first allocation for the String variable and after that the value jumping back and forth. Which is to my understanding the expected behaviour. For my application I wanted to check if I had allocated any memory dynamically, so I guess an easy check would be to check (int)__brkval for zero. Perhaps someone could elaborate if that is always a good idea or not.

So sry for the lengthy post, I just wanted to show what I found, in case someone stumbles over the same kind of question.

Thx @Gerben and @Edgar!

  • 1
    You simply forgot that the compiler optimizes your code. Looking at the generated assembly should be enlightening. – Edgar Bonet May 21 '18 at 16:18
  • 2
    Try declaring the local variable as volatile – Gerben May 21 '18 at 16:23
  • 1
    Yes, the amount of free memory can only be less than 2 KiB on the 328P. I think the problem is that you should use __brkval instead of *__brkval and &__heap_start instead of __heap_start. – Edgar Bonet May 21 '18 at 20:28
  • __brkval may not return to zero once you do any allocation. If you want to know the size of the heap, try: (__brkval ? (int) __brkval - (int) &__heap_start : 0). – Edgar Bonet May 28 '18 at 18:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.