Frequent readers of this blog know that I do not like printf (see “Why I don’t like printf()“), because the standard printf()
adds a lot of overhead and only causes troubles. But like small kids, engineers somehow get attracted by troubles ;-). Yes, printf(
) and especially sprintf()
are handy for quick and dirty coding. The good news is that I have added a lightweight printf()
and sprintf()
implementation to my set of components: the XFormat component. And best of all: it supports floating point formatting :-).
XFormat Processor Expert Component
The sources of XFormat have been provided to my by Mario Viara, who contacted me by email:
“Hello Erich,
My name is Mario Viara and I’m and “old” embedded software engineer, Il write for two different things the first is very simple, i know you do not like printf in your project but during debug and to log information it is very usefull we have a very tiny implementation that you can find attached to this mail and if you want you can add it to your wonderful processor expert library.”
This morning finally I have turned this into a Processor Expert component. And I have to say: that’s indeed a simple and cool printf()
and sprintf()
implementation. All what I did is doing some re-formatting, added the necessary interfaces and voilà: a new component is born 🙂
Component Methods and Properties
The component is very simple, and exposes three methods:
xvformat()
: this is the heart of the module, doing all the formatting. This one uses a pointer to the open argument list.xformat()
: high level interface toxvformat()
with an open argument list.xsprintf()
:sprintf()
implementation which usesxformat()
and uses a buffer for the result string
There is only one setting: if floating point have to be supported or not. This setting creates a define which can be checked in the application code.
sprintf() Usage
Using it for to do a printf()
into a provided buffer (sprintf()
) is very easy, e.g. with
char buf[64]; XF1_xsprintf(buf, "Hex %x %X %lX\r\n",0x1234,0xf0ad,0xf2345678L);
which will store the follow string into buf:
Hex 1234 F0AD F2345678
This string then can be printed or used as needed.
printf() Usage
Using printf()
and for example to print the string to a console requires that a ‘printer’ function is provided. This ‘printer’ function (actually a function pointer) needs to be provided by the application.
First, I define my function which shall output one character at a time. It uses as first argument a void parameter (arg) with which I can pass an extra argument. My example implementation below passes the command line Shell standard I/O handler I’m using in my projects (see “A Shell for the Freedom KL25Z Board“). That handler itself is a function pointer:
static void MyPutChar(void *arg, char c) { CLS1_StdIO_OutErr_FctType fct = arg; fct(c); }
Next, I implement my printf()
function I can use in my application: it uses the same interface as the normal printf(). It unpacks the variable argument list and passes it to xvformat()
function, together with the MyPutChar()
function:
unsigned int MyXprintf(const char *fmt, ...) { va_list args; unsigned int count; va_start(args,fmt); count = XF1_xvformat(MyPutChar, CLS1_GetStdio()->stdOut, fmt, args); va_end(args); return count; }
Then I can call it in my application like printf():
(void)MyXprintf("Hello %s\r\n","World");
The component creates as well a define so the application knows if floating point is enabled or not. Below is a test program which uses some of the formatting strings:
static void MyPutChar(void *arg, char c) { CLS1_StdIO_OutErr_FctType fct = arg; fct(c); } unsigned int MyXprintf(const char *fmt, ...) { va_list args; unsigned int count; va_start(args,fmt); count = XF1_xvformat(MyPutChar, CLS1_GetStdio()->stdOut, fmt, args); va_end(args); return count; } static void TestXprintf(void) { (void)MyXprintf("Hello world\r\n"); (void)MyXprintf("Hello %s\r\n","World"); (void)MyXprintf("Not valid type %q\r\n"); (void)MyXprintf("integer %05d %d %d\r\n",-7,7,-7); (void)MyXprintf("Unsigned %u %lu\r\n",123,123Lu); (void)MyXprintf("Octal %o %lo\r\n",123,123456L); (void)MyXprintf("Hex %x %X %lX\r\n",0x1234,0xf0ad,0xf2345678L); #if XF1_XCFG_FORMAT_FLOAT (void)MyXprintf("Floating %6.2f\r\n",22.0/7.0); (void)MyXprintf("Floating %6.2f\r\n",-22.0/7.0); (void)MyXprintf("Floating %6.1f %6.2f\r\n",3.999,-3.999); (void)MyXprintf("Floating %6.1f %6.0f\r\n",3.999,-3.999); #endif } static void TestXsprintf(void) { char buf[64]; XF1_xsprintf(buf, "Hello world\r\n"); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Hello %s\r\n","World"); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Not valid type %q\r\n"); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "integer %05d %d %d\r\n",-7,7,-7); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Unsigned %u %lu\r\n",123,123Lu); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Octal %o %lo\r\n",123,123456L); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Hex %x %X %lX\r\n",0x1234,0xf0ad,0xf2345678L); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); #if XF1_XCFG_FORMAT_FLOAT XF1_xsprintf(buf, "Floating %6.2f\r\n",22.0/7.0); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Floating %6.2f\r\n",-22.0/7.0); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Floating %6.1f %6.2f\r\n",3.999,-3.999); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); XF1_xsprintf(buf, "Floating %6.1f %6.0f\r\n",3.999,-3.999); CLS1_SendStr((unsigned char*)buf, CLS1_GetStdio()->stdOut); #endif }
Summary
With the XFormat Processor Expert component I have a small printf()
like implementation which is a good alternative to the bloated (and fully featured) sprintf()
and printf()
implementation. It uses a flexible callback mechanism so I can write the text to any channel (bluetooth, wireless, disk, …) I want. The new component is now a member of my components on GitHub (see “Processor Expert Component *.PEupd Files on GitHub“).
Thanks Mario!
Happy Printing 🙂
PS: for ‘normal’ string manipulation functions, see the Utility Processor Expert component, available on the same GitHub repository.
Perhaps Mario could contribute here a comparison between XFormat and the printf() subset used by newlib nano, in terms of code size, speed, etc.
LikeLike
Thanks for posting this – looks useful. Formatted printing has always been the bane of embedded software.
LikeLike
Hi, the printf example you have done in another post is not working for me in KDS 1.1.1 and I can’t seem to find the XFormat component. Has it been removed? What would be another alternative?
Great info in your web! Thanks for sharing.
Thank you!
LikeLike
Hi Alex,
which project are you using? I verified that https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_printf is working for me with KDS v1.1.1
Erich
LikeLike
Hi,
I have tried to do as you explained in another post to use printf in KDS 1.1.1 but the MCU is not sending anything to the OpenSDA MCU. Also, I can’t find that XFormat component in KDS 1.1.1.
Has it been removed?
What can I do in this case?
Great info in your web site.
Thank you.
LikeLike
Hello,
the XFormat component is not part of Kinetis Design Studio, it is one of the McuOnEclipse components. Have you installed them from SourceForge? See https://mcuoneclipse.com/2014/10/21/mcuoneclipse-releases-on-sourceforge/
I hope this helps,
Erich
LikeLike
Hi,
Thank you for your quick answer. Sorry I didn’t know that. I’ve downloaded them. Great stuff. I’m planning to use Kinetis as my new working platform since Microchip has code limited compiles and no ARM core, TI has very expensive software tools, Atmel has no multiplatform software, Cypress (Has awesome platform and software which is even better than Processor Expert IMHO) but they are expensive and no multiplatform and the chose was between Freescale and NXP. Both support mbed and has free multiplatform tools which is awesome, but I prefer the Processor Expert option and the bigger selection of MCUs.
By the way, I have a FRDM-KL25Z which when I changed the firmware of the OpenSDA MCU to use it with mbed I couldn’t get KDS to recognize it so I had to change it back. Is there anyway to be able to have support for boths at the same time? I guess the K64 board can do that, right? I have an LPCXpresso V2 board and does it too. Is there any possibility to achieve the same with the KL25Z?
I really appreciate people sharing info like you. As soon as I get confortable with Freescale I will make tutorials for my youtube channel: Twistx77.
Thank you once again. Have a nice weekend.
Alex.
LikeLike
Hi Alex,
The FRDM-K64F has a different firmware than the FRDM-K24F. What mbed needs is a firmware which recognizes .bin (binary) files. In my view mbed would have been much better off if they would have used s19 instead of bin files, then you would not see that problem. The FRDM-KL25Z (and many other boards) accept S19 files. A solution for you would be that you convert the mbed bin files into S19 files, and then you don’t need to switch.
Erich
LikeLiked by 1 person
Hi, I tested your example and it works perfectly. I don’t see any differences from mine. I will do it again from scratch to figure out what is the problem with mine.
I also found all the examples you have in your github for CW which is awesome.
Thank you and sorry for the inconvenience.
LikeLike
Hello Erich,
Great component, and thank you for making it available to the public.
I am not sure if you are aware of it or not but there is a bug on the Xformat component.
if you try to print negative floating point numbers such as:
-0.56, -0.01, -0.85, etc the xformat outputs the number but not the ‘-‘ sign.
if you then try to print -1.56, -2.01, -10.85, etc it works just find.
I have floating point enable in the component.
So any number with -0.XX or -.XX fails to print the ‘-‘ character.
Any ideas?
Thank you.
LikeLike
Hello,
many thanks for reporting this problem, and indeed: I can reproduce it 😦
I have commited a fix for it here (which is very easy):
https://github.com/ErichStyger/McuOnEclipse_PEx/commit/a5869aa97bcf39259231a3f7f4fe1340ad0f80c8
It will be included in the next component release. Until then, you could do that one line change on your machine too. Let me know if you need any assistance.
LikeLike
Hello,
Oh, that is great!
Thank you for quick Fix.
Awesome!
LikeLike
Erich,
i am seeing this issue:
almost same xsprintf statements, but i swap the last 2 arguments. the last %d always results in ‘0’. i tried %x as well and same result.
XF1_xsprintf(debugMess,”index %d, addr 0x%x : time %d code %d\n”,i,NVM_ERROR_PAGE+i,time,code);
//SerialPuts(Type_00_Console,debugMess);
XF1_xsprintf(debugMess,”index %d, addr 0x%x : time %d code %d\n”,i,NVM_ERROR_PAGE+i,code,time);
//SerialPuts(Type_00_Console,debugMess);
debugMess after 1st XF1_xsprintf;
Name : debugMess
Details:”index 0, addr 0x8f000 : time 19 code 0\n\070942′ ‘0x175902f4’) :\0 CAN ID: 39170942\n\n\0000, com_sp 0.000, com_v -0.011, IDLE, voc_only: algo_init, — 0.000 (0.000), [-1:4] – 0.000 (0.000)\n”, ‘\0′
Default:0x1fffbc1c
Decimal:536853532
Hex:0x1fffbc1c
Binary:11111111111111011110000011100
Octal:03777736034
debugMess after 2nd XF1_xsprintf;
Name : debugMess
Details:”index 0, addr 0x8f000 : time 2147483647 code 0\n\00x175902f4’) :\0 CAN ID: 39170942\n\n\0000, com_sp 0.000, com_v -0.011, IDLE, voc_only: algo_init, — 0.000 (0.000), [-1:4] – 0.000 (0.000)\n”, ‘\0’
Default:0x1fffbc1c
Decimal:536853532
Hex:0x1fffbc1c
Binary:11111111111111011110000011100
Octal:03777736034
LikeLike
Hi David,
Few ideas and questions:
Could it be that you have a possible stack overflow? Can you try with an increased stack size? Because it requires around 100 bytes on the stack.
And did you include the header file âXF1.hâ before you are using it? Because it uses an open argument list this is very important.
I hope this helps,
Erich
LikeLike
Pingback: McuOnEclipse Components: 30-Oct-2016 Release | MCU on Eclipse
Hi Erich,
Thank you for sharing XFormat, it’s very useful for me!
I just want to share my suggestion about this component:
(1) Add snprintf():
/*
** ===================================================================
** Method : XF_xsnprintf (component XFormat)
** Description :
** snprintf() like function
** Parameters :
** NAME – DESCRIPTION
** * buf – Pointer to buffer to be written
** max_len – Max output buffer size
** * fmt – Pointer to formatting string
** argList – Open Argument List
** Returns :
** — – number of characters written, negative for
** error case
** ===================================================================
*/
struct StrOutBuffer {
char * s;
unsigned space;
};
static void putCharIntoBufMaxLen(void *arg, char c) {
struct StrOutBuffer * buff = (struct StrOutBuffer *)arg;
if (buff->space > 0) {
buff->space–;
*(buff->s)++ = c;
}
}
static int xsnprintf(char *buf, unsigned max_len, const char *fmt, va_list args) {
int res;
struct StrOutBuffer out = { buf, max_len };
if (max_len 0) res = out.s – buf;
return res;
}
int XF_xsnprintf(char *buf, unsigned max_len, const char *fmt, …)
{
va_list args;
int res;
va_start(args,fmt);
res = xsnprintf(buf, max_len, fmt, args);
va_end(args);
return res;
}
(2) Add GCC extension:
/* GCC have printf type attribute check. */
#ifdef __GNUC__
#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b)))
#else
#define PRINTF_ATTRIBUTE(a,b)
#endif /* __GNUC__ */
unsigned XF_xformat(void (*outchar)(void *,char), void *arg, const char * fmt, …) PRINTF_ATTRIBUTE(3,4);
int XF_xsprintf(char *buf, const char *fmt, …) PRINTF_ATTRIBUTE(2,3);
int XF_xsnprintf(char *buf, unsigned max_len, const char *fmt, …) PRINTF_ATTRIBUTE(3,4);
Cheers,
-Engin
LikeLike
Hi Engin,
Could you send me that piece of source by email to my email address listed on https://mcuoneclipse.com/about/, as posting sources like this on the web will loose things. For example your code does not include any of the parsing parts.
Thanks!
Erich
LikeLike
Hi Engin,
cool idea about the GNU function attributes (https://gcc.gnu.org/onlinedocs/gcc-3.2/gcc/Function-Attributes.html)!
I did not know this, always learning something new 🙂
Erich
LikeLike
It’s due to HTML tagging in the source code, sorry about that!
I have sent the code directly to your e-mail.
Thanks you!
LikeLike
Hi Engin,
thank you so much! I’m adding this to the component.
Erich
LikeLike
Pingback: Using FreeRTOS with newlib and newlib-nano | MCU on Eclipse
Pingback: Using the GNU Linker Script to know the FLASH and RAM Areas in the Application | MCU on Eclipse
So is this implementation thread-safe?
LikeLike
Yes, it is thread-safe.
LikeLike
Thank you, Erich! As always, you are a great help.
LikeLike
I believe that it is, based on the discussion that it uses the stack and quick inspection of the code, but would feel more confident if I could get confirmation.
LikeLike
Hi,
I’ve been having an issue formatting floating point numbers. Specifically, if I use %2.6f or %2.7f, I just get the integer part of the number, whereas using %f, I get the first six digits after the decimal point.
As an example, I tried the following code:
float Lat = 34.123456;
float Lon = 35.567891;
strlLength = XF1_xsprintf((char*) s,”GPS = %2.7f, %2.7f\r\n”,Lat,Lon);
The result is:
s = “GPS = 34, 35”
Any ideas?
Thanks!
LikeLiked by 1 person
Actually this is a bug (or missing feature) in the original XFormat code. I realize that the version on GitHub (https://github.com/MarioViara/xprintfc) has it fixed, so I have now updated (and fixed) the Processor Expert component for it. I applogize for that issue, and I have sent you the new component to your gmail address. Of course this fix will be in the next update from my side.
Thanks for reporting!
Erich
LikeLiked by 1 person
Pingback: McuOnEclipse Components: 1-Apr-2018 Release | MCU on Eclipse
Hi, Erich
Very interesting your alternative to printf. I’m looking for something like that. In my case, I need to print a string through UART into a Terminal. You call XF1_xvformat like that: XF1_xvformat(MyPutChar, CLS1_GetStdio()->stdOut, fmt, args);. I suppose that in MyPutChar function I should load my UART buffer with each character of my string. But what about CLS1_GetStdio()->stdOut function? What does this function do exactly and what function should I pass as the second parameter to send my data to UART transmit buffer instead of a variable?
Sorry for my question, I’m not experienced about Standard I/O functions.
Thanks and best regards!
Marco Coelho
LikeLike
Hi Marco,
xvformat() can be used with two parameters: the output function plus an optional argument with the output function. In my case I pass the stdio output channel (stdOut) as argument. It would be possible to do this without such an argument.
In your case using an UART: simply pass your UART putChar function as the first argument, and NULL if you don’t need the second one.
I hope this helps,
Erich
LikeLike
Hi Erich,
Interesting ‘bug’ came up last week. I got a Hard Fault because a xsprintf(buff,” %64s “, pchars);
incremented the pchars pointer until it was not pointing at memory.
pchars was supposed to point to a string (in flash), and this time it was pointing at erased flash memory, which is all xFF. but the xsprintf happily keeps copying pchars til it finds a x00 or generates a Hard Fault. So it also appears like it would have filled memory starting at buff with 0xff, and probably ran out of RAM at buff before it ran out of the FLASH that pchars was pointing to.
I rewrote with a memcpy – something it maybe should have been in the first place since it wasn’t really doing any formatting, but it surprised me that the xsprintf could run off the end of the buffer – I had thought you liked it better because it was safer than printf. This incident just reminded me that printf is not really safe at all, including the x varient.
Brynn
LikeLike
I guess the real problem is that I should always use the xsnprintf or snprintf versions in my embedded code, and never the unbounded versions. In fact, a smart coder should probably never use the unbounded versions ever.
Brynn
LikeLike
Yes, exactly. The ‘n’ version is always preferred and is what I’m using. the XFormat version is better compared to the normal printf()/sprintf() because it is reentrant and has a much smaller footprint.
LikeLike
Pingback: Tutorial: How to Optimize Code and RAM Size | MCU on Eclipse
Hi Erich,
I’m trying to use the va_list macro to make my own debug print with variable arguments.
But it just doesn’t work for me. Here is the code I have:
void debugPrint(u8 color,u8 *buffer,const u8 *format, …){
va_list args;
va_start(args, format);
XF1_xsprintf(buffer,format,args);
va_end(args);
scrollPrintColor(buffer,color);
}
// And here is the call to it
debugPrint(RED,buff,”This is a test of the debugPrint %d %d %d %s %d”,1,2,3,”four”,5);
// and what it actually prints:
This is a test of the debugPrint 536872884 1 0 0
// It actually printed an extra space between the final two zeros
Am I doing something wrong?
LikeLike
did you provide a prototype of debugPrint() before using it? You might get a warning (implicit function usage) otherwise?
LikeLike
Hi Erich,
I did get it to work with a macro, using this definition:
#define dPrint(color,buffer,format, …) { XF1_xsprintf(buffer,format,__VA_ARGS__); scrollPrintColor(buffer,color); }
So I guess it is solved, but I thought it should work the other way, too.
Brynn
LikeLike
The macro version ONLY works if there is at least one argument in the var args list.
if there are no arguments, it fails and gives an unhelpful help message saying it expected an arg before the ‘)’, and points to the macro definition and not where the invocation failed.
LikeLike
The problem is:
you call
XF1_xsprintf(buffer,format,args);
but the prototype is
int XF1_xsprintf(char *buf, const char *fmt, …);
which is not the same: ‘…’ is not the same as va_list args.
You need to call instead xsprintf() which I have added to the library a while back for exactly this purpose. It should be present in the same module, otherwise here is it:
#if 1 /* << EST added xsprintf() */
static void putCharIntoBuf(void *arg, char c) {
char **s = (char **)arg;
*(*s)++ = c;
}
int xsprintf(char *buf, const char *fmt, va_list args) {
int res;
res = (int)McuXFormat_xvformat(putCharIntoBuf, (void *)&buf, fmt, args);
*buf = 0;
return res;
}
#endif
LikeLike
I’ll try your answer in a bit, in the meantime, I’ve been using the macro version- but there is a huge problem: if you did something wrong (like not provide at least one arg for the arglist) it generates an error, but points you to the macro definition, not where I invoked it.
Getting told the error is in the macro doesn’t help, as I have no clue as to which time I invoked the macro contained an error.
Especially annoying when I just converted many of my prints over to the new dPrint.
LikeLike
Thanks Erich,
Your answer (using xsprintf instead of XF1_xsprintf ) works great.
The Macro almost-work-around sucks because you cannot tell where actual errors might be.
LikeLiked by 1 person
Yes, macros can be tricky with open parameter lists.
LikeLike