Skip to content

Instantly share code, notes, and snippets.

@ridencww
Last active August 25, 2024 19:34
Show Gist options
  • Save ridencww/4e5d10097fee0b0f7f6b to your computer and use it in GitHub Desktop.
Save ridencww/4e5d10097fee0b0f7f6b to your computer and use it in GitHub Desktop.
Basic printf functionality for the Arduino serial ports
/*
* Simple printf for writing to an Arduino serial port. Allows specifying Serial..Serial3.
*
* const HardwareSerial&, the serial port to use (Serial..Serial3)
* const char* fmt, the formatting string followed by the data to be formatted
*
* int d = 65;
* float f = 123.4567;
* char* str = "Hello";
* serial_printf(Serial, "<fmt>", d);
*
* Example:
* serial_printf(Serial, "Sensor %d is %o and reads %1f\n", d, d, f) will
* output "Sensor 65 is on and reads 123.5" to the serial port.
*
* Formatting strings <fmt>
* %B - binary (d = 0b1000001)
* %b - binary (d = 1000001)
* %c - character (s = H)
* %d/%i - integer (d = 65)\
* %f - float (f = 123.45)
* %3f - float (f = 123.346) three decimal places specified by %3.
* %o - boolean on/off (d = On)
* %s - char* string (s = Hello)
* %X - hexidecimal (d = 0x41)
* %x - hexidecimal (d = 41)
* %% - escaped percent ("%")
* Thanks goes to @alw1746 for his %.4f precision enhancement
*/
void serial_printf(HardwareSerial& serial, const char* fmt, ...) {
va_list argv;
va_start(argv, fmt);
for (int i = 0; fmt[i] != '\0'; i++) {
if (fmt[i] == '%') {
// Look for specification of number of decimal places
int places = 2;
if (fmt[i+1] == '.') i++; // alw1746: Allows %.4f precision like in stdio printf (%4f will still work).
if (fmt[i+1] >= '0' && fmt[i+1] <= '9') {
places = fmt[i+1] - '0';
i++;
}
switch (fmt[++i]) {
case 'B':
serial.print("0b"); // Fall through intended
case 'b':
serial.print(va_arg(argv, int), BIN);
break;
case 'c':
serial.print((char) va_arg(argv, int));
break;
case 'd':
case 'i':
serial.print(va_arg(argv, int), DEC);
break;
case 'f':
serial.print(va_arg(argv, double), places);
break;
case 'l':
serial.print(va_arg(argv, long), DEC);
break;
case 'o':
serial.print(va_arg(argv, int) == 0 ? "off" : "on");
break;
case 's':
serial.print(va_arg(argv, const char*));
break;
case 'X':
serial.print("0x"); // Fall through intended
case 'x':
serial.print(va_arg(argv, int), HEX);
break;
case '%':
serial.print(fmt[i]);
break;
default:
serial.print("?");
break;
}
} else {
serial.print(fmt[i]);
}
}
va_end(argv);
}
@rob4226
Copy link

rob4226 commented Jun 11, 2021

Thanks for this, works well!

@melvin2204
Copy link

melvin2204 commented Dec 27, 2021

For this to work on PlatformIO with an Arduino MKR1300, I had to make a few changes. Testing them in the Arduino IDE made it fail to compile, so I think these should not be added to the script, but I placed them here anyway for the few people using this in PlatformIO with the MKR1300.

  • First I had to include cstdarg.h for va_start to be defined: #include <cstdarg>.
  • For passing the serial interface to the function, I had to change HardwareSerial in the signature to Serial_. void serial_printf(Serial_& serial, const char* fmt, ...). (Probably because it uses SerialUSB)

Thanks for making this gist, I will be using it in my project and updating this comment if I find anything new.

@alw1746
Copy link

alw1746 commented Dec 28, 2021

I use templates to support various serial interfaces in my forked snippet below. Tested in Arduino IDE for Nano, ESP, STM32 mcus.

Serial_printf.h

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment