What's the "best way" to deal with a function needing a string for input?

Hello

I am working on a project that has 10 oLED screens.
I would like to create a function to display text on those screens. Something along the line

Void printOnScreen(byte indexScreen, String MyMsg) {

 TCA9548A(indexScreen);
  display.clearDisplay();
 display.setCursor(0,0);
 display.setTextSize(2);
 display.println(MyMsg);
 display.display();
}

Now I would like to minimize the amount of SRAM used by the program.I have read multiple times than using String is not always a good idea.

Since the message are known, what would be the best way to do this ?

I have about 80 differents messages that could potentially be printed. I supposed I could put them all in an array and use the array index to access them but it would be hard to maintain the code b/c I would need to remember that MsgX is in the position Y in the array.

So some sort of mapping seems to make sense.

What's the "best practice" for such problem ?
Would something like this make sense ?

#define MSG {{1:"Msg1"}, {2:"Msg2"}, {3: "Msg3"}}

Void printOnScreen(byte indexScreen, byte indexMSG) {

 TCA9548A(indexScreen);
  display.clearDisplay();
 display.setCursor(0,0);
 display.setTextSize(2);
 display.println(MSG[indexMSG]);
 display.display();
}

Not sure how to access the value using the key though.

a) put the text in PROGMEM
b) only hand over the position of the text, not a copy of the text.

if you want to start stepwise, just use an array

const char * msg[] {"msg0", "msg1", "msg2"};
  

Those two are not related.

For AVR based boards, the PROGMEM mentioned by @noiasca will work. For other boards, PROGMEM has no effect. Which board are you using?

There is no reason to use String objects if it's fixed text; you can just as well use character arrays which will save you 6 bytes per text.

Void printOnScreen(byte indexScreen, String MyMsg) {

One problem with that function is that it copies the message that you want to print onto the stack; so during the printing to the display you will loose e.g. 20 bytes; after that, it will be available again.

One way to solve that is to use a reference which will only place the reference on the stack (2 bytes on an AVR based board).

Void printOnScreen(byte indexScreen, String &MyMsg) {

Note: I will not nag you about Void with capital V :smiley:

You can define functions for both RAM and PROGMEM strings.

void printOnScreen(const byte indexScreen, const char *MyMsg)
{
  TCA9548A(indexScreen);
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.println(MyMsg);
  display.display();
}

void printOnScreen(const byte indexScreen, const __FlashStringHelper *MyMsg)
{
  TCA9548A(indexScreen);
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.println(MyMsg);
  display.display();
}

Then you can use the F() macro to keep your string constants out of RAM:

printOnScreen(screenIndex, F("Long string to be kept in PROGMEM"));

Or make a RAM array of pointers to messages in PROGMEM:

const char Message27[] PROGMEM = "Long string to be kept in PROGMEM";

const char *Messages[] = 
  {
  Message0, Message1, Messge2, Message3, ...
  Messge27, Message28, Messge29...
  };

  printOnScreen(screenIndex, 
              (__FlashScreenHelper *) Messges[27]);

I am using a TEENSY LC. I've read indeed that PROGMEM wouldn't work with that board.

If I understand correctly __FlashStringHelper will be defining in PROGMEM. Correct ? So in my case with a Teensy LC, the approach would be to use

void printOnScreen(const byte indexScreen, const char *MyMsg)

Is that right ?

Void printOnScreen(byte indexScreen, const char *MyMsg) {

 TCA9548A(indexScreen);
  display.clearDisplay();
 display.setCursor(0,0);
 display.setTextSize(2);
 display.println(MSG[indexMSG]);
 display.display();
}

const char Msg1 = "Hello World";
printOnScreen(1,Msg1);

Wouldn't be better to use #define instead of const ?

I don't know how the Teensy LC compiler decides what variables go into SRAM and what variables stay in FLASH. If all string literals go into FLASH then using character pointers and not String objects should keep them in FLASH. Since String objects are designed for dynamic size changes I would assume that any string literal you copy into a String object would have to go into SRAM.

Does this make sense ?
I am not 100% sure about the use of the pointer and F() macro.

const char * MSG[] = {"Fct 1","Fct 2", "Amp","Tunner"};

void displayOnScreen(byte indexScreen,const char *MyMsg){
         TCA9548A(indexScreen);
  	 display.clearDisplay();
  	 display.setCursor(X,Y);
  	 display.setTextSize(FontSize);
  	 display.println(MyMsg);
  	 display.display();
}

Void setup() {
   displayOnScreen(1,F(MSG[0]) )
}

Also I do have a few messages that are fairly the same (e.g. Fct 1, Fct 2, etc)
Could I perhaps only keep the word "Fct" in the array and dynamically append the integer ? But then how would I append the digits to MSG?

That won't work on an UNO/Nano/Mega because the F() macro only works on string literals.

It will probably work on a Teensy LC because, on devices without PROGMEM, the F() macro only exists for compatibility and does nothing. Of course, if it doesn't do anything, you don't need to use it.