Memories of An Arduino
Memories of An Arduino
https://learn.adafruit.com/memories-of-an-arduino
Memory Architectures 6
• Harvard vs Princeton
• Which is better?
• Modern Hybrids
• Microcontrollers
• A completely different Scale
Arduino Memories 8
• Flash Memory
• SRAM
• EEPROM
Optimizing SRAM 20
• Remove Unused Variables
• F() Those Strings!
• (Park the char* in Harvard PROGMEM)
• Reserve() your strings
• Move constant data to PROGMEM.
• Reduce Buffer Sizes
• Reduce Oversized Variables
• Think Globally. Allocate Locally.
• Global & Static Variables
• Dynamic Allocations
• Local Variables
• The Takeaway?
The most obvious sign of a memory problem is when the compiler tells you that your
sketch is too big.
But many memory problems show much more subtle symptoms. Your program may
load, but not run. It may crash hard, or just start acting funky.
If your program compiles and loads successfully, but any of the following statements
are true, there is a good chance that you have a memory problem.
If you think you might have a memory problem, you can skip right to the "Solving
Memory Problems" page. But you should first take a look through the next few pages
to better understand Arduino memory and how it works.
The Von Neumann () (a.k.a. ()Princeton ()) ()architecture () developed for the
ENIAC ()uses the same memory and data paths for both program and data storage.
Modern Hybrids
These days, most general purpose computers (PC's Mac's etc.) are hybrid designs that
give you the best of both architectures. Deep within the CPU they operate on the
Harvard model using separate caches for instructions and data to maximize
performance. But the instruction and data caches are both loaded automatically from
a common memory space. From a programming perspective, these computers appear
to be pure Von Neumann machines with many gigabytes of virtual storage.
Microcontrollers
Microcontrollers such as the ones that power the Arduinos are designed for
embedded applications. Unlike general purpose computers, an embedded processor
typically has a well defined task that it must perform reliably and efficiently - and at
minimal cost, Microcontroller designs tend to be rather spartan. They forego the
luxuries of multi-layer caching and disk-based virtual memory systems and stick to
what is essential to the task.
The Harvard model turns out to be a good match for embedded applications and the
Atmega 328 used in the Arduino UNO use a relatively pure Harvard architecture.
Programs are stored in Flash memory and data is stored in SRAM.
For the most part, the compiler and run-time systems take care of managing these for
Working in this minimalist environment, you must use your resources wisely.
Arduino Memories
There are 3 types of memory in an Arduino:
Flash Memory
Flash memory is used to store your program image and any initialized data. You can
execute program code from flash, but you can't modify data in flash memory from your
executing code. To modify the data, it must first be copied into SRAM
Flash memory is the same technology used for thumb-drives and SD cards. It is non-
volatile, so your program will still be there when the system is powered off.
Flash memory has a finite lifetime of about 100,000 write cycles. So if you upload 10
programs a day, every day for the next 27 years, you might wear it out.
SRAM
SRAM or Static Random Access Memory, can be read and written from your executing
program. SRAM memory is used for several purposes by a running program:
• Static Data - This is a block of reserved space in SRAM for all the global and
static variables from your program. For variables with initial values, the runtime
system copies the initial value from Flash when the program starts.
Most memory problems occur when the stack and the heap collide. When this
happens, one or both of these memory areas will be corrupted with unpredictable
results. In some cases it will cause an immediate crash. In others, the effects of the
corruption may not be noticed until much later.
While it can't take the place of precious SRAM, there are times when it can be very
useful!
Flash
Measuring Flash memory usage is trivial. The compiler does that for you, every time
you compile!
// ************************************************
// Write floating point values to EEPROM
// ************************************************
void EEPROM_writeDouble(int address, double value)
{
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
EEPROM.write(address++, *p++);
}
}
// ************************************************
// Read floating point values from EEPROM
// ************************************************
double EEPROM_readDouble(int address)
{
double value = 0.0;
byte* p = (byte*)(void*)&value;
for (int i = 0; i < sizeof(value); i++)
{
*p++ = EEPROM.read(address++);
}
return value;
}
SRAM
SRAM usage is more dynamic and therefore more difficult to measure. The freeMemo
ry() function below is one way to do this. You can add this function definition to your
code, then call it from various places in your code to report the amount of free SRAM.
SRAM utilization is dynamic and will change over time. So It is important to call freeM
emory() at various times and from various places in your sketch to see how it
changes over time.
#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));
What freeMemory() is actually reporting is the space between the heap and the
stack. it does not report any de-allocated memory that is buried in the heap. Buried
heap space is not usable by the stack, and may be fragmented enough that it is not
usable for many heap allocations either. The space between the heap and the stack is
what you really need to monitor if you are trying to avoid stack crashes.
Memory is a finite resource on these tiny processors and some applications are just
plain too big for an Arduino. But most code has some room for optimization. So if your
program is just a little overweight, with a little diet and exercise, you can probably
shed enough bytes to make it fit into that Uno again.
This is not meant to be a definitive treatise on how to optimize your code - there are
libraries full of books on the subject. What is presented here are just some simple tips
to help harvest the low-hanging fruit.
Hint - If you are not sure about an #include, a function or a variable. Comment it out. If
the program still compiles, that code is not being used.
The downside of this is that you will need to load your code using an ISP programmer
instead of via a standard USB cable.
Optimizing SRAM
SRAM is the most precious memory commodity on the Arduino. Although SRAM
shortages are probably the most common memory problems on the Arduino. They are
also the hardest to diagnose. If your program is failing in an otherwise inexplicable
fashion, the chances are good you have crashed the stack due to a SRAM shortage.
There are a number of things that you can do to reduce SRAM usage. These are just a
few guidelines to get you started:
Serial.println("Sram sram sram sram. Lovely sram! Wonderful sram! Sram sra-a-a-a-
a-am sram sra-a-a-a-a-am sram. Lovely sram! Lovely sram! Lovely sram! Lovely sram!
Lovely sram! Sram sram sram sram!");
with this:
Serial.println(F("Sram sram sram sram. Lovely sram! Wonderful sram! Sram sra-a-a-
a-a-am sram sra-a-a-a-a-am sram. Lovely sram! Lovely sram! Lovely sram! Lovely
sram! Lovely sram! Sram sram sram sram!"));
With the memory already allocated, String doesn't need to call realloc() if the string
grows in length. In most usages, lots of other little String objects are used temporarily
as you perform these operations, forcing the new string allocation to a new area of
the heap and leaving a big hole where the previous one was (memory fragmentation).
Usually all you need to do is use reserve() on any long-lived String objects that you
know will be increasing in length as you process text.
You can do better with C strings, but if you just follow these guidelines for String
objects, they work nearly as efficiently and using them is so much easier.
Buffers in Libraries:
Also be aware that some libraries allocate buffers behind the scenes that may be
candidates for trimming as well.
System Buffers:
Another buffer hidden deeply in the system is the 64 byte serial receive buffer. If your
sketch is not receiving a lot of high-speed serial data, you can probably cut this buffer
size in half - or maybe even less.
The Serial buffer size is defined in HardwareSerial.cpp. This file can be found in your
Arduino install directory:
....\Arduino-1.x.x\hardware\arduino\cores\arduino\HardwareSerial.cpp
#define SERIAL_BUFFER_SIZE 64
Global and Static variables are the first things loaded into SRAM. They push the start
of the heap upward toward the stack and they will occupy this space for all eternity.
Dynamic Allocations
Dynamicaly allocated objects and data cause the heap to grow toward the stack.
Unlike Global and Static variables, these variables can be de-allocated to free up
space. But this does not necessarily cause the heap to shrink! If there is other
dynamic data above it in the heap, the top of the heap will not move. When the heap
is full of holes like swiss cheese we call it a "fragmented heap".
Local Variables
Every function call creates a stack frame that makes the stack grow toward the heap.
Each stack frame will contain:
This data is usable within the function, but the space is 100% reclaimed when the
function exits!
The Takeaway?
• Avoid dynamic heap allocations - These can quickly fragment the limited heap-
space.
• Prefer local to global allocation - Stack variables only exist while they are being
used. If you have variables that only are used in a small section of your code,
consider making that code into a function and declaring the variables local to
the function.
Using EEPROM
EEPROM is a handy, non-volatile storage space that works well for storing data such
as calibration or tuning constants that are not practical to hard-code into Flash.
It is unusual to run out of EEPROM. And it is not often practical to use EEPROM to
offload SRAM data. But we'll mention it here for completeness. Using EEPROM
requires that you include the EEPROM library.
#include <EEPROM.h>
uint8_t read(int)
Read a byte from the specified EEPROM address
Note that while reads are unlimited, there are a finite number of write cycles (typically
about 100,000).