Implement prinf function to stm32 arduino ide

I have an stm32f103 custom board with a gps attached the scketch below is working ok but i don’t have printf function on this family mcu does someone has a trick to make printf function to work

//#include “stm32f103c8t6.h”
#include <Arduino.h>
//#include <stdio.h>
#include <Adafruit_GPS.h>
#include <HardwareSerial.h>
#include <stdio.h>
// what's the name of the hardware serial port?
#define GPSSerial Serial2

// Connect to the GPS on the hardware port
Adafruit_GPS GPS(&GPSSerial);

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO false

uint32_t timer = millis();

void setup() {
  //while (!Serial);  // uncomment to have the sketch wait until Serial is ready

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial1.begin(115200);
  Serial.println("Adafruit GPS library basic parsing test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(115200);
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  // Set the update rate
  //GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  //GPS.sendCommand(PGCMD_ANTENNA);

  delay(1000);

  // Ask for firmware version
  // GPSSerial.println(PMTK_Q_RELEASE);
}

void loop()  // run over and over again
{
  // read data from the GPS in the 'main loop'
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  //if (GPSECHO)
  //if (c) Serial.print(c);
  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data
    Serial.print(GPS.lastNMEA());    // this also sets the newNMEAreceived() flag to false
    if (!GPS.parse(GPS.lastNMEA()))  // this also sets the newNMEAreceived() flag to false
      return;                        // we can fail to parse a sentence in which case we should just wait for another
  }

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 1000) {
    timer = millis();  // reset the timer

    int H = (GPS.hour);
    int Mn = (GPS.minute);
    int S = (GPS.seconds);
    int nb = ((int)GPS.satellites);

    String T = (String(H) + ":" + String(Mn) + ":" + String(S));
    int Sp = (GPS.speed);
    int Cog = (GPS.angle);

    float Long = (GPS.longitude);
    float LongS = (Long - (Long * 60)) / -1000;
    int LongD = (GPS.longitude) / 100;
    int LongM = ((GPS.longitude) - (LongD * 100));
    char Long1 = (GPS.lon);

    int Lat2 = (GPS.latitude);
    float Lat = (GPS.latitude);
    float LatS = ((Lat2 - Lat) * -60);
    int LatD = (GPS.latitude) / 100;
    int LatM = ((GPS.latitude) - (LatD * 100));
    char Lat1 = (GPS.lat);

    //Serial1.println("%.2i-%i'%2.3f%c-%.2i-%i'%2.3f%c-%.2i-%.2i-%i-%.2i-%.2i-%.2i\\n",LatD,LatM,LatS,Lat1,LongD,LongM,LongS,Long1,Sp,nb,Cog,H,Mn,S);
    Serial1.printf("%.2i-%i'%2.3f%c-%.2i-%i'%2.3f%c-%.2i-%.2i-%i-%.2i-%.2i-%.2i\\n", LatD, LatM, LatS, Lat1, LongD, LongM, LongS, Long1, Sp, nb, Cog, H, Mn, S);

    String message = String(LatD) + "-" + String(LatM) + "-" + String(LatS) + "-" + String(Lat1) + "-" + String(LongD) + "-" + String(LongM) + "-" + String(LongS) + "-" + String(Long1) + "-" + String(Sp) + "-" + String(nb) + "-" + String(Cog) + "-" + String(H) + "-" + String(Mn) + "-" + String(S);
    Serial1.println(message);
  }
}

Have you considered using snprintf() as an alternative to printf() ?

i going to try

Hi @espmax. Which Arduino boards platform did you install via the Arduino IDE Boards Manager to add support for the STM32F103?

I ask because Print::printf is implemented in the popular 3rd party "STM32 MCU based boards" platform (AKA "STM32duino"):

However, it should be noted that this is a divergence from the official Arduino core API, so the use of Print::printf (e.g., Serial1.printf) in your code makes it non-portable to platforms with a core implementation that is faithful to the standardized API. The suggestion @UKHeliBob made is superior in that respect.

For nearly 17 years (the time since I first got involved with Arduino and started asking for better printf() support) many people have been asking for a printf() support or a printf() method to be added to the Print class and Arduino.cc has continuously refused to add it in, even when people from the community, including myself have offered the code. (which is pretty minimal)

At this point in time pretty much every 3rd party core has added a printf() method to their Print class so the Arduino.cc core platforms are essentially the odd man out.
And so from a API perspective, using a Print clasds printf() method offers pretty robust portability, as long you are not using or depending on a core from Arduino.cc

i.e. the Arduino Print class API has effectively been extended to include a printf() method, but the Arduino.cc platform is currently non conformant with the communities version of the Print class API

Why does Arduino.cc still refuse to add a printf() method to their Print class?

Considering your 17 years of asking for it, surely you already know the answer to that question???

But for others who aren't already aware, it is because the founders believe that the printf syntax is not friendly to beginners.

@espmax

See what I mean.
Yep, I’ve heard all the excuses for not implementing it.
The most comical one from Tom Igoe:
“Printf syntax is too scary for Arduino users”,

yet the most common computer output formatting method in programming is the printf-style family of functions. Documentation for it is literally everywhere.
Its seems pretty clear that Arduino.cc will never implement a printf() method in their Print class due to pressure from founders, while the rest of the Arduino community sees no issues with it and has moved on to include it since that is what Arduino users want. Again, at this point, it is the Arduino.cc Print class API that is no longer conformant with the Arduino user community’s choice of Print Class API.

@espmax if you want a function that works like the printf() function (not a printf() function/method in the Print class) that is also portable across all platforms, including those from Arduino.cc, I have a solution for you. It is simple, works really well and as fully portable across all platforms.

Here is what I have done and use for printf() functionality when I need portability..

I created a function called Pprintf()
Which is from ”Printclass Printf” that is truly portable.
It works like fprintf() but instead of passing in a FILE pointer you pass in a Print class object so it can work on all platforms, even the Arduino.cc ones that do not implement a printf() method in their Print class.

It is very simple, and just requires including a small bit of code at the top of your sketch. After than, you can call Pprintf() and have a printf() capabilities using and Printclass object.

See code below , which includes and example sketch using it.

–bill

// PrintClass printf(), AKA Pprintf() works similar to the fprintf() function.
// The differnce is that instead of the first argument being a FILE * pointer
// the first argument is a Print class object.
//
// Since Pprintf(PrintClassobj, const char *format, ...) takes a Print class
// object,  the sketch can direct the printf formatted output to any
// Print class device output.
// i.e the print class object can be Serial, lcd, etc...
// As long as the device supports Print class printing you can use Pprintf()
// to print to it.
//

// define output buffer size (maximum sized output string that can be created)
#define PPRINTF_BUFSIZE 64

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));
}
// this version of the a function is an overload to support uses of the F()
// macro for FLASH based format strings to save RAM for the AVR enviroment.
size_t Pprintf(Print &outdev, const __FlashStringHelper *format, ...)
{
char buf[PPRINTF_BUFSIZE];
    va_list ap;
    va_start(ap, format);
    vsnprintf_P(buf, sizeof(buf), (const char *) format, ap);
    va_end(ap);
    return(outdev.write(buf));
}



// sample code to demostrate use of a printf() like function called Pprintf()
// It uses the Serial object for demonstration.
// To add Pprintf() to your sketch include all the code above this point
// in your sketch.
//------------------------------------------------------------------------

void setup(void)
{
    Serial.begin(9600);

    // nothing has to be initailised. Just call Pprintf()
    Pprintf(Serial, "Pprintf Demo...\n");
}
void loop(void)
{
    Pprintf(Serial, "Seconds up: %04ld\n", millis()/1000);

    // Pprintf() also supports the F() macro for format strings
    Pprintf(Serial, F("Seconds up: %04ld\n"), millis()/1000);
    delay(1000);
}

Then I am surprised that equally unfriendly functions such as snprintf() are supported.

That comes from the standard library. It is not something implemented in the Arduino core API.

Why is a standard "unfriendly" function OK but an equally "unfriendly" core API function not allowed ?

Just because a function might confuse a beginner does not mean that it should not be available to more advanced users

I wasn't aware and was expecting some intricate technical explanation but that genuinely dumbfounded me!

I don't disagree that the syntax can be somewhat intimidating to the beginner at first - it was to me, but can be learned and adapted to. Once understood, is often easier to deploy than using multiple Serial.print/ln statements. Furthermore, adding a printf class would not take away from from programmers wanting to use the existing beginner-friendly print and println, but would add that extra flexibility that more advanced programmer could make use of. Seems to me to be a win:win situation?

An alternative approach is to create a buffer and then use sprintf to create the character string to be output in the buffer, and finally print the buffer content with Serial.println(), an approach I have used a number of times. Although that requires only 3 statements, if it were made possible, using a Serial.printf() one-liner would be that much simpler.

Courtesy of @bperrybap, we do also now have these alternative functions so thank you for posting them. I just tested the first one in Wokwi and it works as expected. Have made a note of them for future use.

It's not a matter of "OK" or "not OK" it just is. Your question is equivalent to asking "if goto is bad, why does Arduino have it?".

It comes from the fact that the "Arduino language" is based on (or just "is" if you prefer) C++.

Printf() is present in arduino, its just not “connected” to anything.

Actually printf() not Printf() but yeah.
For the platforms where printf() exists, it can usually be hooked up to output to a Print class object.
The issue is it is not obvious how to do, is pretty ugly, and is different for each platform.
If you go back and look at the old/original Arduino playground (if you can still find it), I published how to add a printf() method to the Print class, and how to hook up printf() for the AVR (which was the only platform way back then).

I have gotten printf() working on several platforms including AVR and Chipkit, but honestly,

This ugliness with libC printf() and the petty hate of any printf() support by arduino.cc is what drove me to create Pprintf().

IMO, the Pprintf() solution I proposed is a simpler/better solution than getting the libC printf() function to output to a device.

Not to pick on the OP, but consider the originally attempted specifier

Why would you encourage anyone, beginner or not, to do this, along with its fourteen arguments? And looking closer, %2.3f doesn't really work because the total width 2 is shorter than the precision after (and in addition to) the decimal place, 3. To get a leading zero, it should be %06.3f

But for the integers, which don't have a fractional portion, %.2i works because the precision is applied to the whole number. And it does leading zeroes. %02i has the same effect; two different ways to achieve the same thing.

Perhaps Arduino is hoping for more implementations of Printable, something specifically for Print::print and Print::println.

In this case, the latitude and longitude appear to not be decimal degrees, but rather a float in the format DDDMM.MMMM. If I understand correctly, the max longitude is right after 17959.9999 -- which BTW you cannot specify with a 32-bit float. The closest you can get is 17959.9980; off by 20 thousandths of a minute, or about 37 meters in the middle of the Pacific Ocean. The resolution of this number scheme is much better in, say, Ghana.

Anyway, if you're going to provide a particular encoding like that, a companion class that could decompose it and print it nicely would help.

struct DMS : public Printable {
  uint16_t degrees;
  uint8_t minutes;
  uint8_t seconds;
  uint16_t millis;
  bool negative;
  char nsew;
  DMS(double dddmm_mmmm, char nsew) : nsew(nsew) {
    // Don't need both +/- and (N/S or E/W), but negative
    // complicates the math; so always remove it
    negative = dddmm_mmmm < 0;  // e.g. -17959.9980
    if (negative) {
      dddmm_mmmm *= -1;  // 17959.9980
    }
    unsigned trunc = dddmm_mmmm;  // 17959
    trunc -= trunc % 100;         // 17900
    degrees = trunc / 100;        // 179
    dddmm_mmmm -= trunc;          // 59.9980
    minutes = dddmm_mmmm;         // 59
    dddmm_mmmm -= minutes;        // 0.9980
    dddmm_mmmm *= 60;             // 59.88
    seconds = dddmm_mmmm;         // 59
    dddmm_mmmm -= seconds;        // 0.88-ish
    millis = dddmm_mmmm * 1000;   // truncate; don't round up to 1000 millis
    if (minutes >= 60) {
      ++degrees;
      minutes -= 60;
    }
  }
  size_t printTo(Print &p) const override {
    size_t written = 0;
    if (negative) {
      written += p.print('-');
    }
    written += printTo(p, degrees);
    written += p.print("°");  // substitute if DEGREE SIGN not available on output device
    written += printTo(p, minutes);
    written += p.print('\'');
    written += printTo(p, seconds);
    written += p.print('.');
    written += printTo(p, millis, 3);
    written += p.print('"');
    written += p.print(nsew);
    return written;
  }
protected:
  virtual size_t printTo(Print &p, uint16_t v, unsigned width = 2) const {
    static const char z[] = "0000";
    size_t written = 0;
    if (width > sizeof(z)) {
      width = sizeof(z);
    }
    int pad = width;  // could go negative
    if (v < 10) {
      pad -= 1;
    } else if (v < 100) {
      pad -= 2;
    } else if (v < 1000) {
      pad -= 3;
    } else if (v < 10000) {
      pad -= 4;
    } else {
      pad -= 5;
    }
    if (pad > 0) {
      written += p.print(z + sizeof(z) - 1 - pad);
    }
    written += p.print(v);
    return written;
  }
};

void setup() {
  Serial.begin(115200);
  delay(750);
}

void loop() {
  static bool pause;
  static unsigned m;
  if (Serial.available()) {
    Serial.readStringUntil('\n');
    pause ^= true;
  }
  if (pause) {
    return;
  }
  double better = 17960 - 0.0001 * ++m;
  float worse = better;
  Serial.print(better, 5);
  Serial.print('\t');
  Serial.print(worse, 5);
  Serial.print('\t');
  Serial.println(DMS(worse, 'W'));
}

which prints like

17959.99710	17959.99805	179°59'59.882"W
17959.99700	17959.99609	179°59'59.765"W

i often pair the use of sprintf () and Serial.println (s);

Same goes for me... It's a simple "workaround" to avoid any other "extension".

using sprintf() is dangerous. At least use snprintf() to avoid any potential buffer overflow.

–bill

That's minimum width, not total width. %2.3f works just fine.

Not to get a consistent two digits (with leading zero) to the left of the decimal point; e.g. forty-five minutes and how many seconds?

45'9.999"   // 2.3f
45' 9.999"  // 6.3f
45'09.999"  // 06.3f
45'10.000"  // same for all three specifiers