Summary

After I wrote the first article on Implementing PSoC Printf I got feedback from several people who made good suggestions.  Fundamentally, both of the people making commentary suggested ways of improving the memory footprint of PSoC printf.  In this article I am going to explain 5 different implementations and the pros/cons of those implementations.

Scheme Flash Total RAM Heap Stack Data/BSS Comment
vsprintf 4838 2852 2048 128 676 A function using sprintf + macros
iotexpert sprintf 4806 2852 2048 128 676 A macro trick – not as robust as vsprintf
iprintf 2952 2580 2048 128 674 restricted to %s,%d,%c,%x
printf 6496 3492 2048 1024 420 Fully featured printf from previous article
iprintf newlib nano 6496 3492 2048 1024 420 Same as printf (iprintf is aliased to printf)

To get these numbers I used the following simple program to implement PSoC printf with all of the default setting for compiler etc.

int main(void)
{
    CyGlobalIntEnable; /* Enable global interrupts. */
    UART_Start();
    
    printf("Int=%d  String=%s\n",1,"asdf");


    for(;;)
    {
    }
}

You can “git” all of these projects at the IoT Expert GitHub site git@github.com:iotexpert/SerialPrintf.git

vsprintf & DBG_PRINTF

The first suggestion that I got was from a follower in China named Helon Chan.   After a little bit of back and forth, he sent me this code, which I think is very simple and elegant.  The way that this PSoC printf works is that it:

  • Uses “vprintf” which is a form of  “sprintf”.   The “v” in “vsprintf” stands for va_args.  By using the va_arg version of sprinf he eased the calling of sprintf (lines 16-18)
  • Uses a loop to print the buffer onto the screen (line 23-26)

The other nice thing that he did is create a macro that you can turn on and off the printing of debug messages (lines 29-34)

#include "project.h"
#include <stdio.h>
#include <stdarg.h>

#define MAX_FORMAT_BUFFER_SIZE	(255)
static uint8_t s_formatBuffer[MAX_FORMAT_BUFFER_SIZE];

void dbg_printf(char *format,...)                                      ///< Function Name        
{
	uint8_t iWriteNum = 0,i=0;	
	va_list  ap;
	
	if(!format)
		return;
	
	va_start(ap,format);
	iWriteNum = vsprintf((char *)s_formatBuffer,format,ap);
	va_end(ap);
	
	if(iWriteNum > MAX_FORMAT_BUFFER_SIZE)
		iWriteNum = MAX_FORMAT_BUFFER_SIZE;
	
	for(i=0;i<iWriteNum;i++)
    {
		UART_UartPutChar(s_formatBuffer[i]);                            ///< Uart Function        
	}
}

#define DBG_ENABLE                              (1)                                 ///< Enable Printf
#if DBG_ENABLE
    #define DBG_PRINTF(format,...)              dbg_printf(format,##__VA_ARGS__)
#else
    #define DBG_PRINTF(...)
#endif

IoT Expert Version of vsprintf

After I looked at the previous implementation I thought of a really simple way to do the same thing.  Here is my version of the vsprintf:

#define MAX_FORMAT_BUFFER_SIZE	(255)
static uint8_t s_formatBuffer[MAX_FORMAT_BUFFER_SIZE];
#define asprintf(...) sprintf((char *)s_formatBuffer,__VA_ARGS__); UART_UartPutString((const char *)s_formatBuffer)

My version works almost exactly the same and is the same size as the previous version.  The weakness is that it does not error check at all.  Also if you called it enough times it would eventually use more flash than the previous version.

iprintf – An integer restricted printf

I got a tweet from Jose Marcelino in which he references the Cypress Knowledge Base Article KBA87093 in which someone implemented a PSoC printf that works only with a restricted set of formatting codes, %d (integer), %s (string),%x (hex integer) and %c (character).

The function is very easy to use.  All you need to do is copy iprintf.c and iprintf.h into your project.  Then you can use it by calling “iprintf”.  Here is the source code for iprintf.h

#ifndef IPRINTF_H
#define IPRINTF_H
#include <project.h>
void iprintf(char8 *pszFmt,...);
#endif

And iprintf.c

#include "iprintf.h"

static void iputc(char8 c)
{
    UART_UartPutChar(c);
}

static uint8* change(uint32 Index)
{
    return (uint8*)("0123456789abcdef"+Index);
}

void iprintf(char8 *pszFmt,...)
{
    uint8 *pszVal;
    uint32 iVal, xVal, i = 0, buffer[12], index = 1;
    uint8 cVal;
    uint32 *pArg;
    pArg =(uint32 *)&pszFmt;

    while(*pszFmt)
    {
        if('%' != *pszFmt)
        {
            iputc(*pszFmt);
            pszFmt++;
            continue;
        }
        pszFmt++;

        if(*pszFmt == 's')
        {
            pszVal = (uint8*)pArg[index++];
            for(; *pszVal != '\0'; pszVal++)
                iputc(*pszVal);
            pszFmt++;
            continue;
        }
        if(*pszFmt == 'd')
        {
            iVal = pArg[index++];
            i = 0;
            do{
                buffer[i++] = iVal % 10;
                iVal /= 10;
            }while(iVal);
            while(i > 0)
            {
                i--;
                iputc(*change(buffer[i]));
            }
            pszFmt++;
            continue;
        }
        if(*pszFmt == 'c')
        {
            cVal = (uint8)pArg[index++];
            iputc(cVal);
            pszFmt++;
            continue;
        }
        if(*pszFmt == 'x')
        {
            xVal = pArg[index++];
            i = 0;
            do{
                buffer[i++] = xVal % 16;
                xVal /= 16;
            }while(xVal);
            if(i%2!=0)
                buffer[i++]=0;
            if(i<2)
                buffer[i++]=0;

            while(i > 0)
            {
                i--;
                iputc(*change(buffer[i]));
            }
            pszFmt++;
            continue;
        }
        if(pszFmt == '\0')
        {
            break;
        }
    }
}

iprintf newlib nano

I did not realize it, but “iprintf” was actually part of the c-standard library.  This was a version of printf that was optimized for doing integer printfs (like the previous example).  I read on a few sites around the internet about iprintf, however in newlib-nano iprintf is just an exact wrapper around printf so it yields exactly the same results.

Thanks again for all of the feedback.  If you have any other good ideas please email me or leave a comment on this thread.

Recommended Posts

4 Comments

  1. Hello!
    in code section “if(*pszFmt == ‘d’)”
    there is no need to perform “change(buffer[i])”.

    It can be placed more simple, just place (i+48).

  2. “uint32 iVal, xVal”

    it never used mutually. So xVal can be ommited, iVal used in ‘d’-case instead.

    • sorry, ‘x’-case i mean.


Leave a Reply to Alan Hawse Cancel reply

Your email address will not be published. Required fields are marked *