Print formating question

I have an RF frequency synthesizer project in which I enter a frequency from the keyboard to set the frequency and a small OLED display to show the entered frequency. Valid entrys in units of Hz are from 7000000 (7 MHz) to 223999999 (essentially 225 MHz).
The entered frequency is declared as:
unsigned long long ullFreq; // 64 bit uulFreq

Upon Reset, the OLED shows 'Enter Frequency' on the top line.
After entering the desired frequency the OLED shows (for example) '123456789 Hz'.

All this is working as it should. However, the tiny text on the OLED display is difficult to read with old eyes, so I decided to parse out from the ullFreq three longs, lngMhz, lngKhz, and lngHz and print them sepatated by commas. I made the string
strFreqString = String(lngMhz) + (",") + String(lngKhz) + (",") + String(lngHz) + (" Hz");
and printed it with
Serial.print(strFreqString); // print freq
Ok, this works and now the OLED shows
123,456,789 Hz as expected...all good so far...but if the entered frequency has zeros in certain positions, the display gets strange.
Examples: 123056789 shows 123,56,789 Hz
123050005 shows 123,50,5 Hz
(please note that the synthesizer is generating the proper frequencies, it is just the display that is messed-up).
It seems to me that I somehow must make the lngKhz and lngHz variables always print all three chars even tho some may be zero, ie., for example if lngKhz has the value 050, I want to print 050 and not 50.

I'd use sprintf (or snprintf, before the hens start clucking), using %03d (or similar).

sp. "224MHz"

See C static code analysis: "sprintf" should not be used for why snprintf is the preferred choice

I am surprised that it took so long

1 Like

From C static code analysis: "sprintf" should not be used

It is a very common and acceptable pattern to compute the required size of the buffer with a call to snprintf with the same arguments into an empty buffer (this will fail, but return the necessary size), then to call sprintf as the bound check is not needed anymore. Note that 1 needs to be added by the size reported by snprintf to account for the terminal null character.

Common and acceptable to who ?

People with time on their hands.

My SafeString library , available from the Arduino Library manager, has a print( ) method designed for LCD number formatting

size_t print (double d, int decs, int width, bool forceSign=false)
Prints a double (or long/int) to this SafeString padded with spaces (left or right) and limited to the specified width.

Unlike sprintf/snprintf in which you specify a minimum width, the in the SafeString print(..,width, ..) the width is the fixed width and the result will always be that wide.

Here is an example sketch

//https://forum.arduino.cc/t/print-formating-question/886888
// download SafeString V4.1.5+ library from the Arduino Library manager or from
// https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
#include "SafeString.h"

createSafeString(LCDline, 16); // or cSF(LCDline,16),  for a 16 char LCD display line

void setup() {
  Serial.begin(115200); //for debug
  for (int i = 10; i > 0; i--) {
    delay(500);
    Serial.print(i); Serial.print(' ');
  }
  Serial.println("started");
  SafeString::setOutput(Serial); // enable error msgs
}

unsigned long khz = 5;

void loop() {
  if (SafeString::errorDetected()) {
    Serial.println(F("Error in a SafeString operation"));
  }

  // clear previous formatting
  LCDline.clear(); // or LCDline = "";
  LCDline.print(khz, 0, 7); // format khz to take 7 chars width. 0 digits after the decimal point since input is int.
  LCDline.print("Khz"); // add units
  // print to LCD using char* LCDline.c_str()
  Serial.println(LCDline.c_str());

  delay(1000);
  khz = khz * 10; // try a larger number
}

Here is the sample output, with SafeString error msgs enabled

      5Khz
     50Khz
    500Khz
   5000Khz
  50000Khz
 500000Khz
5000000Khz
Error: LCDline.print() width:7 too small to display even just the integer part of 50000000.00
        LCDline cap:16 len:0 ''
       Khz
Error in a SafeString operation

The SafeString.print( ) has a number of advantages over sprintf/snprintf
SafeString.print( ) prevents buffer overflows. sprintf will crash your code if you get the buffer size wrong. SafeString will just raise an error flag and optionally print a helpful error msg.
snprintf prevents buffer overflows (if you use the correct arguments), but just truncates the answer leading to a misleading display.
SafeString raises and error flag and returns a completely blank field if the number of too large to display as in the

       Khz

above. This makes it very easy to see on the LCD that there is a problem.
If you have SafeString error messages disabled you can still check for errors using

  if (SafeString::errorDetected()) {
    Serial.println(F("Error in a SafeString operation"));
  }

snprintf lets you check for an overflow/truncation error, BUT you have to add the code to do the check everwhere you use snprintf which becomes tiresome.

As an alternative to the suggestions already made use if(value < 100) {print 0;}
print value;
Same thing with less than 10.
Code above is to illustrate the idea, not proper code, I hope it is good enough to explain the principal.

I note that you are new here, be aware that there's a long running debate on the forum about the wisdom of using Strings (capital S) Vs using character arrays, AKA C strings (lower case s). There doesn't seem to be a resolution to this debate and it pops up in questions vaguely related to strings or Strings. Don't let this debate distract you from what you are trying to do and the useful bits of the answers you get.

Now I'm back on my PC, not my phone, I mean like this:

void printStuff(long lngMhz, long lngKhz, long lngHz) {
  if (lngMhz < 10) {
    Serial.print (0);
  }
  if (lngMhz < 100) {
    Serial.print(0);
  }
  Serial.print (lngMhz);

  if (lngKhz < 10) {
    Serial.print(0);
  }
  if (lngKhz < 100) {
    Serial.print(0);
  }
  Serial.print(lngKhz);

  if (lngHz < 10) {
    Serial.print(0);
  }
  if (lngHz < 100) {
    Serial.print(0);
  }
  Serial.print(lngHz);
}

That compiles but probably contains some silly error. Play around with it to get it the way you want.

Not intended to be better than the other suggestions, just a different way to do it.

[Edited to remove at least one silly error, thanks @J-M-L ]

Probably needs < instead of > in

1 Like

@PerryBebbington
Before I posted my question I read a bunch of forum messages related to print formatting and came across the Strings vs C string debate and decided to avoid both. I'm a pretty good radio guy but a not-so-good programmer guy so I settle for 'simple' rather than 'elegant'.
I am using a variation of what you suggested:

// fix KHz
if (lngKhz < 10) { strKhz = ("00" + String(lngKhz)); }
else if (lngKhz < 100) { strKhz = ("0" + String(lngKhz)); }
else { strKhz = String(lngKhz); } // no zeros, ok as-is

// fix Hz
if (lngHz < 10) { strHz = ("00" + String(lngHz)); }
else if (lngHz < 100) { strHz = ("0" + String(lngHz)); }
else { strHz = String(lngHz); } // no zeros, ok as-is

then I make a String

strFreqString = String(lngMhz) + (",") + strKhz + (",") + strHz + (" Hz");

then I print it to both the Serial Monitor and to the OLED

Serial.print(strFreqString); // print freq in Serial Monitor
u8x8.print(strFreqString); // print freq on OLED

Works as it should so I'm happy and I want to thank all those who took the time to respond.

1 Like

If you are using Strings then check out my tutorial on Taming Arduino Strings

Then you went ahead and used Strings

1 Like

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