'RAM' overflowed by 0 bytes

Hi,

I would like to trend three temperature readings over a day with a reading every minute.
When I compile the code I get the following error:

c:/users/michael/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: C:\Users\Michael\AppData\Local\arduino\sketches\ACD2F7E090EC811ABE7EA4A6C174DBE2/WH2_WiFi.ino.elf section `.heap' will not fit in region `RAM'
c:/users/michael/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: section .stack_dummy VMA [20007b00,20007eff] overlaps section .heap VMA [20007548,20009547]
c:/users/michael/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: region `RAM' overflowed by 0 bytes
collect2.exe: error: ld returned 1 exit status
exit status 1

Compiling this simplified code for testing:

const int TEMPERATURE_SAMPLES = 1440; // every minute

//Temperature samples for daily trend
int t1TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};
int t2TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};
int t3TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < TEMPERATURE_SAMPLES; i++) {
    t1TemperaturePerMinute[i] = i;
    t2TemperaturePerMinute[i] = i;
    t3TemperaturePerMinute[i] = i;
  }
}

Results in this error:

c:/users/michael/appdata/local/arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin/../lib/gcc/arm-none-eabi/7.2.1/../../../../arm-none-eabi/bin/ld.exe: section .stack_dummy VMA [20007b00,20007eff] overlaps section .heap VMA [20005df0,20007def]
collect2.exe: error: ld returned 1 exit status
exit status 1

Compilation error: exit status 1

Commenting out t2... and t3... in the for loop:

  for (int i = 0; i < TEMPERATURE_SAMPLES; i++) {
    t1TemperaturePerMinute[i] = i;
    //t2TemperaturePerMinute[i] = i;
    //t3TemperaturePerMinute[i] = i;
  }

compiles successfully:

Sketch uses 51880 bytes (19%) of program storage space. Maximum is 262144 bytes.
Global variables use 12500 bytes (38%) of dynamic memory, leaving 20268 bytes for local variables. Maximum is 32768 bytes.

Commenting out just t3... in the for loop:

  for (int i = 0; i < TEMPERATURE_SAMPLES; i++) {
    t1TemperaturePerMinute[i] = i;
    t2TemperaturePerMinute[i] = i;
    //t3TemperaturePerMinute[i] = i;
  }

also compiles successfully:

Sketch uses 51896 bytes (19%) of program storage space. Maximum is 262144 bytes.
Global variables use 18260 bytes (55%) of dynamic memory, leaving 14508 bytes for local variables. Maximum is 32768 bytes.

Going from one to two arrays increases the global variable usage by 5,760 bytes; there are still 14,508 bytes free.
Why does going to three arrays cause a RAM overflow?

I'm using Arduino IDE 2.3.7 and Arduino UNO R4 Boards 1.5.2

thanks,
Michael

After a (very) quick look, my first impression is that you shouldn't add "= {}" to array definitions. You already specify the size between the square brackets, so you don't need to initialize the array (the compiler might be "confused" by this double initialization).
Try this change:

//Temperature samples for daily trend
int t1TemperaturePerMinute[TEMPERATURE_SAMPLES];
int t2TemperaturePerMinute[TEMPERATURE_SAMPLES];
int t3TemperaturePerMinute[TEMPERATURE_SAMPLES];

for a global variable

int t1TemperaturePerMinute[TEMPERATURE_SAMPLES];

or

int t1TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};

is exactly the same thing as they undergo zero-initialization before any other initialization occurs.

In the first declaration, there is no explicit initializer, so the array is zero-initialized automatically because of its static storage duration.

In the second declaration, {} is an empty brace initializer which triggers value-initialization and for an array of fundamental types, value-initialization also results in zero-initialization of every element.

it's not the issue.


The optimizer likely sees you have an unused variable and gets rid of it. So the memory requirements drop.

It actually might even see that you don't use the data you put in the array and never allocate the arrays...

For which board are you trying to compile ? on a UNO you can't even have one array of 1440 ints, that would be 2880 bytes of RAM and you only have 2048 bytes in total.

We are in the UNO R4 WiFi section, so I assume this is the board it uses and... also the compilation information ...

... would seem to indicate this board :roll_eyes:

Guglielmo

1 Like

I missed that (reading on a small screen). Thanks for the hint!

Not really. Your sketch not only uses space for global variables. There is a lot more needed for other variables used by the core and the stack. Obviously especially at the R4 WiFi. Compiling for the R4 Minima is successful.

may be you don't need 32 bits int to represent your temperature ? if you use a 16 bit integral (-32768° to +32767°) then it will fit. If you represent temperature as an int (no precision) may be you can even be ok with -128° to 127° and use only an int8_t for the type in the array, that will give you even more free ram.

this should compile fine

const size_t TEMPERATURE_SAMPLES = 1440; // every minute

//Temperature samples for daily trend
int16_t t1TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};
int16_t t2TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};
int16_t t3TemperaturePerMinute[TEMPERATURE_SAMPLES] = {};

void setup() {

}

void loop() {
  for (size_t i = 0; i < TEMPERATURE_SAMPLES; i++) {
    t1TemperaturePerMinute[i] = i;
    t2TemperaturePerMinute[i] = i;
    t3TemperaturePerMinute[i] = i;
  }
}

Good point.
If a couple of decimal is enough and this application won't be used in steelworks (e.g. temperature ranges are "human"), using an integer with 1/100th of degrees as unit, a 16bit integer could store more than any regular temperature sensor can handle (between -327.68° and +327.67°). So, to get the "real" temperature, for the OP it is just needed to divide the interger by 100 and store the result as a float.
PS: this is like what I did on an old project I made years ago...

It looks like the Uno WiFi has about 6.5k of overhead in the core (shows in the memory summary), and reserves 8k for the "heap" (which does NOT show in the memory summary. Perhaps it should.) Adding your 17k of data, and presumably a few other variables, exceeds the 32k of RAM that exists on the part.

If you don't put data into some of the arrays, the linker decides they aren't used and omits them from the final binary (ie "optimized away.")

Top RAM consumers (empty sketch):

200007cc 00000040 b _usbd_ctrl_buf
20000274 00000040 B g_bsp_group_irq_sources
200019de 00000041 b tud_descriptor_string_cb::idString
2000199c 00000042 b tud_descriptor_string_cb::desc_str
200003f4 00000044 b regs
200017a0 0000005c b adc
200017fc 0000005c b adc1
20000028 00000060 d impure_data
2000185c 00000068 b pwms
200002b8 00000080 B gp_renesas_isr_context
200006e8 0000008c b _hidd_itf
20000348 00000090 b call_stack_info
2000080c 00000098 b _dcd
20000088 0000016c D __global_locale
20000440 000002a8 b _cdcd_itf
20007b00 00000400 b g_main_stack
2000091c 000004ac B _UART1_
20000dc8 000004ac B _UART2_
20001274 000004ac B _UART3_
20001a70 00002000 b g_heap

Thank you all for your advice and information.

The solution was to change the arrays to int16_t types and declare the arrays as static as shown in this post. I did do a search before posting but didn't see that thread until I noticed it was the first one suggested under Related topics in this thread :roll_eyes: .

An interesting observation was that changing the arrays to int16_t in the simplified code for testing did not change the number of variable bytes used, 6740 in both cases.

I use this code to display the daily temperature graphs on an LCD:

void drawTemperatureGraphs() {
//Draw the daily temperature graphs
int pointY, value;
  //Draw DHT temperature graph
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += dhtTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, DHT_TEMP_COLOUR);
  }

  //Draw in temperature graph
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += inTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, IN_TEMP_COLOUR);
  }

  //Draw out temperature graph
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += outTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, OUT_TEMP_COLOUR);
  }
} //void drawTemperatureGraphs()

When compiling the complete project and drawing all three graphs the result is:

Sketch uses 103988 bytes (39%) of program storage space. Maximum is 262144 bytes.
Global variables use 21460 bytes (65%) of dynamic memory, leaving 11308 bytes for local variables. Maximum is 32768 bytes.

If I comment out the Draw DHT temperature graph and only display two graphs the result is:

Sketch uses 103860 bytes (39%) of program storage space. Maximum is 262144 bytes.
Global variables use 18580 bytes (56%) of dynamic memory, leaving 14188 bytes for local variables. Maximum is 32768 bytes.

It appears that the compiler is reserving 2880 bytes for each graph, as if it is creating a new array for each graph.

As a test I split each graph drawing into it's own function and call them separately but it still uses 2880 bytes for each graph drawing function, even if they are not called!

void drawDhtTemperatureGraph() {
int pointY, value;
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += dhtTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, DHT_TEMP_COLOUR);
  }
} //void drawDhtTemperatureGraph()

void drawInTemperatureGraph() {
int pointY, value;
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += inTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, IN_TEMP_COLOUR);
  }
} //void drawInTemperatureGraph()

void drawOutTemperatureGraph() {
int pointY, value;
  for (int i = 0; i < TEMPERATURE_SAMPLES; i = i + minsPerPoint) {
    value = 0;
    for (int j = 0; j < minsPerPoint; j++) {
      value += outTemperaturePerMinute[i + j];
    }
    value = value / minsPerPoint;
    //Calculate the point height based on the graph range
    pointY = yOffset + (graphResolution * value);
    //Draw the point
    tft.drawPixel(i / minsPerPoint + wgGraphOriginX, wgGraphOriginY - pointY, OUT_TEMP_COLOUR);
  }
} //void drawOutTemperatureGraph()

If I comment out the three functions above and don’t call them from the program the result is:

Sketch uses 103588 bytes (39%) of program storage space. Maximum is 262144 bytes.
Global variables use 12820 bytes (39%) of dynamic memory, leaving 19948 bytes for local variables. Maximum is 32768 bytes.

Is there a better way to draw the graphs without using so much global variable memory?

thanks,

Michael

Most likely, it is not the graph itself, but the compiler seeing that the data array that is being graphed is no longer needed. That allows the array to be completely removed during optimization, reducing the memory usage.

Hi David,

the arrays that store the temperature values are still being used in the program, I only posted the snippet of code that displays the temperature values on the LCD.

Which is exactly the reason why you should always post a full sketch so that a problem can bee seen in context

How are they being used? What matters is where data is read from the array, and if the compiler can determine at run-time which particular element is being used, so that it can only store that element and eliminate the remainder of the array. Note that writing data to the array, but never reading it back, will result in its complete elimination during optimization, because the compiler can determine that it is never really needed.

There can also be situations where you do something like calculate the average of all the array elements over the entire day, or look for the high and low values, etc, but then never do anything with the calculated value. Since the calculated value is not needed, the calculation is not needed, and again the array is not needed.

I don’t think the optimizer will shrink an array if you don’t use all the elements. that would change the object’s type, affect sizeof, pointer arithmetic, alignment, and potentially external linKage.

However, as you stated, GCC can eliminate unused objects entirely if they have no observable effect. For example, a static array that is never referenced and whose address is never taken can be removed during optimization

Similarly for dynamically allocated memory, such as malloc or new, GCC does not “shrink” the allocation size, but it may eliminate the allocation entirely if it can prove it is unused and has no side effects.

I've changed the test code to:

const int TEMPERATURE_SAMPLES = 1000;  // every minute

//Temperture samples for daily trend
static int16_t t1TemperaturePerMinute[TEMPERATURE_SAMPLES];
static int16_t t2TemperaturePerMinute[TEMPERATURE_SAMPLES];
static int16_t t3TemperaturePerMinute[TEMPERATURE_SAMPLES];

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
  for (int i = 0; i < TEMPERATURE_SAMPLES; i++) {
    t1TemperaturePerMinute[i] = i;
    t2TemperaturePerMinute[i] = i;
    t3TemperaturePerMinute[i] = i;
  }
  // Serial.println(t1TemperaturePerMinute[0]);
  Serial.println(t1TemperaturePerMinute[TEMPERATURE_SAMPLES - 1]);
  Serial.println(t2TemperaturePerMinute[TEMPERATURE_SAMPLES - 1]);
  Serial.println(t3TemperaturePerMinute[TEMPERATURE_SAMPLES - 1]);
}

Note that I've changed the array length to 1000 for easier arithmetic :grinning_face:

and found the following:

With all of the Serial.println statements commented out global variable use is 6740 bytes.

Each of the Serial.println(txTemperaturePerMinute[TEMPERATURE_SAMPLES - 1]); statements enabled adds another 2000 bytes to the global variable usage.

Only enabling the Serial.println(t1TemperaturePerMinute[0]); statement also adds 2000 bytes to the global variable usage, as per

Removing the static keyword from the three array declarations adds 6000 bytes to the global variable usage regardless of whether the Serial.println statements are commented out or not.

Now what I need to do now is find out why the complete project Global variables use 12820 bytes (39%) of dynamic memory without any temperature arrays.

Michael

That’s illusreqting the fact that the optimizer gets rid of the array. Writing in an array and never using any value is seen as useless and so just removed from your code.

Static makes it easy for the tool chain to see it’s not used. At global scope it might miss it: When you add static, the arrays gain internal linkage status. Now the compiler knows it cannot be referenced from another translation unit. And as within that translation unit, it is only written and never read and its address does not escape, the compiler can prove the stores are dead and can eliminate the object entirely at compile time. No linker reasoning is required.

An empty sketch on an UNO R4 WiFi takes quite a bit of memory

Sketch uses 51864 bytes (19%) of program storage space. Maximum is 262144 bytes.
Global variables use 6740 bytes (20%) of dynamic memory, leaving 26028 bytes for local variables. Maximum is 32768 bytes.

The libraries for the display and temperature sensors may be using large amounts of memory. Inside functions, any local variables that are declared as static will show up in the ram usage. Text literals should not be using ram on the R4.

Impossible for anyone here to provide much help since you do not want to post a complete sketch.

The only time I've seen that is when the array was const, which allows the compiler to resolve a reference to a specific element at compile-time.

Right but it likely got rid of the full array, didn’t it ?