There are times when you may need an array of strings which will never be modified. In this case, you don't want to waste ram storing the constant strings. The most obvious (and incorrect) thing to do is this:

#include <avr/pgmspace.h>

PGM_P array[2] PROGMEM = {
    "Foo",
    "Bar"
};

int main (void)
{
    char buf[32];
    strcpy_P (buf, array[1]);
    return 0;
}

The result is not what you want though. What you end up with is the array stored in ROM, while the individual strings end up in RAM (in the .data section).

To work around this, you need to do something like this:

#include <avr/pgmspace.h>

const char foo[] PROGMEM = "Foo";
const char bar[] PROGMEM = "Bar";

PGM_P array[2] PROGMEM = {
    foo,
    bar
};

int main (void)
{
    char buf[32];
    PGM_P p;
    int i;

    memcpy_P(&p, &array[i], sizeof(PGM_P));
    strcpy_P(buf, p);
    return 0;
}

Looking at the disassembly of the resulting object file we see that array is in flash as such:

00000026 <array>:
  26:   2e 00           .word   0x002e  ; ????
  28:   2a 00           .word   0x002a  ; ????

0000002a <bar>:
  2a:   42 61 72 00                                         Bar.

0000002e <foo>:
  2e:   46 6f 6f 00                                         Foo.

foo is at addr 0x002e. bar is at addr 0x002a. array is at addr 0x0026.

Then in main we see this:

memcpy_P(&p, &array[i], sizeof(PGM_P));
70:  66 0f           add     r22, r22
72:  77 1f           adc     r23, r23
74:  6a 5d           subi    r22, 0xDA       ; 218
76:  7f 4f           sbci    r23, 0xFF       ; 255
78:  42 e0           ldi     r20, 0x02       ; 2
7a:  50 e0           ldi     r21, 0x00       ; 0
7c:  ce 01           movw    r24, r28
7e:  81 96           adiw    r24, 0x21       ; 33
80:  08 d0           rcall   .+16            ; 0x92

This code reads the pointer to the desired string from the ROM table array into a register pair.

The value of i (in r22:r23) is doubled to accommodate for the word offset required to access array[], then the address of array (0x26) is added, by subtracting the negated address (0xffda). The address of variable p is computed by adding its offset within the stack frame (33) to the Y pointer register, and memcpy_P is called.

strcpy_P(buf, p);
82:  69 a1           ldd     r22, Y+33       ; 0x21
84:  7a a1           ldd     r23, Y+34       ; 0x22
86:  ce 01           movw    r24, r28
88:  01 96           adiw    r24, 0x01       ; 1
8a:  0c d0           rcall   .+24            ; 0xa4

This will finally copy the ROM string into the local buffer buf.

Variable p (located at Y+33) is read, and passed together with the address of buf (Y+1) to strcpy_P. This will copy the string from ROM to buf.

Note that when using a compile-time constant index, omitting the first step (reading the pointer from ROM via memcpy_P) usually remains unnoticed, since the compiler would then optimize the code for accessing array at compile-time.

Back to FAQ Index.