2

I wander what is the best option for formatting strings in Arduino for output. I mean what's a preferable way in the point of view of performance, memory usage – things like that. I see, people usually use direct Serial.print/println, like this:

  int x = 5;
  // 1st option
  Serial.print("x = ");
  Serial.println(x);

Or like this:

  // 2nd option
  Serial.println("x = " + String(x));

But what is wrong with, I believe, more traditional way of doing this:

  // 3rd optoin
  char strBuf[50];
  sprintf(strBuf, "x = %d", x);
  Serial.println(strBuf);

  // better one?
3

If you need the result in a single string then your 3rd option is the preferred way.

If you don't, then the first option of printing each part separately is the most efficient in terms of memory.

The second version, String concatenation, is the worst option in all respects and should be avoided at all costs.

I often use sprintf to a buffer if I need a specific format (for instance leading zeroes on a number).

If you want to avoid the extra code bloat of sprintf you can use various combinations of strcpy, strcat and itoa etc to build up a string into a memory buffer (if you really need to put it in a buffer).

Note that on an 8-bit Arduino sprintf has no float support - so you would still need to use dtostrf to format a float into a char buffer first, or print it directly.

| improve this answer | |
2

I agree with Majenko's answer.

I created a simple CStringBuilder class to combine the first and third approach mentioned in your question. It enables to build the c-string with printf and with Print functions, which can print float or IPAddress. It is available in StreamLib in library manager.

#include <CStringBuilder.h>
#include <IPAddress.h>

const char* s = "Lorem ipsum";
char c = 'X';
int i = 42;
float f = PI;
IPAddress ipAddress(192, 168, 0, 1);

void setup()
{
  Serial.begin(115200);
  while (!Serial);

  char buff[150];
  CStringBuilder sb(buff, sizeof(buff));

  sb.print(F("Some text: "));
  sb.println(s);
  sb.print(F("Some char: "));
  sb.println(c);
  sb.print(F("HEX of char: "));
  sb.println(c, HEX);
  sb.print(F("Some integer: "));
  sb.println(i);
  sb.print(F("Some float: "));
  sb.println(f, 3);
  sb.print(F("IP address: "));
  sb.println(ipAddress);

  int l = sb.length();
  sb.print("this text doesn't fit in the remaining space in the buffer");
  if (sb.getWriteError()) {
    sb.setLength(l);
  }
  sb.println("test test");

  Serial.print("size to print: ");
  Serial.println(sb.length());
  Serial.println();
  Serial.println(buff);

  sb.reset();
  sb.printf(F("Formatted: %s;%c;%05d\r\n"), s, c, i);
  Serial.println(buff);
}

void loop()
{

}

Other classes in the library are BufferedPrint and ChunkedPrint. Both support sprintf.

| improve this answer | |
2

The printf() family of functions is far preferable to me as the programmer, but in a resource-constrained environment such as the smaller AVR (and other) processors, the extra code space may not be justified or available, or the performance hit of interpreting the format string may be limiting. And sprintf(buf, ...); Serial.print(buf); also takes a stack hit for the temporary buffer (compared to just printf(...); ) It's a trade-off between code size and speed vs. you having more flexibility to write the code to the create the output you want.

| improve this answer | |
1

The wrong thig with option 3 is the buffer length, you should use snprintf instead.

The other problem is that first you build the string, then you print the output, the program traverses the string twice. Option 2 creates object from string, then applies + operator to create a new string, then prints it - it is comfortable with short strings, but least efficient (depends on the compiler optimalizations).

Option 1 is most effective, since it directly prints arguments to the output char by char (see Arduino Print class) and traverses the arguments only once.

You can use stdarg.h to create one-liner with Serial.prints:

#include <stdarg.h>

void Serialprintln(const char* input...) {
  va_list args;
  va_start(args, input);
  for(const char* i=input; *i!=0; ++i) {
    if(*i!='%') { Serial.print(*i); continue; }
    switch(*(++i)) {
      case '%': Serial.print('%'); break;
      case 's': Serial.print(va_arg(args, char*)); break;
      case 'd': Serial.print(va_arg(args, int), DEC); break;
      case 'b': Serial.print(va_arg(args, int), BIN); break;
      case 'o': Serial.print(va_arg(args, int), OCT); break;
      case 'x': Serial.print(va_arg(args, int), HEX); break;
      case 'f': Serial.print(va_arg(args, double), 2); break;
    }
  }
  Serial.println();
  va_end(args);
}

Then you can use it like sprintf (but efficiently):

void setup() {
  int n = 42;
  float f = 42.42;
  const char* s = "answer";
  Serial.begin(9600);
  // The answer is 42 (101010 BIN, 2A HEX), in float 42.42
  Serialprintln("The %s is %d (%b BIN, %x HEX), in float %f", s, n, n, n, f);
}

void loop() {
}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.