Basic printf implementation

So i wrote a basic printf and i was wondering if I even needed to re-create the wheel, I have seen a few posts in the past but I found nothing of use, and what's more could my implementation be simplified further, i know i have excluded half the types such as %x and such but its a start and better than writing Serial.print 5 times for one sentence.

printf_s("Some int %d, and a float %f, and a unsigned long %l, and a char string '%s'\n", 9, 8.56, 1000UL, "some Test String");


// Don't use printf as the name it will not be called correctly
void printf_s(const char* format, ...) {
  int pos = 0; char c = format[pos++];
  va_list arguments;
  va_start (arguments, NULL);
  c = format[pos = 0];
  while (c != '\0') {
    if (c == '%') {
      const char type = format[++pos];
      if (type == 's')
        Serial.print((char*)va_arg(arguments, char*));
      else if (type == 'd')
        Serial.print((int)va_arg(arguments, int));
      else if (type == 'l')
        Serial.print((unsigned long)va_arg(arguments, unsigned long));
      else if (type == 'f')
        Serial.print((double)va_arg(arguments, double));
    }
    else
      Serial.print(c);
    c = format[++pos];
  }
  va_end (arguments);
}

Someone posted this recently, failed to note who

// programmer misses printf...

void xprintf(const char *format, ...)
{
  char buffer[256];  // or smaller or static &c.
  va_list args;
  va_start(args, format);
  vsprintf(buffer, format, args);
  va_end(args);
  Serial.print(buffer);
}

Works good. No fun writing your own, though.

a7

The printf format is a standard, and it will be hard to implement it.
I use this for the format: https://cplusplus.com/reference/cstdio/printf/

I think that printf() uses a putchar() or so. If that is redirected to the Serial port, then it works. There are libraries for that.
The ESP32 has a Serial.printf(), inclusive 'float' and 'double'.
The Raspberry Pi Pico on the other hand does not have a Serial.printf().

The basic Arduino boards have a sprintf(), so then it is this:

char buffer[40];
snprintf(buffer, sizeof(buffer), "Hello %d", 10);
Serial.write(buffer);

However, a %f is not supported.

Your function supports float for an Arduino Uno, that is nice :smiley:

Oh that's an interesting and simple approach minus the buffer but still worth inspecting, I need to run that and see what it produces. Tyvm.

IMO, the value of xxprintf() is the width formatting and padding and left/right adjustment.

If you look at all the 3rd party non Arduino.cc products, they all include a printf() method in their Print class.

This allows doing.

Serial.printf("this is an integer counter: %04d", counter);

So if using say ESP32, ESP8266, chipkit, Teensy, Intel, rp2040, STM32,.... etc. platform/cores you have a printf() method.

Arduino.cc absolutely refuses to add a printf() method to the Print class any of their platform/cores.

A while back a guy did an interesting formatting library called PrintEx.
It has not been maintained but perhaps worth a look if you are wanting to implement a fully featured formatting library that has a bit less overhead than a full xxprintf() implementation.

--- bill

Here is a simple way to add a printf() like function for the Arduino environment.
I call the function Pprintf() (for "Print Class printf") and it works just like fprintf() but you pass in the Print class instead of a file pointer.
This allows you to direct the output to the device of your choosing.
i.e.

Pprintf(Serial, .....);
Pprintf(lcd, .....);

etc...


void setup(void)
{
	Serial.begin(115200);
	Pprintf(Serial, "Pprintf...\n");
}
void loop(void)
{
	Pprintf(Serial, "Seconds up: %04ld\n", millis()/1000);
	delay(1000);
}

#ifndef PPRINTF_BUFSIZE
#define PPRINTF_BUFSIZE 64
#endif
size_t Pprintf(Print &outdev, const char *format, ...)
{
char buf[PPRINTF_BUFSIZE];
	va_list ap;
	va_start(ap, format);
	vsnprintf(buf, sizeof(buf), format, ap);
	va_end(ap);
	return(outdev.write(buf));
}

There's LibPrintf.

Why do you not simply use real printf???

In this thread I suggested replacing this:

snprintf(t_str, sizeof(t_str), "%.2f", t);
snprintf(h_str, sizeof(h_str), "%.2f", h);

with this (the float is a temperature and humidity, the spacing is for formatting under the comment):

/*float*/,  /*float.width*/, /*decimals*/, /*buffer*/);
dtostrf(    t    ,         5       ,      2      ,    t_str  );
dtostrf(    h    ,         5       ,      2      ,    h_str  );

to resolve snprintf() not supporting %f... but is dtostrf() bad code?

And... what code chunk isn't complete without a SIMULATION

This PR may be relevant to this conversation. It implements the stream insertion operator to provide an easier interface to the Stream.print function. For example,

int a {9};
int b {10};
Serial << "You have " << a << " out of " << b << " retries left.\r\n";
// Prints: "You have 9 out of 10 retries left."
Serial << format(1.2, 4) << ' ' << format(12, BIN) << "\r\n";
// Prints: "1.2000 1100"
1 Like

What benefit does this PR bring compared to the Streaming library?
https://github.com/janelia-arduino/Streaming
(Available in the Library manager.)

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.