Spare Time Labs 2.0

Welcome


EazyCNC


jDraft 2.0




PureJavaComm


PIC CDC ACM


Weather


Ten-Buck Furnace



H8S Bootloader



Camera Calibration



Multitouch



Myford VFD


Fun with HC08


bl08


printf II


Java Goes Native



Densitometer


printf


jApp


Igloo


New Furnace


New Furnace
Part II


Linux 101


H8S/gcc


Quickie


Gas Fired Furnace


Down Memory Lane


Exlibris


Wheel Patterns


Glitches


CHIP-8


eDice


Animato


jDraft


JNA Benchmark


Contact Info
Created 1.3.2008

A tiny printf revisited

Some years ago I wrote a small printf function for my own purposes to be used in embedded application development as described on this old page.

Recently I had reason revisit this issue when playing around with an even smaller micro controller, the HC08. Somewhat to my supprice the memory foot print of my function was rather large at around about 2.5 kBytes. This prompted me to investigate the code density issue of the compilers I was using a litle bit (no pun intended) further.

I'm fond of the Small Device C Compiler, SDCC among friends, because I want to use my Mac for everything and it is almost the only C compiler for 8 bit compilers that runs on Mac OS X. It is Free software and has been ported to Mac OS and Linux, with precompiled binaries available, which is always a great bonus, compiling Free software being what it is. (Mind you I did get a clean compile almost out of the box with SDCC, so cudos to the developers.) Windows user have a much wider choice of compilers as many tool vendors dish out cripled demo version of their products for free.

But back to the main story here.

After examing the code produced by SDCC I rewrote my tiny printf with a view to optimize it for small 8 bit micro controllers, with a few hundred bytes of RAM and some kilobytes of Flash. In doing this I had to sacrifice some of the features of the original code. Because SDCC by default generates non re-entrant code and re-entrancy for a printf function in a small device is of questinable value anyway, re-entrancy was the first feature to go. I also sacrificed the rather nice implementation of sprintf and the function pointer based call back to the putchar function, opting instead to statically link to an extern function. Also the support for long int had to go.

You can download the optimized tiny printf function here or browse it at the end of this page.

After optimization the code is now half its size in lines and fits in about 1.1 kB of Flash and uses some 20 bytes of RAM memory.

Note that that the code is optimized for size, not speed. If your are interested in optimizing the speed Douglas W Jones makes good reading.

There is a much faster tiny printf function and a much smaller printf function that comes with SDCC but they are written in assembly language. Now I'm a fairly dab hand at assembly language having grown up together with the microprocessors in the 70's and 80's but at this day and age I try to avoid it unless absolutely necessary. With ever increasing memory sizes and clock speeds even in 8 bit microcontrollers, I see little point in handcrafting something like printf in assembly language.

For fun I downloaded and installed the CodeWarrior compiler from Freescale site and compiled the revised tiny printf with it.

Here are the code sizes produced by SDCC and CodeWarrior for the original printf and the optimesed version

Compiler Optimized Original
CodeWarrior 589(832) 1332(1683)
SDCC 1188 (1619) 2485 (3210)

The number in parentheses includes the runtime libries pulled in by the compiler/linker.

The results are based on SDCC version 2.7.4 and CodeWarrior 5.0.14.6124 compiler DLL's .

Disappointingly the code size produced with SDCC is almost twice that of CW, I guess there is no such thing as free lunch after all, but hey, SDCC is Free so maybe I'll get around to improving it's code generation to rival that of CodeWarriors!

The code is GPL and BSD lincensed, download the BSD licensed version from the link above or use the GPL licensed code from this page below.

Well, that's about it folks, hope someone makes some use of the information presented here.

I'll include the source code for the revised printf below. To use it you need to link it with your project and provide your own putchar function.

br Kusti

PS A user contributed this file BM-PC MDA direct screens.


Here comes the header file:

/*
File: printf.h

Copyright (C) 2004,2008  Kustaa Nyholm

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

This library is realy just two files: 'printf.h' and 'printf.c'.

They provide a simple and small (+100 loc) printf functionality to 
be used in embedded systems.

I've found them so usefull in debugging that I do not bother with a 
debugger at all.

They are distributed in source form, so to use them, just compile them 
into your project. 

The formats supported by this implementation are: 'd' 'u' 'c' 's' 'x' 'X'.

Zero padding and field width (limited to 255) are also supported.

The memory foot print of course depends on the target cpu, compiler and 
compiler options, but a rough guestimate (based on a HC08 target) is about 
600 - 1100 bytes for code and some twenty bytes of static data.  Note
that this printf is not re-entrant. 

Note that the code expects that int size is 16 bits, and that char is
8 bits.

To use the printf you need to supply your own character output function, 
something like :

void putchar (char c)
	{
	while (!SERIAL_PORT_EMPTY) ;
	SERIAL_PORT_TX_REGISTER = c;
	}


The printf function is actually a macro that translates to 'tfp_printf'. 
This makes it possible to use it along with 'stdio.h' printf's in a single 
source file. You just need to undef the names before you include the 'stdio.h'.
Note that these are not function-like macros, so if you have variables
or struct members with these names, things will explode in your face.
Without variadic macros this is the best we can do. If it is a  problem 
just give up the macros and use the functions directly or rename them.

For further details see source code.

regs Kusti, 26.2.2008
*/


#ifndef __TFP_PRINTF__

#define __TFP_PRINTF__


#include <stdarg.h>


void tfp_printf(char *fmt, ...);

#define printf tfp_printf 


#endif





And here is the actual function:

/*
File: printf.c

Copyright (C) 2004,2008  Kustaa Nyholm

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
//putchar#include <stdio.h>

#include "printf.h"


void putchar(char c);

static char* bf;
static char buf[12];
static unsigned int num;
static char uc;
static char zs;

static void out(char c) {
    *bf++ = c;
    }

static void outDgt(char dgt) {
	out(dgt+(dgt<10 ? '0' : (uc ? 'A' : 'a')-10));
	zs=1;
    }
	
static void divOut(unsigned int div) {
    unsigned char dgt=0;
	num &= 0xffff; // just for testing the code  with 32 bit ints
	while (num>=div) {
		num -= div;
		dgt++;
		}
	if (zs || dgt>0) 
		outDgt(dgt);
    }	

void tfp_printf(char *fmt, ...)
	{
	va_list va;
	char ch;
	char* p;
	
	va_start(va,fmt);
	
	while ((ch=*(fmt++))) {
		if (ch!='%') {
			putchar(ch);
			}
		else {
			char lz=0;
			char w=0;
			ch=*(fmt++);
			if (ch=='0') {
				ch=*(fmt++);
				lz=1;
				}
			if (ch>='0' && ch<='9') {
				w=0;
				while (ch>='0' && ch<='9') {
					w=(((w<<2)+w)<<1)+ch-'0';
					ch=*fmt++;
					}
				}
			bf=buf;
			p=bf;
			zs=0;
			switch (ch) {
				case 0: 
					goto abort;
				case 'u':
				case 'd' : 
					num=va_arg(va, unsigned int);
					if (ch=='d' && (int)num<0) {
						num = -(int)num;
						out('-');
						}
					divOut(10000);
					divOut(1000);
					divOut(100);
					divOut(10);
					outDgt(num);
					break;
				case 'x': 
				case 'X' : 
				    uc= ch=='X';
					num=va_arg(va, unsigned int);
					divOut(0x1000);
					divOut(0x100);
					divOut(0x10);
					outDgt(num);
					break;
				case 'c' : 
					out((char)(va_arg(va, int)));
					break;
				case 's' : 
					p=va_arg(va, char*);
					break;
				case '%' :
					out('%');
				default:
					break;
				}
			*bf=0;
			bf=p;
			while (*bf++ && w > 0)
				w--;
			while (w-- > 0) 
				putchar(lz ? '0' : ' ');
			while ((ch= *p++))
				putchar(ch);
			}
		}
	abort:;
	va_end(va);
	}



C-code converted to html with the wonderfull code2html unix script!