XFormat, a Lightweight printf() and sprintf() Alternative

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 Component

XFormat Component

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:

XFormat Methods

XFormat 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 to xvformat() with an open argument list.
  • xsprintf(): sprintf() implementation which uses xformat() 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.

XFormat Properties

XFormat Properties

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.

43 thoughts on “XFormat, a Lightweight printf() and sprintf() Alternative

  1. Perhaps Mario could contribute here a comparison between XFormat and the printf() subset used by newlib nano, in terms of code size, speed, etc.

    Like

  2. 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!

    Like

  3. 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.

    Like

      • 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.

        Like

        • 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

          Liked 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.

        Like

  4. 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.

    Like

  5. 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

    Like

    • 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

      Like

  6. Pingback: McuOnEclipse Components: 30-Oct-2016 Release | MCU on Eclipse

  7. 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

    Like

  8. 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!

    Like

  9. Pingback: Using FreeRTOS with newlib and newlib-nano | MCU on Eclipse

  10. Pingback: Using the GNU Linker Script to know the FLASH and RAM Areas in the Application | MCU on Eclipse

  11. 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!

    Liked 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

      Liked by 1 person

  12. Pingback: McuOnEclipse Components: 1-Apr-2018 Release | MCU on Eclipse

  13. 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

    Like

    • 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

      Like

  14. 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

    Like

    • 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

      Like

      • 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.

        Like

  15. Pingback: Tutorial: How to Optimize Code and RAM Size | MCU on Eclipse

  16. 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?

    Like

  17. 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

    Like

  18. 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.

    Like

    • 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

      Like

      • 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.

        Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.