Re: Using progmem with a big menu

If you think keyboard time is a real obstacle, you probably need a little more practice :slight_smile:
In general, keyboard time is insignificant compared to thinking time, building time, and debugging time.
I mean, you can't have more than 30,000 characters of menus, and typing that many characters can't take that many hours anyway...

Also, for declarations like that, try copy-and-paste. Start with one:

PROGMEM prog_char pg_HelloWorld[] = "Hello, World!";

Then, select that row (have the cursor at the beginning, and press shift-downarrow to select it), ctrl-C to copy, ctrl-V to paste/insert.

PROGMEM prog_char pg_HelloWorld[] = "Hello, World!";
PROGMEM prog_char pg_HelloWorld[] = "Hello, World!";

Now, change the name of the variable and content of the string.

PROGMEM prog_char pg_HelloWorld[] = "Hello, World!";
PROGMEM prog_char pg_Goodbye[] = "Good-bye!";

Similarly, where you would use a string, use a strcpy_P into a shared scratch buffer:

Serial.println("Hello, World!"); // old
Serial.print(strcpy_P(scratch, pg_HelloWorld)); // new

Keep going for each new string you add! It's really not a whole lot of typing compared to all the other hard work of building a working system, IMO.

I think this will help

http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_rom_array

You could combine all your menu items into a single string and then pick out the part you want. Like this:

#include <avr/pgmspace.h>

const PROGMEM char menu1[] = 
  "Item1\0" 
  "Item2\0" 
  "Item3\0" 
  "Item4";

// Function to print a PROGMEM string
void print_P(Print& device, const PROGMEM char* s)
{
  for (size_t i = 0; i < strlen_P(s); ++i)
  {
    device.print(pgm_read_byte_near(s + i));
  }
}

// Function to print a PROGMEM string followed by newline
void println_P(Print& device, const PROGMEM char* s)
{
  print_P(device, s);
  device.println();
}

// Print one item from a composite string
void printItem_P(Print& device, const PROGMEM char* s, int item)
{
  while (item != 0)
  {
    s += strlen_P(s) + 1;
    --item;
  }
  print_P(device, s);
}

// Test program
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  for (int i = 0; i < 4; ++i)
  {
    printItem_P(Serial, menu1, i);
    Serial.println();
  }
  delay(1000);
}

Not as pretty or as fast as using an array of const char*, but it works.

Talking of cut and past, here's an extract cut from a program I did a while back

typedef struct peripheral {
	char	name[10];
	int	addresses [20];
};

PROGMEM static peripheral peripherals [] = {
	{"PORTB",	{0x36,0x37,0x38,0xFFFF}},
	{"PORTA",	{0x39,0x3A,0x3B,0xFFFF}},
	{"TMR0",	{0x35,0x53,0x52,0x34,0x33,0x32,0x59,0x58,0xFFFF}},
	{"TMR1",	{0x20,0x50,0x4F,0x47,0x46,0x49,0x4E,0x45,0x4D,0x4C,0x4B,0x4A,0x59,0x58,0x44,0xFFFF}},
	{"USI",		{0x2F,0x30,0x2E,0x2D,0x31,0xFFFF}},
	{"AC",		{0x28,0x29,0x21,0x22,0xFFFF}},
	{"ADC",		{0x26,0x25,0x24,0x27,0x23,0x21,0x22,0xFFFF}},
	{"EEPROM",	{0x3C,0x3D,0x3E,0x3F,0xFFFF}},
	{"MCU",		{0x5A,0x5B,0x51,0x48,0x55,0x44,0xFFFF}}
};

peripheral p;
for (per = 0; per < N_PERIPHERALS; per++) {
			// get the register structure from PROGMEM
			memcpy_P(&p, &peripherals[per], sizeof (p));
			Serial << "-->" << p.name << endl;
		}

If the typedef was just a char array you'd be right.

In my case however all the strings are short (max 8 chars + space) so I was happy to use the fixed length array. If your menu items are similar this might be an appropriate method.


Rob

Delta_G:
Yup, that solution puts the array in flash and the strings in RAM. Defeats the purpose.

Which solution is that? The one that declares one char array per strings does not, and the one that declares one big char array with the strings all glommed together also does not. (Note: the second has no comma between the strings!)

Cutting and pasting doesn't teach me anything about C or C++. My purpose here is to learn something, not get some smart alec talking down on me with detailed instructions on cut and paste.

I'm sorry, I did not mean to come across as snarky, and I apologize if you somehow were offended. I responded to the complaint that using PROGMEM is a lot of typing, with a description of how I reduce typing in this situation. If your goal is something other than reducing typing (such as finding a class wrapper solution), then you probably should mention that in your question.

Delta_G:
If this works, then why can't I use the String class and say:

const PROGMEM String menu_1[] = {"Menu Item 1" , "Menu Item 2" , "Menu Item 3" };

Is it the difference between using char and char*?

I'm sorry if I'm getting too involved. I'm more interested in learning how this works right now than just finding a way to write working code.

The difference is subtle, for sure!

When you declare an array of char, you can initialize that array using string literal syntax, but no string literal is actually generated by the compiler. This is a feature of the C/C++ programming language. Thus, this code creates an ARRAY and initializes it:

PROGMEM prog_char const ps_HelloWorld[] = "Hello, World!";

The compiler will generate 14 bytes of initialized data section, initializing it to "Hello, World!\0" and generate a relocation symbol named ps_HelloWorld to mean "the address of this element in the data section." This symbol is marked with the "PROGMEM" attribute, and thus goes into flash in the linker.

Meanwhile, this code generates both a string literal (which is initialized data), AND a pointer (which is also data):

PROGMEM prog_char const *ps_HelloWorld = "Hello, World!";

This generates one initialized data section element with 14 bytes, containing "Hello, World!\n" for the string literal (because now it's a string literal initializing a pointer -- not a string literal syntax initializing an array!) This literal is not marked PROGMEM.
It also generates an actual initialized pointer, named ps_HelloWorld, and marks it as initialized with "the address of the string literal containing "Hello, World!\n". This pointer is marked PROGMEM, and occupies two bytes of the program memory at that point.

The string-literal-can-initialize-arrays syntax does carry through to structures:

struct MyString {
  char data[17]; // remember space for terminating NUL!
};

PROGMEM MyString data[] = {
  { "first!" },
  { "second!" },
};

This generates an array of MyString. That array is marked PROGMEM. Each element in the array in turn contains an array of characters, which is initialized by a string literal syntax (but doesn't generate a string literal!) Because the top-level array is marked PROGMEM, each of the direct storage elements is placed in PROGMEM. Beware, though -- each element is now 17 bytes, no matter whether you use all of it or not!

The difference between array (with initializer syntax) and pointer is subtle, but very important, because it tells the compiler to allocate different chunks of memory.
A pointer is a chunk of memory that has size 2 bytes (on AVR). Referencing the symbol of type pointer is the same thing as "read the value in these two bytes."
A string literal is a chunk of memory that has size N bytes. Referencing this string literal results in a relocation record saying "substitute with a pointer value of wherever the linker places this chunk of data in memory."
An array is a chunk of memory that has a size that you determine. The array can be automatically sized and initialized based on string literal initializer syntax, but is not the same thing as a string literal. Referencing this array results in a relocation record saying "substitute with a pointer value of wherever the linker places this structure in memory."
The PROGMEM attribute tells the linker to put the chunk of memory in flash, rather than SRAM. (Actually, SRAM data is probably put in flash AND SRAM, because it needs to be copied into SRAM on boot ... but a good boot loader at least compresses the SRAM initialized data a bit if possible)
So, the main problem here is that you can only apply the PROGMEM attribute to symbol names that you declare, not directly to string literals.

The reason the String class doesn't work is that it takes a pointer as initializer. Pointers mean that string literals turn into real literals, not array-initialization-syntax, and thus get placed in the data section without the PROGMEM attribute.

If this syntax were legal, you could place string literals in PROGMEM and get a reference to them in regular RAM:

prog_char const *str = PROGMEM "string"; // doesn't work

But that doesn't work :frowning:

As you can see, this is all intimately tied into how the compiler generates storage and symbols and relocation records for your declarations, and how the linker interprets the attributes of those symbols and records.

I hope this helps, and doesn't somehow insult anyone.

http://arduiniana.org/libraries/flash/

Delta_G:
SO does that mean the first level of that array is in SRAM? That's OK with me, but is that where it is?

Where are all the pointers? Are they part of the 18 bytes? Or is that the buffer array from the print function?

There are no pointers or "first level". What you have created is a 2 dimensional array, not an array of pointers to arrays. It's all in flash so not taking up any RAM.

Here is a user interface (including menu and lots more functions) library that stores menus in FLASH. Take a look if you like on how I did it.

Here is some code snippet from one included example (make the list/menu any length you want):
Notice that the pointers to each string of different length (not necessarily up to 16) are stored in PROGMEM TOO! Also my menu scrolls long items so they can be a lot longer than 16 characters :wink:

PROGMEM prog_char pasta_00[]="Spaghetti";
PROGMEM prog_char pasta_01[]="Rotelle";
PROGMEM prog_char pasta_02[]="Rotini";
PROGMEM prog_char pasta_03[]="Fettuccine";
PROGMEM prog_char pasta_04[]="Lasagne";
PROGMEM prog_char pasta_05[]="Penne";
PROGMEM prog_char pasta_06[]="Rigatoni";
PROGMEM const char *pasta_items[]= {pasta_00,pasta_01,pasta_02,pasta_03,pasta_04,pasta_05,pasta_06};

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.