how to use repeated strings efficiently

I have a serial-controlled peripheral that needs a string command of 29 characters before sending the command data. The print format used is: Serial.println(Command + CommandData). I declare the oft-repeated Command string as a "const String". CommandData is usually 5-10 characters. There are over 30 uses of the print command in the sketch, and it is now ~2.7kB too large for my ATTiny841 mcu. Without the print commands the sketch is 6674B, so the print statements account for ~4300B. What can be done to reduce the size of the sketch?

OK First step I made a printCommand(String command) function:

void printCommand(String commandData)
{
  const String PLXSETSTR = "CELL,SET,ONSHEET,Simple Data,"; 
  Serial.println(PLXSETSTR + commandData);
}

A typical command looks like:

    printCommand((String)"B,12,1");

Now I only overflow by 3.1kB (beyond 8kB). If I turn off the calls to printCommand the sketch size is only 5.2kB. The function is called 57 times with differing command data. Each command data is 5-10 characters so maybe 400 characters are now contained in the printCommand calls as commandData. I don't understand how this turns into a difference of 5.9kB!

get rid of the String class altogether and use char buffers aka c-strings
you'll save plenty of program memory as the String class is heavy

instead of doing Serial.println(Command + CommandData); do

Serial.print(Command);
Serial.println(CommandData);

with both elements being char buffers (the first one being static can even be using PROGMEM)

It is not a good idea to use the String (capital S) class on an Arduino as it can cause memory corruption in the small memory on an Arduino. Just use cstrings - char arrays terminated with 0.

Also, you need to post a complete program if you want help. The problems are almost always in the other part.

...R

So could I also store each command cstring in progmem and use a pointer to get them? I am very not good with pointers. Do you have an example of how I could do that?

perigalacticon:
So could I also store each command cstring in progmem and use a pointer to get them?

do they change? if they only live in flash memory then you can't change them. but for static commands that's OK. if you need to dynamically calculate the command, like "[color=blue]x=[/color][color=red]2[/color][color=blue],y=[/color][color=red]7[/color]" where 2 and 7 can change, then you could have [color=blue]x=[/color] and [color=blue],y=[/color] as static commands and just print the numbers dynamically using multiple print commands

int x = 2;
int y = 7;

// ... later in the code ...

Serial.print(F("x="));
Serial.print(x);
Serial.print(F(",y="));
Serial.println(y); // note that I'm using println here and not just print, this is if your command needs to end with a new line

Read the PROGMEM doc to see how you use that

Ok here's a test sketch:

const char PLXSETSTR[29] = {"CELL,SET,ONSHEET,Simple Data,"};

void setup()
{
  Serial.begin(115200);
  Serial.println(F("SETUP PLX-DAQ")); // result displayed in Excel DirectDebugWindow
  delay(500);


  Serial.print(PLXSETSTR);
  Serial.println(F("A,6,0"));
}

void loop()
{

}

Now if commands are given 57x I think it's going to fill the memory with trying to print PLXSETSTR 57x right? Even if I use F("PLXSETSTR") it's going to fill the flash with 57x PLXSETSTR right? I am just learning about pointers but it seems you should be able to keep recalling the same cstring from progmem to use in the code without repeatedly storing it 57x in progmem flash right? How do you do that?

Now if commands are given 57x I think it's going to fill the memory with trying to print PLXSETSTR 57x right? Even if I use F("PLXSETSTR") it's going to fill the flash with 57x PLXSETSTR right?

Possibly with some agressive optimization flag the compiler can become smart and notice it's the same string and as it's a constant string, will only save it once but I don't think indeed it's the default behavior.

if you are using it a lot then you can also always declare it once at the beginning of your code and give it a nice name

const static char message[] PROGMEM = "This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very  long command line";

void setup() {
  Serial.begin(115200);
  Serial.println((const __FlashStringHelper *) message);
}

void loop() {}

this will use 1722 bytes

and

const static char message[] PROGMEM = "This is a very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very  long command line";

void setup() {
  Serial.begin(115200);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
  Serial.println((const __FlashStringHelper *) message);
}

void loop() {}

will use 1778 bytes, so clearly no duplication, the difference in size is just the function call to Serial.println multiple times

What are you doing there with (const __FlashStringHelper *) ?

I'm making sure to call the right version of the println() method, the one expecting data in flash memory

I have a mystery, after using the: Serial.println((const __FlashStringHelper *) message); method, I find subsequent println calls do not work as expected. For example:

printCommandAndVal("B,6,",10);

void printCommandAndVal(char commandData[], int commandVal)
{
  itoa(commandVal, commandValChar, 10);
  strcat (commandData, commandValChar);
  Serial.print((const __FlashStringHelper *) PLXSETSTR);
  Serial.println(commandData);
  Serial.println("ABCD");

}

gives the output:

CELL,SET,ONSHEET,Simple Data,B,6,10
2291

it appears the default method of println is not reset, is this true?

I tried several variations of :

Serial.println((const char)"ABCD");

and none seem to revert println back to it's normal print capabilities. Does anyone know how to do this?

There is no "default" print method. The library implements various print methods with different types of possible arguments. At compile time, the compilation process looks at what is the type (or guesses the most suitable type) of your parameters and this is the function called - it's hard wired for that specific call. The fact that I'm using explicitly (const __FlashStringHelper *) is because in that case the compiler is not guessing right, just sees a pointer and assumes it's pointing towards normal memory and thus calls code reading from memory, not from flash.

The fact that your code does not work is because you have bugs :slight_smile:

You do strcat (commandData, commandValChar); => Are you mixing up strcat() arguments' order and messing up your memory? It's strcat(char * destination, const char * source );

Also do you have enough space reserved in commandValChar and (hint) isn't that buffer you should print rather than your function parameter commandData once you've built up its content? (Again it's totally unnecessary to build up a buffer when you have the data already in variables, just do two separate print with your data)

Regarding Serial.println((const char)"ABCD"); => why are you trying to convince the compiler that your char buffer holding multiple characters is just one constant character? "ABCD" is a const char [color=red][b]*[/b][/color], it is a pointer somewhere in memory at the start of the memory address where the 5 bytes of the ASCII representation of your c-string "ABCD" are stored, with a '\0' at the end and you don't need to tell that to the compiler because it knows that already.

Thanks for your reply I had put the project aside for a while though because of many bugs I couldn't quite get figured out and priority of other projects. The strcat command is set up correctly and there is enough buffer size. Regarding "(const char)" that was just an example of one of the many combinations of casts I tried to use. Do you know which one should work?

I think you got solutions in this thread that are good, but beyond what you really need at this point. First of all, it should be pointed out that you don't need to concatenate output, since it goes sequentially to the same place anyway. So you can just:

void printCommand(char* commandData)
{
  Serial.print( F("CELL,SET,ONSHEET,Simple Data,") );
  Serial.println(commandData);
}

By the way, you don't need to count characters here and you don't need those braces, the compiler will do it for you, replace:

const char PLXSETSTR[29] = {"CELL,SET,ONSHEET,Simple Data,"};

with

const char PLXSETSTR[] = "CELL,SET,ONSHEET,Simple Data,";

or

const char* PLXSETSTR = "CELL,SET,ONSHEET,Simple Data,";