Tips and tricks
When browsing C and C++ code on line you will often come across snippets of code that make use of the printf() function that can insert values from a list of variables into a string before sending the result to whatever is defined as stdout – which in many instances is some sort of window or terminal. Like me, you have probably felt deprived when working with Serial.print().
printf()
There is a way to wrap the functionality of printf() and use it to create strings to be sent to the Serial monitor. You should note that the full range of printf() options is not supported on AVR boards like the Uno. This saves some memory overhead by disabling support for floating point values on that range of Arduinos. Testing on a 32 bit Arduino Due indicates that everything seems to be fully supported there.
Here is a wrapper that uses printf() to format a string and then passes it to the Serial Monitor.
#include <stdarg.h>
#define PRETTY_BUFF 80
void setup() {
Serial.begin(115200);
pretty("This is the %dth test of %d \n", 5, 10);
}
int pretty(char *str, ...) {
char buff[PRETTY_BUFF];
va_list args;
va_start(args, str);
vsnprintf(buff, sizeof(buff), str, args);
int res = Serial.print(buff);
va_end(args);
return res;
}
If you have a program that needs to send lots of similar output with embedded values then this approach could be well worth the overhead. Certainly, it is more flexible than the << operator overload we have already used to enhance Serial output. This wrapper returns the int value returned by the Serial.print functions but this can be ignored (as shown). This example has also specified a buffer size of 80 bytes and that value represents a limit for the final string created by printf(). 80 bytes has a historical justification (80 columns on a punch card, 80 characters across a VT52 terminal) but can and should be adjusted to suite your program needs.
Strictly, the string passed to the printf() function is the format string as it includes one or more format specifier. In the example above there are two specifiers for an integer value (%d). The format string is followed by the two int values to be converted to strings and then inserted into the specified positions.
The printf() function has a host of format specifiers and modifiers and they can be used to great effect when formatting data to be presented to a user. A useful starter guide is included in these tables.
Format Specifier | Sample | |
d | Decimal representation of an integer (with sign if negative) | 27 |
u | Decimal representation of an unsigned integer | 32768 |
o | Octal | 27 |
x | Hexadecimal (lower case) | ff |
X | Hexadecimal (Upper case) | FF |
f | Decimal Floating Point (not supported on AVR – e.g. Uno) | 123.456 |
e | Scientific notation | 3.2589e-1 |
c | Character | 'h' |
s | string as char[] | "Hello" |
p | Pointer address | |
% | Acts as an escape character for % as char constant | %% |
Modifier | ||
+ | Forces a number to have a sign even when positive (%+d) | +67 |
' ' (space) | Forces a space in place of a sign for positive numbers (e.g. % d) | |
0 | Adds preceding zeros (see width) | 000765 |
(width) | A value representing the desired width of formatted number. May be preceding zeros or blanks | |
* | A placeholder for the width with the value supplied as an argument Not supported on AVR (e.g.Uno) | |
.number | Specifies the number of decimal places (not relevant on AVR) |
If you are wondering why floating point values are not supported on AVR based Arduinos then take some time and start writing a function to convert an arbitrary floating point value to a string. After a first attempt, think about the full range of potential values that might be held in a double type and just how they might be reasonably presented. This is very challenging stuff.
vsnprintf_P()
I spotted a function on the Arduino site that wrapped a version of vsnprintf_P() that can manage string constants accessed using the F macro. I present my version as a variant on the printf() wrapper above with all thanks and attribution to the anonymous author of the original.
#include <stdarg.h>
#define PRETTY_BUFF 80
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
prettyP(F("This string is %d chars long"), 28);
}
int prettyP(const __FlashStringHelper *str, ...) {
char buff[PRETTY_BUFF];
va_list args;
va_start(args, str);
vsnprintf_P(buff, sizeof(buff), (const char *)str, args);
va_end(args);
return Serial.print(buff);
}
The function vsnprintf_P() is not supported on the 32 bit Arduinos like the Due as they have a different memory configuration.