How to store and read a struct from program memory

I am working on a data logger system that will have dozens of varieties. But all of them gather data from modbus. Which means the data values to log are in a straight uint16 array. There needs to be a corresponding array of strings to place at the header of the datalog file. The PROGMEM reference shows how to make and read an array of strings. I also need to related the string to the location in the modbus array. A struct of two items, the string and the reference to the modbus array. I have not been able to find a good reference to how to define and read the struct from PROGMEM.

Anyone have an example or good reference?

I had shared this a while back

#include <avr/pgmspace.h>

enum situation_t : uint8_t {single, couple, RIP};

const char s0[] PROGMEM = "single";
const char s1[] PROGMEM = "couple";
const char s2[] PROGMEM = "rest in peace";

const char* const descriptionSituation[] PROGMEM = {s0, s1, s2};

struct person_t {
  const char * firstName;
  const uint8_t d;
  const uint8_t m;
  const uint16_t y;
  situation_t situation;
};


// for char array, you define them first somewhere in flash memory
const char auguste[] PROGMEM = "Auguste";
const char camille[] PROGMEM = "Camille";
const char victor[] PROGMEM = "Victor";
const char juliette[] PROGMEM = "Juliette";
const char someone[] PROGMEM = "Mr Unknown";

const person_t population[] PROGMEM =
{
  {auguste, 12, 11, 1840, RIP}, 
  {camille, 8, 12, 1864, RIP},
  {victor, 26, 2, 1802, RIP},
  {juliette, 10, 4, 1806, RIP},
  {someone, 1, 1, 2000, single}
};


void setup() {
  Serial.begin(115200);
  for (const auto& member : population)
  {
    Serial.println((const __FlashStringHelper *) pgm_read_word(&(member.firstName)));
    Serial.print  ((uint8_t) pgm_read_byte_near(&(member.d)) ); Serial.print("/");
    Serial.print  ((uint8_t) pgm_read_byte_near(&(member.m)) ); Serial.print("/");
    Serial.println((uint16_t) pgm_read_word_near(&(member.y)) );
    Serial.println((const __FlashStringHelper *) pgm_read_word(&(descriptionSituation[(uint8_t) pgm_read_byte_near(&(member.situation))])));
    Serial.println(F("------------------------------"));
  }
}

void loop() {}
    Serial.print  ((uint8_t) pgm_read_byte_near(&(member.d)) ); Serial.print("/");
    Serial.print  ((uint8_t) pgm_read_byte_near(&(member.m)) ); Serial.print("/");
    Serial.println((uint16_t) pgm_read_word_near(&(member.y)) );

What are the casts for? Obfuscation? Promoting the idea any PROGMEM data has to be casted?

Serial.write('/');

is better than

Serial.print("/");

The cast is to ensure print is called using the right type/signature as __LPM or __LPM_word wont know if what you return is signed or not for example.

Regarding print versus write, its a personal preference. If you send only one byte it’s faster to call directly write rather than Serial.print(‘/‘); // with a char(which just adds an indirection). May be the compiler will be smart and inline the call but this way I’m sure I skip the extra useless call.

Using Serial.print("/");is even worse as it will cost you an extra byte in SRAM for the trailing NULL char and you call write with an array so will have a useless loop to extract just the first char.
=> Quite inefficient in CPU or SRAM resource

J-M-L:
The cast is to ensure print is called using the right type/signature as __LPM or __LPM_word wont know if what you return is signed or not for example.

#define __LPM_word_enhanced__(addr)         \
(__extension__({                            \
    uint16_t __addr16 = (uint16_t)(addr);   \
    uint16_t __result;                      \
    __asm__ __volatile__                    \
    (                                       \
        "lpm %A0, Z+"   "\n\t"              \
        "lpm %B0, Z"    "\n\t"              \
        : "=r" (__result), "=z" (__addr16)  \
        : "1" (__addr16)                    \
    );                                      \
    __result;                               \
}))

You cast to the return type that is already there (and what is implied by the name, byte and word).

I did not argue against the (const __FlashStringHelper*) cast, that is necessary.

J-M-L:
Regarding print versus write, its a personal preference. If you send only one byte it’s faster to call directly write rather than Serial.print(‘/‘); // with a char(which just adds an indirection). May be the compiler will be smart and inline the call but this way I’m sure I skip the extra useless call.

Using Serial.print("/");is even worse as it will cost you an extra byte in SRAM for the trailing NULL char and you call write with an array so will have a useless loop to extract just the first char.
=> Quite inefficient in CPU or SRAM resource

If you are aware of all that, why do you use the worse variant in your example?

Yes as I said, casting is more for the general case. An it would be two bytes too, but signed.

LOL, I did not even read back, clearly did not apply my personal preference :grin:

Note to self: read what you post especially at 4am :wink: