Questions reading PROGMEM data

Hi everybody,

My level is beginner.
I use ESP8266 boards such as the Wemos D1 and other similar variants.

Having run into issues memory issues using the String class, I have decided to switch over to char arrays and the use of PROGMEM in order to minimize my RAM usage. Amongst various things, I have studied the following page: ESP8266 PROGMEM

I was looking at the code for the 2D string array:

// Define Strings
const char string_0[] PROGMEM = "String 0";
const char string_1[] PROGMEM = "String 1";
const char string_2[] PROGMEM = "String 2";
const char string_3[] PROGMEM = "String 3";
const char string_4[] PROGMEM = "String 4";
const char string_5[] PROGMEM = "String 5";

// Initialize Table of Strings
const char* const string_table[] PROGMEM = { string_0, string_1, string_2, string_3, string_4, string_5 };

char buffer[30]; // buffer for reading the string to (needs to be large enough to take the longest string

void setup() {
  Serial.begin(9600);
  Serial.println("OK");
}

void loop() {
  for (int i = 0; i < 6; i++) {
    strcpy_P(buffer, (char*)pgm_read_dword(&(string_table[i])));
    Serial.println(buffer);
    delay(500);
  }
}

My (limited) understanding is as follows:

  • string_table contains a list of un-mutable (constant) pointers to un-mutable (constant) char arrays
  • said pointer names are inside the {} of string_table
  • string_0 … string_5 are constant char arrays and therefore can be put into PROGMEM as global variables
  • We must use special functions/macros to handle PROGMEM data. In this example we use strcpy_P and pgm_read_dword
  • In this code, we create a char array called buffer that will reside in RAM and where we will copy the data from string_x using the list of pointers contained in string_table
  • The pgm_read_dword macro contains pointers to an array of characters (char*)
  • It points to the address of the pointer contained in string_table by the use of the “&” sign and as a result somehow reads the data inside the relevant string_x PROGMEM array (“string 0”, etc…)
    * The data read is then copied into the variable called buffer using the “special” progmem function strcpy_P
    My questions are as follows:
    1) I am a bit fuzzy, programmatically speaking, as to how the link between the list of pointer names inside string_table[] and the contents of the string_x[] arrays are made. I guess this is something internal to the working of the pgm_read macro. I can see that the pointer names and the names of the strings storing data inside the various string_x[] arrays are the same but beyond that I am not certain.
    Does anybody have a clear understanding as to how this works?
    2) The pgm_read macros are reading the contents of the string_x arrays with the help of the string_table array. I was wondering how I could read the contents of the string_table array that contains the list of the pointer names. When trying to read the contents via a Serial.Print(string_table*) I was expecting to get the names of the pointers listed inside that array but instead I get the contents of the various string_x arrays as if I was using the pgm_read macro. For example, a Serial.print(string_table[0]) would return the contents of string_[0] which is “String 0” and not the expected content of string_table[0] which I thought would be “string_0”.*
    How can I read the contents of the string_table[] array without returning the contents of string_x[]?
    I have more questions, but I’ll keep it at that for now to keep it simple :slight_smile:
    Thanks!

My (limited) understanding is as follows:

  • string_table contains a list of un-mutable (constant) pointers to un-mutable (constant) char arrays

That is correct.

  • said pointer names are inside the {} of string_table

That is wrong. The string_table stores the addresses of the array, not the names.

  • string_0 .... string_5 are constant char arrays and therefore can be put into PROGMEM as global variables

Correct.

  • We must use special functions/macros to handle PROGMEM data. In this example we use strcpy_P and pgm_read_dword

Correct.

  • In this code, we create a char array called buffer that will reside in RAM and where we will copy the data from string_x using the list of pointers contained in string_table

Correct.

  • The pgm_read_dword macro contains pointers to an array of characters (char*)

That is so far out in left field that I don't even know where to begin. The function, pgm_read_dword, does not contain any data, pointers or otherwise. The function is passed an argument that defines where the data to be read is, and it returns that data.

  • It points to the address of the pointer contained in string_table by the use of the "&" sign and as a result somehow reads the data inside the relevant string_x PROGMEM array ("string 0", etc...)

See above.

  • The data read is then copied into the variable called buffer using the "special" progmem function strcpy_P

There's nothing special about the function. It's a function, just like digitalRead().

  1. I am a bit fuzzy, programmatically speaking, as to how the link between the list of pointer names inside string_table

If you stop thinking of variables having names that mean anything, and start thinking of variables as addresses, the confusion you are exhibiting will go away.

@PaulS: Thank you for the replies.

That is wrong. The string_table stores the addresses of the array, not the names.

What I meant is that it stores the names of pointers that refer to the addresses, I did not express myself properly, so I think this is what you mean as well.

That is so far out in left field that I don't even know where to begin. The function, pgm_read_dword, does not contain any data, pointers or otherwise. The function is passed an argument that defines where the data to be read is, and it returns that data.

Understood. If I were to make a baseball analogy, I would say that I feel like I am not even in the ballpark yet, just walking blindfolded trying to find it. :slight_smile:

If you stop thinking of variables having names that mean anything, and start thinking of variables as addresses, the confusion you are exhibiting will go away.

I hear you! I feel like it is becoming a little bit clearer already.

I was wondering if there was a way to read the name of the pointers in the array string_table not because they holds values or addresses but to use is them as trigger points in the loop for other actions.

In other words, while running the loop if pointer name being used with the current "i" counter number is equal to "string_0" (for example) then do this, Else...

I know I could use the "i" counter number as a trigger point but then it forces me to manage an equivalence table between the pointer name I am looking for and the "i" counter currently being used in the loop.

As a beginner, I was looking if there was a way to output the name of a pointer (not the address of said pointer nor the value the address it points to is) but it does not seem so obvious to me despite a lot of googling. So basically use a pointer's name as a variable to trigger an action.

I hope I am not being too confusing nor too confused!

Cheers for your valuable input.

What I meant is that it stores the names

The array does NOT store names. It stores addressed. The compiler manages the mapping of names to addresses, so you normally don't have to worry about it. But the values you are assigning to the array are NOT names. They are addresses.

I would say that I feel like I am not even in the ballpark yet

You at least know that there is a ballpark somewhere nearby, and you seem determined to find it - unlike an awful lot of other newbies. So, you ARE doing some things right.

I was wondering if there was a way to read the name of

The Arduino knows nothing about names. So, no.

If you want to see a number, like 6, and use the value that string_6 points to, you need to use a switch statement where the case and the variable name only coincidentally have a visible relationship. The compiler will substitute the correct address when you use the name string_6 anywhere in the code.

But, you know that address of string_i is in the ith position in the string_table array, so the name is irrelevant.

It would really be better is the string names did NOT have numeric suffixes in that example.

The array does NOT store names. It stores addressed. The compiler manages the mapping of names to addresses, so you normally don’t have to worry about it. But the values you are assigning to the array are NOT names. They are addresses.

So I was still confused, I am clear on this now.

The Arduino knows nothing about names. So, no.

If you want to see a number, like 6, and use the value that string_6 points to, you need to use a switch statement where the case and the variable name only coincidentally have a visible relationship. The compiler will substitute the correct address when you use the name string_6 anywhere in the code.

But, you know that address of string_i is in the ith position in the string_table array, so the name is irrelevant.

It would really be better is the string names did NOT have numeric suffixes in that example.

It all makes sense, I agree. I also agree that the example could be made a little bit less confusing naming-wise but I am glad it is there as it is super helpful.

I will just keep track of which iterations of the loop I need to act differently on. It will be simpler. At some stage I was thinking about using pointers of pointers but then again I would only be able to extract the address of where a pointer is stored and not the name of the pointer itself and it would introduce complexity with the joys of double referencing and de-referencing on top of the fact that we are dealing with constant pointers to constant arrays. As a beginner I would rather stick to the KISS principle anyway.

My learning journey with PROGMEM shall continue: I saw in the pgm library that there are lots of various versions of the pgm_read macro some reading various types of data, some using “near”, some using “far”. I even saw somebody created a pgm_read _anything macro, I’ll have a look into that as well, that sounds interesting.

The time you took to help me is much appreciated, thanks again.