Store constants and arrays in program memory

In many cases, our embedded projects have to deal with various constants or arrays that aren’t changed during program execution. So why put them in RAM if we can store constant data in flash memory and leave RAM untouched. RAM is precious in micro-controllers, so leave it for stack and heaps. For instance, you are using an LCD in your project and want to send “Hello World” to it, you simply grab one of common LCD libraries and use commands like:

LCDstring("Hello World");

By using this innocent action we end up in storing this string in flash memory during compilation and loading it to RAM during program start-up routines in the microcontroller. When LCDstring() command is executed string is read from SRAM not from FLASH. So we end up with two copies of same string (flash and SRAM), and worse – we occupy SRAM with constants. AVR flash memory locations can be read by the program, so this feature can be used to read constants directly from flash without loading them to RAM. Before doing this we need to use one special library:

#include <avr/pgmspace.h>

This library contains all necessary tools that make possible to store data inside flash memory and read from it.

Ways to work with program memory

Let’s begin with a simple example. We often need to store some data array like a text message, signal table, or tone data. So any string can be stored in flash memory this way:

const char message[] PROGMEM = “Hello World”

We used an exceptional PROGMEM modifier that indicates compiler to store this string in flash memory. This is nothing more than replacement of __attribute__((__progmem__)) earlier used in WinAVR. It simply indicates linker to put data in the program section that lies in flash memory. To read this string from Flash memory, we need to use a particular routine that uses LPM command. Luckily pgmspace.h library has already done this for us. To read one byte from Flash, we have to use pgm_read_byte(address) command. As you can see, our previous LCDstring(“Hello World”); command won’t work as it only deals with strings stored in RAM. To make it possible, we have couple of options. If we need to send string once, we can use PSTR macro that allows creating inline strings like this:

LCDstring(PSTR("Hello World"));

But it allows using this string only once. If you need to use same string multiple times then you should go with the second option – write a small routine that reads whole string byte by byte and sends to LCD like this:

void LCDFstring(const uint8_t *flashstring)
{
    uint8_t i;
    for (i=0; pgm_read_byte(&flashstring[i]); i++)
    {
        LCDchar(pgm_read_byte(&flashstring[i]));
    }
}

As our string always ends with ‘\0’ – null. For loop will terminate at this point. In our example, we read single bytes and send to LCD byte by byte. When we need to send a string to LCD we call this function like this:

LCDFstring(&message[0]);

or simply

LCDFstring(message);

because the array variable always holds the address of the first element.

If you look at pgmspace.h library, you will find more functions. For instance, you can work with words and double words with commands pgm_read_word(); pgm_read_dword().

Using PROGMEM pointers

Your program probably won’t be dealing with one or couple strings stored in Flash memory. In many cases, you may build a multilevel menu or multiple messages for various situations. In order to make your program modular you will need to deal with pointers to arrays. It may sound complex, but in fact, it puts some order in your program.

Let us say we are building a simple menu system. We put each menu text in program memory. And then, make another array that holds pointers to each menu item.

#include <avr/io.h>
#include <avr/pgmspace.h>
//menu items stored in flash
const uint8_t Menu1[] PROGMEM = "Menu1";
const uint8_t Menu2[] PROGMEM = "Menu2";
const uint8_t Menu3[] PROGMEM = "Menu3";
//array pointers to menu items
const uint8_t *Menu[] = {Menu1, Menu2, Menu3};
//menu item number
volatile uint8_t Item;
int main (void)
{
   while (1) //Loop
   {
        //get menu Item value during some interrupt
        LCDFstring(Menu[Item]);
   }
}

Now we can send any menu string to LCD by changing only an array index of Menu. Everything seems quite OK, but pointers to arrays are loaded in RAM. If we make this array quite big – it may also take a significant amount of RAM. Since this array of pointers doesn’t change, we can also put it to Flash with some minor changes in code:

#include <avr/io.h>
#include <avr/pgmspace.h>
//menu items stored in flash
const uint8_t Menu1[] PROGMEM = "Menu1";
const uint8_t Menu2[] PROGMEM = "Menu2";
const uint8_t Menu3[] PROGMEM = "Menu3";
//array pointers to menu items
const uint8_t *Menu[] PROGMEM= {Menu1, Menu2, Menu3};
//menu item number
volatile uint8_t Item;
int main (void)
{
   while (1) //Loop
   {
        //get menu Item value during some interrupt
        LCDFstring((uint8_t*)pgm_read_word(&Menu[Item]));
   }
}

First of all, we added a PROGMEM attribute to our array of pointers. It tells you to store pointers in Flash memory. Then we made some changes to LCDFstring() function:

LCDFstring((uint8_t*)pgm_read_word(&Menu[Item]));

We have to read a pointer value from Flash memory by using pgm_read_word() function. Why read word if your array is defined as byte? This is because GCC compiler uses two bytes for pointers. So in order to avoid compiler warnings we need to read these pointers as words and then typecast to a byte-sized pointer.

Short overview of pgmspace.h

Take a look at pgmspace.h library for more exciting stuff. As I mentioned you can store and read bytes, words, double words and floats. When defining arrays, instead of using, you can use specially defined types like prog_uint8_t that replace uint8_t PROGMEM. This way, expressions become shorter, like:

const prog_uint8_t Menu1[] = "Menu1";

This will place the string in Flash memory. Personally me, I would not recommend using such expressions as it brings more confusion than benefit. Better stick with PROGMEM keyword as it is more visible and easy to track.

Also, in pgmspasce.h you will find several handy functions and macros that allows you to deal with strings stored in program memory. Typically string functions operate with strings stored in RAM. But what if you want to compare couple strings where one is placed in RAM and another in FLASH. For this, you would use following function:

strcmp_P("ram string", PSTR("flash string"));

You will find other string functions that operate in same manner as standard ones. Each of them has a post-fix “_P,” indicating that this function deals with strings stored in a flash.

7 Comments:

  1. re: const char message[] PROGMEM = “Hello World”

    This doesn’t need the since the double quotes tells the compiler it is a string and therefore gets a terminating null added anyway.

  2. (re:post since the form deleted the [backslash] zeroes)

    const char message[] PROGMEM = “Hello World\0”

    This doesn’t need the \0 since the double quotes tells the compiler it is a string and therefore gets a terminating null added anyway.

  3. Thanks. Fixed that.
    Actually this isn’t an error – adding “\0” to the end of string doesn’t affect compiler – it adds one terminating null with or without it in code. I notice some people do add this “\0” in strings while some don’t.

  4. Actually it generates two nulls (and should do since that’s what you’re telling the compiler to do). Sorry, it’s an error!

  5. OK didn’t checked that before. Now I looked at hex and really it generates two nulls at the end of string. Somehow I supposed that compiler understands this and adds single null. Thanks again.

  6. I tried your example and it did not work. After much work, I discovered that for your example to work that the NVM_CMD register must be set to 0x00. It would be helpful for this to be mentioned in your article. Otherwise, the article is good.

Comments are closed