Форматирование переменных из вариационной функции

У меня есть функция, которая объединяет строку печати, однако у меня возникают проблемы с правильным форматированием некоторых строк.

Использование Serial.print правильно форматирует вывод keypad.getKey(), однако, когда я пытаюсь вывести тот же формат с помощью printConcatLine(), это не дает мне тот же выход. Я пробовал использовать его как char, так и как int...

void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 'c') {
          Serial.print(va_arg(params, const char *));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}

char btnPressed = keypad.getKey();
  if (btnPressed) {
    Serial.print("BtnPressed: ");
    Serial.println(btnPressed);
    // Вывод: "BtnPressed: 1"

    printConcatLine("ci", "BtnPressed: ", btnPressed); 
    // Вывод: "BtnPressed: 49"

    printConcatLine("cc", "BtnPressed: ", btnPressed); 
    // Вывод: "BtnPressed: "
  }

Я также пытался изменить:

va_arg(params, const char *)

в

va_arg(параметры, символы)

что тоже ничего не дало.

Как мне изменить вызов va_arg(), чтобы он правильно выводил тот же результат, что и Serial.print()?

, 👍1

Обсуждение

Кстати, в chipKIT мы предоставляем удобную функцию Serial.printf("...", ..., ..., ...), которая идеально подходит для этого. Форматирование обрабатывается [этим](https://github.com/chipKIT32/chipKIT-core/blob/master/pic32/cores/pic32/doprnt.cpp) кодом, который сам был взят непосредственно из RetroBSD., @Majenko

Поскольку вы используете серийный номер для печати строки, зачем ее объединять? Просто распечатайте его кусками., @Delta_G


3 ответа


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

2

Эдгар опередил меня на несколько секунд, но мой ответ почти такой же, как и у него, так что сначала прочитайте его.

Однако я добавлю несколько дополнительных примечаний и указателей:

  • Лучше присвоить va_args фактической переменной. Это упрощает чтение и понимание того, что происходит, и более точно позволяет перегрузке функции печати знать, что должно произойти (по крайней мере, с точки зрения читателя), чем простое приведение.
  • Байты и символы преобразуются в целые числа при передаче в функцию с переменным числом аргументов, поэтому вы должны обращаться с ними как с таковыми
  • float и double оказались одинаковыми на Arduino на основе AVR, поэтому ваш код там будет в порядке, но этого нельзя сказать о других чипах. . Если вы хотите, чтобы ваш код был переносимым, вам следует разделить float и double на отдельные обработчики, поскольку они используют разные размеры.
  • Было бы неплохо «пропустить» неизвестные символы в строке формата, чтобы вы могли добавить туда дополнительное форматирование. См. мой пример ниже.

Вот мой вариант того же кода. Обратите внимание на двоеточие и пробел в формате, которые используются для форматирования вывода (это можно улучшить, добавив % перед любыми символами формата, как это делает printf). Мне также нравится использовать отдельный указатель для перебора строки формата, чтобы исходный начальный указатель оставался доступным, если мне когда-нибудь понадобится.

Также обратите внимание на разницу между c и i: используйте c для печати буквы из символа, а i, чтобы вместо этого напечатать число в символе.

void printConcatLine(const char *mask, ...) {
    va_list params;
    va_start(params, mask);

    char *ptr = (char *)mask;
    while (*ptr != '\0') {
        if (*ptr == 'c') {
            int c = va_arg(params, int);
            Serial.write(c);
        } else if (*ptr == 'i') {
            int i = va_arg(params, int);
            Serial.print(i);
        } else if (*ptr == 's') {
            const char *s = va_arg(params, const char *);
            Serial.print(s);
        } else if (*ptr == 'f' || *ptr == 'd') {
            // Будьте осторожны с этим. Это не портативно. На float AVR
            // и double — это одно и то же, но на других это не так
            // микроконтроллеры. Их лучше разделить.
            double d = va_arg(params, double);
            Serial.print(d);
        } else {
            Serial.write(*ptr);
        }
        ptr++;
    }

    va_end(params);
    Serial.println();
}

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    if (btnPressed) {
        printConcatLine("c: sisd", 'Q', "BtnPressed: ", btnPressed, " done ", flt);
    }
}

void loop() {
}
,

сколько места во флэш-памяти это экономит по сравнению с printf? в моем тесте нет., @Juraj

@Juraj Много. Взгляните на ссылку в моем комментарии выше для «легкого» форматирования printf, и вы увидите, как много в нем задействовано., @Majenko

извини. нет. printf производит немного меньший код. Я использую свою оболочку StreamLib для printf для Serial https://github.com/jandrassy/StreamLib/blob/3b2393284315de939105a0c11df50be1471f6cfe/src/FormattedPrint.cpp#L28, @Juraj

@Juraj Возможно, на Arduino, но тогда в Arduno printf отсутствует поддержка с плавающей запятой, которая включена в код здесь. Если вы уберете поддержку float, вы, скорее всего, обнаружите, что этот код значительно меньше. Поддержка поплавка огромна., @Majenko

Я скомпилировал обе версии для Uno _с поддержкой float_ и, к моему удивлению, версия Juraj меньше (текст: 4814, данные: 68, bss: 166), чем ваша (текст: 6226, данные: 62, bss: 166)., @Edgar Bonet

@EdgarBonet Это сюрприз, надо сказать., @Majenko

поддержка printf float с -Wl,-u,vfprintf -lprintf_flt почему-то безусловна. это делает даже Блинка большим, @Juraj


2

В формате "ci" аргумент интерпретируется как целое число, и вывод правильный: 49 — это код ASCII '1'.

В формате "cc" аргумент интерпретируется как строка (указатель на массив символов), что неверно.

Одним (плохим) решением является использование формата "cc" и передача &btnPressed как Аргумент. Проблема с этим подходом в том, что байт в памяти сразу после btnPressed может не быть нулем, и формат ожидает Строка с нулевым завершением.

Лучше использовать один формат для char и другой для строки. Я предлагаю "c" и "s" соответственно. Но то вы должны знать, что существуют специальные правила продвижения связаны с вариативными функциями. Играя с вашим кодом, компилятор напомнил мне об этом:

предупреждение: 'char' преобразуется в 'int' при передаче через '...'

Таким образом, функция должна ожидать int и привести его к char по порядку. для вызова правильной перегрузки Serial.print():

void printConcatLine(const char* mask, ...) {
    va_list params;
    va_start(params, mask);

    while(*mask != '\0') {
      if (*mask == 'i') {
          Serial.print(va_arg(params, int));
      } else if(*mask == 's') {
          Serial.print(va_arg(params, const char *));
      } else if(*mask == 'c') {
          Serial.print((char) va_arg(params, int));
      } else if (*mask == 'f' || *mask == 'd') {
          Serial.print(va_arg(params, double));
      } 
      ++mask;    
    }

    va_end(params);
    Serial.println();
}

Используется следующим образом:

printConcatLine("sc", "BtnPressed: ", btnPressed); 
// Вывод: "BtnPressed: 1"
,

2

Я рекомендую использовать printf, если вам не нужна поддержка float в printf. Этот код создает скомпилированный код немного меньшего размера, чем тот, что был в ответе Маженко, и обладает полной мощностью printf, за исключением float.

#include <StreamLib.h>

void setup() {
    Serial.begin(115200);
    char btnPressed = 1;
    float flt = 3.141592653;

    byte b;
    BufferedPrint bp(Serial, &b, 1);

    if (btnPressed) {
        bp.printf("%c: %s %i %s", 'Q', "BtnPressed: ", btnPressed, " done ");
        bp.println(flt, 4);
    }
}

void loop() {
}

Конечно, с помощью StreamLib вы можете печатать с плавающей запятой с помощью обычной одиночной функции печати для с плавающей запятой, такой же, как для последовательного или сетевого клиента, со вторым параметром для количества знаков после запятой.

Если вы хотите создать отформатированную C-строку, вы можете использовать CStringBuilder библиотеки StreamLib. Он также строит строку с функциями print и также имеет 'printf'.

,

По поводу «_меньшей программы_»: меньший скомпилированный код?, @Edgar Bonet

@EdgarBonet, да, скомпилированный код меньше, @Juraj