Adding printf to print class

My main goal is to add printf to print class so that Serial class will have printf. I want to use sprintf to help but I don't know how to define a parameter list that is not fixed in length. Any suggestions? Thanks.

The keyword you are looking for is varargs. Personally, I've never used it, and I'm not sure that C++ (due to overloading) supports it.

Thanks Paul. I'm reading on wiki about varargs. It seems difficult but I will give it a try.

Variadic functions in C, C++
To portably implement variadic functions in the C programming language, the standard stdarg.h header file should be used. The older varargs.h header has been deprecated in favor of stdarg.h. In C++, the header file cstdarg should be used.[1]

To create a variadic function, an ellipsis (...) must be placed at the end of a parameter list. Inside the body of the function, a variable of type va_list must be defined. Then the macros va_start(va_list, last fixed param), va_arg(va_list, cast type), va_end(va_list) can be used. For example:

#include <cstdarg.h>
 
double average(int count, ...)
{
    va_list ap;
    int j;
    double tot = 0;
    va_start(ap, count); //Requires the last fixed parameter (to get the address)
    for(j=0; j<count; j++)
        tot+=va_arg(ap, double); //Requires the type to cast to. Increments ap to the next argument.
    va_end(ap);
    return tot/count;
}

This will compute the average of an arbitrary number of arguments. Note that the function does not know the number of arguments or their types. The above function requires that the types be double, and the number of arguments is passed in the first argument. In some other cases, for example printf, the number and types of arguments are figured out from a format string. In both cases, this depends on the programmer to actually supply the correct information. If fewer arguments are passed in than the function believes, or the types of arguments are incorrect, this could cause it to read into invalid areas of memory and can lead to vulnerabilities like the format string attack.

Yes you can get "printf()" to work. I have posted several times about this.
What makes things very difficult is C++.
(small rant: C++ SUCKS! when it comes to formatting text output, nothing over the years works as well/easy as ...printf() functions)

For printf() to work you need varargs and C++ enforces too many things to support the use of varargs.

The key is getting C++ to drop back to C where you can get some useful stuff done.
The tricky part in this case is getting back to C++ because so many of the
arduino libraries like the serial library were written in C++ rather than C.

The latest glcd v3 library includes "printf" support.
There is an example of how to do this in the GLCDdiags example sketch and in the gText library module.

There are few ways to do this, but the main difficultly is returning from C back to C++
The key is you have to create a C++ aware function that uses C calling convention.
This is so that the low level vprintf() function can call back into your code. (It is C not C++)
Then your C++ function (which uses a C calling convention) can call back into your C++ class i/o code
like normal C++ code.

What makes it difficult is that some amount of context must be saved so that when vprintf() calls you
you can get back enough information to call your C++ class properly. And this is where the difficulty is.
While you can write a function that works with a given class, you cannot write a mapping function
that works with any class.

i.e. you can write a function that would work for serial but would not work for something else.
However, it still can get the job done.

Here is an example of how to add a printf() function that the GLCDdiags sketch uses.
(well it used to, but not anymore). printf() works just like you expect it to.

It uses varargs and some fancy gcc variadic macros to create a new "function" that not only adds printf() support
but automagically puts the formatting string in program memory so that precision RAM is not wasted.
(I see no reason to ever put a printf() formatting strings in RAM)
Also, by using the vprintf() functionality, you don't burn additional precious RAM by having to due do silly things
like allocate a static or temporary buffer for sprintf().
While this may make a C++ persons head hurt, there is no way to get this kind of functionality from C++.

/*
 * Define a REAL printf since Arduino doesn't have one
 *
 * SerialPrintf() will automatically put the format string in AVR program space
 * 
 * You can simple change/rename "SerialPrintf" in the macro below to be "printf" if you like.
 * instead of the using the additional macro for printf.
 * Using the printf() macro below allows easily changing were printf is directed in the case
 * you have multiple places. Either way works.
 */

#define printf(...) SerialPrintf(__VA_ARGS__)

#define SerialPrintf(fmt, ...) _SerialPrintf(PSTR(fmt), ##__VA_ARGS__)

extern "C" {
  int serialputc(char c, FILE *fp)
  { 
      if(c == '\n')
        Serial.write('\r'); 
    Serial.write(c); 
  }
}


void _SerialPrintf(const char *fmt, ...)
{
FILE stdiostr;
va_list ap;

  fdev_setup_stream(&stdiostr, serialputc, NULL, _FDEV_SETUP_WRITE);

  va_start(ap, fmt);
  vfprintf_P(&stdiostr, fmt, ap);
  va_end(ap);
}

SerialPrintf() is a wrapper macro whose purpose is to force the string constant to program memory.
This allows calling doing things like printf("hello"); and not having t mess around or think about all
the messy program memory stuff. "it just works" automagically for you.
So then _SerialPrintf() sets up the fdev structure to provide the glue to allow
vfprintf() to call our serialputc() function. serialputc() is declared to use a C calling
convention so that normal non C++ code can call it. But since the code is compiled as
C++ it is still C++ aware which is what allows the serialputc() to call the c++ serial class
functions. So once inside serialputc() we simply call our serial class write
function to slam out the character. There is some newline processing that must be done to
be compatible with normal printf() formating strings and the serial class string functions.

Now where the above breaks down is if you are having to deal with multiple instances.
The above code "knows" the instance/object: "Serial". But if you have several, it gets more
complicated. This is the case in the glcd library because the user can create multiple text areas
and you want to be able to "printf()" to the desired one.

To do this you have to save and recover the the "this" object.
Luckily the FILE structure has room for some user data.
I'll reproduce the what the glcd library gText class does to add multiple instance printf() support:

/*
 * Support for printf().
 * This code plays a few games with the AVR stdio routines.
 *
 * The Printf() functions fudge up a STDIO stream to point back to a C callable function
 * which recovers the C++ text area object (this) and then prints the character
 * using the C++ text area object.
 */

extern "C"
{
  int glcdputc(char c, FILE *fp)
  {
  gText *gtp;

	gtp = (gText *) fdev_get_udata(fp);
	gtp->write((uint8_t) c);
	return(0);
  }
}

/**
 * print formatted data
 *
 * @param format string that contains text or optional embedded format tags
 * @param ... Depending on the format string, the function may expect a sequence of additional arguments.
 *
 * Writes a sequence of data formatted as the @em format argument specifies.
 * After the @em format parameter, the function expects at least as many additional
 * arguments as specified in @em format.
 * The format string supports all standard @em printf() formating % tags.
 *
 * @note
 *	By default @em printf() has no floating support in AVR enviornments.
 *	In order to enable this, a linker option must be changed. Currenly,
 *	the Arduino IDE does not support modifying the linker options.
 *
 * @see Printf_P()
 */ 


void gText::Printf(const char *format, ...)
{
static FILE stdiostr;

	va_list ap;

	fdev_setup_stream(&stdiostr, glcdputc, NULL, _FDEV_SETUP_WRITE);
	fdev_set_udata(&stdiostr, this);

	va_start(ap, format);
	vfprintf(&stdiostr, format, ap);
	va_end(ap);
}

/**
 * print formatted data
 *
 * @param format string in AVR progmem that contains text or optional embedded format tags
 * @param ... Depending on the format string, the function may expect a sequence of additional arguments.
 *
 * See gText::Printf() for full details.
 * @see Printf()
 */ 


void gText::Printf_P(const char *format, ...)
{
static FILE stdiostr;

	va_list ap;

	fdev_setup_stream(&stdiostr, glcdputc, NULL, _FDEV_SETUP_WRITE);
	fdev_set_udata(&stdiostr, this);

	va_start(ap, format);
	vfprintf_P(&stdiostr, format, ap);
	va_end(ap);
}

Just like the first simpler example, the code sets up a FILE structure but then it also saves
away the C++ "this" pointer for later use by the glcdputc() function so that the proper instance
can be recovered.
The reason I said earlier that you can't create a generic mapping function can be seen in glcdputc()
In order for glcdputc() to be able to call the proper write() function using the "this" pointer,
it has to know the class for "this".

NOTE: There is a very unfortunate side effect in the second example. The strings are not automatically mapped to
program space and must be done externally. I have not figured out how to create a macro that can modify
a C++ class member calling functions arguments. If anyone knows how this can be done, I'd love
to hear about it.

What all this is really doing is providing printf() support to the serial class not adding printf() to the print class.

In fact if you are going to use printf() you want to avoid anything and everything in the print class.
printf() costs about 1.8k of code. While this sounds like a lot it will quickly be smaller than using
the print class functions if you have much formatted output. The key is not using any print class functions
so you don't end up with the overhead of both the printf() routines as well as the print class (which is somewhat
of a pig for what it does).
Also, the printf() functions by default do not support floating point. So if you want/need to output floating
point numbers you are SOL as the Arduino IDE will not allow you to alter the linker options (they hard coded that
stuff in the java code).
There was a post in one of the forums where somebody posted the updated java code to get the options from
a file rather than have them hard coded. It works and I have it running.
I think I even created an issue for this. But alas the Arduino powers that
be have not chosen to make this a priority to fix.

Hopefully, that gets you going.
For simple printf() support on the Serial class you can simply cut and paste the first example.

--- bill

It's been done:

http://www.utopiamechanicus.com/208/sprintf-arduino/

Based on code from here:

http://www.arduino.cc/playground/Main/Printf

void SerialPrint(char *format,...)
{
  char buff[128];
  va_list args;
  va_start (args,format);
  vsnprintf(buff,sizeof(buff),format,args);
  va_end (args);
  buff[sizeof(buff)/sizeof(buff[0])-1]='\0';
  Serial.print(buff);
}

There is a memory issue from bringing a large component like printf into your code, but the improved writing may be considerered worthwhile.

I would highly discourage any approach that uses "sprintf()" style buffer filling with
fixed buffers because there are better approaches that are just as easy to implement
that don't suffer from the additional RAM overhead.
See the lower examples on the Printf playground page rather than the quoted code example above.
The lower examples show how to use the stdio FILE structure linking (same as my examples)
rather than the fixed buffer brute force approach.

The other thing to keep in mind is the usage of RAM for the printf() formatting
strings. If there are very many formatting strings, RAM can easily be wiped out.

The first example I provided also solves that issue by wrapping the printf functions in
a macro that automatically causes the string to be moved program memory,
where none of the above examples dealt with that potential problem.

--- bill

BTW, do the examples on playground really work? (They don't work for me when using the IDE)
They use floating point printf formatting, which
requires the floating point versions of the printf library which requires changing the linker options, right?
How do you do that from the IDE? (last I looked the linker options were hard coded in the JAVA code).
Is there some capability in the IDE that I've overlooked?

Wow, this is pretty serious coding I'm not used to. Thanks Bill. I will definitely look into it. I wish there were a Serial.printf ready for use. That will be a blessing to old dogs with C background.

liudr:
Wow, this is pretty serious coding I'm not used to. Thanks Bill. I will definitely look into it. I wish there were a Serial.printf ready for use. That will be a blessing to old dogs with C background.

Hey I'm an "old dog" too... (starting doing embedded C 30 years ago)

You could add the printf() function but the easiest way to do that would require updating/modifying the core code Print class,
which wouldn't be that difficult but it would be a little bit more coding effort than using the above example that creates
a new global printf() "function". It would be code very similar to the code in my second example,
which is what the glcd library gText class does.

I'd do it to my own private arduino development tree but I haven't figured out how to patch
the argument list of a C++ class member function using the C preprocessor.
(the printf() macros I had used that mechanism to force the strings into program space)
Without that, you have to deal with all the program space stuff directly: PROGMEM, PSTR() etc...
by creating global strings that use PROGMEM attribute or something like PSTR() on the
constant strings on every single call to the function.

For now, I prefer the ease of use of a simple printf("string"); with no thought or effort of having to deal
with the progmem stuff.

The non floating point printf() is about 1.8k of code.
If code space is an issue, you could switch the bootloader to use the optiboot
bootloader, you will get back 1.5k.
And if you were go re-write the HardwareSerial begin() code to use a table lookup for the baud
rate vs the deceptively simple looking yet large inline baud rate calculations, you could free up about another
400-600 bytes of code space. This is because the compiler will do the calculation vs the AVR having to do the calculation
runtime which is quite expensive in terms of code space,
especially since most serial applications could be covered by a very small list of common standard baud rates.

--- bill

@liudr

Do you actually need printf functionality in the Serial (i.e. using in with more than once serial port)?

If not, it's much easier to just enable printf to use the default Serial port. It was the first thing I did when starting with the Arduino.

Later on when I discovered I was using a lot of RAM with all my debug I discovered vfprintf_P and converted all my debug to use program space strings. I wrapped all my debug messages with macros so the application code debug didn't have to worry about program space strings.

e.g. dprintf("Hello, world\n");

would create a program space string and ultimately call vfprintf_P.

Iain

sixeyes:
@liudr

Do you actually need printf functionality in the Serial (i.e. using in with more than once serial port)?

If not, it's much easier to just enable printf to use the default Serial port. It was the first thing I did when starting with the Arduino.

Later on when I discovered I was using a lot of RAM with all my debug I discovered vfprintf_P and converted all my debug to use program space strings. I wrapped all my debug messages with macros so the application code debug didn't have to worry about program space strings.

e.g. dprintf("Hello, world\n");

would create a program space string and ultimately call vfprintf_P.

Iain

I had assumed people were not talking about pulling in printf() functionality into Serial,
but that they were talking about adding it to Print which would add it to Serial by default.
It is not much code to add the xxxprintf() hooks
to Print. It would be very close to the code that I did in the glcd library. (example 2 above)

But I'm with you, I think being able to automagically stuff the printf format strings
in progmem is the way to go and that, as you said, requires a wrapper macro to insert
the progmem string declaration.
That is what my first example did. It created a printf() "function" (really a macro) that
did everything for you using vfprintf_P().

--- bill