How can I reduce the SRAM memory used by this array of structs

I have an array of message structs that I’m using in my project. Everything works just fine but I can see that if I use many more elements than the 7 I already have, I’ll be out of SRAM pretty quickly.

This is the definition of the first element, with all others following the same pattern:

    messages[0].messageId = 1;
    strcpy(messages[0].textLine1, "Take the two green &");
    strcpy(messages[0].textLine2, "black tablets in top");
    strcpy(messages[0].textLine3, "row of blister pack.");
    messages[0].myDoWNum = 32; // every day
    messages[0].myHour = 8;
    messages[0].myMinute = 0;
    messages[0].followonMId = 2;

It looks like the amount of SRAM being used per array element is 70 bytes - the compiler reports that increase when I add another element to the array.

I’ve tried using F() like this

strcpy(messages[0].textLine1, F("Take the two green &"));

but that just gives a compiler error.

Any thoughts on this?

Thanks :)

You haven't provided the declaration of the struct that's instantiated as the messages[] array.

You also haven't told us which Arduino board you're using.

Try strcpy_P():
strcpy_P(messages[0].textLine1, (char *)F("Take the two green &"));

I can't comment on an array of structs that you haven't shown.

As for your second point, you get a compiler error because that's not a situation where the F() macro is used.

Here is a complete, compilable sketch for the R3 that demonstrates an approach that works for copying string literals out of Flash.

void setup() {
   char buf[80];
   Serial.begin(115200);
   strcpy_P(buf, PSTR("This is a test"));
   Serial.println(buf);
}

void loop() {
}

Is all of that known at compile time, or do the values change at run time?

They’re static - no change during run time

It’s a Nano - not sure how that affects what I thought was a purely programming question - but I am a bear of little brain ;)

As for the struct definition, here it is:

struct MessageStruct {
    int messageId;
    char textLine1[21];
    char textLine2[21];
    char textLine3[21];
    byte myDoWNum;
    byte myHour;
    byte myMinute;
    int followonMId;
};
MessageStruct messages[8];

I hope that helps :)

See post #7 for the struct definition.

I’ll try your suggestion and see if it helps - thanks :)

C strings are pointers. You only have to copy the pointer to the static text, not the characters. Make the pointers in your struct point to strings in program memory and use the related functions to show the texts.

Then you initialize the array with all the data when it is declared.

struct MessageStruct {
  int messageId;
  char textLine1[21];
  char textLine2[21];
  char textLine3[21];
  byte myDoWNum;
  byte myHour;
  byte myMinute;
  int followonMId;
};
const MessageStruct messages[8] PROGMEM = {
  {1,  "Take the two green &",  "black tablets in top",  "row of blister pack.",  32,  8,  0,  2},
  {2,  "This is the 2nd line",  "come more text",  "even more text.",  32,  8,  0,  3},
};

How you access the data depends on the code. Use special instructions to access the data directly from PROGMEM, or you could copy one of the array elements to ram and work from there.

And now that you've shared the struct, and told us that it's unchanging, following the examples shown in PROGMEM reference page tells you do do something like this:

struct MessageStruct {
    const int messageId;
    const char *textLine1;
    const char *textLine2;
    const char *textLine3;
    const byte myDoWNum;
    const byte myHour;
    const byte myMinute;
    const int followonMId;
};

const char string_0[] PROGMEM = "Take the two green &";
const char string_1[] PROGMEM = "black tablets in top";
const char string_2[] PROGMEM = "row of blister pack.";

const MessageStruct messages[8] PROGMEM = {
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2},
   {1, string_0, string_1, string_2, 32, 8, 0, 2}
};

void setup() {
   char buffer[80];
   
   Serial.begin(115200);
   for( int i=0; i<8; ++i ) {
      strcpy_P(buffer, (char *)pgm_read_ptr(&messages[i].textLine1));
      Serial.println(buffer);
      strcpy_P(buffer, (char *)pgm_read_ptr(&messages[i].textLine2));
      Serial.println(buffer);
      strcpy_P(buffer, (char *)pgm_read_ptr(&messages[i].textLine3));
      Serial.println(buffer);
   }
}

void loop() {
}

And the build results:

arduino-cli compile -b arduino:avr:uno --warnings all --output-dir ~/tmp --no-color (in directory: /home/me/Documents/sketchbook/Uno_R3/test)
Sketch uses 4656 bytes (14%) of program storage space. Maximum is 32256 bytes.
Global variables use 188 bytes (9%) of dynamic memory, leaving 1860 bytes for local variables. Maximum is 2048 bytes.Compilation finished successfully.

188 bytes of RAM used. Which is virtually the same amount of RAM used in the empty sketch that simply opens Serial.

void setup() {
   Serial.begin(115200);
}

void loop() {
}
arduino-cli compile -b arduino:avr:uno --warnings all --output-dir ~/tmp --no-color (in directory: /home/me/Documents/sketchbook/Uno_R3/test)
Sketch uses 4296 bytes (13%) of program storage space. Maximum is 32256 bytes.
Global variables use 184 bytes (8%) of dynamic memory, leaving 1864 bytes for local variables. Maximum is 2048 bytes.
Compilation finished successfully.

The buffer is not needed.

   for( int i=0; i<8; ++i ) {
      Serial.println((const __FlashStringHelper*)pgm_read_ptr(&messages[i].textLine1));
      Serial.println((const __FlashStringHelper*)pgm_read_ptr(&messages[i].textLine2));
      Serial.println((const __FlashStringHelper*)pgm_read_ptr(&messages[i].textLine3));
   }

Since each line is 20 characters, I'm guessing this is for an LCD display, if the LCD library supports the F() macro then it will work with __FlashStringHelper*.

Your suggestion to use:

strcpy_P(messages[0].textLine1, (char *)F("Take the two green &"));

works like a charm. It reduces the memory used by 62 bytes for each array element. So thanks for that :D

I’m still learning and can’t get my head around (char *).

I understand it’s to do with pointers, so maybe it assigns to the array element text line component a pointer to the text in program memory (put there with F())???

There’s a lot here to digest and it’s gone midnight here in the uk - so I’ll be doing a lot of pondering, cogitating and trying stuff tomorrow.

Meantime, thanks everyone for diving in and helping :smiley:

Yea, but it's kind of a hack. Putting the entire struct into PROGMEM as suggested in Post #10 is better.

It probably inherits from the Print class so it should.

F() macro only works for print().

You can store C zero-terminated strings in flash using PROGMEM but not C++ String objects. You can use them from there.

Yes, the LCD library is good with using F().

As for using PROGMEM in the way suggested, for now I’d rather have the readability of the “hack” than the extra few bytes saved by using PROGMEM

If ever there’s a need to have lots more messages, then that’ll be the way to go.

Thanks for your help - much appreciated :D

Regardless of whether you store the array in ram or PROGMEM, why are you copying constant values into it instead of just initializing it with the data?

In that case, you can make it a little less hacky by going with a proper C++ cast instead of the C-style one that popped into my head at first.

strcpy_P(messages[0].textLine1, reinterpret_cast<const char *> (F("Take the two green &")));

Either way, it won't free up nearly as much memory as what's suggested in Post #10.