There are a number of things that you can do to reduce SRAM usage. These are just a few guidelines to get you started:
Remove Unused Variables
If you are not sure whether a variable is being used or not, comment it out. If the sketch still compiles, get rid of it!
F() Those Strings!
(Park the char* in Harvard PROGMEM)
Literal strings are repeat memory offenders. First they take up space in the program image in Flash, then they are copied to SRAM at startup as static variables. This is a horrible waste of SRAM since we will never be writing to them.
Paul Stoffregen of PJRC and Teensyduino fame developed the F() macro as a super-simple solution to this problem. The F() macro tells the compiler to keep your strings in PROGMEM. All you have to do is to enclose the literal string in the F() macro.
For example, replacing this:
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!");
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!"));
Reserve() your strings
The Arduino string library allows you to reserve buffer space for a string with the reserve() function. The idea is you can prevent String from fragmenting the heap by using reserve(num) to pre-allocate memory for a String that grows.
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.
Move constant data to PROGMEM
With older Atmel/Microchip AVR microcontrollers like the ATtiny85 (Trinket Classic), ATmega328 (Classic Arduino Uno and others) etc., PROGMEM allows for static data to be stored in flash program space, possibly saving SRAM. On newer boards, this is unnecessary as this usage is automatic (board dependent).
Data items declared as PROGMEM on AVRs do not get copied to SRAM at startup. They are a little less convenient to work with (they require special code syntax to read in & out of flash), but the use can save significant amounts of SRAM with programs using a lot of fixed data (like strings or fixed arrays). The basic Arduino reference for PROGMEM is here. To quote:
PROGMEM
is useful only when working with old AVR boards (Uno Rev3, Leonardo etc.). Newer Arduino and other boards (Due, MKR WiFi 1010, GIGA R1 WiFi etc.) automatically use the program space when a variable is declared as aconst
.However, for retro compatibility,
PROGMEM
can still be used with newer boards. This implementation currently lives in pgmspace.h.
Reduce Buffer Sizes
Buffer and Array Allocations:
If you allocate a buffer, make sure it is no bigger than it needs to be.
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
Look for the line:
#define SERIAL_BUFFER_SIZE 64
And change it to 32 or less.
Reduce Oversized Variables
Don't use a float when an int will do. Don't use an int when a byte will do. Try to use the smallest data type capable of holding the information.
Try to use only integers
Using floating point (decimal) numbers causes code bloat and more memory usage. Printing a floating point number also does this. If you don't need huge precision, try to use only integer (whole) numbers.
Global & Static Variables
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
Dynamically allocated objects and data can 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:
- All parameters passed to the function
- All local variables declared in the function.
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.
Page last edited May 01, 2024
Text editor powered by tinymce.