Jak wydrukować wiele zmiennych w ciągu?


46

Powiedzmy, że mam kilka zmiennych, które chcę wydrukować na terminalu. Jaki jest najłatwiejszy sposób na wydrukowanie ich w ciągu?

Obecnie robię coś takiego:

Serial.print("Var 1:");Serial.println(var1);
Serial.print(" Var 2:");Serial.println(var2);
Serial.print(" Var 3:");Serial.println(var3);

Czy jest na to lepszy sposób?


Pomysł, ale nie wiem, czy to zadziała, to jakaś modyfikacja tego ... Znowu nie wiem, czy to jest obsługiwane w Arduino: stackoverflow.com/questions/804288/...
apnorton

Odpowiedzi:


37

ardprintfto funkcja, którą zhakowałem razem, która symuluje printfpołączenie szeregowe. Tę funkcję (podaną na dole) można wkleić na początku plików, w których funkcja jest potrzebna. Nie powinno powodować żadnych konfliktów.

Można go nazwać podobnym do printf. Zobacz to w akcji w tym przykładzie:

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int l=2;
  char *j = "test";
  long k = 123456789;
  char s = 'g';
  float f = 2.3;

  ardprintf("test %d %l %c %s %f", l, k, s, j, f);

  delay(5000);

}

Dane wyjściowe są zgodne z oczekiwaniami:

test 2 123456789 g test 2.30

Prototypem funkcji jest:

int ardprintf(char *, ...);

Zwraca liczbę argumentów wykrytych w wywołaniu funkcji.

Oto definicja funkcji:

#ifndef ARDPRINTF
#define ARDPRINTF
#define ARDBUFFER 16
#include <stdarg.h>
#include <Arduino.h>

int ardprintf(char *str, ...)
{
  int i, count=0, j=0, flag=0;
  char temp[ARDBUFFER+1];
  for(i=0; str[i]!='\0';i++)  if(str[i]=='%')  count++;

  va_list argv;
  va_start(argv, count);
  for(i=0,j=0; str[i]!='\0';i++)
  {
    if(str[i]=='%')
    {
      temp[j] = '\0';
      Serial.print(temp);
      j=0;
      temp[0] = '\0';

      switch(str[++i])
      {
        case 'd': Serial.print(va_arg(argv, int));
                  break;
        case 'l': Serial.print(va_arg(argv, long));
                  break;
        case 'f': Serial.print(va_arg(argv, double));
                  break;
        case 'c': Serial.print((char)va_arg(argv, int));
                  break;
        case 's': Serial.print(va_arg(argv, char *));
                  break;
        default:  ;
      };
    }
    else 
    {
      temp[j] = str[i];
      j = (j+1)%ARDBUFFER;
      if(j==0) 
      {
        temp[ARDBUFFER] = '\0';
        Serial.print(temp);
        temp[0]='\0';
      }
    }
  };
  Serial.println();
  return count + 1;
}
#undef ARDBUFFER
#endif

** Aby wydrukować %znak, użyj %%. *


Teraz dostępne na listach Github .


3
Fajny pomysł, chociaż czułem, że może być bardziej minimalistyczny, więc przepisałem tę wersję na jedną bez buforowania. Każdy zainteresowany może sprawdzić treść: gist.github.com/EleotleCram/eb586037e2976a8d9884
eleotlecram

13

Normalnie nie zadałbym dwóch odpowiedzi na pytanie, ale znalazłem to tylko dzisiaj, gdzie możesz używać printf bez bufora.

// Function that printf and related will use to print
int serial_putchar(char c, FILE* f) {
    if (c == '\n') serial_putchar('\r', f);
    return Serial.write(c) == 1? 0 : 1;
}

FILE serial_stdout;

void setup(){
    Serial.begin(9600);

    // Set up stdout
    fdev_setup_stream(&serial_stdout, serial_putchar, NULL, _FDEV_SETUP_WRITE);
    stdout = &serial_stdout;

    printf("My favorite number is %6d!\n", 12);
}

void loop() {
  static long counter = 0;
  if (millis()%300==0){
    printf("millis(): %ld\tcounter: %ld (%02X)\n", millis(), counter, counter++);
    delay(1);    
  }
}

Nadal ma to ograniczenie zmiennoprzecinkowe.

edytuj: Myślałem, że zrobię trochę testów na tym, i działa całkiem dobrze. Dodałem lepszy test do pętli ze sformatowanym wyjściem.


O rany, to spoko. printf jest o wiele bezpieczniejszy niż sprintf. Daje ci ciągi formatujące za darmo, co jest świetne. Fajna sztuczka. Dzięki. (Głosowano)
Duncan C

Jedno pytanie: serial_putchardlaczego w swojej funkcji nie składasz instrukcji return return !Serial.write(c);? Czy to nie jest czystsze niż operator trójkowy do odwracania sensu wartości logicznej?
Duncan C

To dobra uwaga i podoba mi się. Kod nie był mój i wkleiłem go tak, jak go znalazłem.
Madivad

Dzięki za serial_putcharfunkcję. Działa uczta. :-) Czy można naprawić ograniczenie zmiennoprzecinkowe ?
Greenonline

4

To chyba nie jest lepsze, po prostu inne. Możesz użyć obiektu String do wydruku. Obiekty te umożliwiają konkatenację i obsługują automatyczne rzutowanie czcionek.

Serial.begin(9600);
String label = "Var";
const byte nValues = 3;
int var[nValues] = {36, 72, 49};

for (int i = 0; i < nValues; i++) {
    String stuff = label + i + ": ";
    Serial.println(stuff + var[i]);
}

4
Oczywiście ważne jest, aby uważać na limity pamięci. Wiele konkatenacji i innych operacji strunowych w jednym miejscu może zajmować zaskakująco dużo miejsca.
Peter Bloomfield

@ PeterR.Bloomfield Absolutnie prawda! Dlatego wspomniałem, że ten wariant nie jest lepszy;)
Klaus-Dieter Warzecha

4

Zwykle korzystałem z Tabs, aby poprawić porządek w Szeregowym. Ułożenie elementów tak, jak ja, pozwala arduino strzelać tak szybko, jak to możliwe, jednocześnie będąc w stanie zauważyć pewne zmiany w zmiennych.

Wypróbuj coś takiego:

Serial.println("Var 1:\tVar 2tVar 3:");
Serial.print("\t");
Serial.print(var1);
Serial.print("\t");
Serial.print(var2);
Serial.print("\t");
Serial.print(var3);
Serial.println();

Lub coś w tym stylu:

Serial.print("Var 1:");Serial.println(var1);
Serial.print("\tVar 2:");Serial.println(var2);
Serial.print("\tVar 3:");Serial.println(var3);

Szczerze mówiąc, robię to samo („\ t” i „\ n”) i zwykle unikam rozdzierających kod dzwonków i gwizdków obiektu String.
Klaus-Dieter Warzecha

1
@KlausWarzecha, rzadko nadaję nazwę zmiennej, ponieważ są w ładnych kolumnach. Ułatw także wyświetlanie losowych wydruków, które nie pasują do tej składni
Steven10172

4

Używam tego tylko do debugowania, ale:

int a = 10;
int b = 20;
Serial.println("a = " + String(a) + " and b = " + String(b));

co to jest String $?
Juraj

LMFTFM (Pozwól mi to naprawić).
linhartr22

2

Jestem nowicjuszem w świecie Arduino, ale ostatnio odkryłem, że jest to zwykły C ++ (bez wyjątków i prawdopodobnie polimorfizmu). Ale nadal możesz cieszyć się szablonami. Więc moim rozwiązaniem jest użycie następujących szablonów:

void myprint(void)
{
  Serial.println("");
}

template<typename ...Args>
void myprint(const uint64_t & val, Args && ...args)
{
  serialPrintUint64(val);
  myprint(args...);
}

template<typename T, typename ...Args>
void myprint(const T & t, Args && ...args)
{
  Serial.print(t);
  myprint(args...);
}

....

// somewhere in your code
myprint("type: ", results.decode_type, 
        "\t value: ", results.value, 
        "\t addr: ", results.address,
        "\t cmd: ", results.command);

Fajną rzeczą jest to, że nie wykorzystuje tutaj żadnej dodatkowej pamięci i dodatkowego przetwarzania.


1

Zwykle (boleśnie) trzymam się wielu linii, Serial.printale kiedy staje się zawiły, wracam do sprintf. Jest to denerwujące, ponieważ musisz mieć dostępny bufor.

Użycie jest tak proste (??) jak:

char buffer[35]; // you have to be aware of how long your data can be
                 // not forgetting unprintable and null term chars
sprintf(buffer,"var1:%i\tvar2:%i\tvar3:%i",var1,var2,var3);
Serial.println(buffer);

Słowo ostrzeżenia, nie obsługuje (domyślnie) typów pływających.


1
sprintf to okropna obrzydliwość. Nie pisz bezpiecznie, łatwe do przekroczenia bufory itp. To narzędzie z lat 60. To powiedziawszy, też go używam, ale to nie jest dla osób o słabym sercu ...
Duncan C

Aby uniknąć przekroczenia, użyj snprintf ... BTW większość moderowanych IDE (NIE IDE Arduino) sprawdzi format ciągu w stosunku do podanych typów zmiennych i wyświetli ostrzeżenie.
następny hack

1

Używanie Streaming.hzamiast

Serial.print("Var 1:");Serial.println(var1);
Serial.print(" Var 2:");Serial.println(var2);
Serial.print(" Var 3:");Serial.println(var3);

można pisać

Serial << "Var 1:" << var1) << " Var 2:" << var2 << " Var 3:" << var3 << endl;

Definicja <<in Streaming.hw efekcie przekłada to na szereg zwykłych Serial.print()wywołań. To znaczy <<cukier syntaktyczny, realizowany bez zwiększania rozmiaru kodu.

Jeśli nie masz Streaming.hzainstalowanej, pobierz Streaming5.zipz arduiniana.org . Rozpakuj go w katalogu bibliotek, na przykład w ~/sketchbook/libraries. Dodaj linię #include <Streaming.h>w szkicach, których używasz <<jako operatora strumienia.

Dostępne są specyfikatory konwersji bazowej _HEX, _DEC, _OCT i _BIN, a także funkcja _FLOAT (z liczbą miejsc dziesiętnych) i endl. Na przykład, aby wydrukować wartości szerokości i długości geograficznej w formie „Twoje współrzędne to -23.123, 135.4567”, można napisać:

Serial << "Your coordinates are " << _FLOAT(latitude,3) << ", " << _FLOAT(longitude,4) << endl;

Można to również zapisać jako

Serial << F("Your coordinates are ") << _FLOAT(latitude,3) << ", " << _FLOAT(longitude,4) << endl;

który zachowałby dłuższy ciąg w PROGMEM zamiast wprowadzać go do pamięci RAM.

Uwaga: Streaming.h nie buduje żadnych ciągów jako takich; po prostu dostarcza tekst swoich <<-strumentów do strumienia. Klasa PString na arduiniana można budować ciągi z wejściami strumienia, jeśli struny zamiast wyjścia strumieniowej są pożądane lub konieczne.


1

Użycie będzie zależeć od typu danych zmiennych.

Jeśli tak int, to byłoby %dlub %i Jeśli tak string, byłoby%s

Opakowanie do printf

Możesz zmienić limit w zależności od swoich wymagań

#include <stdarg.h>
void p(char *fmt, ... ){
    char buf[128]; // resulting string limited to 128 chars
    va_list args;
    va_start (args, fmt );
    vsnprintf(buf, 128, fmt, args);
    va_end (args);
    Serial.print(buf); // Output result to Serial
}

Źródło: https://playground.arduino.cc/Main/Printf

Przykłady użycia:

p("Var 1:%s\nVar 2:%s\nVar 3:%s\n", var1, var2, var3); // strings
p("Var 1:%d\nVar 2:%d\nVar 3:%d\n", var1, var2, var3); // numbers

ESP8266

Jest wbudowany w Serialklasę frameworka. Nie potrzeba dodatkowej biblioteki ani funkcji.

// strings
Serial.printf("Var 1:%s\nVar 2:%s\nVar 3:%s\n", var1, var2, var3);
// numbers
Serial.printf("Var 1:%d\nVar 2:%d\nVar 3:%d\n", var1, var2, var3);

Więcej informacji na temat wskazówek dotyczących formatowania na stronie odniesienia do formatu printf: http://www.cplusplus.com/reference/cstdio/printf/

\n jest sekwencją zmiany znaczenia dla wstawiania wiersza.

Sekwencje specjalne są używane do reprezentowania niektórych znaków specjalnych w literałach ciągów i literałów znakowych.

Źródło: http://en.cppreference.com/w/cpp/language/escape

[EDYCJA] - Jak wspomniał @Juraj, nie jest dostępny w większości modułów AVR. Dodałem więc wzmiankę o ESP8266 i opakowanie printf dla popularnych modułów AVR


to nie jest prawda. nie ma klasy szeregowej. printf byłby w klasie Print, ale nie jest w najczęściej używanym pakiecie AVR
Juraj,

@Juraj masz rację, przetestowałem go tylko na ESP8266, które mają go ( link ) i myślałem, że pochodzi z rdzenia arduino. Zaktualizuje odpowiednio moją odpowiedź
Remi

dla funkcji p dodałbym jeszcze jedną opinię, jeśli to możliwe.
Juraj,

to jest stare pytanie i nie mogę ocenić starych odpowiedzi, ponieważ nie wiem, co było dostępne w 2014 roku. Ale teraz są biblioteki do zawijania strumienia wydruku w strumieniu wydruku z implementacją printf.
Juraj,

0

Jednym z możliwych rozwiązań jest:

Serial.println((String)"Var 1:" + var1 + " Var 2:" + var2 + " Var 3:" + var3);


-1

Od http://playground.arduino.cc/Main/Printf Zauważyłem, że działa to dobrze na moim mega2560

To wszystko, co właśnie zadziałało, nie ma potrzeby vsnprintf_P lub PROGMEM ...

#include "Arduino.h"
void local_printf(const char *format, ...)
{
static char line[80];
va_list args;
va_start(args, format);
int len = vsnprintf(line, sizeof(line), format, args);
va_end(args);
for (char *p = &line[0]; *p; p++) {
    if (*p == '\n') {
        Serial.write('\r');
    }
    Serial.write(*p);
}
if (len >= sizeof(line))
    Serial.write('$');
}

void setup()
{
Serial.begin(115200);
local_printf("%s:%d: %s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
}

void loop()
{
static int count=0;
local_printf("%s:%d: %s %d\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, count++);
delay(1*1000);
}

// src/main.c:24: void setup()
// src/main.c:30: void loop() 0
// src/main.c:30: void loop() 1

1
Dlaczego ktokolwiek miałby to robić, zamiast używać printf()samego siebie ?
Edgar Bonet

-3
int Money_amount = 55;

Serial.print(String("New amount: $" + Money_amount));

Na terminalu zobaczysz:

New amount: $55

1
Nie można połączyć int z ciągiem c z +operatorem.
gre_gor
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.