ฉันจะพิมพ์หลายตัวแปรในสตริงได้อย่างไร


46

สมมติว่าฉันมีตัวแปรบางอย่างที่ฉันต้องการพิมพ์ไปยังเทอร์มินัลวิธีที่ง่ายที่สุดในการพิมพ์ในสตริงคืออะไร?

ขณะนี้ฉันทำอะไรแบบนี้:

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

มีวิธีที่ดีกว่าในการทำเช่นนี้?


ความคิด แต่ฉันไม่รู้ว่ามันจะใช้งานได้หรือเปล่าคือการดัดแปลงบางอย่าง ... อีกครั้งฉันไม่รู้ว่ามันรองรับ Arduino นี้หรือไม่: stackoverflow.com/questions/804288/…
apnorton

คำตอบ:


37

ardprintfเป็นฟังก์ชั่นที่ฉันแฮ็คเข้าด้วยกันซึ่งจำลองprintfผ่านการเชื่อมต่อแบบอนุกรม ฟังก์ชั่นนี้ (รับที่ด้านล่าง) สามารถวางในจุดเริ่มต้นของไฟล์ที่จำเป็นต้องใช้ฟังก์ชั่น ไม่ควรสร้างความขัดแย้งใด ๆ

printfจะสามารถเรียกว่าคล้ายกับ ดูการทำงานในตัวอย่างนี้:

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);

}

ผลลัพธ์ตามที่คาดไว้คือ:

test 2 123456789 g test 2.30

ฟังก์ชั่นต้นแบบคือ:

int ardprintf(char *, ...);

ส่งคืนจำนวนอาร์กิวเมนต์ที่ตรวจพบในการเรียกใช้ฟังก์ชัน

นี่คือนิยามฟังก์ชัน:

#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

** หากต้องการพิมพ์%ตัวอักษรให้ใช้%%. *


ตอนนี้ที่มีอยู่บนจิสต์ Github


3
ความคิดที่ดีถึงแม้ว่าฉันรู้สึกว่ามันอาจจะดูมินิมอลมากกว่านี้ฉันก็เลยเขียนมันซ้ำอีกครั้งโดยที่ไม่มีบัฟเฟอร์ ทุกคนที่สนใจสามารถดูส่วนสำคัญ: gist.github.com/EleotleCram/eb586037e2976a8d9884
eleotlecram

13

ปกติฉันจะไม่ตอบคำถามสองข้อ แต่ฉันเพิ่งพบสิ่งนี้วันนี้ที่คุณสามารถใช้ printf โดยไม่ต้องบัฟเฟอร์ใด ๆ

// 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);    
  }
}

สิ่งนี้ยังคงมีข้อ จำกัด ของจุดลอยตัว

แก้ไข: ฉันคิดว่าฉันจะทำการทดสอบเล็กน้อยเกี่ยวกับเรื่องนี้และทำงานได้ค่อนข้างดี ฉันเพิ่มการทดสอบที่ดีขึ้นในลูปด้วยเอาต์พุตที่จัดรูปแบบแล้ว


โอ้ชายคนนั้นเจ๋ง printf นั้นปลอดภัยกว่า sprintf มาก มันให้ฟอร์แมทสตริงฟรีซึ่งยอดเยี่ยม เคล็ดลับเด็ด ๆ ขอบคุณ (โหวตแล้ว)
Duncan C

หนึ่งคำถาม: ในคุณserial_putcharฟังก์ชั่นทำไมไม่ทำให้คำสั่งที่ส่งกลับมาreturn !Serial.write(c);? ไม่สะอาดกว่าผู้ประกอบการ trinary สำหรับการย้อนกลับความรู้สึกของค่าตอบแทนแบบบูล?
Duncan C

นั่นเป็นจุดที่ดีและฉันชอบมัน รหัสไม่ใช่ของฉันและฉันวางมันเมื่อฉันพบมัน
Madivad

ขอบคุณสำหรับserial_putcharฟังก์ชั่น มันทำงานได้ดี :-) คุณสามารถแก้ไขข้อ จำกัด จุดลอยตัวได้หรือไม่?
Greenonline

4

นี่อาจจะไม่ดีกว่าก็แตกต่างกัน คุณสามารถใช้วัตถุStringสำหรับการส่งออก วัตถุเหล่านี้อนุญาตให้มีการต่อข้อมูลและสนับสนุนการพิมพ์อัตโนมัติ

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
เห็นได้ชัดว่าเป็นสิ่งสำคัญที่จะต้องระมัดระวังการ จำกัด หน่วยความจำ การต่อข้อมูลจำนวนมากและการดำเนินการกับสตริงอื่น ๆ ในที่เดียวสามารถใช้พื้นที่จำนวนที่น่าประหลาดใจ
Peter Bloomfield

@ PeterR.Bloomfield จริงแน่นอน! นั่นเป็นเหตุผลว่าทำไมฉันบอกว่าตัวแปรนี้ไม่ดีกว่า;)
Klaus-Dieter Warzecha

4

ฉันมักจะใช้แท็บเพื่อทำให้ทุกอย่างเข้ากันได้ดีขึ้นในอนุกรม การมีสิ่งต่าง ๆ เข้ามาเหมือนฉันทำให้อาร์ดิโนยิงเร็วที่สุดเท่าที่จะเป็นไปได้ในขณะที่สามารถสังเกตการเปลี่ยนแปลงบางอย่างของตัวแปรได้

ลองสิ่งนี้:

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();

หรืออะไรทำนองนี้:

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

สุจริตฉันทำแบบเดียวกัน ("\ t" และ "\ n") และตามปกติหลีกเลี่ยงระฆังสตริงวัตถุและเสียงนกหวีด bloating รหัส
Klaus-Dieter Warzecha

1
@KlausWarzecha ฉันไม่ค่อยให้ชื่อตัวแปรเนื่องจากอยู่ในคอลัมน์ที่ดี นอกจากนี้ยังทำให้ง่ายขึ้นในการดูการพิมพ์แบบสุ่มที่ไม่ตรงกับไวยากรณ์นี้
Steven10172

4

ฉันใช้เพื่อการดีบักเท่านั้น:

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

String $ คืออะไร
Juraj

LMFTFM (ให้ฉันแก้ไขสำหรับฉัน)
linhartr22

2

ฉันเป็นมือใหม่ในโลก Arduino แต่เมื่อเร็ว ๆ นี้ฉันพบว่านี่เป็นเพียง C ++ ปกติ (โดยไม่มีข้อยกเว้นและอาจมีหลายรูปแบบ) แต่คุณยังสามารถเพลิดเพลินกับแม่แบบ ดังนั้นโซลูชันของฉันคือใช้เทมเพลตต่อไปนี้:

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);

สิ่งที่ดีที่นี่คือมันไม่ได้ใช้หน่วยความจำเพิ่มเติมและการประมวลผลเพิ่มเติมที่นี่


1

ฉันมักจะ (เจ็บปวด) ติดกับหลายบรรทัดแต่เมื่อมันกลายเป็นความซับซ้อนผมกลับไปSerial.print sprintfเป็นเรื่องน่ารำคาญที่คุณต้องมีบัฟเฟอร์สำหรับมัน

การใช้งานนั้นง่าย (??) เป็น

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);

คำเตือนแม้ว่าจะไม่สนับสนุนการลอยตัวตามค่าเริ่มต้น


1
sprintf เป็นสิ่งที่น่ารังเกียจ ไม่พิมพ์อย่างปลอดภัยง่ายต่อการบุกรุกบัฟเฟอร์ ฯลฯ เป็นเครื่องมือจากทศวรรษที่ 1960 ที่กล่าวว่าฉันใช้มันเช่นกัน แต่มันก็ไม่ได้เป็นเพราะใจอ่อน ....
Duncan C

เพื่อหลีกเลี่ยงการใช้งานมากเกินไปให้ใช้ snprintf ... BTW ส่วนใหญ่ของ moder IDEs (ไม่ใช่ Arduino IDE) จะตรวจสอบรูปแบบสตริงกับประเภทตัวแปรที่มีให้และจะส่งสัญญาณเตือน
next-hack

1

ใช้Streaming.hแทน

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

หนึ่งสามารถเขียน

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

ความหมายของ<<ในStreaming.hในผลแปลว่าเป็นชุดของสามัญSerial.print()สาย นั่นคือ<<น้ำตาลซินแทกติกถูกนำไปใช้โดยไม่เพิ่มขนาดโค้ด

ถ้าคุณไม่ได้Streaming.hติดตั้งได้รับStreaming5.zipจากarduiniana.org Unzip ~/sketchbook/librariesไว้ในไดเรกทอรีห้องสมุดของคุณตัวอย่างเช่นใน เพิ่มบรรทัด#include <Streaming.h>ภายในภาพร่างที่คุณใช้<<เป็นผู้ดำเนินการสตรีม

specifiers ฐานแปลง _HEX, _DEC, _OCT และ _BIN มีให้เช่นเดียวกับฟังก์ชั่น _FLOAT (มีจำนวนทศนิยม) endlและ ตัวอย่างเช่นหากต้องการพิมพ์ค่าละติจูดและลองจิจูดในรูปแบบเช่น "พิกัดของคุณคือ -23.123, 135.4567” สามารถเขียนได้:

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

สิ่งนี้สามารถเขียนเป็น

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

ซึ่งจะเก็บสตริงที่ยาวกว่าใน PROGMEM แทนที่จะนำมาไว้ใน RAM

หมายเหตุStreaming.h อย่าสร้างสตริงใด ๆ เช่น; มันเพิ่งส่งข้อความของ - <<อาร์กิวเมนต์ไปยังกระแส ระดับ PString ที่ arduinianaสามารถสร้างสตริงจากปัจจัยการผลิตกระแสถ้าสตริงแทนที่จะเอาท์พุทสตรีมเป็นที่ต้องการหรือจำเป็น


1

การใช้งานจะขึ้นอยู่กับชนิดข้อมูลของตัวแปรของคุณ

หากพวกเขาintจะเป็น%dหรือ%i ถ้าพวกเขาstringจะเป็น%s

กระดาษห่อสำหรับ printf

คุณสามารถเปลี่ยนขีด จำกัด ตามความต้องการของคุณ

#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
}

ที่มา: https://playground.arduino.cc/Main/Printf

ตัวอย่างการใช้งาน:

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

มันอยู่ในSerialชั้นเรียนของกรอบ ไม่จำเป็นต้องใช้ไลบรารีหรือฟังก์ชั่นเพิ่มเติม

// 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);

รายละเอียดเพิ่มเติมเกี่ยวกับเคล็ดลับการจัดรูปแบบในหน้าอ้างอิงรูปแบบ printf: http://www.cplusplus.com/reference/cstdio/printf/

\n เป็นลำดับ escape สำหรับการป้อนบรรทัด

Escape sequences ใช้เพื่อแทนอักขระพิเศษบางตัวภายในตัวอักษรสตริงและตัวอักษรตัวอักษร

แหล่งที่มา: http://en.cppreference.com/w/cpp/language/escape

[แก้ไข] - ตามที่ @Juraj พูดถึงมันไม่สามารถใช้ได้กับโมดูล AVR ส่วนใหญ่ ดังนั้นฉันจึงเพิ่ม ESP8266 การพูดถึงและ wrapper printf สำหรับโมดูล AVR ทั่วไป


นี่ไม่เป็นความจริง. ไม่มีคลาสอนุกรม printf น่าจะอยู่ในคลาสพิมพ์ แต่มันไม่ได้อยู่ในแพ็คเกจ AVR ที่ใช้มากที่สุด
Juraj

@Juraj คุณพูดถูกฉันได้ทดสอบเฉพาะกับ ESP8266 ที่มีมัน ( ลิงค์ ) และคิดว่ามันมาจาก arduino core จะอัปเดตคำตอบของฉันตามลำดับ
Remi

สำหรับฟังก์ชั่น p ฉันจะเพิ่ม downvote อีกหนึ่งอันถ้าเป็นไปได้
Juraj

นี่เป็นคำถามเก่าและฉันไม่สามารถตัดสินคำตอบเก่าได้เพราะฉันไม่รู้ว่ามีอะไรบ้างในปี 2014 แต่ตอนนี้มีห้องสมุดสำหรับห่อสตรีมพิมพ์ในสตรีมพิมพ์ด้วยการใช้งาน printf
Juraj


-1

จากhttp://playground.arduino.cc/Main/Printf ฉันสังเกตว่านี่ใช้งานได้ดีกับ mega2560 ของฉัน

นั่นคือทั้งหมดที่ใช้งานได้โดยไม่ต้องใช้ vsnprintf_P หรือ 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
ทำไมทุกคนต้องการทำเช่นนี้แทนที่จะใช้printf()ตัวเอง ?
Edgar Bonet

-3
int Money_amount = 55;

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

คุณจะเห็นใน terminal:

New amount: $55

1
คุณไม่สามารถต่อ int กับ c-string กับ+โอเปอเรเตอร์ได้
gre_gor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.