/*-------------------------------------------------------------------------
   printf_tiny.c - Tiny printf routine for use with sdcc/mcs51

   Copyright (C) 2004, Paul Stoffregen, paul@pjrc.com

   This library is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this library; see the file COPYING. If not, write to the
   Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
   MA 02110-1301, USA.

   As a special exception, if you link this library with other files,
   some of which are compiled with SDCC, to produce an executable,
   this library does not by itself cause the resulting executable to
   be covered by the GNU General Public License. This exception does
   not however invalidate any other reasons why the executable file
   might be covered by the GNU General Public License.
-------------------------------------------------------------------------*/

/*
 * This tiny printf uses minimal code space, and it is fully reentrant
 * and register bank neutral (usually safe to call from within an
 * interrupt routine).  Code size is under 270 bytes.  Only one library
 * function is called (_gptrget, 41 bytes), in addition to calls to
 * putchar().
 *
 * Five simple formats are supported
 *
 *	%d	signed 16 bit integer decimal (-32768 to 32767)
 *	%u	unsigned 16 bit integer decimal (0 to 65535)
 *	%s	string, takes a 24 bit generic pointer
 *	%c	character.  You must explicitly cast to char in SDCC
 *	%x	16 bit integer in hex (0 to FFFF)
 *
 * For a more complete printf that supports longs, floating point and
 * field width, try using printf_fast() or printf_large().
 */


/* This removes the negative number code, causing "%d" to be the same
   as "%u".  If you don't care about printing negative numbers, this
   will save 21 bytes of code. */
/* #define ALWAYS_PRINT_UNSIGNED */

/* Directly output characters to the serial port using simple polling,
   rather than calling putchar().  This saves 14 bytes, plus the size
   of putchar. */
/* #define DIRECT_SERIAL_OUTPUT */



/* extern void putchar(char ); */


#define print_zero_flag PSW.5


#if !defined(__SDCC_mcs51) || defined(__SDCC_USE_XSTACK) || defined(_SDCC_NO_ASM_LIB_FUNCS)
  /* Does printf_tiny really work on ds390 and ds400?
     If it does, enable them in the line above */
  #if defined(__SDCC_USE_XSTACK)
    #warning "printf_tiny not built, does not support --xstack"
  #elif defined(_SDCC_NO_ASM_LIB_FUNCS)
    #warning "printf_tiny not built, _SDCC_NO_ASM_LIB_FUNCS defined"
  #endif
  /* Disable "ISO C forbids an empty source file" warning message */
  #pragma disable_warning 190
#else /* defines are compatible with printf_tiny */



void printf_tiny(__code const char *fmt, ...) __reentrant
{
	fmt;	/* suppress unreferenced variable warning */

	__asm

printf_begin:
	mov	a, _bp		/* r0 will point to va_args (stack) */
	add	a, #253
	mov	r0, a		/* r0 points to MSB of fmt */
	mov	dph, @r0
	dec	r0
	mov	dpl, @r0	/* dptr has address of fmt */
	dec	r0


printf_main_loop:
	clr	a
	movc	a, @a+dptr	/* get next byte of fmt string */
	inc	dptr
	add	a, #256 - 37
	jz	printf_format	/* check for '%' */
	add	a, #37
	jz	printf_end_h
	lcall	printf_putchar
	sjmp	printf_main_loop
printf_end_h:
	ljmp	printf_end


printf_format:
	setb	print_zero_flag
	clr	a
	movc	a, @a+dptr	/* get next byte of data format */
	inc	dptr
	push	dph
	push	dpl


printf_format_s:
	/*cjne	a, #'s', printf_format_c*/
	cjne	a, #115, printf_format_c
printf_string:
	/* print a string... just grab each byte with __gptrget */
	/* the user much pass a 24 bit generic pointer */
	mov	b, @r0		/* b has type of address (generic *) */
	dec	r0
	mov	dph, @r0
	dec	r0
	mov	dpl, @r0	/* dptr has address of user's string */
	dec	r0
printf_str_loop:
	lcall	__gptrget
	jz	printf_format_done
	inc	dptr
	lcall	printf_putchar
	sjmp	printf_str_loop


printf_format_c:
	/*cjne	a, #'c', printf_format_d*/
	cjne	a, #99, printf_format_d
	dec	r0
	mov	a, @r0		/* Acc has the character to print */
	dec	r0
	lcall	printf_putchar
	sjmp	printf_format_done


printf_format_d:
	/*cjne	a, #'d', printf_format_u*/
	cjne	a, #100, printf_format_x
#ifndef ALWAYS_PRINT_UNSIGNED
	mov	a, @r0
	jnb	acc.7, printf_uint
	dec	r0
	mov	a, @r0
	cpl	a
	add	a, #1
	mov	@r0, a
	inc	r0
	mov	a, @r0
	cpl	a
	addc	a, #0
	mov	@r0, a
	/*mov	a, #'-'*/
	mov	a, #45
	lcall	printf_putchar
#endif
	sjmp	printf_uint


printf_format_x:
	/*cjne	a, #'x', printf_format_u*/
	cjne	a, #120, printf_format_u
	mov	dph, @r0
	dec	r0
	mov	dpl, @r0
	dec	r0
	clr	a
printf_hex:
	lcall	printf_phex_lsn
	mov	a, dph
	lcall	printf_phex_msn
	mov	a, dph
	lcall	printf_phex_lsn
	mov	a, dpl
	lcall	printf_phex_msn
	mov	a, dpl
	lcall	printf_phex_lsn
	jnb	print_zero_flag, printf_format_done
	/*mov	a, #'0'*/
	mov	a, #48
	lcall	printf_putchar
printf_format_done:
	pop	dpl
	pop	dph
	ljmp	printf_main_loop


printf_format_u:
	/*cjne	a, #'u', printf_format_done*/
	cjne	a, #117, printf_format_done
printf_uint:
	mov	a, @r0
	mov	r2, a
	dec	r0
	mov	a, @r0
	mov	r1, a
	dec	r0
printf_int2bcd:
	mov	r4, #16
	mov	r5, #39
	lcall	div_by_sub
	mov	r7, a
	mov	r4, #232
	mov	r5, #3
	lcall	div_by_sub
	swap	a
	mov	dph, a
	mov	r4, #100
	mov	r5, #0
	lcall	div_by_sub
	orl	dph, a
	mov	a, r1
	mov	b, #10
	div	ab
	swap	a
	orl	a, b
	mov	dpl, a
	mov	a, r7
	sjmp	printf_hex


	/* Divide r2/r1 by r5/r4 using successive subtraction
	   returns quotient in r2/r1 and remainder in acc. */
div_by_sub:
	mov     r3, #0
div_by_sub_loop:
	inc     r3
        clr     c
        mov     a, r1
        subb    a, r4
        mov     r1, a
        mov     a, r2
        subb    a, r5
        mov     r2, a
        jnc     div_by_sub_loop
        dec     r3
        mov     a, r1
        add     a, r4
        mov     r1, a
        mov     a, r2
        addc    a, r5
        mov     r2, a
        mov     a, r3
        ret


	/* print a hex digit, either upper 4 bit (msn) or lower 4 bits (lsn) */
printf_phex_msn:
	swap	a
printf_phex_lsn:
	anl	a, #15
	jnz	printf_phex_ok
	jb	print_zero_flag, printf_ret
printf_phex_ok:
	clr	print_zero_flag
	add	a, #0x90
	da	a
	addc    a, #0x40
	da	a
printf_putchar:
#ifdef DIRECT_SERIAL_OUTPUT
	jnb	ti, printf_putchar
	clr	ti
	mov	sbuf, a
#else
	push	dph
	push	dpl
	push	b
	mov	dpl, a
	mov	a, r0
	push	acc
	lcall	_putchar
	pop	acc
	mov	r0, a
	pop	b
	pop	dpl
	pop	dph
#endif
printf_ret:
	ret


printf_end:
	__endasm;
}


#endif /* defines compatible with printf_tiny */
