PROGMEM, PGM_P and passing a PROGMEM array as an argument in 1.6.0

So I’ve been doing a lot of searching on this topic to try and figure it out.

My goal, as I will provide an example below, is to have a menu class which when instantiated, takes a menu argument. This argument is an array of strings (not Strings) all of which are stored in PROGMEM.

First I went to the Arduino PROGMEM page: http://arduino.cc/en/Reference/PROGMEM
I think this is outdated and no longer correct for 1.6.0. The example code fails with error: 'prog_char' does not name a type and a little digging in pgmspace.h leads to this about prog_char:

This typedef is now deprecated because the usage of the progmem attribute on a type is not supported in GCC. However, the use of the progmem attribute on a variable declaration is supported, and this is now the recommended usage.

Here’s the documentation for pgmspace.h: avr-libc: <avr/pgmspace.h>: Program Space Utilities

So much of what I found searching is no longer correct. I did find some examples so I think this means that it is now correct to do:

// These are pointers to arrays held in PROGMEM.
const char string_0[] PROGMEM = "String 0";  
const char string_1[] PROGMEM = "String 1";  
const char string_2[] PROGMEM = "String 2";
// ... and so on.
// So now we want an array of pointers to PROGMEM, but also in PROGMEM....
// We have 
PGM_P const string_table1[] PROGMEM = {string_0,string_1,string_2}; 
//which is the same as
const char * const string_table1[] PROGMEM  = {string_0,string_1,string_2};
// Which if I try to parse it means we have a const pointer to an array of three constant PROGMEM pointers in PROGMEM?

In my application I will have many string_tables all with variable numbers of strings in them.
So now I want a class which takes a string_table as an argument as well as an argument that tells the class how many items are in the table.

Below is the example code. It compiles, and the part in setup() works, but I’m having trouble accessing the menu strings from within the printMenu method of the MenuState object.

#include <avr/pgmspace.h>

class MenuState {
  public:
    MenuState(uint8_t menu_size, PGM_P menu_labels); 
    void  printMenu();
  private:
    PGM_P _menu_labels; // Same as const char * _menu_labels;
    uint8_t  _menu_size;
};

MenuState::MenuState(uint8_t menu_size, PGM_P menu_labels){
  this->_menu_size = menu_size;
  this->_menu_labels = menu_labels;
}
  
void MenuState::printMenu(){ // THIS DOES NOT WORK, but see setup()
  char buf[20]; 
  for ( uint8_t i = 0 ; i < _menu_size; i++ ) {
    // This is the same as the code in setup but substitute _menu_labels for MAIN_MENU_LABELS
    const char * menu_pgm_ptr = (PGM_P)pgm_read_word((_menu_labels[i]));
    strcpy_P(buf,menu_pgm_ptr);
    Serial.println(buf); // prints garbage
  }
}

const uint8_t MAIN_MENU_SIZE = 7;
const char main_menu_1[] PROGMEM = "Sampling";
const char main_menu_2[] PROGMEM = "Next Coll.";
const char main_menu_3[] PROGMEM = "Edit Collect.";
const char main_menu_4[] PROGMEM = "File Ops.";
const char main_menu_5[] PROGMEM = "Review Current";
const char main_menu_6[] PROGMEM = "Diagnostics";
const char main_menu_7[] PROGMEM = "Power off";
PGM_P const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};
// same as 
//const char * const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};

MenuState testMenu(MAIN_MENU_SIZE,(PGM_P)MAIN_MENU_LABELS);


void setup() {
  char buf[20];
  Serial.begin(57600);
  // I can print out a single menu item like this:
  strcpy_P(buf,main_menu_1);
  Serial.println(buf);
  // To get the same thing out of MAIN_MENU_LABELS,
  // MAIN_MENU_LABELS[1] should be the pointer to main_menu_2 in PROGMEM
  const char * menu_pgm_ptr = (PGM_P)pgm_read_word(&(MAIN_MENU_LABELS[1]));
  // So menu_pgm_ptr is a pointer to a string in program space.
  strcpy_P(buf,menu_pgm_ptr);
  Serial.println(buf); // And that works too!
  Serial.println("Now the hard way");
  testMenu.printMenu();
}

void loop() {}

So I’m pretty sure there’s something wrong about how I’m passing MAIN_MENU_LABELS to the class constructor, but I don’t see what the correct way is.

    // This is the same as the code in setup but substitute _menu_labels for MAIN_MENU_LABELS
    const char * menu_pgm_ptr = (PGM_P)pgm_read_word((_menu_labels[i]));

...
  const char * menu_pgm_ptr = (PGM_P)pgm_read_word(&(MAIN_MENU_LABELS[1]));

No it isn’t the same. You omitted a “&” and hoped for the best, eh?

This is an array of pointers:

PGM_P const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = 
  {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};

This is not (it is a single pointer):

    PGM_P _menu_labels; // Same as const char * _menu_labels;

Then you had to cast to make it compile:

MenuState testMenu(MAIN_MENU_SIZE,(PGM_P)MAIN_MENU_LABELS);

This works:

#include <avr/pgmspace.h>

class MenuState {
  public:
    MenuState(uint8_t menu_size, PGM_P * menu_labels); 
    void  printMenu();
  private:
    PGM_P * _menu_labels; // Same as const char * _menu_labels;
    uint8_t  _menu_size;
};

MenuState::MenuState(uint8_t menu_size, PGM_P * menu_labels){
  this->_menu_size = menu_size;
  this->_menu_labels = menu_labels;
}
  
void MenuState::printMenu(){ 
  char buf[20]; 
  for ( uint8_t i = 0 ; i < _menu_size; i++ ) {
    // This is the same as the code in setup but substitute _menu_labels for MAIN_MENU_LABELS
    const char * menu_pgm_ptr = (PGM_P)pgm_read_word(&(_menu_labels[i]));
    strcpy_P(buf,menu_pgm_ptr);
    Serial.println(buf);
  }
}

const uint8_t MAIN_MENU_SIZE = 7;
const char main_menu_1[] PROGMEM = "Sampling";
const char main_menu_2[] PROGMEM = "Next Coll.";
const char main_menu_3[] PROGMEM = "Edit Collect.";
const char main_menu_4[] PROGMEM = "File Ops.";
const char main_menu_5[] PROGMEM = "Review Current";
const char main_menu_6[] PROGMEM = "Diagnostics";
const char main_menu_7[] PROGMEM = "Power off";
PGM_P const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};
// same as 
//const char * const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};

MenuState testMenu(MAIN_MENU_SIZE,(PGM_P *) MAIN_MENU_LABELS);


void setup() {
  char buf[20];
  Serial.begin(115200);
  // I can print out a single menu item like this:
  strcpy_P(buf,main_menu_1);
  Serial.println(buf);
  // To get the same thing out of MAIN_MENU_LABELS,
  // MAIN_MENU_LABELS[1] should be the pointer to main_menu_2 in PROGMEM
  const char * menu_pgm_ptr = (PGM_P)pgm_read_word(&(MAIN_MENU_LABELS[1]));
  // So menu_pgm_ptr is a pointer to a string in program space.
  strcpy_P(buf,menu_pgm_ptr);
  Serial.println(buf); // And that works too!
  Serial.println("Now the hard way");
  testMenu.printMenu();
}

void loop() {}

(I changed the baud rate, I get impatient with slow baud rates). :stuck_out_tongue:

More info: Gammon Forum : Electronics : Microprocessors : Putting constant data into program memory (PROGMEM)

Thank You. I think I had tried with and without the &. Must have forgotten to put it back.

So in order to try and understand how this is parsed, as you said:

PGM_P const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = 
  {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};

is an array of pointers. So there is an array of PROGMEM pointers in PROGMEM. The address of this array must be in a non-PROGMEM pointer so we can get at it. Is it correct to say that &MAIN_MENU_LABELS is this address, in SRAM, which contains a const char which is the address in PROGMEM of an array of pointers to PROGMEM? There's probably a better way to say this.

I was under the (incorrect) assumption that since you have to use special functions such as pgm_read_ptr() to get pointer data out of PROGMEM as you have to do with functions like pgm_read_word() to get values from PROGMEM. I.e MAIN_MENU_LABELS would be incorrectly interpreted as a pointer to SRAM unless you specifically told the program to interpret it as a pointer to PROGMEM. I suppose that either the addresses are unique and the location can be derived from the value, or that the compiler keeps track of all this.

When you instantiate the object casting MAIN_MENU_LABELS as a (PGM_P *):

MenuState testMenu(MAIN_MENU_SIZE,(PGM_P *) MAIN_MENU_LABELS);
// this is the same as
MenuState testMenu(MAIN_MENU_SIZE,(const char * *) MAIN_MENU_LABELS);

Does this multiple indirection mean MAIN_MENU_LABELS is a pointer to an array(pointer)?

Do you have any good links on multiple indirection? While I can see in this instance what it's doing, I would like to understand it better.

I was under the (incorrect) assumption that since you have to use special functions such as pgm_read_ptr() to get pointer data out of PROGMEM as you have to do with functions like pgm_read_word() to get values from PROGMEM. I.e MAIN_MENU_LABELS would be incorrectly interpreted as a pointer to SRAM unless you specifically told the program to interpret it as a pointer to PROGMEM. I suppose that either the addresses are unique and the location can be derived from the value, or that the compiler keeps track of all this.

I don't really understand that TBH.

Does this multiple indirection mean MAIN_MENU_LABELS is a pointer to an array(pointer)?

That's exactly what it is. Anything like this is a pointer to pointers:

char * foo [] = { ... };

The "char " means the **data type* is a pointer, and the array declaration means foo is a pointer to this array. It has to be. foo [0] on its own is just one element, so foo [] has to be an array of elements and therefore similar in behaviour to a pointer.

Do you have any good links on multiple indirection? While I can see in this instance what it's doing, I would like to understand it better.

Not really but look on StackOverflow or similar. The concept is very widely used. The fact that it is in PROGMEM is not relevant to the concept.

So in order to try and understand how this is parsed, as you said:

PGM_P const MAIN_MENU_LABELS[MAIN_MENU_SIZE] PROGMEM = 
  {main_menu_1,main_menu_2,main_menu_3,main_menu_4,main_menu_5,main_menu_6,main_menu_7};

You have three things here:

menu1 -->  text xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
menu2 -->  text xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
menu3 -->  text xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
menu4 -->  text xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
menu5 -->  text xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
...

Two of them are pointers (lots of them) and the things they point to (lots of them).

Both of them are in PROGMEM. Now the menu array (pointing to menu1) is not. You could put it there, but then you would have to use pgm_read_word() to correctly obtain the value in it.