Is there a better way to format text data for display?

Not sure what you are trying to say here.
printf() was originally a unix C library function going back to around 1970 that prints formatted text based on a formatting string.
The string constant issue on Harvard architecture processors like the AVR is totally unrelated.
On Harvard processors like the AVR, a string constant that is linked into the final image stored in flash must be copied to SRAM to access directly by the processor. I.e. if the processor wants to access data through a pointer, the data must be in the data area (RAM). If constant data isn't moved from flash to RAM, then helper functions must be used to access the data directly from flash which is in a different address space than data.
i.e. you can't use a C/C++ pointer to directly access data stored in FLASH since FLASH is a different address space than RAM on a Harvard Architecture processor.
You commented on some of this FLASH stuff back in post #43.

Yes that is the answer. I said that a printf() method is in the Print class of the 3rd party cores. And it got there by those 3rd party developers taking the open source from the Print class in the official Arduino core code and then modifying it to add the printf() method to their version of the Print class.
The core that the 3rd party developers ship in their board support packages included a modified core than includes a printf() method in the Print class.
It is a very trivial thing to do so it isn't a developer resource issue.
It is a preference/policy thing at Arduino.cc to not include it even thought it has been requested for many years.

The Print class is included in each Arduino core library.
Arduino.cc provides their own cores for their boards and the 3rd party board developers like ESP, Chipkit, etc... provide their own core.
Since it is open source you could go in and modify the Arduino.cc core code Print class to add a printf() method.
In fact the Arduino playground page that I wrote and provided a link to explained how to do that.

If the amount of string data is small I wouldn't worry about it or use the AVR specific F() macro stuff.
And then for formatted output just use sprintf() and then print the buffer or write a wrapper around the xxprintf() functions like the Pprintf() one I provided.

I don't like mucking up my code with non standard AVR specific stuff unless its necessary.

--- bill

1 Like

Total agreement here from way back in the day.

AVR LibC stdio.h gives us printf() and printf_P() where the latter gets the format string from a pointer into flash while the former points into RAM. Too bad we don't have printf_E() that points into EEPROM!

@hcb007 ... for Arduino you don't need to know C++ completely, really only minimally, but you should pick up plain old C first and most. Hey, it is a much thinner book and shares a lot with Basic.

One C subject deemed non-beginner here is pointers. A pointer is a data address coupled with a data type (type for the compiler, only the address is kept in data but always compiled to get the variable type 'right'). Pointers are powerful to say the least, those who use them successfully are Not Beginners. Same goes for users of bits and bit-logic. Yeah, get another pin since you don't have time for that just now.
Be aware that pointers to RAM, flash, or EEPROM matter and that the F() macro and certain functions get around some of that. So maybe two pins.

It is in the AVR LibC stdio library which is used by Arduino.
As far as I can tell, it wasn't so before COVID.

But in this thread on post #7, @gcjr says that this code compiles and works.

and I don't see cryptic handstanding being required though he does use int main() so I may have missed something.
OTOH AVR LibC and Arduino are still WIP.

#include <stdint.h>

#define BTN1_PIN 40
#define BTN2_PIN 38

struct Button {
    uint8_t             pin;
    bool                lastState;
    unsigned long       lastDebounceTime = 0;
    const unsigned long debounceDelay = 50;
};


Button btn1 = { BTN1_PIN };
Button btn2 = { BTN2_PIN, 1, 50 };

bool checkButton(Button &btn) {
    return btn.lastDebounceTime == btn.debounceDelay;
}

#include <stdio.h>

int
main ()
{
    printf (" %s\n", checkButton (btn1) ? "true" : "false");
    printf (" %s\n", checkButton (btn2) ? "true" : "false");
    return 0;
}

The C printf() function has existed in AVR libC for ages. At least going back to 2008 when I first looked at it. It is not a new thing.

You seem to be confusing the ARV libC stdio C function printf() with a C++ printf() function/method in the C++ Print class.

A C++ Print class printf() function/method, should it have one, and the AVR libC stdio C function printf() are two very different things.
They are even called differentlly.
Think printf() vs Serial.printf()
The quote from me above is about adding a printf() function/method to the Arduino C++ Print class to extend the Arduino Print class to include a printf() frunction within the Arduino Print class so that something like Serial.printf() exists/works.
The Arduino Print class is a C++ class that is a proprietary thing invented by Arduino. The Print class is the Arduino equivalent to stdio.
The Print class has virtul function linkage to output and read data to device classes and is implemented by Print.h and Print.cpp in the core library that each vendor provides as part of their board support package.
The Arduino.cc version of the Arduino C++ Print class code does not contain a printf() function but the 3rd party vendors have added a printf() to their Print class that comes in their core libraries for their board support packages.

Adding a printf() function to the Arduino C++ Print class involves creating a C++ wrapper function/method in the Print class around the C functions sprintf() or snprintf() functions and does not use or involve the stdio printf() C function at all.

The reason to add a printf() function/method to the Print class is that it is adds xxprintf() formatting capabilities to all device libraries that already have Print class support. It is easy for board support core developers to implement and easy to use for users as it simply extents the existing Print class functionality and does the output the "Arduino" way without having to do any sort of additional stdio C library linkage setup.
i.e. when you add a printf() method to the Print class things like Serial.printf() can be used.
Without it, it would fail to compile.

I didn't see anything that stated whether that code actually worked.
i.e. does it actually output any characters?
It seemed to only say that it compiled.
My suspicion is it doesn't actually work since there is no other code in that example that initializes any h/w to do the actual output. And how would the printf() stdio linkage code under the hood know where or how to send the characters?

I am not aware of whether Arduino has recently done something to automatically setup the AVR libC stdio printf() function to work with the Arduino Print class of a default object like HardwareSerial Serial at some default baud rate.
I would have to do some digging and testing to confirm this.
My suspicion is that Arduino hasn't done anything and that using printf() doesn't work "out of the box" - at least on AVR platforms.

Keep in mind that the printf() in AVR libC has existed for ages, and in my past testing, an Arduino sketch compiled on an AVR based platform that used printf() would compile and link just fine but there would not be any output unless the sketch also created a putc() function and used the AVR libC functions to set up the stdio linkage to use it.
i.e. the default AVR libC stdio STDOUT output is to silently drop all the characters since there is no default linkage to tell it what function to call to output the characters.

I'll do some testing later today to verify if printf() now works out of the box, including that example code linked to above.
My suspicison at this point is that any code that uses printf() in a Arduino sketch on an AVR platform but doesn't set up the AVR libC stdio linkage will compile and link ok, but silently outputs nothing.

--- bill

1 Like

That code doesn't work in the Arduino IDE because the version of c++ in the IDE doesn't let you both set defaults for the struct and also use brace initializers. I get:

/private/var/folders/7g/2b2t5vv15cg3yrp46fm2yv7h0000gt/T/.arduinoIDE-unsaved2025422-26233-jmjjpr.nmsy/sketch_may22a/sketch_may22a.ino:14:26: error: could not convert '{40}' from '<brace-enclosed initializer list>' to 'Button'
 Button btn1 = { BTN1_PIN };
                          ^
/private/var/folders/7g/2b2t5vv15cg3yrp46fm2yv7h0000gt/T/.arduinoIDE-unsaved2025422-26233-jmjjpr.nmsy/sketch_may22a/sketch_may22a.ino:15:33: error: could not convert '{38, 1, 50}' from '<brace-enclosed initializer list>' to 'Button'
 Button btn2 = { BTN2_PIN, 1, 50 };
                                 ^
exit status 1

Compilation error: could not convert '{40}' from '<brace-enclosed initializer list>' to 'Button'

OTOH it could be that I don't give a shift about the C++ side of it.

OK, which is fine, to each is own, but the code you provided in post #63 doesn't work, so I still believe you have some misconceptions about using printf() and printf_P() from Arduino sketches vs adding a printf() method inside the Arduino Print class or creating a C++ wrapper function like my Pprintf() function.

Arduino is a C++ development environment so getting the AVR libC printf() and printf_P() functions to actually work is non trivial.
IMO, getting it to work is considerably beyond the typical Arduino user.
It involves not only to do some magic foo to interface the C++ sketch code to the AVR libC C functions but some magic foo to create and setup the linkage necessary to allow the stdout capability of that AVR libC functions like printf() and printf_P() to send their output to the Arduino Print class write() function of the desired Print class object.

i.e. you can't just call printf()
You have to do some magic stuff in your sketch to get it to work.
And then even after you get stdout to work, to use printf_P() you have to use PSTR() instead of F() for the format string becuse F() is a C++ construct and printf_P() is a C function. AVR libC provides a PSTR() macro to use which is similar to F() in the Arduino world.

Whereas when a printf() method exists in the Print class, things "just work".
No magic stuff. You can just call the function like Serial.printf() and it works.
Even for format strings that use F()
Same is true for my Pprintf() function since it is a C++ wrapper function that wraps around the Arduino C++ Print class.


For all those still following along and are interested in using "printf" like formatted output, here are some additional comments and examples on a couple of options.

  • using the AVR libC printf() and printf_P() functions
  • using my Pprintf() function

To use printf() you need some real magic to create the linkage.
This linkage is very core specific, the magic you do for the AVR core will be different for other cores. The example provided shows how to do this for the AVR core library which uses the AVR libC library.
For printf_P() which is a AVR proprietary call to use strings stored in flash you have to use the PSTR macro instead of the F() macro.
All of this means that using printf() and printf_P() are non portable.

To use Pprintf() you also need a bit of magic, but the magic is portable and works on any Arduino core. And using F() for format strings "just works".

Look over the examples and you can see the differences.
IMO, having a printf() method in the Print class is the ideal solution but so far Arduino.cc has refused to put it into their cores for more than 15 years.
You could edit the Print class files yourself to add it
or if you want a portable solution, you could use my Pprintf() function and just cut/paste a small bit of code into your sketch.

I have included some examples that I wrote to demonstrate each way of doing xxprintf() output.

Example of using printf() and printf_P() on AVR platform

// sample code to demostrate use of printf() and printf_P() 
// on Arduino AVR platforms.
// It uses the Serial object for demonstration.


// magic foo for the printf() linkage to get the stdout output 
// from printf() and send it to the Arduino Print class object.

#include <stdio.h>
extern "C"
{
static FILE mystdout; // declare the FILE structure to use for stdout
  int _putchar(char c, FILE *fp)
  {
  Print *pcp;

	pcp = (Print *) fdev_get_udata(fp); // recover the Print class object
	pcp->write((uint8_t) c);
	return(0);
  }
}

// function to initilize all the data structures for stdout
// to use _putchar() for the output
void initSTDOUT(Print & outdev)
{
	// initalize the AVR libC stdio STDOUT linkage to link printf()
	// output to an Arduino Print class object output by linking it to
	// the _putchar() function above and storing a pointer to 	
	// the desired Print class object.	

	// fill in the FILE structure used for stdout to point to 
	// to the _putchar() function for charactre output.
	fdev_setup_stream(&mystdout, _putchar, NULL, _FDEV_SETUP_WRITE);

	// save a pointer to the Print class so _putchar can recover it
	// to know where to send the output
	fdev_set_udata(&mystdout, &outdev);
	stdout = &mystdout;

	// at this point printf() is now linked to _putchar() which will
	// send output to the Print class object passed in.
}



// To add printf() and printf_P() support to your sketch include all the
// code above this point to your sketch.
// Add a call to initSTDOUT() in setup()
// to set up the linkage to your desired Print class object to use
// for the printf() and printf_P() output.
//------------------------------------------------------------------------

void setup(void)
{
	// Initialize the Serial device
	Serial.begin(9600);
	
 	// initalize the printf() linkage to use Serial object for output
	// this also initializes things for printf_P()
	initSTDOUT(Serial);

	printf("printf Demo...\n");
}
void loop(void)
{
	printf("  printf: Seconds up: %04ld\n", millis()/1000);

	// to use printf_P() to directly use string constants stored in 
	// FLASH without the RAM copy, you must use the PSTR() macro
	// as the proprietary Arduino AVR F() which is supported by the
	// the Arduino environment is a C++ construct that will
	// not work with printf_P() as printf_P() is C code.

	printf_P(PSTR("printf_P: Seconds up: %04ld\n"), millis()/1000);
	delay(1000);
}

Example for Pprintf() that works on any platform

// 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, ...) taks 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 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 demonstrate 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);
}

Example of printf() method in Print class.
Note: this will not work on the AVR platform. It works on 3rd party platforms
like ESPxxx, chipkit, etc...

// Example of how to use a printf() method when it is included in the
// Print class.
// Since it is in the Print class, there no magic to do.
// just use it.

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

	Serial.printf("printf Print Class Demo...\n");
}
void loop(void)
{
	Serial.printf("Seconds up: %04ld\n", millis()/1000);

	delay(1000);
}
4 Likes

And you explained why well enough for me to facepalm!

Without that C++ Serial, I have no Arduino-provided means to code serial IO even though Github might or I might dig back into the serial code i have written once before and roll my own, none of which is official.

I get your point, I need to drag myself awake and not present such empty arguments. I can only offer excuses that I won't bother wasting time over, I did lapse so I'm sorry bout that!

1 Like

And the use of portability in Arduino has been abundantly shown since Arduino has been adapted to So Many Newer Chips/Cores.

So I should care about the C++ even as a user.

1 Like

First, I'm sorry for the delay; I was away a few days and when I got back yesterday I spent time getting my design done in KiCAD. More on that later.

Writing your own bugs...yes, I think I grasp the concept. Having gotten this "close" to the hardware, I see how that could be easily done. Programming for a MCU is dramatically different from coding for a PC. The clock works, sunset/sunrise times calculate properly, I can offset the sunrise/sunset times by independent amounts to cause action before, on, or after the events independently. I have no interface to adjust the time/date but, for now, this is only for me and will likely always just be for me: I can grab the board and hook it to a PC if the clock battery eats it.

I watched the video. 1) I don't want to think of the genius it took to envision that project, 2) the insane genius it took to code it (and, debug it), or 3) the patience it took to connect it up, let alone in a coherent presentation.

What's scary is I'm getting a feel for the MCUs...okay, this does this, that does that...what do I need...oh, okay, this will do. Oddly, I feel comfortable with the Arduino now. I still suck at it, but it's not the boogey man it was 3 weeks ago. My dad messed with electronics and was never especially good at it and didn't do much. I've followed in his footsteps and have monkeyed with it for 27 years or so. Many years went by doing nothing, then fits and spurts. Thank God, somehow, coming to this project...I'm oddly better than I ever was even though I've not been doing it. My brain has changed.

In that vein: I was messing with cobbling a prototype together on a breadboard PCB and got frustrated with only 6 components on the board and my inability to fit all the various pieces into a box I was designing (space constraints). I thought I'd try my hand at a real PCB. I've done a few but it was when I had a t-shirt press and I could put ink through a silkscreen on a board as etch resist. I left that press with the ex. And the few designs I did were done in a graphics program, real grassroots stuff. I downloaded KiCAD. Wow, that got good. I worked on it a few hours last night and this morning and I submitted an order to PCBWay this morning. We'll see how that goes.

I'll have a breakout board for the UI (5 buttons, 2 in-use, 3 for future expansion, plus 2 LEDs), 2 opto-isolated relays on their own boards (Amazon specials...but they seem pretty good). The main components fit on a 50 x 50mm board. I had a bit of feature creep: I have a few LED headlamp bulbs lying around...I have an output to flick one on for 5ms each second and I'm adding an external switch to verify the action has taken place which will then trigger the flashing, super-bright LED so I can see at a distance that the action took place.

Still ain't got nuthin' on the guy with that LED cube.

--HC

Okay, yes, I see about the "f". I commonly do that: oversimplify and think "I've got it". Reading what you wrote I seem to recall "printf" as "formatted print"...maybe from the Pascal class I took in college...maybe. Somewhere in my head it rings a bell.

--HC

I see that I confused the effs. I'm still slow and it takes a bit to try to understand what is still quite foreign to me. Printf() doing formatted text makes sense whereas that f being a reference to F() or any similar functionality which is specific to one architecture doesn't make sense now.

I think I'm getting it better: it's going to be an inception/activation/power-on function to copy string literals from Flash to SRAM so that the code can access that information at runtime. That there are helper functions to get it sounds like a "juice/squeeze" thing and your point about not mucking up your code with oddball stuff makes sense.

Slowly...I'm getting there. Or closer, anyway. Thank you.

--HC

Understood re C vs C++ but, to be completely honest, the way I learn this stuff is to come up with a goal and then do whatever it takes to get there. I'm not good at sitting and reading a book on these things: I just start coding and bumble around and look up whatever it is that I'm stumped on, for the most part. Forums like this are an invaluable resource.

Pointers.... I have a function in my current project that needs to receive several variables and modify a couple of them. Since I needed to modify more than one of them I had to use pointers or break the coding into multiple functions. I used pointers. Everything was great until *foo += 60; didn't increase the value at the address of *foobar but increased the pointer. I got that working with (*foo) += 60;, I even made a post on that thinking I figured something out which didn't immediately seem to be in the forum and might be helpful to others. Then, reading my own code, it seems to work on one line as *foo += bar; (which I'd overlooked when modifying my code) but later lines I had to use the parentheses. The only difference seems to be if I'm using ++ or a digital amount (1, 2, 3, 4, 60, whatever) versus adding another variable. Anyway...yes...pointers.

I'm okay with bit logic. Not great, but ANDing to check a bit, I understand. XORing, yeah, sort of okay, maybe for flipping a bit. Bit shifting...nope. I'll show my foolishness here: I haven't run across a situation where I need the bit operators (or I don't think I have, anyway). Usually, when I say stuff like that, someone points out how whatever I didn't think I needed is actually what I really needed I just didn't know it yet.

Thank you. Everyone's time to weigh in and share information is appreciated.

--HC

Well said. Read it twice. To be a little funny: STDOUT >> void;

--HC

  • Say you have a 32 bit unsigned long variable. number32
    Say you have 4 byte variables (unsigned char), these are ASCII characters. ascii[0], ascii[1], ascii[2], ascii[3].
    You can pack the 4 ASCII characters into number32

Edit, & should be |

number32 = ascii[0];
number32 = (number32 << 8) & ascii[1];
number32 = (number32 << 8) & ascii[2];
number32 = (number32 << 8) & ascii[3];

// number32 now contains our 4 ACSII characters.

Didn't you mean to OR in the ascii value to stuff it into the lower 8 bits of number32 instead of ANDing it with the shifted value?
which will strip off the bits you just shifted over.

--- bill

2 Likes

:scream:

  • Yes OR

number32 = ascii[0];
number32 = (number32 << 8) | ascii[1];
number32 = (number32 << 8) | ascii[2];
number32 = (number32 << 8) | ascii[3];

Just when I'm thinking I'm pretty good at this embedded MCU coding stuff...this above. Yeah, I don't know boo. Impressed.

--HC

Okay, I'll ask dumb questions.

To your example:
I know what the ASCII #s are, more or less. I know 65 = "A", 66 = "B", etc. Characters (for lack of a better term) have a numeric identifier, 65 is the numeric identifier for "A". I know we can express any integer in binary, to keep it simple. "A" = 65 = 01000001. CR = 13...LF = 10, or vice versa, as I recall, special-use ASCII values.

Looking up ascii[], it returns the decimal equivalent of what's in the brackets.

First dumb question: if ascii[0] = decimal 0, why not simply use the naked digit: number32 = 0?

I know that ascii['0'] isn't ascii[0].

As I formulated my next dumb question, I think I get it: number32 first is 32 zeroes, binary zero, 32 bits. We set that to 0 but we could have set it to 65 which would get us 24 zeroes, left to right, and then: 01000001 after that, on the right end. (number32 << 8) will take those 32 bits and shift them 8 places to the left. This will lose the 8 MSB (most significant bit(s), the leftmost 8 bits) and fill the 8 LSB (least significant bit(s), the rightmost 8 bits) with zeroes. When we OR 00000000000000000100000100000000 with 00000001 (ascii[1]) we'll get that long binary from before but it will end with ...0001.

       00000000 00000000 01000001 00000000
OR                                                          00000001
equals
        00000000 00000000 01000001 00000001

Repeating those steps get us the 4 individual bytes wrapped up in one 32 bit storage.

Last dumb question for this post: why would we want to do that? Except...when I was passing 2 variables of type int to a function and having to use pointers to get data "back"...I could have used one 32 bit var to pass in, each simple (<= 256) integer in the first 16 bits, manipulated them in the function, then returned a 32 bit var with....yeah, maybe that use case?

--HC

That was just to show that the simple AVR core (in every one of the many-many ATtiny and ATmega chips) is capable of doing 100's of things smoothly together.

I once saw a radio built on a copper clad board. Strips were sawed off one edge then crazy glued on top, making a ground plane and a few traces. Components were soldered to the copper faces and it was an amazing way to make a board.

I would not make a pcb until all the bugs were worked out and operating for a good while.

1 Like