Jump to content
oPossum

Tiny printf() - C version

Recommended Posts

This is a tiny printf() function that can be used with the chips that come with the Launchpad. Code size is about 640 bytes with CCS.

 

There are 7 format specifiers:

%c - Character

%s - String

%i - signed Integer (16 bit)

%u - Unsigned integer (16 bit)

%l - signed Long (32 bit)

%n - uNsigned loNg (32 bit)

%x - heXadecimal (16 bit)

 

Field width, floating point and other standard printf() features are not supported.

 

printf() code

#include "msp430g2231.h"
#include "stdarg.h"

void putc(unsigned);
void puts(char *);

static const unsigned long dv[] = {
//  4294967296      // 32 bit unsigned max
   1000000000,     // +0
    100000000,     // +1
     10000000,     // +2
      1000000,     // +3
       100000,     // +4
//       65535      // 16 bit unsigned max     
        10000,     // +5
         1000,     // +6
          100,     // +7
           10,     // +8
            1,     // +9
};

static void xtoa(unsigned long x, const unsigned long *dp)
{
   char c;
   unsigned long d;
   if(x) {
       while(x < *dp) ++dp;
       do {
           d = *dp++;
           c = '0';
           while(x >= d) ++c, x -= d;
           putc(c);
       } while(!(d & 1));
   } else
       putc('0');
}

static void puth(unsigned n)
{
   static const char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
   putc(hex[n & 15]);
}

void printf(char *format, ...)
{
   char c;
   int i;
   long n;

   va_list a;
   va_start(a, format);
   while(c = *format++) {
       if(c == '%') {
           switch(c = *format++) {
               case 's':                       // String
                   puts(va_arg(a, char*));
                   break;
               case 'c':                       // Char
                   putc(va_arg(a, char));
                   break;
               case 'i':                       // 16 bit Integer
               case 'u':                       // 16 bit Unsigned
                   i = va_arg(a, int);
                   if(c == 'i' && i < 0) i = -i, putc('-');
                   xtoa((unsigned)i, dv + 5);
                   break;
               case 'l':                       // 32 bit Long
               case 'n':                       // 32 bit uNsigned loNg
                   n = va_arg(a, long);
                   if(c == 'l' &&  n < 0) n = -n, putc('-');
                   xtoa((unsigned long)n, dv);
                   break;
               case 'x':                       // 16 bit heXadecimal
                   i = va_arg(a, int);
                   puth(i >> 12);
                   puth(i >> 8);
                   puth(i >> 4);
                   puth(i);
                   break;
               case 0: return;
               default: goto bad_fmt;
           }
       } else
bad_fmt:    putc(c);
   }
   va_end(a);
}

 

test code

#include "msp430g2231.h"

void serial_setup(unsigned out_mask, unsigned in_mask, unsigned duration);

void printf(char *, ...);

void main(void)
{
   char *s;
   char c;
   int i;
   unsigned u;
   long int l;
   long unsigned n;
   unsigned x;

   // Disable watchdog 
   WDTCTL = WDTPW + WDTHOLD;

   // Use 1 MHz DCO factory calibration
   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   // Setup the serial port
   // Serial out: P1.1 (BIT1)
   // Serial in:  P1.2 (BIT2)
   // Bit rate:   9600 (CPU freq / bit rate)  
   serial_setup(BIT1, BIT2, 1000000 / 9600);

   printf("%s", "\r\n*** printf() test ***\r\n");

   s = "test";
   c = 'X';
   i = -12345;
   u =  12345;
   l = -1234567890;
   n =  1234567890;
   x = 0xABCD;

   printf("String        %s\r\n", s);
   printf("Char          %c\r\n", c);
   printf("Integer       %i\r\n", i);
   printf("Unsigned      %u\r\n", u);
   printf("Long          %l\r\n", l);
   printf("uNsigned loNg %n\r\n", n);
   printf("heX           %x\r\n", x);

   printf("multiple args %s %c %i %u %l %n %x\r\n", s, c, i, u, l, n, x);

   printf("\r\n*** Done ***\r\n");

   for(;;
}

post-2341-135135518296_thumb.png

Share this post


Link to post
Share on other sites
So... this can be used in place of itoa an such for printing the serial port or a LCD display?

 

Yes, you can easily use it with LCD or other character output devices.

 

Just write a putc() function that writes a character to the device.

 

There must also be a puts() function that writes a string to the device, that can be this...

void puts(char *s) { while(*s) putc(*s++); }

 

Integers are always base 10 with this tiny printf().

itoa() typically allows the base to be specified.

Share this post


Link to post
Share on other sites

In IAR workbench use this way:

 

Go to Project -> Options (Alt F7) -> General options -> Library options -> set Printf formatter to Tiny. This works for printf() and also for sprintf().

 

...and that's all, you don't need wrote any code (except of yours version of putchar() to LCD or serial port).

Share this post


Link to post
Share on other sites

I've had a working itoa and ftoa. When using RobG's lcd backpack, I use his sendDataArray to output to the display or TXString from TI's virtual_com_commands to push to a serial terminal. Previously, on other mcu devices, I would use sprintf() but it took a lot of code space. These new functions make more sense now. Thanks guys.

Share this post


Link to post
Share on other sites

Thanks a million oPossum, code looks tight and very compact.

 

I have a couple possibly irritating questions, I don't really get the va_xxx parts:

 

        va_list a;
       va_start(a, format);
       va_end(a);

 

What exactly are these doing and where are they defined? I apologize, I'm not using a 430, and don't have launchpad installed.

 

EDIT: Dohhh! They are stdarg.h

 

Name 	Description 	 	 	 	 	 	compatibility
va_start 	Start iterating arguments with a va_list 	C89
va_arg 	Retrieve an argument 	 	 	 	C89
va_end 	Free a va_list 	 	 	 	 	 	C89
va_copy 	Copy contents of one va_list to another 	C99

Share this post


Link to post
Share on other sites

I agree: thanks oPossum, very useful code.

 

I made a few changes for myself, I'm passing them along

 

const char hexChar[] = "0123456789ABCDEF";
#define hex(m) hexChar[m & 0x0F]
static const unsigned int dv[] = {
//  4294967296      // 32 bit unsigned max
   1000000000,     // +0
    100000000,     // +1
     10000000,     // +2
      1000000,     // +3
       100000,     // +4
//       65535      // 16 bit unsigned max
        10000,     // +5
         1000,     // +6
          100,     // +7
           10,     // +8
            1,     // +9
            0
};

static const unsigned int hv[] = {0x10000000, 0x1000000, 0x100000, 0x10000, 0x1000, 0x100, 0x10, 1, 0};

/* this only works for base==10 or 16 */
char* strAtoI(char * str, unsigned int value, int base) {
unsigned int d;
char c;
const unsigned int *dp;

if (base==16) {
	*str++ = '0'; *str++ = 'x';
	dp = hv;	// start at the top of the array
} else
	dp = dv; // select the base 10 array
while (value < *dp)	// move to the correct decade
	dp++;
do {
	d = *dp++;
	c = 0;    // count binary
	while((value >= d) && (d!=0)) ++c, value -= d;
	*str++ = hex(c);   //convert to ASCII
} while(d>1);
return str;
}

 

I merged the hex decode into the AtoI, because it ended up smaller than a bunch of >>12, >>8, >>4 calls, and much smaller if you include the >>28, >>24, >>20 and >>16 for printing longs.

 

I also merged the 0 case into the algorithm. This gets rid of the "if (x)" case; each array has a 0 terminator to help the algorithm.

 

The code above adds a "0x" to hex digits; rip it out if you don't want it. And the code above prints to a buffer; it's easy

to change it to putc() calls.

 

Cheers,

Pat

Share this post


Link to post
Share on other sites

I use the following asm to convert integers into BCD so they can be printed using the hex print function. This example only converts 16 bit integers to values up to 4 digits, but you could increase the input or output by simply chaining rlc and dadd steps. Within this limitation it's much more efficient both in space and time than a table driven solution. The following is in the GCC3 style, but could easily be adapted to whatever else you're using.

 

int input, output;

__asm__ (
   "clr     %0          \n"
   "setc                \n"
   "rlc     %1          \n"
".Lagain%=:              \n"
   "dadd    %0, %0      \n"
   "rlc     %1          \n"
   "jnz     .Lagain%=   \n"
: "=g" (output), "+g" (input)
: : "cc" );

Share this post


Link to post
Share on other sites

hi

I was trying to use this and cant get it to compile

I am getting these errors:

 

 

run placement fails for object ".bss", size 0x22c (page 0). Available ranges: RAM size: 0x80 unused: 0x4a max hole: 0x48 printf line 0 1323453815793 518

 

placement fails for object ".text", size 0xf0e (page 0). Available ranges: FLASH size: 0x7e0 unused: 0x64e max hole: 0x64e printf line 0 1323453815793 517

 

run placement fails for object ".cio", size 0x120 (page 0). Available ranges: RAM size: 0x80 unused: 0x4a max hole: 0x48 printf line 0 1323453815793 519

 

I was trying to use the example with a terminal on my PC.

I took both programs and put them in the same project then tried to compile, am I doing this wrong?

I am using the g2231chip.

 

any help would be useful

thanks

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×