Trains4Africa
Because all Boys (and some girls) love Trains

Optimizing Arduino Memory Use

Optimizing Arduino Memory Use

An Arduino has two main kinds of memory: flash and SRAM. Flash is where the program itself is stored, and can’t be changed except when you load a new program (called a “sketch” for some reason) from a computer. It also keeps what’s loaded into it even if power is turned off. When you Verify or Upload a sketch, it will tell you in the window how much flash you have and how much it used (if you have enabled “verbose mode” in the Preferences). Each time you load a new sketch, it overwrites the old one; an Arduino only holds one program at a time, and when power is provided to the Arduino that program runs, forever (unless it crashes; and there’s a reset button for that).

The SRAM is used for storing data, like the last reading from a sensor. What’s in there goes away when the power is turned off. Most modern Arduinos have about 32K of flash, which is pretty small, and limits the size of the programs (sketches) you can load. But SRAM is the real limit for many things. You only get 2 KB in most cases, and that’s tiny. It will hold less than 1,000 integers, or 500 floating-point numbers, and some of it needs to be used by the Arduino. You really need to be careful about planning so you can minimize what you really need to save. And if you try to use too much, you normally don’t get a nice warning, the Arduino just doesn’t work. You won’t even get it to print out any debugging “println” statements you put in; it just dies until you load a working program.

Any variable you define, either at the top of the program, inside a function, or even “on the fly” in something like a for loop, will likely use SRAM, although some variables that only last a short time may be optimized into registers, and never stored in SRAM.

Here’s an important reminder: because the contents of SRAM go away when the Arduino is turned off, you can’t store things like “track seven is in use” between sessions on your layout (unless you leave the power on and never have power failures where you live). Each time the Arduino starts up (via power up or reset), all of its variables are reinitialized to their defaults, and it needs to re-learn the environment it is working with. That’s an essential aspect to consider when designing a system. However, you can store some essential information between uses.

There’s actually a third kind of memory, EEPROM, which can be written to and will be saved over a power interruption. This is a bit more complicated to use (you have to read/write individual bytes, not larger amounts like integers or strings of characters) and it is very limited (just 1000 bytes on the Uno, or 500 integers or 250 floating point numbers). This is what to use if you need to remember something from session to session, but be careful not to write to it too often. EEPROM will wear out (it takes hundreds of thousands of writes, but an Arduino can do 300 EEPROM writes per second if you’re careless and in theory you could destroy a memory cell in 5 minutes, and the whole EEPROM in under two days).

But before saving something, keep in mind that things can change in the real world between sessions. Just because track seven was in use when the power was turned off, doesn’t mean that the great five-fingered crane didn’t remove the cars there, or worse relocate them to track six while the Arduino was off. It’s almost always better to relearn things at startup than to try saving them.

Being Efficient

The fundamental idea is to plan ahead, and not store anything more than you need to, and to store what you do need to keep in the most efficient form.

Strings

Every quoted string in your program (like the ones you put in a println for debugging) goes into SRAM. Comment out the ones you don’t need, or wrap them up in a conditional:

//#define MY_DEBUG

#if defined(MY_DEBUG)
Serial.print(“About to process number “);Serial.println(i);
#endif

The above, with the MY_DEBUG commented out, will not use any SRAM. When you remove the two slashes from in front of the #define, the program will print out the string, and use memory to store it. And one MY_DEBUG can enable many debugging statements throughout the program (or you can have several different defines, and reduce memory requirements by only turning on the ones you need).

There’s actually a clever way to put the string into flash memory, which helps cut down on wasted SRAM, see below.

Numbers and Booleans

Being efficient also means using the right data types for numbers. Don’t use a long (four bytes) to store a number than will never be larger than 32K, use an int (two bytes). Don’t use an int if a number will always be positive and less than 255, use a byte (one byte). And don’t use a float (four bytes and less precision) if what you really need to work with is an integer.

byte a = 1; // uses one byte, can store 0 to 255
int b = 1; // uses two bytes, can store -32,768 to 32,767
unsigned int c = 1; // uses two bytes, can store 0 to 65,535
long d = 1; // uses four bytes, can store -2,147,483,648 to 2,147,483,647
unsigned long e = 1; // uses four bytes, can store 0 to 4,294,967,295
float f = 1.0; // uses four bytes, and can store huge numbers, but 4.0/2.0 may not equal 2.0

At the same time, don’t use too small a variable if you aren’t sure. Declaring a variable as a byte, and using it with a function that returns an int should properly truncate small values and not overwrite other parts of memory, but if you try to store 257 in a byte, what you get is a 1.

One form of this that may not be quite as obvious: a boolean variable takes one byte. In “The Arduino Language” (which is C++), true and false are merely the numbers 1 and 0 respectively. Many routines essentially return “true” (1) and “false” (0) in the form of integers. For example, digitalRead returns either a HIGH (1) or LOW (0) in an int, using two bytes. If you need to store a bunch of these, the wasted bytes add up. Instead, convert this to a boolean:

boolean pinVal = (digitalRead(pinNum) == HIGH); // set pinVal to true if the pin is HIGH, and false if it is LOW

Putting it where you want

Okay, that’s all well and good, but can you control which memory is used? Well yes, to an extent. Unfortunately, most things are going to end up in SRAM.

If it’s something that than can change during program execution, especially if it’s going to change often, it’s probably best left in SRAM, which is the default for any variables you define. SRAM is fast, so the program won’t slow down when you read or save it. If you need to save it in case the system is restarted, you should keep a working copy in SRAM and only write that to EEPROM when absolutely necessary, to avoid wearing out the flash memory cells.

If it’s a constant, you might think it could easily be stored in flash, but in general that doesn’t seem to be the case. And while there’s a method for putting constants in flash, it’s probably best suited for fairly large constants (arrays and strings) rather than simple numbers.

Storing Large Constants in Flash

Some constants, like text strings, are quite large. However, by default these are stored in SRAM. There’s a way to store these in flash, which on the Arduino is called “program memory”. The PROGMEM keyword forces a variable to be stored in flash (effectively making it a constant). However, there are some special routines needed to actually read such a value out of flash to use it, so this can’t be used (at least not easily) to store simple constants.

For example, longString will be stored in SRAM by default, occupying 62 bytes (internally there’s a null on the end to terminate the string).

char longString[] = “This is a long string to store in memory if we don’t need to.”;
Serial.println(longString);

To put that in flash and still use it,

char buffer[62];
char longString1[] PROGMEM = “This is a long string to store in memory if we don’t need to.”;
char longString2[] PROGMEM = “This is another long string to store in memory.”;
PGM_P strArr[2] PROGMEM = {
longString1,
longString2
};
strcpy_P( buffer, (PGM_P)pgm_read_word( &(strArr[0]) ) );
Serial.println(buffer);
strcpy_P( buffer, (PGM_P)pgm_read_word( &(strArr[1]) ) );
Serial.println(buffer);

Which is a lot of work to go to, just to save 62 bytes (and we may have used those up with the strcpy code).

So, it’s there, and if you’re doing something with a lot of data it’s an option, but it’s not going to help for smaller scale data storage needs.

To use PROGMEM, you need to include the library:

#include

The Arduino folks don’t make the documentation easy to find (probably because it’s written for fairly knowledgeable programmers). Hidden away deep inside the Arduino application directory there’s an HTML file giving an overview (file pgmspace.html), however that dates from 2008. A newer version is online at this location, but I’m not sure which is applicable to Arduino (I haven’t played with this as yet). You can find the internal one at:

…/hardware/tools/avr/doc/avr-libc/pgmspace.html

So far, the only online description other than the documentation I’ve found is this one.

Simple Strings in Flash

There’s a simple way to put strings used in a print or println in flash memory, the F macro (described here).

Serial.println(F(“I am in Flash.”));

will print out the string, using some SRAM for a buffer but keeping the string itself in Flash memory. This way if you have lots of different strings to print, you only use a small amount of SRAM. Because the buffer needs to be as long as the longest string, if you have really long text to print out, break it up into strings no more than a couple-hundred characters each and print them separately.

Using EEPROM

What about things you want to save from one session to the next? This is where the EEPROM memory comes in. But, as cautioned above, be sure that it’s going to be useful to store something, and that it wouldn’t be more reliable to re-learn it the next time.

As noted the Arduino’s EEPROM memory is very limited in size. Additionally, you can’t define variables as being “in EEPROM”, you need to use a library routine to read and write it explicitly, probably to remind you that it has a limited life and writing to it isn’t something you should do too often. And it takes a long time to write to EEPROM, about 3.3 milliseconds, which is thousands of times longer than a memory access.

Also, you can store only one byte at a time, so to store the value of an integer “myInt” in cells 5 and 6 of EEPROM, you could use:

EEPROM.write(5, lowByte( myInt ) );
EEPROM.write(6, highByte( myInt ) );

which takes 6.6 milliseconds and uses two bytes of precious EEPROM.

That’s the general solution, but suppose you know “myInt” will only take a few values, say 0 to 4, here’s a simpler solution:

switch (myInt) {
case 0: EEPROM.write(5, 0 ); break;
case 1: EEPROM.write(5, 1 ); break;
case 2: EEPROM.write(5, 2 ); break;
case 3: EEPROM.write(5, 3 ); break;
case 4: EEPROM.write(5, 4 ); break;
}

This takes 3.3 milliseconds (plus a few microseconds for the switch) and uses only one byte of EEPROM. Of course it won’t work if you later change the program so that myInt can hold more values, unless you remember to update the switch. This is “fragile” code since it can cause unexpected problems later when things change, so in general it’s not a good idea to work this way.

A simpler and better method to gain the same benefit for an integer is to use just the lowByte function:

EEPROM.write(5, lowByte( myInt ) );

This stores the part of the integer that contains positive numbers from 0 to 255. It won’t work if the number is negative, or if it’s 256 or larger. But it will work for small numbers and it’s much less fragile than a switch statement naming each number.

But a switch statement may still be useful. Suppose what you really have is a string that you need to remember, but you know it will be one of a limited number. Use lowByte to store the number of the string as above, but when reading:

char myString[20];
switch (EEPROM.read(5)) {
case 0: strlcpy( myString, “Grape”, sizeof(myString)); break;
case 1: strlcpy( myString, “Apple”, sizeof(myString)); break;
case 2: strlcpy( myString, “Orange”, sizeof(myString)); break;
case 3: strlcpy( myString, “Pear”, sizeof(myString)); break;
case 4: strlcpy( myString, “Tangerine”, sizeof(myString)); break;
default:
strlcpy( myString, “Some unknown fruit”, sizeof(myString));
}

And here, if there’s something other than 0 – 4 saved, you’ll at least get a useful warning. Because the strings are part of the program code here, they’ll end up in SRAM memory, but you still need to create a variable long enough for the longest one (that’s “Some unknown fruit”, which is 19 characters plus a null on the end to terminate it), and that variable will be in SRAM since you are changing it after reading the EEPROM. This isn’t very efficient of SRAM, but it does only use one byte of EEPROM.

An Example

And we can apply the PROGMEM method to store these strings more efficiently also:

char myString[20];
char str1[] PROGMEM = “Grape”;
char str2[] PROGMEM = “Apple”;
char str3[] PROGMEM = “Orange”;
char str4[] PROGMEM = “Pear”;
char str5[] PROGMEM = “Tangerine”;
char str6[] PROGMEM = “Some unknown fruit”;
PGM_P strArr[6] PROGMEM = {
str1,
str2,
str3,
str4,
str5,
str6
};

switch (EEPROM.read(5)) {
case 0: strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[0]) ) ); break;
case 1: strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[1]) ) ); break;
case 2: strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[2]) ) ); break;
case 3: strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[3]) ) ); break;
case 4: strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[4]) ) ); break;
default:
strcpy_P( myString, (PGM_P)pgm_read_word( &(strArr[5]) ) );
}

With this, we’ve moved the strings into flash, and stored one byte in EEPROM plus a twenty-character buffer in SRAM (and some code to fetch strings into the buffer).

Oddly, having gone through all of that, my test program reports using two bytes more memory with the “store in SRAM” version than with the “store in PROGMEM” version. I’m not sure if it’s reporting incorrect numbers, or if the extra code is just too much for it to be efficient with such small strings.