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 != '#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; } } }'; 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 == '#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; } } }') { 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.
4 Comments
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).
Yup… that works too…
“uint32 iVal, xVal”
it never used mutually. So xVal can be ommited, iVal used in ‘d’-case instead.
sorry, ‘x’-case i mean.