Be wise in using Microcontroller’s memory

Memory is a finite resource on these tiny processors and some applications are just plain too big for an microcontroler(uC). But most code has some room for optimization. So if program is just a little overweight, with a little diet and exercise, probably shed enough bytes to make it fit into uC. As we know, there are 3 types of memory in uC:

  • Flash or Program Memory
  • SRAM
  • EEPROM

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.
  • Heap – The heap is for dynamically allocated data items. The heap grows from the top of the static data area up as data items are allocated.
  • Stack – The stack is for local variables and for maintaining a record of interrupts and function calls. The stack grows from the top of memory down towards the heap. Every interrupt, function call and/or local variable allocation causes the stack to grow. Returning from an interrupt or function call will reclaim all stack space used by that interrupt or function.

EEPROM

EEPROM is another form of non-volatile memory that can be read or written from your executing program. It can only be read byte-by-byte, so it can be a little awkward to use. It is also slower than SRAM and has a finite lifetime of about 100,000 write cycles (you can read it as many times as you want).

Several Memory Problems

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. One way to diagnose memory problems is to measure how much memory is in use.

Optimize Flash memory

Flash memory usage is depend on code and compiler. If it have reached or exceeded the space available, some of these optimizations may help get you back under the limit.

Remove Dead Code

If project is a mash-up of code from several sources, chances are there are parts that are not getting used and can be eliminated to save space.

  • Unused Libraries – Are all the #include libraries actually used?
  • Unused Functions – Are all the functions acutally being called?
  • Unused Variables – Are all the variables actually being used?
  • Unreachable Code – Are there conditional expressions which will never be true?
 Hint: If  not sure about an #include, a function or a variable. Comment it out. If the program still compiles, that code is not being used. Just remove it!

Consolidate Repeated Code

If any sequence of code statements in two or more places, consider making a function out of them.

Eliminate the Bootloader

If space is really-really tight, you might consider eliminating the bootloader. This can save as much as 2K or 4K of Flash – depending on which bootloader you are currently using.

The downside of this is that you will need to load your code using an ISP programmer instead of via a standard USB cable.

 

Optimize SRAM

SRAM is the most precious memory commodity. SRAM shortages are probably the most common memory problems. They are also the hardest to diagnose. If program is failing in an otherwise inexplicable fashion, a crashed may happen in the stack due to a SRAM shortage. There are a number of things that can do to reduce SRAM usage.

Remove Unused Variables

If not sure whether a variable is being used or not, comment it out. If the sketch still compiles, get rid of it!

Use F() Macro

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("This string will be stored in memory. Horible waste of SRAM!");

with this:

  Serial.println(F(" This string will be stored in memory. Horible waste of SRAM!"));

Will save bytes of wonderful SRAM!

Reserve() your strings

The string library allows to reserve buffer space for a string with the reserve() function. The idea is 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.

Move constant data to PROGMEM.

Data items declared as PROGMEM do not get copied to SRAM at startup. They are a little less convenient to work with, but they can save significant amounts of SRAM. The basic Arduino reference for PROGMEM is here. And there is a more detailed tutorial on the subject here.

#define FS(x) (__FlashStringHelper*)(x)
const char MyText[]  PROGMEM  = { “My flash based text” };

void setup() {
Serial.begin(57600):
Serial.println(FS(MyText));
}

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.

For an Example, if using Arduino library, Serial buffer can be reduced. 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. Use char instead of int if only need 255 bits data.

 

Think Globally. Allocate Locally.

Let’s have another look at how SRAM is used (and abused):

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

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:

  • 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.

 

Optimize 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>

The EEPROM library gives us 2 functions:

uint8_t read(int) //Read a byte from the specified EEPROM address

void write(int, uint8_t) //Write a byte to the specified EEPROM address

  • Note that while reads are unlimited, there are a finite number of write cycles limited (typically about 100,000).
  • Always check that EEPROM is ready before reading/writing to it (eeprom_is_ready function)
  • Always prefer the update functions rather than the write ones, as update checks first if the stored data is different than the data, so it erases / writes the new data only if it has changed. Check first if the value has changed before writing, or even use the EEPROMex alternative EEPROM Arduino library.
  • Read / write operations on EEPROM should never be interrupted : you should always disable/clear interrupts (cli()) before any operation and re-enable/set interrupts after (sei()).

Here is the example:

while (!eeprom_is_ready());

cli();

if(eeprom_read_word((uint16_t*)addr) != sensorValue) {

eeprom_write_word((uint16_t*)addr, sensorValue);

}

sei();