How do you Implement printf in GCC from Newlib?
Asked Answered
Z

1

5

I'm struggling to properly implement printf from newlib into my esp32, using GCC.

I've gone through the newlib documentation and it gives me general information about how printf is called, but doesn't explain the back end implementation to me.

Based on my current research I have determined that printf outputs a formatted string to the STDOUT. On a PC this was simpler for me to understand because there's a console window that would display the formatted output from printf, however on an embedded system I understand that you have to tell the library where to redirect the formatted output of printf to and that's what I'm trying to figure out.

Again, based on of my research I have come to understand that some functions are required to accomplish this, specifically the function _write.

I'm finding it very difficult on how to bridge the gap between printf and utilizing the _write function. I'm hoping someone here can help me understand how to properly implement printf.

And if I missed some documentation that clearly explains this, then please redirect me to that. I tried reading the newlib documentation, as well as GCC related documentation, but nothing really mentions how to use printf, but there is plenty of documentation on how to call printf and format the string, but that part is easy. I need to know how to get the formatted string from the STDOUT of the MCU.

Thanks to all!

Zach answered 6/3, 2019 at 1:15 Comment(2)
You can find implementations of printf() and its relatives of greater or lesser complexity on the web. If you find int printf(const char * restrict fmt, ...) { va_list args; va_start(args, fmt); int rc = vfprintf(stdout, fmt, args); va_end(args); return rc; } then you've got a relay function and you need to find the implementation of vfprintf() instead. But that too is available…Liborio
@JonathanLeffler : Except if Newlib is used there is already a printf() implementation. The question I think should be about how to retarget Newlib so that its printf() works rather then how to implement printfTanto
T
9

In Newlib you don't implement printf() that is included in the library. You simply implement a minimal set of syscalls to support the library. The stream device sycalls API comprises of open, close, read and write (or reentrant versions with the _r suffix) - the reentrancy in this is useful if you are using multi-threading and need a per thread errno (amongst any implementation specific re-entrancy requirements).

If all you are implementing is stdout (the stream used by printf(), putchar(), puts() etc.) and are only supporting a single device (typically a UART) and are not concerned about the ability to redirect or reentrancy, then open, close and read can be empty, and write can simply output the provided buffer directly to your low-level serial I/O API:

int _write(int handle, char *data, int size ) 
{
    int count ;

    handle = handle ; // unused

    for( count = 0; count < size; count++) 
    {
        outputByte( data[count] ) ;  // Your low-level output function here.
    }

    return count;
}

Note that handle here is unused. For stdout it will be 1 (stdin = 0 and stderr = 2). The handle argument would be used if you wanted separate output devices for stdout and stderr or if you were supporting additional devices or a file system and fopen or stdout redirection. It is used to identify a steam opened by open. By ignoring it all stream output (such as fprintf() will be handled in the same way and output to the same device); in many cases (where printf() is just a means of getting debug output, or your application has no filesystem you won't care.

Given that write function, printf() will "just work" (in the simplest possible manner) because under the hood all stdio output functions call write). A low-level output function that is buffered and non-blocking (e.g. an interrupt driven UART driver) is advised.

Obviously if you want to accept input on stdin too, you would implement a similar read function.

If you want a heap (malloc() etc) you will also need to implement sbrk / sbrk_r. I would suggest that you at least implement that if nothing else in Newlib syscalls.

A more sophisticated treatment for syscalls implementation is discussed by Bill Gaitliff in Porting and Using Newlib in Embedded Systems, while basic implementation is discussed at here while example minimal implementation stubs similar to that above are provided in the Newlib documentation itself.

Tanto answered 6/3, 2019 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.