PROGMEM in structures

I thought to save some RAM in my sketch. I wanted to see if a structure could hold variables stored in PROGMEM and in RAM. The sketch below runs but, I'm perplexed. I assumed that putting char arrays in PROGMEM would significantly drop RAM usage and correspondingly increase PROGMEM usage. This is not what I see. With PROGMEM in effect the sketch uses 1996 bytes flash and 258 bytes dynamic. With PROGMEM commented out and the char* in effect there are 1992 bytes flash used and 238 bytes dynamic used

struct menuItems {         // create menuitem structure
  char topLineText[17] PROGMEM;       // menu item name
  // char* topLineText;       // menu item name
  char bottomLineText[17] PROGMEM;    // mostly parameter units/prompts
  // char* bottomLineText;    // mostly parameter units/prompts
  int8_t noOfParms;        // for 'mode', 'manual', and 'current position' selections
  int8_t parmVal;          // the current value of the parameter
};

// populate the menuItem list
menuItems textAndParms[] = {
  "  Home", "User Input", 6, 0,   // will use turretPosnText members for display
  "Mode", " Saving settings", 6, 0   // will use IndexModeText members for display
};
const int8_t menuItem_qty = (sizeof(textAndParms) / sizeof(textAndParms[0]));

typedef struct {
  char stationtext[17];
} stationlist;
const stationlist turretPosnTextP[] PROGMEM =
{ "posn #1",
  "posn #2",
  "posn #3"
};

void setup() {
  Serial.begin(230400);

  Serial.print(textAndParms[0].topLineText);
  Serial.println(textAndParms[0].bottomLineText);  // can't change when correctly stored in PROGMEM
  //    textAndParms[0].bottomLineText = "altered data";
  //  Serial.println(textAndParms[0].bottomLineText);  // can't change when correctly stored in PROGMEM
  //  gives error - incompatible types in assignment of 'const char [13]' to 'char [17]'
  Serial.println(textAndParms[0].noOfParms);
  textAndParms[0].noOfParms = 73;  // this works 'cuz always stored in RAM
  Serial.println(textAndParms[0].noOfParms);
  Serial.print(textAndParms[01].topLineText);
  Serial.println(textAndParms[01].bottomLineText);  // can't change when correctly stored in PROGMEM
/*
 * Below is truly in PROGMEM
 */
 Serial.print( (__FlashStringHelper*) &turretPosnTextP[0]);

}

void loop() {
  // put your main code here, to run repeatedly:
}

The code above runs and gives this output with either version of the line declaring top/bottomLineText.

 [color=blue]   HomeUser Input
6
73
Mode Saving settings
posn #1[/color]

Now, since Serial.print is used sans the Flash helper the chars are apparently not in PROGMEM. And yet, I cannot assign new values to them so it's apparently not in RAM - or it's protected. Is PROGMEM taking over and enforcing a 'const' sort of storage?

Is it even possible to have mixed storage types in a structure?

put the strings themselves in PROGMEM

put a pointer to the string in your struct.

Have you tried to remove PROGMEM from the struct and declare "textAndParms" as "const menuItem textAndParms[] PROGMEM = { ... }"?

BulldogLowell and Danois90 are right.

See a 'struct' as a type of a variable, like a 'int' or a 'float'. You can not break the 'struct' and store it in different locations. You have to glue everything together in code.

The PROGMEM tells the compiler to store it in a segment that is located in flash. I assume that it is ignored in the struct type definition. The compiler could give a warning, but somehow it didn't.

When you create the data 'textAndParms', that is where PROGMEM can be used to put everything in flash memory.

Do you want to add new stations ? or change names ? or change nothing ? The data could be stored in EEPROM, or everything in flash with with PROGMEM. I don't understand why you want some parts in ram and some parts in flash.

The structure textAndParms was set up as a sort of mini-database. That is, I wanted all the items related to one menu selection together. The two 'text' elements are unchanging while noOfParms and parmVal can be changed at will during runtime. In fact, parmVal is restored from EEPROM on startup.

Only two items are shown, in the full program there are fourteen menu items. It occurred to me early on to put the unchanging string text in PROGMEM and access those things with an array index but, as stated I wanted to keep everything together. I moved a good bit of other text to PROGMEM so the impetus for saving RAM went away.

I made a (feeble) attempt to implement @Bulldog Lowell's and @Danois90's suggestions but this const/pointer/PROGMEM thing is a bafflement to me.

struct menuItems {         // create menuitem structure
  char* topLineText;       // menu item name
  char* bottomLineText;    // mostly parameter units/prompts
  int8_t noOfParms;        // for 'mode', 'manual', and 'current position' selections
  int8_t parmVal;          // the current value of the parameter
};

// populate the menuItem list
menuItems textAndParms[] = {
  const char* home, const char* user, 6, 0,
};

const char home PROGMEM = {"Home"};  //  strings stored in PROGMEM
const char user PROGMEM = {"User"};
const char mode PROGMEM = {"Mode"};
const char saving PROGMEM = {"Saving settings"};

typedef struct {
  char stationtext[17];
} stationlist;
const stationlist turretPosnTextP[] PROGMEM =
{ "posn #1",
  "posn #2",
  "posn #3"
};

void setup() {
  Serial.begin(230400);

  Serial.print(textAndParms[0].topLineText);
  Serial.println(textAndParms[0].bottomLineText);  // can't change when correctly stored in PROGMEM
  //    textAndParms[0].bottomLineText = "altered data";
  //  Serial.println(textAndParms[0].bottomLineText);  // can't change when correctly stored in PROGMEM
  //  gives error - incompatible types in assignment of 'const char [13]' to 'char [17]'
  Serial.println(textAndParms[0].noOfParms);
  textAndParms[0].noOfParms = 73;  // this works 'cuz always stored in RAM
  Serial.println(textAndParms[0].noOfParms);
  Serial.print(textAndParms[01].topLineText);
  Serial.println(textAndParms[01].bottomLineText);  // can't change when correctly stored in PROGMEM
  /*
     Below is truly in PROGMEM
  */
  Serial.print( (__FlashStringHelper*) &turretPosnTextP[0]);

}

void loop() {
  // put your main code here, to run repeatedly:
}

And these are the - cryptic - error messages.

PROGMEM_in_structure:10: error: expected primary-expression before 'const'

const char* home, const char* user, 6, 0,

^

PROGMEM_in_structure:10: error: expected '}' before 'const'

PROGMEM_in_structure:10: error: expected ',' or ';' before 'const'

PROGMEM_in_structure:11: error: expected declaration before '}' token

};

^

exit status 1 expected primary-expression before 'const'

What sort of 'primary expression' can go before const?

Thanks for the responses.

you could try like this:

const char* const menuText[][2] PROGMEM = {
  {"Hello World", "detonate->"},
  {"GoodBye World", "->ouch<-"},
  {"Hello Afterworld", "wow"},
};

struct Menu {
  const char* title;
  const char* option;
  uint8_t someVal;
  uint8_t someOtherVal;
};

Menu myMenu[] = {
  {menuText[0][0], menuText[0][1], 0, 0},
  {menuText[1][0], menuText[1][1], 0, 0},
  {menuText[2][0], menuText[2][1], 0, 0},
};

Menu currentMenu = myMenu[0];

void setup() {
  Serial.begin(9600);
  for (Menu item : myMenu){
    Serial.println(item.title);
    Serial.println(item.option);
    Serial.println();
  }
  
  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);
  
  currentMenu = myMenu[1];
  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);

  currentMenu = myMenu[2];
  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);
}

void loop() {
  // put your main code here, to run repeatedly:

}

BulldogLowell:
you could try like this:

That does work. Another new construction to work through. :confused: Anyway, since Serial.print is used to display the menu text/numbers this tells me the data is no longer in PROGMEM - if it were, FlashStringHelper would be needed to display, no? For confirmation I added a line which alters one of the menu text items then prints the new text. It follows then that no memory was saved. I believe it’s as @Koepel stated, you can’t combine these things in one structure. It truly seems the only way to achieve this is to have two separate arrays, one with text in PROGMEM and one in RAM for the uint8_t variables.

const char* const menuText[][2] PROGMEM = {
  {"Hello World", "detonate->"},
  {"GoodBye World", "->ouch<-"},
  {"Hello Afterworld", "wow"},
};

struct Menu {
  const char* title;
  const char* option;
  uint8_t someVal;
  uint8_t someOtherVal;
};

Menu myMenu[] = {
  {menuText[0][0], menuText[0][1], 93, 170},
  {menuText[1][0], menuText[1][1], 6, 3},
  {menuText[2][0], menuText[2][1], 2, 9},
};

Menu currentMenu = myMenu[0];

void setup() {
  Serial.begin(230400);
  for (Menu item : myMenu) {
    Serial.println(item.title);
    Serial.println(item.option);
    Serial.println(item.someVal);
    Serial.println(item.someOtherVal);
    Serial.println();
  }

  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);
  Serial.println(currentMenu.someVal);
  Serial.println(currentMenu.someOtherVal);
  
  currentMenu.option = "new data"; // data changeable, not in PROGMEM
  
  Serial.println(currentMenu.option);
  currentMenu = myMenu[1];
  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);

  currentMenu = myMenu[2];
  Serial.println(currentMenu.title);
  Serial.println(currentMenu.option);
}

void loop() {
  // put your main code here, to run repeatedly:

}

Anyway, since Serial.print is used to display the menu text/numbers this tells me the data is no longer in PROGMEM - if it were, FlashStringHelper would be needed to display, no?

No. The print() method is overloaded to take a FlashStringHelper without your "help".

@PaulS, I don't know if this is a proper test but I tried to translate what I think you wrote into:

/* flash string test
*/

const char test1[] PROGMEM = "test1";
const char test2[] PROGMEM = "test number two";
char test3[] = "test three of three";

void setup() {
  Serial.begin(230400);
  Serial.println(test1);
  Serial.println(test2);
  Serial.println(test3);
  Serial.println("--------------");
  Serial.println((__FlashStringHelper*) test1);
  Serial.println((__FlashStringHelper*) test2);
  Serial.println((__FlashStringHelper*) test3);
}

void loop() {
  // put your main code here, to run repeatedly:

}

And got this output:

test three of three
--------------
test1
test number two
⸮⸮⸮⸮-⸮ ⸮⸮+⸮~⸮!⸮⸮⸮⸮ߑϑ⸮⸮⸮⸮⸮ߐϐ⸮⸮⸮⸮⸮⸮⸮a⸮⸮⸮⸮⸮⸮]⸮⸮⸮⸮⸮⸮_⸮s⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮1⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮"⸮⸮/⸮⸮⸮\⸮O⸮e⸮ ⸮s⸮'⸮⸮⸮⸮⸮⸮

I'm unsure what exactly is meant by 'take a FlashStringHelper without your "help"'.

I'm unsure what exactly is meant by 'take a FlashStringHelper without your "help"'.

You know that you can Serial.print() an int, right? So, given this:

   int val = 42;

what would the Serial.print() call look like? ANY answer other than

Serial.print(val);

is adding extra "help" that the function doesn't need.

Why are you explicitly performing a cast? Do you KNOW that you can meaningfully cast a char array, that lives in SRAM at run time, to a __FlashStringHelper pointer? The results of your test PROVE that you can't.

PaulS: Why are you explicitly performing a cast? Do you KNOW that you can meaningfully cast a char array, that lives in SRAM at run time, to a __FlashStringHelper pointer? The results of your test PROVE that you can't.

What I *don't *know would fill a wiki. I was just throwing things at the wall and seeing what would stick.

I've been reading and running short examples about classes and function pointers and libraries - because these things seem possibly relevant - trying to absorb enough terminology to be able to ask the right question. Not there yet.

dougp: I've been reading and running short examples about classes and function pointers and libraries - because these things seem possibly relevant - trying to absorb enough terminology to be able to ask the right question. Not there yet.

relative to your original post, you may not find the answer in classes or function pointers or libraries.

Just go through the tutorials involving PROGMEM. You see, it is a very different process getting data from Flash than it is from the heap or off the stack.

an understanding of pointers and how memory is used would be more helpful, I feel.

good luck!!