Skip to content

Instantly share code, notes, and snippets.

@ridencww
Last active June 12, 2023 19:12
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • 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);
}
@alw1746
Copy link

alw1746 commented May 28, 2020

Thanks! Of all the printf workarounds and libraries I tried, this is the simplest and best and prints 32768.0 correctly. I made a small mod to permit precision like in stdio:

int places = 2;
if (fmt[i+1] == '.') i++;     //allows %.4f precision like in stdio printf (%4f will still work).
if (fmt[i+1] >= '0' && fmt[i+1] <= '9') {

@ridencww
Copy link
Author

ridencww commented May 28, 2020 via email

@hubercats
Copy link

Dear Sir - I'm brand new to the world of arduino programming, having written my first sketch yesterday. Having some experience with C, I immediately tried using the printf() function only to find that it was not available in the arduino IDE. I downloaded your zip file and would like to add it to the IDE but it appears to be a source file as opposed to a library. Is your code intended to be explicitly added to a given sketch, or can it be installed as a library somehow? Thanks! - Jim

@hubercats
Copy link

Dear Sir - I discovered a library from MIT that offers printf functionality so please ignore my previous comment. I don't want you to waste your time. - Thanks again - Jim

@alw1746
Copy link

alw1746 commented Jan 23, 2021

@hubercats - this is what I do:
-rename SerialPrintf.txt to Serial_printf.h
-copy to the sketchbook\libraries folder
-include in sketch:
#include "Serial_printf.h"
-replace all Serial.println( with Serial_printf(Serial,
Serial_printf(Serial,"Pot adc: %d\n",adc);

@hubercats
Copy link

hubercats commented Jan 24, 2021

@alw1746

Dear Alex,
Thanks very much for sharing your methodology.

Best regards,

Jim

@hubercats - this is what I do:
-rename SerialPrintf.txt to Serial_printf.h
-copy to the sketchbook\libraries folder
-include in sketch:
#include "Serial_printf.h"
-replace all Serial.println( with Serial_printf(Serial,
Serial_printf(Serial,"Pot adc: %d\n",adc);

@ridencww
Copy link
Author

The code was posted as a gist instead of creating a library because it consisted of a single method instead of a class or series of methods. In any case, Alex offered a good approach. I just cut-and-paste the gist into any program that I need printf, but I like his suggestion.

Here are the steps that I took to implement his suggestion:

  1. Create a folder called "Serial_printf" under the libraries folder in the Arduino sketchbook folder, "C:\Users\ridencww\Documents\Arduino\libraries" in my case.

  2. Create a new text file called "Serial_printf.h" in the new folder and copy the contents of the gist into this new file.

  3. Change the name of the method in the new file from "serial_printf" to "Serial_printf".

After doing that, all should work as Alex stated.

-include in sketch:
#include "Serial_printf.h"
-replace all Serial.println( with Serial_printf(Serial,
Serial_printf(Serial,"Pot adc: %d\n",adc);

@hubercats
Copy link

@ridencww : Thanks so much for the helpful tip. - Best - Jim

@Rudi56
Copy link

Rudi56 commented Feb 1, 2021

Great job, thank you so much for sharing your code!
Best, Rudi

@alegazzi
Copy link

alegazzi commented Feb 2, 2021

Thanks for sharing .. really fantastic your solution. But I would like to know if there is a possibility to modify to use the flash, just like Serial.print (F ("---"));
Thanks again.
Alegazzi

@alw1746
Copy link

alw1746 commented Feb 3, 2021

Standing on the shoulders of various giants, I've come up with a simple C++ template. Not sure if it's the best solution but it works with flash or normal string. A char buffer is used but deallocated upon return from helper function.

Serial_printf.h

@alegazzi
Copy link

alegazzi commented Feb 3, 2021 via email

@ridencww
Copy link
Author

ridencww commented Feb 3, 2021

If you have a lot of fairly sizable format strings, then using F() could save some memory. However when I've used the routine in my projects, the savings wouldn't have been worth the overhead. It could be more useful to support F() as a surrogate to char* in a varargs parameter. It all depends upon the number of string literals your program is using in these statements,

There is a good discussion of F() from 2012 in the Arduino forum that may be worth looking at: https://forum.arduino.cc/index.php?topic=91314.0

In any event, Alex did provide a path forward for you to explore and all the parts are there. Good coding.

@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