Understanding implementing of table of strings using PROGMEM

I don't want to just blindly use the notation provided in Arduino Referance.
There I have an example of the String table sent to flash memory.

Here are individual strings sent to flash memory

#include <avr/pgmspace.h>
const char string_0[] PROGMEM = "String 0"; // "String 0" etc are strings to store - change to suit.
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";

So as I understand it e.g. string_1 is now a pointer to the corresponding string in Flash memory
And here is the table of Pointers to those strings

const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5};

So now I want to understand the loop to retrieve all strings in that table and printing them

  for (int i = 0; i < 6; i++) {
    strcpy_P(buffer, (char *)pgm_read_word(&(string_table[i])));  // Necessary casts and dereferencing, just copy.
    Serial.println(buffer);
    delay(500);
  }

Buffer is char table as large as the longest string we try to retreive.

strcpy_P()) function returns a pointer to the destination string dest. Questions:

  • I am not sure why this function was called here. I would expect a function to load the buffer not a function to return the pointer

pgm_read_byte() is a macro that read a word of data stored in a specified address(PROGMEM area).
So pgm_read_word(&(string_table[i])) seems clear. It is reading a word (2bytes) from the pointer defined by string_table[i]. But few questions here:

  • what is the role of (char *) before the call of pgm_read_byte()
  • what is the role of "&"
  • Any string in a table is longer then 2 bytes . Buffer is 30 bytes where in this command we identify how many bytes should we read for selected string.

Any clarification here would be greatly appreciated

...which, in the code you posted isn't used.

You read the String until you encounter the null terminator.

I believe it is like this.

This indicates the reference to where the data is stored in the memory, more for the compiler side of things.

The reference is only a word long, most microcontrollers don't have more then 65K of memory.

I may not of got all the terms right.

I got this from official Arduino reference.
So is there other way of getting the same result but not using this function?

Do you mean not using strcpy_P?
Sure, just use pgm_read_byte until you encounter the terminator.
(But obviously, you'd really want to use strcpy_P, because that's already been written and debugged)

1 Like

strcpy_P copies the string in PROGMEM into the buffer, then returns a pointer to the buffer. Most of the str functions return pointers to the string destination, in case you want to use the function embedded within another function.

pgm_read_word() returns a uint16_t, the cast to char* is needed to match the function parameter for strcpy_P.

The & indicate the address of string_table[i], instead of the value stored there.
The number of bytes to read in the function is determined by the length of the string in PROGMEM, strcpy_P copies until it encounters the terminating null character.

It is possible to print the string directly from PROGMEM, without needing to copy to a buffer.

  for (int i = 0; i < 6; i++) {
    Serial.println((__FlashStringHelper*)pgm_read_ptr(&(string_table[i])));
    delay(500);
  }

pgm_read_ptr() is similar to pgm_read_word, except that it does not require that you know the size of a pointer on the processor you are using.

__FlashStringHelper* specifies a pointer to a string in PROGMEM, as opposed to a pointer in ram. There is an overload of print() that is specifically coded for strings in PROGMEM.

Ok. So both points you commented on make sense. So I provide word long address to the memory and the strcpy_P reads data to a buffer until the null terminator

Yes, I believe so.

Correct me if I am wrong anyone.

Thank you for a comprehensive analysis:)
That pretty much calrifies things.

Is there a function that allows me to read and show the content of progmem regardless of the individual strings saved to it.

So in my example strcpy_P starts loading buffer with data until it ancounters terminating null character.
Is there a function that would allow me to load the whole buffer until it is full so I can display it.
This is just out of curiosity.
Which other functions form that library are worth getting familiar with. There are loads of them..

The following sketch and the associated comments may provide some clarification.

const char string_0[] PROGMEM = "String 0"; //string goes into flash because of PROGMEM

void setup()
{
  Serial.begin(9600);
  byte charLen = strlen_P((const char*)string_0); //strlen_P() is variant of strlen()
  for (int i = 0; i <charLen; i++)
  {
    char y = pgm_read_byte_near(&string_0[i]); 
    Serial.print(y);  //shows: String 0
  }
  Serial.println();

}

void loop()
{
}

1. The strlen_P() function deals with string stored in Program Memory (the flash). It takes a pointer (which holds the base address of the string stored in flash) as argument. Using the pointer, the function scans the memory and counts the number of characters/bytes present in the string until the null-character ('\0') is encountered.

2. The cast (const char*) is there to indicate that the pointer refers to a string whose elements are characters and cannot be modified.

3. The pgm_read_byte_near() function takes the address of the string as argument and hence the ampersand (&) operator.

Yes, in your string_table[] example you don't have to know the specific string name, just it's location in the table.

Don't think so.

Not sure what you are asking, what would be the point in printing random data that exists beyond the end of the string? print() prints string data until it encounters the terminating null, then stops. The null is what indicates the end of a string stored as a char array.
If you really want to fill the entire buffer, you can use memcpy_P and give the size of the buffer, but then printing will need to be done differently, print() will still stop at the null.

If all the strings are the same length, or nearly the same length, then they can be stored as a two-dimensional array to avoid the table of char*. Accessing the string is then a bit less convoluted.

const char string_table[][9] PROGMEM = {
  "String 0",
  "String 1",
  "String 2",
  "String 3",
  "String 4",
  "String 5",
};

void setup() {
  Serial.begin(9600);
  Serial.println(F("\nstartup"));
  delay(500);
  for (int i = 0; i < 6; i++) {
    Serial.println((__FlashStringHelper*)string_table[i]);
    delay(500);
  }
  Serial.println(F("\nend program"));
}

void loop(){
}
1 Like

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