форматирование строк в Arduino для вывода

Я размышляю, какой наилучший вариант форматирования строк в Arduino для вывода. Я имею в виду, какой способ предпочтительнее с точки зрения производительности, использования памяти и тому подобного. Я вижу, люди обычно используют прямой Serial.print/println, например:

  int x = 5;
  // 1 - й вариант
  Serial.print("x = ");
  Serial.println(x);

Или вот так:

  // 2-й вариант
  Serial.println("x = " + String(x));

Но что плохого в том, я полагаю, более традиционном способе сделать это:

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

  // лучший вариант?

, 👍10


5 ответов


Лучший ответ:

7

Если вам нужен результат в виде одной строки, то ваш 3-й вариант является предпочтительным способом.

Если вы этого не сделаете, то первый вариант печати каждой детали отдельно является наиболее эффективным с точки зрения объема памяти.

Вторая версия, конкатенация строк, является наихудшим вариантом во всех отношениях, и ее следует избегать любой ценой.

Я часто использую sprintf для буфера, если мне нужен определенный формат (например, начальные нули в числе).

Если вы хотите избежать дополнительного раздувания кода sprintf, вы можете использовать различные комбинации strcpy, strcat, itoa и т.д. Для создания строки в буфере памяти (если вам действительно нужно поместить ее в буфер).

Обратите внимание, что на 8-битном Arduino sprintf не имеет поддержки float, поэтому вам все равно нужно будет использовать dtostrf для форматирования float в буфер символов или распечатать его напрямую.

,

3

Я согласен с ответом Маенко.

Я создал простой класс CStringBuilder, чтобы объединить первый и третий подходы, упомянутые в вашем вопросе. Это позволяет создавать c-строку с помощью printf и функций печати, которые могут печатать float или IPAddress. Он доступен в StreamLib в диспетчере библиотек.

#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()
{

}

Другими классами в библиотеке являются BufferedPrint и ChunkedPrint. Оба поддерживают sprintf.

,

2

Семейство функций printf() гораздо предпочтительнее для меня как программиста, но в среде с ограниченными ресурсами, такой как меньшие процессоры AVR (и другие), дополнительное пространство кода может быть неоправданным или недоступным, или снижение производительности при интерпретации строки формата может быть ограниченным. И sprintf(buf, ...); Serial.print(buf); также принимает удар стека для временного буфера (по сравнению с просто printf(...);) Это компромисс между размером кода и скоростью по сравнению с у вас есть больше гибкости, чтобы написать код для создания нужного результата.

,

printf будет использовать примерно то же буферное пространство, что и sprintf; он должен где-то буферизировать печать., @Wexxor

Хорошее замечание о пространстве данных (ЕСЛИ мы знаем, что семейство printf() использует буфер в стеке, а не статический). Мой комментарий о пространстве кода по-прежнему остается в силе., @JRobert


2

Неправильный параметр с опцией 3 - это длина буфера, вместо этого вы должны использовать snprintf.

Другая проблема заключается в том, что сначала вы создаете строку, затем печатаете выходные данные, программа дважды пересекает строку. Вариант 2 создает объект из строки, затем применяет оператор + для создания новой строки, затем печатает ее - это удобно для коротких строк, но наименее эффективно (зависит от оптимизации компилятора).

Вариант 1 наиболее эффективен, поскольку он напрямую выводит аргументы в выходной символ по символу (см. Arduino Print class ) и просматривает аргументы только один раз.

Вы можете использовать stdarg.h для создания однострочника с 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);
}

Затем вы можете использовать его как sprintf (но эффективно):

void setup() {
  int n = 42;
  float f = 42.42;
  const char* s = "answer";
  Serial.begin(9600);
  // Ответ 42 (101010 BIN, 2A HEX), с плавающей точкой 42.42
  Serialprintln("The %s is %d (%b BIN, %x HEX), in float %f", s, n, n, n, f);
}

void loop() {
}
,

0

Третий вариант, sprintf, небезопасен и может привести к переполнению буфера, если размер буфера указан неправильно. Вместо этого вы можете использовать snprintf . Однако я бы рекомендовал мою библиотеку SafeString и учебное пособие для общей обработки и вывода строк.

,