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.

Advertisements

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

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

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

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

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

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

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

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

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

What do you think?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s