Computing stuff tied to the physical world

Saving RAM space

In AVR, Software on May 23, 2011 at 00:01

Yesterday’s post was about finding out how much free memory there is in an ATmega running your sketch.

The most common out-of-memory case is free RAM, which is where all the interesting stuff happens – not surprising, if you interpret “interesting” as “changing”, which by necessity has to happen mostly in RAM.

Let’s go into some ways to reduce RAM usage. As mentioned yesterday, C strings are often a major cause of RAM bloat. Here’s part of a simple sketch to report which one of five buttons have been pressed:

Screen shot 2011 05 22 at 21 34 00

Let’s assume that the checkButton() returns a value from 1 to 5 when a button press has been detected, and 0 otherwise. The problem? We’ve just used about 150 bytes of RAM…

Given how simple and regular this example is, here’s an easy way to improve on it:

Screen shot 2011 05 22 at 21 34 45

Total RAM usage will drop to just over 50 bytes.

Here’s another way to do it:

Screen shot 2011 05 22 at 21 42 43

This one uses 42 bytes for the data, and 26 bytes for the remaining two strings, i.e. total 68 bytes. I’ve included this example because it illustrates a more data-driven approach, but it leads to some waste because the colors array requires a fixed amount of 6×7 character space.

Here’s a variation of that, which is more idiomatic in C:

Screen shot 2011 05 22 at 21 46 14

It differs in a subtle but important detail: the array is now an array of pointers to string constants.

Estimating RAM use is slightly more involved: 1+4+6+5+7+7 bytes for the strings (including the zero byte at the end of each one) = 30 bytes, PLUS 12 bytes for the pointer array (6 pointers, each 2 bytes). That’s still 42 bytes, so no gain compared to the previous fixed-size array.

Using standard C/C++, that’s about all you can do. And it still wastes some 40..70 bytes of RAM. This may not sound like much, but keep in mind that the same will happen everywhere you use a string in your code. C strings are painfully awkward for tiny embedded MPU’s such as the ATmega and ATtiny series.

Fortunately, there is one more trick at our disposal, which removes the need for RAM altogether …

The trick is to place these strings in flash memory, alongside the code, and extract the characters of the string whenever we need them. It’s a great trick, but it will affect our sketch everywhere, unfortunately.

First of all, we need to include this line at the top of our sketch:

    #include <avr/pgmspace.h>

This header file gives access to a number of preprocessor macros and functions, needed to define strings in the proper way, and to read the character data from flash memory at run time.

The reason for this added complexity, is that flash memory isn’t simply an “address” you can read out. The AVR family uses two separate address spaces for code and data. This is called a Harvard architecture. As far as pointers go in C, there is no access to data in flash memory. Well – there is, because function pointers in C automatically refer to code in flash memory, but there is no way to mix these: data pointers cannot access flash, and function pointers cannot refer to RAM.

Back to the task at hand. We need a small utility function which can print a string located in flash ROM memory:

    void showString (PGM_P s) {
        char c;
        while ((c = pgm_read_byte(s++)) != 0)
            Serial.print(c);
    }

Note that the argument is not a const char*, but a PGM_P (defined in the pgmspace.h include file).

Now let’s redo the code with this ROM-based approach:

Screen shot 2011 05 22 at 22 16 10

The result? No RAM is used up by any of these strings, yippie!

The price to pay is a slightly larger compiled sketch, and more importantly: we have to use that “PSTR(…)” notation with each of the strings to make it all work.

This technique is not invasive, i.e. you don’t have to choose between RAM-based and ROM-based C strings for the entire sketch. It’s probably easier to only do this in those parts of your sketch which use lots of strings.

  1. Really nice trick. Isn’t there a possibility like with the pic familly to store constant literal string in program memory by default ?

    • Not that I know of… and probably impossible, because string pointers can’t be distinguished from flash mem pointers on AVR. Then again, I haven’t examined PIC’s way of doing this. It’d sure be a lot more convenient.

  2. Isn’t the service round here wonderful :-)

  3. Great article to understand how it works. Here is a powerfull arduino library that encapsulates the flash memory access. http://arduiniana.org/libraries/flash/

  4. I’ve been tempted for a couple of years now to try to do a compiler in which all pointers were tagged, so that flash, RAM, and EEPROM addresses could be intermixed. It wouldn’t be C – that’s too hard – probably something with a Tcl-like syntax. To get performance and not be a memory hog, it would need to do at least some dataflow analysis: “I know that the pointer I have here must be in flash, don’t bother checking.”

    But it would be a lot more effort than I have time for (or that I foresee having time for in the next couple of years). I also have a sneaking suspicion that just as I got it done, the world would move from AVR to whatever the next platform will be, and I’d be stuck with a white elephant.

    AVR is unusual in that the separate address spaces actually occupy the same address range. It’s commoner nowadays to map them into different address ranges, so that as soon as you have a pointer, you know what sort of memory it points to. AVR saves a little bit of silicon by using different instructions to access the different flavors of memory, so that the timing of individual instructions is independent of the memory they’re addressing.

    All embedded processors are weird.

    • Hi Kevin,

      All embedded processors are weird.

      Yes, but isn’t it amazing what a little bit of silicon can be made to do with a few mW of power.

  5. Thanks for that article, these types of posts are particularly useful for me as I’m teaching myself C as I go. I’m currently wading through sprintf and drawstring with float and int variables to drive the GLCD, it’s hard going, for me!

    • Glad you like it. Every person pushing to learn something new has my respect. Persevering through the hard bits is what takes us forward.

  6. Great timing. I actually diagnosed (from your previous post) that my arduino project was running out of ram. This method brought my ram up to 980 bytes available (after setup runs) from 350 prior to using this method. Now it actually runs. Somehow i still have what appears to be a memory leak with my Ethernet Shield implementation, but that’s a project for tomorrow :)

    If you don’t mind, i’ll post this info on my site and cite you as a source.

  7. Arduino’s new-extension branch, which has started beta testing to become Arduino 1.0, supports a F() macro which does this automatically with Serial.print.

    Serial.println(F(“a string which is in flash only, but does not require writing your own showString function because this support is built into upcoming versions of Arduino”));

Comments are closed.