Best way to store loads of strings?

Hello everybody!

I am working on a project that is not small by any measure and im now starting to think about memory footprint..

Im currently using previously defined strings, since i want to print many of the strings to both a 16x2char LCD as well as serial, and i figured it would be better to only store the string once.

// strings (got loads of them, about 20-30 more)
#define STR_FUNC "Function "
#define STR_IS_ON "is ON"
#define STR_IS_OFF "is OFF"

bool boolVar = false; // bogus boolean, to provide a example code that compiles
void setup() {
  Serial.begin(9600);
}
void loop() {
  Serial.print(STR_FUNC);
  if (boolVar) {
    Serial.print(STR_IS_ON);
  }
  else {
    Serial.print(STR_IS_OFF);
  }
}
-------------------------
Expected result: "Function is OFF"

Ive been investigating a bit, trying to find out if "my" way is a decent solution or if i should go for another alternative, but i find it hard to find any concrete answers..

Ive found two other options, both involving progmem.
The first is to add the F(string) macro, but from what i understand, i would then have to scrap my list of strings and instead use a string inside each print statement.

The other is to use:

const char CLI_STR_POWER[]  PROGMEM = "POWER";

But from what ive gathered, i wont be able to use that progmem-stored variable in a Serial.print().

So, given that im using print from Serial as well as liquidcrystal, what is my best alternative to keep the memory usage to a minimum?

Serial.print((__FlashStringHelper*)CLI_STR_POWER);

Should work with any library that inherits from the print class.

  1. #define is a macro definition. It has ZERO effect on the compiled code over writing out the code verbatim longhand.
    THe C pre-processor just replaced everywhere is sees STR_IS_ON in your program with "is ON" and compiles that instead.
    So #define is not going to help.

  2. If you write this code...

Serial.println("Hello World!");
Serial.println("Hello World!");

...(or as many copies of that string as you want) then the compiler is not stupid. It sees the "const" and you get only 1 copy of the string "Hello World!" in Flash.
Also that single copy is itself copied into 1 copy in RAM at startup.

  1. The F macro interferes with the compilers ability to optimise away multiple copies of identical const strings.
    So the above code generates two copies in Flash.
    On the plus side it doesn't create a copy in RAM at startup.

Meanwhile...

const char CLI_STR_POWER[]  PROGMEM = "POWER";

Creates only one copy in Flash again.
The down side is you must write your own code using pgm_read_****_near functions from avr/pgmspace.h

Thank you folks for the assistance!

Its been some horrible work to change my approach, but the results are in. :slight_smile:

OLD way (#define´s):
Sketch uses 16282 bytes (53%) of program storage space. Maximum is 30720 bytes.
Global variables use 1155 bytes (56%) of dynamic memory, leaving 893 bytes for local variables. Maximum is 2048 bytes.
freeMem: 879 (bytes?)

NEW way (PROGMEM):
Sketch uses 16354 bytes (53%) of program storage space. Maximum is 30720 bytes.
Global variables use 649 bytes (31%) of dynamic memory, leaving 1399 bytes for local variables. Maximum is 2048 bytes.
freeMem: 1453 (bytes?)

the freeMem above is a returned value from a function i found online: GitHub - mpflaga/Arduino-MemoryFree: basic functions and example to use show used RAM and use less of it.

extern char *__brkval;
    
int freeMemory() {
    char top;
    return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}

It is supposed to measure how much space there is between the heap and the stack in sram.

The code snippet was taken from Measuring Memory Usage | Memories of an Arduino | Adafruit Learning System but the original code is on the first link above.

#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__
    
int freeMemory() {
    char top;
#ifdef __arm__
    return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
    return &top - __brkval;
#else  // __arm__
    return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

The project is currently above 2500 lines of code and is currently about 95-97% done, just missing a few small features, mainly the control code for a e-bike controller.
I aim to enable/disable the eBike motor and feed it "bogus" data like cadence (pedal rpm) and speed, so it will start driving sooner as well as cut off when brakes are applied.

But everything else is there, battery monitoring and power management, software-controlled shutdown (including system timeout with auto-shutoff), display (including configuration screen), buttons and light management, speedometer/odometer, store/retrieve previously configured values from EEPROM and so on.
Im guessing that this is the year my program will finally be labeled "done", after "only" 6 years of development, time spent mostly on learning C.. :slight_smile: