问题
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 off 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!
回答1:
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.
来源:https://stackoverflow.com/questions/55014043/how-do-you-implement-printf-in-gcc-from-newlib