如何在一个字符串中打印多个变量?


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

Answers:


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 gists上找到


3
不错的主意,尽管我觉得它可能更简单,所以我将此版本重写为一个版本而无需缓冲。任何有兴趣的人都可以查看要点:gist.github.com/EleotleCram/eb586037e2976a8d9884
eleotlecram 2014年

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语句return !Serial.write(c);?是不是比三元运算符更干净的用于反转布尔返回值的含义?
邓肯C

很好,我喜欢。该代码不是我的,发现时我将其粘贴了。
Madivad 2014年

感谢您的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
显然,注意内存限制很重要。在一个地方进行许多串联操作和其他字符串操作可能会使用惊人的空间。
彼得·布卢姆菲尔德

@ PeterR.Bloomfield绝对正确!这就是为什么我提到此变体并不更好的原因;)
Klaus-Dieter Warzecha 2014年

4

我通常使用Tab键来使序列中的内容排列得更好。像我一样安排一切,让arduino尽可能快地触发,同时能够注意到变量的某些变化。

尝试这样的事情:

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”),并且通常避免使用代码膨胀的String对象。
Klaus-Dieter Warzecha 2014年

1
@KlausWarzecha,我很少给出变量名,因为它们在漂亮的列中。还可以更轻松地查看与该语法不匹配的随机打印输出
Steven10172,2014年


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年代的工具。这就是说,我用它,但它不是为微弱的心脏......
邓肯ç

为了避免溢出,请使用snprintf ...顺便说一句,大多数现代IDE(不是Arduino IDE)都会根据所提供的变量类型检查字符串格式,并会发出警告。
下一次黑客攻击

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;

<<in 的定义Streaming.h实际上将其转换为一系列普通Serial.print()调用。也就是说,<<语法糖是在不增加代码大小的情况下实现的。

如果尚未Streaming.h安装,请Streaming5.ziparduiniana.org获得。将其解压缩到您的库目录中,例如~/sketchbook/libraries#include <Streaming.h><<用作流运算符的草图中添加直线。

提供了基本转换说明符_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 不会像这样构建任何字符串。它只是将其<<参数的文本传递到流中。如果需要或需要字符串而不是流输出,arduinianaPString类可以从流输入构建字符串。


1

用法将取决于变量的数据类型。

如果是int,则为%d%i 如果是string,则为%s

包装包装

您可以根据自己的要求更改限额

#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 是换行符的转义序列。

转义序列用于表示字符串文字和字符文字中的某些特殊字符。

来源:http//en.cppreference.com/w/cpp/language/escape

[编辑] -正如@Juraj所提到的,它在大多数AVR模块上不可用。所以我增加了ESP8266的提法和常见AVR模块的printf包装器


这不是真的。没有串行类。printf将在Print类中,但不是在最常用的AVR软件包中
Juraj

@Juraj,您是对的,我只在ESP8266上对其进行过测试(链接),并认为它来自arduino内核。将会相应地更新我的答案
Remi

对于p函数,如果可能的话,我会再添加一个下投票。
朱拉杰

这是一个旧问题,我无法判断旧答案,因为我不知道2014年有什么可用。但是现在有了一些库,可以通过printf实现将Print流包装在Print流中。
朱拉杰

0

一种可能的解决方案是:

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


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

您将在终端上看到:

New amount: $55

1
您不能使用+运算符将int连接到c字符串。
gre_gor
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.