Macro definition and __VA_ARGS__

Hello everyone,

I have an Arduino Mega running a webServer with SD data log, until now the data log was “simultaneous” using three macros (below) to print to the serial monitor and to the sd card.

#define PRINT(...) if (!AJAX_REQUEST) {Serial.print(__VA_ARGS__); File logFile = SD.open(LOGS, FILE_WRITE); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); logFile.print(timeStamp);} logFile.print(__VA_ARGS__); logFile.close(); stamp = false;}
#define PRINTLN(...) if (!AJAX_REQUEST) {Serial.println(__VA_ARGS__); File logFile = SD.open(LOGS, FILE_WRITE); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); logFile.print(timeStamp);} logFile.println(__VA_ARGS__); logFile.close(); stamp = true;}
#define PRINTF(...) if (!AJAX_REQUEST) {Serial.printf(__VA_ARGS__); File logFile = SD.open(LOGS, FILE_WRITE); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); logFile.print(timeStamp);} logFile.printf(__VA_ARGS__); logFile.close(); stamp = true;}

Ok, so far so good. Everything works flawless, even the timestamp…
But then I started to feel some delay due to the “small” sd card writes, and mutiple file open and close since I can’t left the log file open because the webserver files are in the sd too.

So, I’m trying to change the macros to do a char array concatenation and in the main loop I check when the array is almost full to only then write it to the sd card (below).

#define PRINT(...) if (!AJAX_REQUEST) {Serial.print(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} strcat(backup, (const char *)__VA_ARGS__);} stamp = false;
#define PRINTLN(...) if (!AJAX_REQUEST) {Serial.println(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} strcat(backup, (const char *)__VA_ARGS__); strcat(backup, "\r\n");} stamp = true;
#define PRINTF(...) if (!AJAX_REQUEST) {Serial.printf(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} char printf_buf[LRG_BUF]; sprintf(printf_buf, (const char *)__VA_ARGS__); strcat(backup, printf_buf); strcat(backup, "\r\n");} stamp = true;

The PRINT and PRINTLN macros work’s perfect, but the PRINTF only print garbish, because I don’t know how to parse the VA_ARGS separatelly for the format and the variables.

I don’t fully understand this VA_ARGS thing, I acctually just got it from that serial debug sketches and adapt to my needs. I searched alot for something like this, but as my programming skills are quite basic, I can’t manage to understand this part that as I see is pure C++ language.

Here is some links I found to be usefull while trying to understand it, mostly I got from the printf() function acctually.

    void printf(const __FlashStringHelper *format, ...) {
      char buf[PRINTF_BUF];
      va_list ap;
      va_start(ap, format);
      vsnprintf_P(buf, sizeof(buf), (const char *)format, ap); // progmem for AVR
      for (char *p = &buf[0]; *p; p++) {
        write(*p);
      }
      write('\r\n');
      va_end(ap);
    }

va_start
va_arg
va_end

Any help are really appreciated.

@Arank

strcat(backup, (const char *)__VA_ARGS__);

This is the problem. The symbol you are trying to use is part of the C/C++ language pre-processing. The symbol is not a string though it will become string substitution in the pre-processing.

You can force a symbol to a language string with hash (#).

strcat(backup, (const char *) "" #__SYMBOL__);

If this will work with VA_ARG I dont know but give it a try.

strcat(backup, (const char *) "" # __VA_ARGS__);

Cheers!

Ref. You can find some example here https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/Trace.hh#L143

How do you call the PRINTF() macro? Do you use the F() macro to put your format string in PROGMEM? If so you will need call the version of sprintf() that expects the format to be in PROGMEM. I think that is sprintf_P().

kowalski: This is the problem. The symbol you are trying to use is part of the C/C++ language pre-processing. The symbol is not a string though it will become string substitution in the pre-processing.

@kowalski, thanks for your answer.

I tried what you suggested, but it seens to print the full statement as a literal text string, see an example above.

[13:31:08] F("webServer initializing...")
[13:31:08] F("memoryFree() = 10310 bytes."), freeMemory()

johnwasser: How do you call the PRINTF() macro? Do you use the F() macro to put your format string in PROGMEM? If so you will need call the version of sprintf() that expects the format to be in PROGMEM. I think that is sprintf_P().

@johnwasser, thanks for your answer.

Yes, I use the F() macro, mine printf function is being overloaded to accept both, see above from the Print.h file.

   void printf(const char *format, ...) {
      char buf[PRINTF_BUF];
      va_list ap;
      va_start(ap, format);
      vsnprintf(buf, sizeof(buf), format, ap);
      for (char *p = &buf[0]; *p; p++) {
        write(*p);
      }
      write('\r\n');
      va_end(ap);
    }
#ifdef F // check to see if F() macro is available
    void printf(const __FlashStringHelper *format, ...) {
      char buf[PRINTF_BUF];
      va_list ap;
      va_start(ap, format);
      vsnprintf_P(buf, sizeof(buf), (const char *)format, ap); // progmem for AVR
      for (char *p = &buf[0]; *p; p++) {
        write(*p);
      }
      write('\r\n');
      va_end(ap);
    }
#endif

Acctually, right now, all my calls to it are with the F() macro, but it works without too... Here is an example:

PRINTF(F("memoryFree() = %d bytes."), freeMemory());
PRINTF("memoryFree() = %d bytes.", freeMemory());

Arank:
@johnwasser, thanks for your answer.

Yes, I use the F() macro, mine printf function is being overloaded to accept both, see above from the Print.h file.

Yes, but what about the sprintf function you use in that macro? The standard library functions aren’t overloaded (the FlashStringHelper trick is part of the Arduino libraries).

johnwasser: Yes, but what about the sprintf function you use in that macro? The standard library functions aren't overloaded (the FlashStringHelper trick is part of the Arduino libraries).

Oh, you mean the sprintf(), not the printf(), sorry I read wrong.

Even with the sprintf_P() the compiler give error (the same one as with sprintf() without the (const char *)).

error: cannot convert 'const __FlashStringHelper*' to 'const char*' for argument '2' to 'int sprintf_P(char*, const char*, ...)'

And if I try the sprintf_P() with the (const char *) it print's garbish again...

Try this version:

#define PRINTF(format, ...) if (!AJAX_REQUEST) {Serial.printf(format, __VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} char printf_buf[LRG_BUF]; sprintf_P(printf_buf, (const char *)format, __VA_ARGS__); strcat(backup, printf_buf); strcat(backup, "\r\n");} stamp = true;

johnwasser: Try this version:

#define PRINTF(format, ...) if (!AJAX_REQUEST) {Serial.printf(format, __VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} char printf_buf[LRG_BUF]; sprintf_P(printf_buf, (const char *)format, __VA_ARGS__); strcat(backup, printf_buf); strcat(backup, "\r\n");} stamp = true;

Only print's garbish... :sob:

This works fine for me:

char backup[500];

#define PRINTF(format, ...) \
  {\
    char timeStamp[12]; \
    sprintf(timeStamp, "[%02d:%02d:%02d] ", 12, 00, millis()/1000);\
    strcat(backup, timeStamp);\
    char printf_buf[100]; \
    sprintf_P(printf_buf, (const char *)format, __VA_ARGS__);\
    strcat(backup, printf_buf);\
    strcat(backup, "\r\n");\
  }

void setup() {
  Serial.begin(9600);
  backup[0] = 0;
}

void loop() {
  PRINTF(F("String: %s, hex: 0x%02X "), "first test", 42);
  PRINTF(F("String: %s, hex: 0x%02X "), "second test", 53);
  PRINTF(F("String: %s, hex: 0x%02X "), "third test", 64);
  Serial.write(backup);
  backup[0] = 0;
  delay(2000);
}

This version also works for me:

#define PRINTF(...) \
  {\
    char timeStamp[12]; \
    sprintf(timeStamp, "[%02d:%02d:%02d] ", 12, 00, millis()/1000);\
    strcat(backup, timeStamp);\
    char printf_buf[100]; \
    sprintf_P(printf_buf, (const char *)__VA_ARGS__);\
    strcat(backup, printf_buf);\
    strcat(backup, "\r\n");\
  }

Are you, perhaps, NOT putting your format string in PROGMEM?

johnwasser: This version also works for me:

#define PRINTF(...) \
  {\
    char timeStamp[12]; \
    sprintf(timeStamp, "[%02d:%02d:%02d] ", 12, 00, millis()/1000);\
    strcat(backup, timeStamp);\
    char printf_buf[100]; \
    sprintf_P(printf_buf, (const char *)__VA_ARGS__);\
    strcat(backup, printf_buf);\
    strcat(backup, "\r\n");\
  }

Ok, I'm using this now and the PRINTF works perfectly.

I haven't figured it out before, but the PRINT and PRINTLN are printing garbish too. I already tried to use strcat_P, following your logic on the PRINTF, but it hangs and the SD Card stops working (the files don't open). I tried strcpy_P before the strcat, but it hangs too.

This work's.

char backup[500];

#define PRINTLN(...) strcat_P(backup, (const char *)__VA_ARGS__); strcat(backup, "\r\n"); Serial.print(backup); memset(backup, 0, sizeof(backup));

void setup() {
  Serial.begin(9600);
  Serial.println("iniciando");
}

void loop() {
  PRINTLN(F("String: 123"));
  delay(2000);
}

But when I change the strcat, to strcat_P (like this) on my skecth, the SD Card stops working.

#define PRINT(...) if (!AJAX_REQUEST) {Serial.print(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} strcat_P(backup, (const char *)__VA_ARGS__); stamp = false;}
#define PRINTLN(...) if (!AJAX_REQUEST) {Serial.println(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} strcat_P(backup, (const char *)__VA_ARGS__); strcat(backup, "\r\n"); stamp = true;}
#define PRINTF(...) if (!AJAX_REQUEST) {Serial.printf(__VA_ARGS__); if (stamp) {char timeStamp[12]; sprintf(timeStamp, "[%02d:%02d:%02d] ", hour(), minute(), second()); strcat(backup, timeStamp);} char print_buf[LRG_BUF]; sprintf_P(print_buf, (const char *)__VA_ARGS__); strcat(backup, print_buf); strcat(backup, "\r\n"); stamp = true;}


void datalog() {
  File logFile = SD.open(LOGS, FILE_WRITE);
  if (logFile) {
    logFile.print(backup);
    logFile.close();
  }
  else {
    PRINTF(F("Failed to open %s."), LOGS);
  }
  memset(backup, 0, sizeof(backup));
}

I don't have any ideia why... :fearful:

Ok, I solved it.

The problem was that I had one call to PRINT(variable), so all thing hangs... I change it to PRINTF(F("%s"), variable), and all worked fine.

Thanks very much johnwasser!! ;D

Ok, acctually I'm using this version of this.

#define PRINT(...) if (PRINT_REQUEST) {if (SERIAL_PRINT) {Serial.print(__VA_ARGS__);} if (SD_CARD) {if (PRINT_TIMESTAMP && timeStatus() == timeSet) {char timeStamp[12] = {}; sprintf_P(timeStamp, PSTR("[%02d/%02d][%02d:%02d:%02d]"), day(), month(), hour(), minute(), second()); strcat(BACKUP, timeStamp);} strcat_P(BACKUP, (const char *)__VA_ARGS__); PRINT_TIMESTAMP = false; dataLogger();}}
#define PRINTLN(...) if (PRINT_REQUEST) {if (SERIAL_PRINT) {Serial.println(__VA_ARGS__);} if (SD_CARD) {if (PRINT_TIMESTAMP && timeStatus() == timeSet) {char timeStamp[12] = {}; sprintf_P(timeStamp, PSTR("[%02d/%02d][%02d:%02d:%02d]"), day(), month(), hour(), minute(), second()); strcat(BACKUP, timeStamp);} strcat_P(BACKUP, (const char *)__VA_ARGS__); strcat_P(BACKUP, PSTR("\r\n")); PRINT_TIMESTAMP = true; dataLogger();}}
#define PRINTF(...) if (PRINT_REQUEST) {if (SERIAL_PRINT) {Serial.printf(__VA_ARGS__);} if (SD_CARD) {if (PRINT_TIMESTAMP && timeStatus() == timeSet) {char timeStamp[12] = {}; sprintf_P(timeStamp, PSTR("[%02d/%02d][%02d:%02d:%02d]"), day(), month(), hour(), minute(), second()); strcat(BACKUP, timeStamp);} char printf_buffer[LRG_BUF] = {}; sprintf_P(printf_buffer, (const char *)__VA_ARGS__); strcat(BACKUP, printf_buffer); PRINT_TIMESTAMP = false; dataLogger();}}
#define PRINTFN(...) if (PRINT_REQUEST) {if (SERIAL_PRINT) {Serial.printfn(__VA_ARGS__);} if (SD_CARD) {if (PRINT_TIMESTAMP && timeStatus() == timeSet) {char timeStamp[12] = {}; sprintf_P(timeStamp, PSTR("[%02d/%02d][%02d:%02d:%02d]"), day(), month(), hour(), minute(), second()); strcat(BACKUP, timeStamp);} char printf_buffer[LRG_BUF] = {}; sprintf_P(printf_buffer, (const char *)__VA_ARGS__); strcat(BACKUP, printf_buffer); strcat_P(BACKUP, PSTR("\r\n")); PRINT_TIMESTAMP = true; dataLogger();}}

I would like to know if it's possible to change it from a Macro definition to a normal function?! There is something like the VA_ARGS to be used in a function to substitute this macro?