форматирование строк в 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);
// лучший вариант?
@zhekaus, 👍10
5 ответов
Лучший ответ:
Если вам нужен результат в виде одной строки, то ваш 3-й вариант является предпочтительным способом.
Если вы этого не сделаете, то первый вариант печати каждой детали отдельно является наиболее эффективным с точки зрения объема памяти.
Вторая версия, конкатенация строк, является наихудшим вариантом во всех отношениях, и ее следует избегать любой ценой.
Я часто использую sprintf
для буфера, если мне нужен определенный формат (например, начальные нули в числе).
Если вы хотите избежать дополнительного раздувания кода sprintf
, вы можете использовать различные комбинации strcpy
, strcat
, itoa и т.д.
Для создания строки в буфере памяти (если вам действительно нужно поместить ее в буфер).
Обратите внимание, что на 8-битном Arduino sprintf
не имеет поддержки float, поэтому вам все равно нужно будет использовать dtostrf
для форматирования float в буфер символов или распечатать его напрямую.
Я согласен с ответом Маенко.
Я создал простой класс 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.
Семейство функций printf() гораздо предпочтительнее для меня как программиста, но в среде с ограниченными ресурсами, такой как меньшие процессоры AVR (и другие), дополнительное пространство кода может быть неоправданным или недоступным, или снижение производительности при интерпретации строки формата может быть ограниченным. И sprintf(buf, ...); Serial.print(buf);
также принимает удар стека для временного буфера (по сравнению с просто printf(...);
) Это компромисс между размером кода и скоростью по сравнению с у вас есть больше гибкости, чтобы написать код для создания нужного результата.
Неправильный параметр с опцией 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() {
}
Третий вариант, sprintf, небезопасен и может привести к переполнению буфера, если размер буфера указан неправильно. Вместо этого вы можете использовать snprintf . Однако я бы рекомендовал мою библиотеку SafeString и учебное пособие для общей обработки и вывода строк.
- String() против char для простого управления потоком
- Как разделить входящую строку?
- Как вывести несколько переменных в строке?
- Очень простая операция Arduino Uno Serial.readString()
- Проблемы с преобразованием byte[] в String
- Arduino Преобразование std:string в String
- Чтение строки, разделенной запятыми
- Как прочитать входящие ШЕСТНАДЦАТИРИЧНОЕ значение из serial метод read ()?
printf будет использовать примерно то же буферное пространство, что и sprintf; он должен где-то буферизировать печать., @Wexxor
Хорошее замечание о пространстве данных (ЕСЛИ мы знаем, что семейство printf() использует буфер в стеке, а не статический). Мой комментарий о пространстве кода по-прежнему остается в силе., @JRobert