This page's comments/discussion on Arduino Forum: Arduino Forum - Wiki discuss: Corrupt Array Variables And Memory
This is a little writeup, with an example (carried out in the Ubuntu Linux 10.04 environment), of the issues discussed in these forum topics:
In essence, what these topics illustrate, is the fact that the Arduino's AVR microcontroller is of modified Harvard architecture: "with physically separate storage and signal pathways for instructions and data". This means that the process of memory allocation of variables in this architecture, is slightly different from the usual process when programming usual PCs (to which most users may be used to). See also Data Storage & Retrieval - Measuring Stuff?
Let's illustrate that with a simple example. This example was developed on an Arduino Duemilanove with an Atmel ATmega328 microcontroller, so let us first include its memory specifications:
Flash Memory: 32 KB (ATmega328) of which 2 KB used by bootloader SRAM: 2 KB (ATmega328) EEPROM: 1 KB (ATmega328)
Now, let us consider the following Arduino .pde program as our starting point:
// minitest.pde
unsigned long mydata_count;
static const long SERSPEED=115200;
static const int SIZE=100;
static const char RSTSTR[] = "RESET!!";
void setup()
{
Serial.begin(SERSPEED);
mydata_count = 0;
}
void loop()
{
Serial.println(mydata_count, DEC);
mydata_count++;
if (mydata_count == SIZE) {
doReset();
}
}
void doReset()
{
Serial.println(RSTSTR);
mydata_count = 0;
}
The example, simply, increases a counter, prints its value through the serial port, and then after the counter reaches a certain value (here 100), it is reset - and the process starts all over again. This first example builds and runs just fine on an Arduino, and the output of this program is:
0 1 2 3 4 ... 98 99 RESET!! 0 1 2 3 ...
In the example, all the unchanging values used have been declared as static const variables. Note that the counter is of type unsigned long (and this is in order to illustrate what happens to variables with sizes greater than a byte; as an unsigned long is 32 bits, or 4 bytes in size).
After building (by clicking 'Verify' in the Arduino IDE), the Arduino IDE informs us about the binary produced:
Binary sketch size: 2342 bytes (of a 30720 byte maximum)
While this piece of info would suggest that everything should be fine - as we will see later, this binary sketch size does not necesarilly inform us whether we have allocated enough memory for our variables! That is because for variables, we need information about utilization of static RAM (SRAM); however, this is not included in the default report of the Arduino IDE (see the posts above, though, for a patch).
At this point, let us note that:
.hex or .elf file), in the Linux platform, will usually be under /tmp/build*.tmp/ folder. (see Arduino Build Process)
After we have built the example, we have several tools in Linux at our disposal to further check the output binary - in terms of the memory specifications listed above:
$ avr-size /tmp/build6964438790091573694.tmp/minitest.cpp.elf
text data bss dec hex filename
2324 18 164 2506 9ca /tmp/build6964438790091573694.tmp/minitest.cpp.elf
$ avr-objdump -h /tmp/build6964438790091573694.tmp/minitest.cpp.elf
/tmp/build6964438790091573694.tmp/minitest.cpp.elf: file format elf32-avr
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00000012 00800100 00000914 000009a8 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 00000914 00000000 00000000 00000094 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .bss 000000a4 00800112 00800112 000009ba 2**0
ALLOC
3 .stab 000046bc 00000000 00000000 000009bc 2**2
CONTENTS, READONLY, DEBUGGING
4 .stabstr 0000310e 00000000 00000000 00005078 2**0
CONTENTS, READONLY, DEBUGGING
$ nm /tmp/build6964438790091573694.tmp/minitest.cpp.elf | sort
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 a __tmp_reg__
00000000 T __vectors
00000000 W __heap_end
00000000 W __vector_default
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000001 a __zero_reg__
00000034 a __CCP__
00000034 a __CCP__
00000034 a __CCP__
00000034 a __CCP__
00000034 a __CCP__
00000034 a __CCP__
00000034 a __CCP__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003d a __SP_L__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003e a __SP_H__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
0000003f a __SREG__
00000068 T __ctors_start
00000068 T __trampolines_end
00000068 T __trampolines_start
0000006a T __ctors_end
0000006a T __dtors_end
0000006a T __dtors_start
0000006a W __init
00000076 T __do_copy_data
00000082 t .do_copy_data_loop
00000086 t .do_copy_data_start
0000008c T __do_clear_bss
00000094 t .do_clear_bss_loop
00000096 t .do_clear_bss_start
0000009c T __do_global_ctors
000000a4 t .do_global_ctors_loop
000000ac t .do_global_ctors_start
000000ba T __bad_interrupt
000000ba W __vector_1
000000ba W __vector_10
000000ba W __vector_11
000000ba W __vector_12
000000ba W __vector_13
000000ba W __vector_14
000000ba W __vector_15
000000ba W __vector_17
000000ba W __vector_19
000000ba W __vector_2
000000ba W __vector_20
000000ba W __vector_21
000000ba W __vector_22
000000ba W __vector_23
000000ba W __vector_24
000000ba W __vector_25
000000ba W __vector_3
000000ba W __vector_4
000000ba W __vector_5
000000ba W __vector_6
000000ba W __vector_7
000000ba W __vector_8
000000ba W __vector_9
000000be T _Z7doResetv
000000dc T loop
0000012e T setup
00000150 T __vector_18
000001ca T _ZN14HardwareSerial5beginEl
000003bc T _ZN14HardwareSerial5writeEh
000003e2 t _GLOBAL__I_rx_buffer
00000456 T _ZN5Print5writeEPKc
00000486 T _ZN5Print5writeEPKhj
000004c4 T _ZN5Print5printEPKc
000004d4 T _ZN5Print11printNumberEmh
00000608 T _ZN5Print5printEmi
00000626 T _ZN5Print5printEli
00000692 T _ZN5Print7printlnEv
000006c0 T _ZN5Print7printlnEmi
000006d6 T _ZN5Print7printlnEPKc
000006ec T main
000006fa T __vector_16
0000078a T init
000007fe T __mulsi3
0000083c T _div
0000083c T __divmodhi4
00000850 t __divmodhi4_neg2
00000856 t __divmodhi4_exit
00000858 t __divmodhi4_neg1
00000862 T __udivmodsi4
0000086e t __udivmodsi4_loop
00000888 t __udivmodsi4_ep
000008a6 T __divmodsi4
000008ba t __divmodsi4_neg2
000008c8 t __divmodsi4_exit
000008ca t __divmodsi4_neg1
000008dc T __udivmodhi4
000008e4 t __udivmodhi4_loop
000008f2 t __udivmodhi4_ep
000008ff W __stack
00000904 T __tablejump2__
00000908 T __tablejump__
00000910 T _exit
00000910 W exit
00000912 t __stop_program
00000914 A __data_load_start
00000914 T _etext
00000926 A __data_load_end
00800100 D __data_start
00800100 d _ZL6RSTSTR
00800108 V _ZTV14HardwareSerial
00800112 B __bss_start
00800112 B mydata_count
00800112 D __data_end
00800112 D _edata
00800116 B rx_buffer
0080019a B Serial
008001ad B timer0_overflow_count
008001b1 B timer0_millis
008001b5 b timer0_fract
008001b6 B __bss_end
008001b6 N _end
008001b6 N __heap_start
00810000 N __eeprom_end
U __cxa_pure_virtual
In essence, avr-size gives the same information in decimal, as the first column of avr-objdump -h output in hexadecimal - the sizes of .text, .data and .bss sections; nm gives the memory layout, so we can confirm that these sizes are correct.
avr-objdump can also be used to generate assembly listing with the '-S' switch from the binary file
gcc compiler to output .map files - however you need to use a makefile, which is not included with the Arduino IDE;
gcc commands (Shift+'Verify') during Arduino IDE build - and then simply repeat key commands, with arguments added for generating .map files
However, for the purpose of this discussion, we can just use avr-size to obtain the sizes of .text, .data and .bss sections. For more on these sections, see avr-libc: Memory Sections.
One more note - SERSPEED in the code above, needs to be defined as long, since that is what Serial.begin expects; however, even if you define it as int (in which case the serial port will start wrong - and there will be no serial output), there will be no change in the output of the avr-size, avr-objdump and nm commands!
Now, let us complicate our example a bit, and introduce an array. So, the example becomes:
// minitest.pde
unsigned long mydata_count;
static const long SERSPEED=115200;
static const int SIZE=100;
static const char RSTSTR[] = "RESET!!";
unsigned long mydata[SIZE];
void setup()
{
Serial.begin(SERSPEED);
mydata_count = 0;
}
void loop()
{
mydata[mydata_count] = mydata_count;
Serial.println(mydata[mydata_count], DEC);
mydata_count++;
if (mydata_count == SIZE) {
doReset();
}
}
void doReset()
{
Serial.println(RSTSTR);
mydata_count = 0;
}
This example still builds and runs well, and produces the same output as the initial example; the Arduino IDE now reports:
Binary sketch size: 2364 bytes (of a 30720 byte maximum)
while avr-size reports:
$ avr-size /tmp/build6964438790091573694.tmp/minitest.cpp.elf text data bss dec hex filename 2346 18 564 2928 b70 /tmp/build6964438790091573694.tmp/minitest.cpp.elf
Now, let's keep in mind these quotes from Arduino Forum - SRAM size compile time check/report.:
'''text''' = code (flash) memory '''data''' = initialised data (in RAM - includes strings etc) '''bss''' = non-initialised data (actually normally set to 0x00) also in RAM. So total RAM usage = (data + bss), but this does '''not''' include dynamic memory allocated from the heap at run time... ... We also must store all initialised data in flash so: FLASH = text+data RAM = data + bss
So, keeping in mind that SRAM=data+bss, let us make a small comparison between the initial and the array example:
| Example | IDE | .text | .data | .bss | SRAM |
|---|---|---|---|---|---|
| Initial ex. | 2342 | 2324 | 18 | 164 | 182 |
| Array ex. | 2364 | 2346 | 18 | 564 | 582 |
We sized our array mydata to be 100 elements of unsigned long big; and since unsigned long is 4 bytes in size, the mydata array should take up 400 bytes - and as we can see, that is exactly the difference of .bss sizes between the array example and initial example binaries.
mydata array, but we didn't initialize it - memory for it was allocated in the .bss section (the section for non-initialized data).
At this point, let's try to play around a bit with the declaration of the array, and see what kind of effect it will have on the memory sections:
| Example | IDE | .text | .data | .bss | SRAM |
|---|---|---|---|---|---|
unsigned long mydata[SIZE]; | 2364 | 2346 | 18 | 564 | 582 |
static unsigned long mydata[SIZE]; | 2364 | 2346 | 18 | 564 | 582 |
static unsigned long mydata[SIZE] = { 0L }; | 2364 | 2346 | 18 | 564 | 582 |
static unsigned long mydata[SIZE] = { 2L }; | 2764 | 2346 | 418 | 164 | 582 |
We can see that only when we initialize an array to a value other than 0, the memory allocation of mydata array moves in the .data section; however, that results with no changes in SRAM allocation.
At this point, we come to the key question of this post - given the same code example, what is the maximum value of SIZE we can use for the mydata array - for an ATmega328?
As noted above, for an ATmega328, we have 2 KB - or 2048 bytes - of SRAM at disposal; and for 100 unsigned long elements, we use in total 582 bytes of SRAM.
unsigned long elements.
unsigned long elements were already included in those 582 bytes, that means that the total size of the array will be 100+366=466 unsigned long elements.
The thing is, in spite of this fine calculation - using 466 bytes for the size of the array, will NOT work on an Arduino with an ATmega328: either the serial output will be garbled; or there will be no output at all (in other words, the Arduino will 'crash')!
mydata as 'static' - or whether we initialize it!
That means, that the calculation based on SRAM utilization based only on .data and .bss is, in essence, unreliable.
These symptoms are essentially the same as described in Arduino - Memory: "If you run out of SRAM, your program may fail in unexpected ways; it will appear to upload successfully, but not run, or run strangely."
Some other pointers can be found on: Issue 40 - arduino - SRAM Memory size check:
Please have the IDE check the linker output, and report an error if the SRAM usage exceeds the size determined by the CPU selected in the preferences menu. .. Consider that the RAM usage changes as the program runs, so even if you fit under the limit when the program starts, you can run out of RAM in the middle of execution ... This would essentially require an arduino simulator to be run for some unspecified time.
and Arduino Forum - Serial data being corrupted:
Why doesn't avr-gcc warn when the RAM is filling up? The most the compiler could do is warn how much static memory (heap) is being used. A significant part of RAM utilization is determined at runtime, and it is frequently dependent on the actual input data, so it can change between program executions. The compiler has no way of knowing what data you will feed the program, and only the vaguest guess at how functions will be called. Automatic variables and saved registers consume RAM on the stack when functions are called. Functions don't get called at compile time, they get called at runtime.
We might also remember the statement above, that SRAM=data+bss "...does not include dynamic memory allocated from the heap at run time" - however, none of that should be applicable here, since we don't use malloc to allocate memory (and hence, all allocations are known a-priori).
So, the only thing we are left with, is to take the above value of 466 as an upper bound, SIZEmax, and then keep decreasing it until we find a value that makes the code work properly. So, starting with this kind of definitions:
static const int SIZE=466; ... static unsigned long mydata[SIZE];
Here is a log of outcomes of changing SIZE and rebuilding:
| SIZE | IDE | .text | .data | .bss | SRAM | status | simavr | emulino |
|---|---|---|---|---|---|---|---|---|
| 466 | 2364 | 2346 | 18 | 2028 | 2046 | no out | avr_sadly_crashed | segfault |
| 450 | 2364 | 2346 | 18 | 1964 | 1982 | corrupt out | runs ok | segfault |
| 440 | 2364 | 2346 | 18 | 1924 | 1942 | runs ok | runs ok | runs ok |
| 445 | 2364 | 2346 | 18 | 1944 | 1962 | runs ok | runs ok | runs ok |
| 449 | 2364 | 2346 | 18 | 1960 | 1978 | corrupt out | runs ok | crashes |
| 448 | 2364 | 2346 | 18 | 1956 | 1974 | no out | runs ok | segfault |
| 447 | 2364 | 2346 | 18 | 1952 | 1970 | corrupt out | runs ok | freeze |
| 446 | 2364 | 2346 | 18 | 1948 | 1966 | runs ok | runs ok | segfault |
| 445 | 2364 | 2346 | 18 | 1944 | 1962 | runs ok | runs ok | runs ok |
In the table above, "status" refers to the result obtained by building the .pde with the given SIZE, uploading it to the Arduino, and observing the output via
screen -L /dev/ttyUSB0 115200
The "-L" argument allows capture to a logfile, which can reveal whether the output is corrupt (when it is corrupt, usually the number sequence will change at random, and the code may never enter the doReset function).
So we can see that the highest number of elements that is "guaranteed" to work is, in fact, 445, which is 21 elements less than the initial maximum of 466 - or 84 bytes less from the "theoretical" SRAM maximum (derived from .data and .bss sizes only) for ATmega328. Obviously, we would like some measure that would inform us of this margin - which is why we may consider simulators and debugging.
Also in the table above, there are "simavr" and "emulino" columns; these refer to the output obtained by using two emulators for Linux that can be used for the Arduino (but need to be compiled from source):
./emulino /tmp/build7967507421090092066.tmp/minitest.cpp.hex
./run_avr -f 800 -m atmega328 /tmp/build7967507421090092066.tmp/minitest.cpp.hex
We can notice that simavr is somewhat optimistic in respect to how the Arduino behaves (that is, it will simulate an OK process for values of SIZE, for which the real Arduino will have problems); whereas emulino is even somewhat pesimistic (as it segfaults for a value of SIZE=446, for which the real Arduino seems to work OK).
Note also that there is another simulator, simulavr, which could possibly work - however, it doesn't seem to support ATmega328 (seemingly, not even in its latest git version)
The good thing about simavr or simulavr is that they can be used to debug AVR programs, by establishing a (local socket) connection to gdb; however, that needs to be a special installation of gdb for AVR, known as avr-gdb.
gdb-avr and simulavr in the main repositories; however, both of those are outdated, and should probably be built from source (see Bug #407367 in gdb-avr (Ubuntu): 'gdb-avr is outdated')
simavr is a bit too optimistic, it is likely that even a gdb approach may be unfruitful - as simavr simply does not display any problems, for sizes where the real Arduino has difficulties.
Also, note that emulino can be compiled with a #define TRACE in cpu.c, which will generate a tremendeous ammount of debug information - though not easily readable; the output could easily reach 70 MB text for logging just 100 working steps; and if there is a 'crash' afterwards, it can keep looping for more than 200MB without actually crashing. Also, in this case the simulation will run extremely slow - with piping stderr to file, it will take up to 3-5 seconds for a step to be printed out on stdout.
Another option for debugging this problem could be to use AVR Studio. It is Windows only, however it can be made to run under Wine (see AVR Studio on Linux). In principle, one can open the *.elf file in AVR studio, and run a debug session in assembler.
As a closing note, there is yet another way to debug these memory allocation issues in Arduino, listed in the forum entry Arduino Forum - How much ram is being used?. There, a memory profiling function is implemented in the Arduino sketch, which calculates sizes of heap, etc at runtime - and prints them out through serial.
As we have seen in this specific problem, in a real Arduino, we'd either get no output, or a corrupt output when "SRAM is maxed" - and in that case, we would not be able to rely on its serial printout either; and once sizes fit and the process starts working, the printout should not point to any discrepancies (and thus does not necesarrily bring us closer to identifying an "easy" condition for a maximum size of the array).
Obviously, if we add the memory profiling function, the available memory will change, and hence we have to go through the manual process of starting with a small array size, and then identifying a maximum array size, again. So let's do that; below is the example code we used so far - with the memory profiler function added (note that it uses freeMemory via MemoryFree.h, which is also given in the thread above), and starting array size of 100 elements (400 bytes):
// minitest.pde
static unsigned long mydata_count;
static const long SERSPEED=115200;
static const int SIZE=100;
static const char RSTSTR[] = "RESET!!";
static unsigned long mydata[SIZE];
#include <MemoryFree.h>
extern unsigned int __data_start;
extern unsigned int __data_end;
extern unsigned int __bss_start;
extern unsigned int __bss_end;
extern unsigned int __heap_start;
//extern void *__malloc_heap_start; --> apparently already declared as char*
//extern void *__malloc_margin; --> apparently already declared as a size_t
extern void *__brkval;
// RAMEND and SP seem to be available without declaration here
int16_t ramSize=0; // total amount of ram available for partitioning
int16_t dataSize=0; // partition size for .data section
int16_t bssSize=0; // partition size for .bss section
int16_t heapSize=0; // partition size for current snapshot of the heap section
int16_t stackSize=0; // partition size for current snapshot of the stack section
int16_t freeMem1=0; // available ram calculation #1
int16_t freeMem2=0; // available ram calculation #2
//* This function places the current value of the heap and stack pointers in the
// * variables. You can call it from any place in your code and save the data for
// * outputting or displaying later. This allows you to check at different parts of
// * your program flow.
// * The stack pointer starts at the top of RAM and grows downwards. The heap pointer
// * starts just above the static variables etc. and grows upwards. SP should always
// * be larger than HP or you'll be in big trouble! The smaller the gap, the more
// * careful you need to be. Julian Gall 6-Feb-2009.
// *
uint8_t *heapptr, *stackptr;
uint16_t diff=0;
void check_mem() {
stackptr = (uint8_t *)malloc(4); // use stackptr temporarily
heapptr = stackptr; // save value of heap pointer
free(stackptr); // free up the memory again (sets stackptr to 0)
stackptr = (uint8_t *)(SP); // save value of stack pointer
}
void setup()
{
Serial.begin(SERSPEED);
mydata_count = 0;
}
void loop()
{
mydata[mydata_count] = mydata_count;
memrep();
Serial.println(mydata[mydata_count], DEC);
mydata_count++;
if (mydata_count == SIZE) {
doReset();
}
}
void doReset()
{
Serial.println(RSTSTR);
mydata_count = 0;
}
void memrep() // run over and over again
{
Serial.print("\n\n--------------------------------------------");
Serial.print("\n\nLOOP BEGIN: get_free_memory() reports [");
Serial.print( freeMemory() );
Serial.print("] (bytes) which must be > 0 for no heap/stack collision");
Serial.print("\n\nSP should always be larger than HP or you'll be in big trouble!");
check_mem();
Serial.print("\nheapptr=[0x"); Serial.print( (int) heapptr, HEX); Serial.print("] (growing upward, "); Serial.print( (int) heapptr, DEC); Serial.print(" decimal)");
Serial.print("\nstackptr=[0x"); Serial.print( (int) stackptr, HEX); Serial.print("] (growing downward, "); Serial.print( (int) stackptr, DEC); Serial.print(" decimal)");
Serial.print("\ndifference should be positive: diff=stackptr-heapptr, diff=[0x");
diff=stackptr-heapptr;
Serial.print( (int) diff, HEX); Serial.print("] (which is ["); Serial.print( (int) diff, DEC); Serial.print("] (bytes decimal)");
Serial.print("\n\nLOOP END: get_free_memory() reports [");
Serial.print( freeMemory() );
Serial.print("] (bytes) which must be > 0 for no heap/stack collision");
// ---------------- Print memory profile -----------------
Serial.print("\n\n__data_start=[0x"); Serial.print( (int) &__data_start, HEX ); Serial.print("] which is ["); Serial.print( (int) &__data_start, DEC); Serial.print("] bytes decimal");
Serial.print("\n__data_end=[0x"); Serial.print((int) &__data_end, HEX ); Serial.print("] which is ["); Serial.print( (int) &__data_end, DEC); Serial.print("] bytes decimal");
Serial.print("\n__bss_start=[0x"); Serial.print((int) & __bss_start, HEX ); Serial.print("] which is ["); Serial.print( (int) &__bss_start, DEC); Serial.print("] bytes decimal");
Serial.print("\n__bss_end=[0x"); Serial.print( (int) &__bss_end, HEX ); Serial.print("] which is ["); Serial.print( (int) &__bss_end, DEC); Serial.print("] bytes decimal");
Serial.print("\n__heap_start=[0x"); Serial.print( (int) &__heap_start, HEX ); Serial.print("] which is ["); Serial.print( (int) &__heap_start, DEC); Serial.print("] bytes decimal");
Serial.print("\n__malloc_heap_start=[0x"); Serial.print( (int) __malloc_heap_start, HEX ); Serial.print("] which is ["); Serial.print( (int) __malloc_heap_start, DEC); Serial.print("] bytes decimal");
Serial.print("\n__malloc_margin=[0x"); Serial.print( (int) &__malloc_margin, HEX ); Serial.print("] which is ["); Serial.print( (int) &__malloc_margin, DEC); Serial.print("] bytes decimal");
Serial.print("\n__brkval=[0x"); Serial.print( (int) __brkval, HEX ); Serial.print("] which is ["); Serial.print( (int) __brkval, DEC); Serial.print("] bytes decimal");
Serial.print("\nSP=[0x"); Serial.print( (int) SP, HEX ); Serial.print("] which is ["); Serial.print( (int) SP, DEC); Serial.print("] bytes decimal");
Serial.print("\nRAMEND=[0x"); Serial.print( (int) RAMEND, HEX ); Serial.print("] which is ["); Serial.print( (int) RAMEND, DEC); Serial.print("] bytes decimal");
// summaries:
ramSize = (int) RAMEND - (int) &__data_start;
dataSize = (int) &__data_end - (int) &__data_start;
bssSize = (int) &__bss_end - (int) &__bss_start;
heapSize = (int) __brkval - (int) &__heap_start;
stackSize = (int) RAMEND - (int) SP;
freeMem1 = (int) SP - (int) __brkval;
freeMem2 = ramSize - stackSize - heapSize - bssSize - dataSize;
Serial.print("\n--- section size summaries ---");
Serial.print("\nram size=["); Serial.print( ramSize, DEC ); Serial.print("] bytes decimal");
Serial.print("\n.data size=["); Serial.print( dataSize, DEC ); Serial.print("] bytes decimal");
Serial.print("\n.bss size=["); Serial.print( bssSize, DEC ); Serial.print("] bytes decimal");
Serial.print("\nheap size=["); Serial.print( heapSize, DEC ); Serial.print("] bytes decimal");
Serial.print("\nstack size=["); Serial.print( stackSize, DEC ); Serial.print("] bytes decimal");
Serial.print("\nfree size1=["); Serial.print( freeMem1, DEC ); Serial.print("] bytes decimal");
Serial.print("\nfree size2=["); Serial.print( freeMem2, DEC ); Serial.print("] bytes decimal");
Serial.println();
}
Here, binary sketch size is 5190 bytes, while avr-size reports:
$ avr-size /tmp/build7967507421090092066.tmp/minitest.cpp.elf text data bss dec hex filename 4416 774 588 5778 1692 /tmp/build7967507421090092066.tmp/minitest.cpp.elf
which means SRAM=774+588=1362 bytes; and the output is like:
... -------------------------------------------- LOOP BEGIN: get_free_memory() reports [650] (bytes) which must be > 0 for no heap/stack collision SP should always be larger than HP or you'll be in big trouble! heapptr=[0x654] (growing upward, 1620 decimal) stackptr=[0x8E5] (growing downward, 2277 decimal) difference should be positive: diff=stackptr-heapptr, diff=[0x291] (which is [657] (bytes decimal) LOOP END: get_free_memory() reports [650] (bytes) which must be > 0 for no heap/stack collision __data_start=[0x100] which is [256] bytes decimal __data_end=[0x406] which is [1030] bytes decimal __bss_start=[0x406] which is [1030] bytes decimal __bss_end=[0x652] which is [1618] bytes decimal __heap_start=[0x652] which is [1618] bytes decimal __malloc_heap_start=[0x652] which is [1618] bytes decimal __malloc_margin=[0x3EE] which is [1006] bytes decimal __brkval=[0x658] which is [1624] bytes decimal SP=[0x8E7] which is [2279] bytes decimal RAMEND=[0x8FF] which is [2303] bytes decimal --- section size summaries --- ram size=[2047] bytes decimal .data size=[774] bytes decimal .bss size=[588] bytes decimal heap size=[6] bytes decimal stack size=[24] bytes decimal free size1=[655] bytes decimal free size2=[655] bytes decimal 99 RESET!! -------------------------------------------- LOOP BEGIN: get_free_memory() reports [650] (bytes) which must be > 0 for no heap/stack collision SP should always be larger than HP or you'll be in big trouble! heapptr=[0x654] (growing upward, 1620 decimal) stackptr=[0x8E5] (growing downward, 2277 decimal) difference should be positive: diff=stackptr-heapptr, diff=[0x291] (which is [657] (bytes decimal) LOOP END: get_free_memory() reports [650] (bytes) which must be > 0 for no heap/stack collision __data_start=[0x100] which is [256] bytes decimal __data_end=[0x406] which is [1030] bytes decimal __bss_start=[0x406] which is [1030] bytes decimal __bss_end=[0x652] which is [1618] bytes decimal __heap_start=[0x652] which is [1618] bytes decimal __malloc_heap_start=[0x652] which is [1618] bytes decimal __malloc_margin=[0x3EE] which is [1006] bytes decimal __brkval=[0x658] which is [1624] bytes decimal SP=[0x8E7] which is [2279] bytes decimal RAMEND=[0x8FF] which is [2303] bytes decimal --- section size summaries --- ram size=[2047] bytes decimal .data size=[774] bytes decimal .bss size=[588] bytes decimal heap size=[6] bytes decimal stack size=[24] bytes decimal free size1=[655] bytes decimal free size2=[655] bytes decimal 0 ...
Notice in the log above, that none of the information printed changes between steps!
In this case, we got usage of SRAM=1362 bytes, which should mean that 2048-1362=686 bytes remain; however, as free_memory() reports 650 bytes, we will take that as a starting point:
unsigned long elements
SIZEmax = 262 elements.
| SIZE | IDE | .text | .data | .bss | SRAM | status | simavr | emulino |
|---|---|---|---|---|---|---|---|---|
| 262 | 5190 | 4416 | 774 | 1236 | 2010 | no out | freezes | loops to LOOP BEGIN: |
| 252 | 5190 | 4416 | 774 | 1196 | 1970 | no out | freezes | loops to LOOP BEGIN: |
| 242 | 5190 | 4416 | 774 | 1156 | 1930 | runs ok | runs ok | runs ok |
Again, we get OK performance, if we allocate about 80 bytes less, than the first estimation of SIZEmax ! For 242 elements, we get a report:
... LOOP BEGIN: get_free_memory() reports [82] (bytes) which must be > 0 for no heap/stack collision SP should always be larger than HP or you'll be in big trouble! heapptr=[0x88C] (growing upward, 2188 decimal) stackptr=[0x8E5] (growing downward, 2277 decimal) difference should be positive: diff=stackptr-heapptr, diff=[0x59] (which is [89] (bytes decimal) LOOP END: get_free_memory() reports [82] (bytes) which must be > 0 for no heap/stack collision __data_start=[0x100] which is [256] bytes decimal __data_end=[0x406] which is [1030] bytes decimal __bss_start=[0x406] which is [1030] bytes decimal __bss_end=[0x88A] which is [2186] bytes decimal __heap_start=[0x88A] which is [2186] bytes decimal __malloc_heap_start=[0x88A] which is [2186] bytes decimal __malloc_margin=[0x3EE] which is [1006] bytes decimal __brkval=[0x890] which is [2192] bytes decimal SP=[0x8E7] which is [2279] bytes decimal RAMEND=[0x8FF] which is [2303] bytes decimal --- section size summaries --- ram size=[2047] bytes decimal .data size=[774] bytes decimal .bss size=[1156] bytes decimal heap size=[6] bytes decimal stack size=[24] bytes decimal free size1=[87] bytes decimal free size2=[87] bytes decimal ...
If we again start increasing SIZE, we will find that corrupt output starts occuring at SIZE of 244 elements, in which case a real Arduino output can be like:
...
LOOP BEGIN: get_free_memory() reports [74] (bytes) which must be > 0 for no heap/stack collision
SP should always be larger than HP or you'll be in big trouble!
heapptr=[0xFFFF82B7] (growing upward, -32073 decimal)
stack{90}{STX}r=[0x8E5] (growing downward, 2277 decimal)
difference should be positive: diff=stackptr-heapptr, diff=[0xFFFF862EQ (which is [-31186] (bytes decimal)
LOOP END: ge{EOT}74] (bytes) which must be > 0 for no heap/stack collision
__data_st{EOT}100{BD}{82}which is [256] bytes decimal
__data_end=[0x406{BD}{82}which is [1030] bytes decimal
__bss_start=[0x406{BD}{82}which is [1030] bytes decimal
__bss_end=[0x892{BD}{82}which is [2194] bytes decimal
__heap_start=[0x892{BD}{82}which is [2194] bytes decimal
__malloc_heap_start=[0x892{BD}{82}which is [2194] bytes decimal
__malloc_margin=[0x3EE{BD}{82}which is [1006] bytes decimal
__brkval=[0x898{BD}{82}which is [2200] bytes decimal
SP=[0x8E7{BD}{82}which is [2279] bytes decimal
RAMEND=[0x8FF{BD}{82}which is [2303] bytes decimal
--- section size summaries ---
ram size=[2047] bytes decimal
.data size=[774] bytes decimal
.bss size=[1164] bytes decimal
heap size=[6] bytes decimal
stack size=[24] bytes decimal
free size1=[79] bytes decimal
free size2=[79] bytes decimal
...
And here, in between the corrupt characters (marked with {}), we gain another parameter explaining why we get a failure now - the difference between stack and heap pointer is negative: diff=[0xFFFF862EQ (which is [-31186] (bytes decimal).
simavr is again more optimistic (it works without a crash); while emulino freezes after several iterations (however, its diff is not negative - although, it decreases with every run).
SIZE of 243, simavr, emulino and real Arduino output show the same memory profile function output.
So, although a 100% working recipe for finding safe maximum array sizes, cannot really be stated - as a brief conclusion, we can say that: in order to find the maximum size for a single array in an Arduino sketch, which will fill out maximum available ammount of SRAM without corrupting the running process, we can first start with a smaller array of safe, known size - and then:
First approximation (compile time, without memory profile function):
SRAM as .data+.bss
maxSRAM for a chip, get remain as maxSRAM-SRAM
remain as a safety margin: saferemain = remain - 80
saferemain to estimate initial SIZEmax of array
SIZE until its max value for proper performance is found.
Second approximation (runtime, with memory profile function):
remain as whatever freeMemory() returns
remain as a safety margin: saferemain = remain - 80
saferemain to estimate initial SIZEmax of array
SIZE until its max value, that still keeps the difference between stack and heap pointer positive, is found.
Smilen Dimitrov