Developing in C for the ATmega328: Using PROGMEM

4 minute read

Where I discuss how to use PROGMEM (storing values in Flash memory) for storage.

Introduction

Storing message strings in AVR RAM can become prohibitively expensive, very quickly. With only 2KB of RAM, the Uno needs as much RAM available for dynamic storage, while message strings are best stored in Flash memory. Due to the AVR microcontroller’s different architecture, you can’t simply read the Flash memory as reading RAM. You also can’t simply “embed” strings in Flash, by making them a constant, there are very specific commands to do both of these tasks.

This entry will describe three different methods of printing the contents of Flash memory along with storing and retrieving values as well.

Storing in PROGMEM

The simple way to store data in PROGMEM is to use the PROGMEM macro along with the C language declaration constructs:

const char ParameterA[] PROGMEM = "Parameter A";
const char ParameterB[] PROGMEM = "Parameter B";
  1. const: In this case, since the data will be in Flash memory, it won’t be alterable. The C99 avr-gcc compiler will demand this.
  2. char: For string data, make it a char.
  3. ParameterA[]: Name it and make it an array so we can store a string.
  4. PROGMEM: Places all of it in program memory (Flash) and not RAM.
  5. “Parameter A” the desired text to be stored, typically, static error or informational messages.

Simple byte access

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void printbychar(const char* ParameterName, uint16_t value)
    {
        // uint8_t max = 11;
        uint8_t c;
        printf("The value of ");
        for (uint8_t i=0; i < strlen_P(ParameterName); i++)
        {
            c = pgm_read_byte(&(ParameterName[i]));
            putchar(c);
        }
        printf(" is %d\n", value);
    }

This is the most simple and common method used to access variables in PROGMEM, the pgm_read_byte().

  • Line 1: pointer to the message array and a value passed to the function
  • Line 6: the macro strlen_P will provide the length of the message array
  • Line 8: pgm_read_byte will fetch each character from Flash
  • Line 9: print the character
  • Line 11: finish by printing the value passed as well

I use this same method to fetch the constants required for creating a tone via one of the ATmega PWM channels. There are three constants, each one in an array in Flash and the array is indexed by a musical note. When tone() is provided a note, it uses pgm_read_byte(array(note)) to fetch each one of the constants, then it uses the three constants to program the PWM.

Simple, non-standard string access

1
2
3
4
5
// Add -Wno-format to the CPP line in the Makefile (see line 47) 
void PrintParameterValue(const char* ParameterName , uint8_t ParameterValue)
{
    printf("The value of %S is %d\n", ParameterName, ParameterValue); 
}

This method is recommended by Dean Camera and uses a non-standard version of format to make it work. In this case, we treat the array as a C string and print using the %S format. The capital S format was written to print strings stored in Flash memory. It isn’t part of standard C and will cause an warning unless you specifically use -Wno-format when you compile.

More complex, yet standard string access

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PGM_P const array[2] PROGMEM = 
{
    ParameterA,
    ParameterB
};

int printProgmem (uint8_t msg, uint16_t value)
{
    // RAM buffer used for print message
    char print_buffer[32];
    // Declare variable ptr, so that is a pointer to a string in program space.
    PGM_P ptr;
    
    // Set of 3 instructions, which fetch the string, place in RAM, then print
    // The memcpy_P() function is similar to memcpy(), 
    // except the src string resides in program space.
    memcpy_P(&ptr, &array[msg], sizeof(PGM_P));

    // The strcpy_P() function is similar to strcpy(),
    // except that src is a pointer to a string in program space.
    strcpy_P(print_buffer, ptr);
    printf("The value of %s is %d\n", print_buffer, value);

    return 0;
}

This last version is from the AVR-GCC FAQ. While an explanation is provided, its a bit terse and not entirely comprehisible (to me.)

In a nutshell, the memory is setup in the same manner as the previous two examples. They (Lines 1-5) add an array which consists of the two addresses of the arrays in program memory (PROGMEM). The access to the messages is via this second array. An index (msg) is provided to select the specific array in Flash memory (Line 17) and that array is used to fill a buffer (Line 21) which is subsequently printed (Line 21).

The value of this approach is that it doesn’t require the non-standard format of the previous example. I thought it would be faster, however, all three methods take the same amount of time to print.

Sources

Comments powered by Talkyard.