Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 20 of 20

Thread: My Kingdom for printf()

  1. #1
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828

    My Kingdom for printf()

    From this forum, I copied the code shown below, to try to get printf() or kin to work on Teensy3 latest.
    though Serial.print works OK to the USB virtual serial COM port, using the code below causes the CPU to crash/reset.
    I also tried a version of printf() in C that I have. It doesn't work right either. It is all standalone code; doesn't use stdio.h or the va_list and others.
    My kingdom for printf() !! (no floats needed).

    This code seems of AVR vintage, rather than T3, but anyway...
    Code:
          /* 
          vsnprintf() test sketch for Teensy3
          Jan 2, 2013
           */
    
          #undef  __STRICT_ANSI__
    
          #ifdef ARDUINO
          #if (ARDUINO >= 100)
          #include <Arduino.h>
          #else
          #include <WProgram.h>
          #endif
          #endif
    
          #include<stdio.h>
          #include<stdarg.h>
    
          #define FPRINTF_BUFSZE 256
            char buf[FPRINTF_BUFSZE];
            
          void serial_out_len(int len,char* b)
          {
            b[len] = 0; // null terminate string
            Serial.print(b);
          }
    
          int test_vsnprintf(const char format[],...)
          {
            // program memory version of printf - copy of format string and result share a buffer
            // so as to avoid too much memory use
    
            char *fptr, *rptr;
            int rlen = sizeof(buf);
    
            // buf just used for result string
            fptr = (char *)&format[0];
            rptr = &buf[0];
    
            va_list args;
            va_start (args,format);
    
            int len = vsnprintf(rptr, rlen, fptr, args );
    
            va_end (args);
    
            serial_out_len(len,rptr);
    
            return len;
          }

    With apologies for the length, here's the stand-alone printf() that doesn't work right. I suspect it's a GCC command line option that's not in Arduino, considering how stack frames are built, w.r.t. how the variable length arg list is handled by the compiler as setup by Arduino's IDE.

    sorry for the length...Maybe this code is useful to someone else... the printf() and sprintf() are near the end. I renamed them kprintf() because printf() conflicts with the GCC compiler defaults - there needs to be a command line option invoked, something like -no-printf is what I've used on other systems.

    Code:
    void printchar(char **str, int ch)
    {
    	//extern int putchar(int c);
    	if (str) { // if str is not null, store ch into that buffer as in sprintf()
    		**str = ch;
    		++(*str);
    	}
        else
    	Serial.write((char)ch); //CHAR_OUT(ch);
            //(void)uart0Putch(ch);
         
    }
    
    #define PAD_RIGHT 1
    #define PAD_ZERO 2
    
    int prints(char **out, const char *string, int width, int pad)
    {
      int pc = 0, padchar = ' ';
        int len = 0;
        const char *ptr;
    
    
    	if (width > 0) {
    
    		for (ptr = string; *ptr; ++ptr) ++len;
    		if (len >= width) width = 0;
    		else width -= len;
    		if (pad & PAD_ZERO) padchar = '0';
    	}
    
    	if (!(pad & PAD_RIGHT)) {
    		for ( ; width > 0; --width) {
    			printchar (out, padchar);
    			++pc;
    		}
    	}
    
    	for ( ; *string ; ++string) {
    		printchar (out, *string);
    		++pc;
    	}
    	for ( ; width > 0; --width) {
    		printchar (out, padchar);
    		++pc;
    	}
    
    	return pc;
    }
    
    /* the following should be enough for 32 bit int */
    #define PRINT_BUF_LEN 12
    
    
    int printi(char **out, int i, int b, int sg, int width, int pad, int letbase)
    {
    	char print_buf[PRINT_BUF_LEN];
    	register char *s;
    	register int t, neg = 0, pc = 0;
    	register unsigned int u = i;
    
    	if (i == 0) {
    		print_buf[0] = '0';
    		print_buf[1] = '\0';
    		return prints (out, print_buf, width, pad);
    	}
    
    	if (sg && b == 10 && i < 0) {
    		neg = 1;
    		u = -i;
    	}
    
    	s = print_buf + PRINT_BUF_LEN-1;
    	*s = '\0';
    
    	while (u) {
    		t = u % b;
    		if( t >= 10 )
    			t += letbase - '0' - 10;
    		*--s = t + '0';
    		u /= b;
    	}
    
    	if (neg) {
    		if( width && (pad & PAD_ZERO) ) {
    			printchar (out, '-');
    			++pc;
    			--width;
    		}
    		else {
    			*--s = '-';
    		}
    	}
    
    	return pc + prints (out, s, width, pad);
    }
    
    int print(char **out, int *varg)
    {
    	int width, pad;
    	int pc = 0;
    	char *format = (char *)(*varg++);
    	char scr[2];
    
    	for (; *format != 0; ++format) {
    		if (*format == '%') {
    			++format;
    			width = pad = 0;
    			if (*format == '\0') break;
    			if (*format == '%') goto out;
    			if (*format == '-') {
    				++format;
    				pad = PAD_RIGHT;
    			}
    			while (*format == '0') {
    				++format;
    				pad |= PAD_ZERO;
    			}
    			for ( ; *format >= '0' && *format <= '9'; ++format) {
    				width *= 10;
    				width += *format - '0';
    			}
    			if( *format == 's' ) {
    				register char *s = *((char **)varg++);
    				pc += prints (out, s?s:"(null)", width, pad);
    				continue;
    			}
    			if( *format == 'd' ) {
    				pc += printi (out, *varg++, 10, 1, width, pad, 'a'); // can be signed
    				continue;
    			}
    			if( *format == 'x' ) {
    				pc += printi (out, *varg++, 16, 0, width, pad, 'a');
    				continue;
    			}
    			if( *format == 'X' ) {
    				pc += printi (out, *varg++, 16, 0, width, pad, 'A');
    				continue;
    			}
    			if( *format == 'u' ) {
    				pc += printi (out, *varg++, 10, 0, width, pad, 'a'); // unsigned int
    				continue;
    			}
    			if( *format == 'c' ) {
    				/* char are converted to int then pushed on the stack */
    				scr[0] = *varg++;
    				scr[1] = '\0';
    				pc += prints (out, scr, width, pad);
    				continue;
    			}
    		}
    		else {
    		out:
    			printchar (out, *format);
    			++pc;
    		}
    	}
    	if (out) **out = '\0';
    	return pc;
    }
    
    /* assuming sizeof(void *) == sizeof(int) */
    
    int kprintf(const char *format, ...)
    {
    	int *varg = (int *)(&format);
    	return print(0, varg); // 0 means stdout
    }
    
    int ksprintf(char *out, const char *format, ...)
    {
    	int *varg = (int *)(&format);
    	return print(&out, varg);
    }
    
    
    
    
    #define TEST_PRINTF
    
    #ifdef TEST_PRINTF
    
    int testPrintf(void)
    {
    	char *ptr = "Hello world!";
    	char *np = 0;
    	int i = 5;
    	unsigned int bs = sizeof(int)*8;
    	unsigned int x = 0x40000500;
    	int mi;
    
    
    	mi = (1 << (bs-1)) + 1;
    	kprintf("%s\n", ptr);
    	kprintf("printf test\n");
    	kprintf("%s is null pointer\n", np);
    	kprintf("%d = 5\n", i);
    	kprintf("%d = - max int\n", mi);
    	kprintf("char %c = 'a'\n", 'a');
    	kprintf("hex %x = fff\n", 0xfff);
    	kprintf("hex addr %x\n", x);
    	kprintf("hex %02x = 00\n", 0);
    	kprintf("hex %X = 0xABCDEF\n", 0xABCDEF);
    	kprintf("signed %d = unsigned %u = hex %x\n", -3, -3, -3);
    	kprintf("%d %s(s)%", 0, "message");
    	kprintf("\n");
    	kprintf("%d %s(s) with %%\n", 0, "message");
    	ksprintf(buf, "justif: \"%-10s\"\n", "left"); kprintf("%s", buf);
    	ksprintf(buf, "justif: \"%10s\"\n", "right"); kprintf("%s", buf);
    	ksprintf(buf, " 3: %04d zero padded\n", 3); kprintf("%s", buf);
    	ksprintf(buf, " 3: %-4d left justif.\n", 3); kprintf("%s", buf);
    	ksprintf(buf, " 3: %4d right justif.\n", 3); kprintf("%s", buf);
    	ksprintf(buf, "-3: %04d zero padded\n", -3); kprintf("%s", buf);
    	ksprintf(buf, "-3: %-4d left justif.\n", -3); kprintf("%s", buf);
    	ksprintf(buf, "-3: %4d right justif.\n", -3); kprintf("%s", buf);
    
    	return 0;
    }
    
    /*
     * if you compile this file with
     *   gcc -Wall $(YOUR_C_OPTIONS) -DTEST_PRINTF -c printf.c
     * you will get a normal warning:
     *   printf.c:214: warning: spurious trailing `%' in format
     * this line is testing an invalid % at the end of the format string.
     *
     * this should display (on 32bit int machine) :
     *
     * Hello world!
     * printf test
     * (null) is null pointer
     * 5 = 5
     * -2147483647 = - max int
     * char a = 'a'
     * hex ff = ff
     * hex 00 = 00
     * signed -3 = unsigned 4294967293 = hex fffffffd
     * 0 message(s)
     * 0 message(s) with %
     * justif: "left      "
     * justif: "     right"
     *  3: 0003 zero padded
     *  3: 3    left justif.
     *  3:    3 right justif.
     * -3: -003 zero padded
     * -3: -3   left justif.
     * -3:   -3 right justif.
     */
    
    
    //#ifdef __cplusplus
    //}
    //#endif
    
    #endif
    Last edited by stevech; 06-25-2013 at 06:22 AM.

  2. #2
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    well, OK on the kingdom.
    Here's a printf() that runs on the Teensy3. Probably will, but untested on others.
    If FreeRTOS is in use, the code uses a mutex to prevent reentrancy since the C library routines are not, for this case.
    You'll need to #define KPRINTF_BUF_SIZE at say, 128 bytes.
    And #define _FREE_RTOS_ if applicable.
    See the TODO's in the code. One concerns lack of return value for Serial.print for USB vs. documentation for Arduino, unless I'm doing something wrong.

    Code:
    #include <stdarg.h>
    
    char buf[KPRINTF_BUF_SIZE]; 
    
    /*
      * like printf() but output goes to "Serial.print".
      * If FREE_RTOS is defined, a mutex is used to protect from reentrancy.
      * TODO: Success/fail checks for xSemaphoreCreateMutex() and xSemaphoreTake() timeout.
      * Returns number of chars that did not fit into I/O port device buffer or 0.
      * Note:  Serial.print on the USB returns 0 in error, causing a flawed return value here.
    */
    int kprintf(char *format, ...)  {
      int n;
    #ifdef _FREE_RTOS_
      static xSemaphoreHandle kprintfLock = 0; // mutual exclusion for non-reentrant va_start
      
      if (kprintfLock == 0)  { 
        // first time only
        kprintfLock = xSemaphoreCreateMutex();
        xSemaphoreGive(kprintfLock);
      }
      
      if ((xSemaphoreTake(kprintfLock, configTICK_RATE_HZ / 4)) == pdPASS)  // TODO if pdFAIL, do more than just discard the print data.
    #endif
    
      {
        va_list args;
        va_start (args, format);
        vsnprintf(buf, sizeof(buf), format, args); // does not overrun sizeof(buf) including null terminator
        va_end (args);
        // the below assumes that the new data will fit into the I/O buffer. If not, Serial may drop it.
        //   if Serial had a get free buffer count, we could delay and retry. Such does exist at the device class level, but not at this level.
        n = strlen(buf) - Serial.print(buf); // move chars to I/O buffer, freeing up local buf
    #ifdef _FREE_RTOS_
        xSemaphoreGive(kprintfLock);
    #endif
    
      }
      return n; // number of chars unable to fit in device I/O buffer (see bug notice above)
    }

  3. #3
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    cool! I'm using it for modify RF24 library for Teensy3, I will check later if it works. At the first compile I've notice that size of the sketch (the gettinStarted of the RF24 library) enlarged quite a lot, from 12000 to 34000 but will check later what really happen. Again, thanks for sharing!

  4. #4
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    small formatter for (vprintf()), no floats, normally is 2-4KB.
    Arduino seems to sometimes pull in lots of functions libraries that aren't called/used. Maybe even all functions in a referenced library, called or not.

  5. #5
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Location
    Ayer Massachussetts
    Posts
    4,324

    Cool

    I think that you should declare 'buf' static, so that it avoids any conflicts with similarly named structures in other modules.

    If the Serial.print immediately copies the data to its own buffers, I would suggest making buf an auto variable, instead of static, so that the buffer is not permanently allocated. I haven't looked into the Serial.print implementation, but I would hope it would copy the bytes immediately to a local buffer, and then spool them out to the I/O device. Assuming Serial.print also does the mutexes, you then wouldn't need the FREE_RTOS ifdefs.

    Note, that in the AVR library (Teensy 2.0/2.0++), the default *printf functions do not support floating point (%g, %f, %e, etc.). I believe the Arm (Teensy 3.0) library does support them, but if you use floating point, you should test it out. The reason is the floating point support libraries take up a lot of space. Back when I was doing embedded GCC compiler development, on some of the boards, if we used the standard printf, it would easily consume 3/4 flash memory just to support all of the options printf drags in and floating point emulation.

    I've thought about having my own wrapper for tracing, adding support for setting the default output to different devices (standard USB, i2c LCD, bluetooth serial device, etc.). The idea would be my libraries would just do normal tracing without having to have separate #ifdef's for each type of output device.
    Last edited by MichaelMeissner; 07-01-2013 at 12:44 AM.

  6. #6
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    yes, static.
    As to making it a local... I always worry about putting 100+ byte item in a local on these small microprocessors because we don't know the stack size. And versus the stack size per task of FreeRTOS. So instead of making it a local, for FreeRTOS I used the mutex to protect the non-reentrant use of the buf. After the buffer is copied to the serial port driver, the buffer can be freed. One non-RTOS like thing: I see in the serial drivers that there's a while() loop if the output ring buffer on the port is full. I think it should copy all that will fit and return the number that were unsent, instead of looping. Isn't that how stdio works?

    floating point: I rarely use it. Very rare. Usually can get by with longs or fixed point.

    Suggestions welcome.
    I'm so used to printf() that it just had to be, for Arduino.

    I wrote a 4 task program for FreeRTOS on the T3 - they all did printf's. One did so with a random time delay. Seemed to hold up OK.
    Last edited by stevech; 07-01-2013 at 01:16 AM.

  7. #7
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    It's curious because arduino grows from 6K to 8K but teensy3 grows from 13K to 38K! This for the library I'm actually working on.

    a fast example:

    Code:
    #include <stdarg.h>
    
    int kprintf(char *format, ...)  {
      int n;
      char buf[128];
      va_list args;
      va_start (args, format);
      vsnprintf(buf, sizeof(buf), format, args); // does not overrun sizeof(buf) including null terminator
      va_end (args);
      // the below assumes that the new data will fit into the I/O buffer. If not, Serial may drop it.
      // if Serial had a get free buffer count, we could delay and retry. Such does exist at the device class level, but not at this level.
      n = strlen(buf) - Serial.print(buf); // move chars to I/O buffer, freeing up local buf
      return n; // number of chars unable to fit in device I/O buffer (see bug notice above)
    }
    
    void setup() {
      // put your setup code here, to run once:
      Serial.begin(38400);
      //Serial.println("\n\rThis is a test.../\n\r");
      kprintf("\n\rThis is a test.../\n\r");
    }
    
    void loop() {
      // put your main code here, to run repeatedly: 
    
    }
    On Teensy3 --------------------------------
    Using Serial.println will result in a code of 10.192 bytes, using kprintf will grow up at 33.192 bytes!

    On Arduino UNO -----------------------------
    Same code. Serial.println results in 1906 bytes, kprintf grows at 3402 bytes.
    Using printf on arduino will result in 3176 bytes, not too far from the kprintf version.
    Last edited by sumotoy; 07-01-2013 at 02:53 AM.

  8. #8
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    I wonder why T3 grows so much? I recall reading here about a flaw that unneeded functions are pulled from libraries.

    Well, for now, T3's 128KB leaves much room for such.

  9. #9
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Location
    Ayer Massachussetts
    Posts
    4,324

    Cool

    Quote Originally Posted by stevech View Post
    I wonder why T3 grows so much? I recall reading here about a flaw that unneeded functions are pulled from libraries.

    Well, for now, T3's 128KB leaves much room for such.
    I believe it is due to the default libc printf for ARM includes floating point support, while the default libc printf for AVR does not include floating point support. On the Teensy 3.0, since it doesn't have hardware floating point, this will force the software floating point emulation routines to be loaded. If you switch to using the i-* variant of printf that is provided (vsniprintf), it will use the printf that is compiled without the floating point support. I don't recall if it is compiled without the long or long long support.

    Lets see, I compiled a simple test that used a static buffer.
    • In the first test, I just did a Serial.begin and Serial.print of a statically initialized buffer, and it compiled to 10,148 bytes.
    • In the second test, I used sprintf to initialize the buffer with a %d to get printf pulled in, and it compiled to 33,184 bytes.
    • In the third test, I used siprintf instead of sprintf, and it compiled to 17,548 bytes.


    Note, in the original Teensy releases, Paul did not use the options to eliminate unused functions, but he does use it now. However, since the *printf family of functions has no idea what the format string contains, it has to have all of the support for any possible argument, such as %e, and the floating point handling tends to pull in the whole floating point emulator.
    Last edited by MichaelMeissner; 07-01-2013 at 04:24 AM.

  10. #10
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    In other small memory micros I've used, there are two versions of the printf and scanf library- with and without floating point. The linker, and in the case of IAR, the IDE has a user choice of "printfSmall" or some such. If %f is used at runtime with the small library, you get junk. Maybe that's what should be done for the T3?

    With 128KB, not an issue, assuming no big use of RAM for the larger library.

  11. #11
    Senior Member sumotoy's Avatar
    Join Date
    Nov 2012
    Location
    Venezia, Italia
    Posts
    421
    Really interesting this journey into those mysterious c commands; now I know that exist a restrict version of sprintf. Just spent several interesting hours into c library pages to discover new stuff.
    So the equivalent restricted version of vsnprintf can be..... vsniprintf? Seems the compiler doesn't kick me off and programs fall off at 21k. RF24 library use of sprintf doesn't seems use any floating point so I can live with that!

  12. #12
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Location
    Ayer Massachussetts
    Posts
    4,324
    Quote Originally Posted by stevech View Post
    In other small memory micros I've used, there are two versions of the printf and scanf library- with and without floating point. The linker, and in the case of IAR, the IDE has a user choice of "printfSmall" or some such. If %f is used at runtime with the small library, you get junk. Maybe that's what should be done for the T3?
    Back in the 1990's, when I worked for Cygnus Solutions, I thought it might be a good idea if the GCC compiler targeting embedded boards could detect if you only called printf, and friends with integral/char small types and automatically switch to call the i versions of the printf functions that were in the 'newlib' library that Cygnus used for the libc/libm on embedded ports. The Arm libraries at least are newlib based, but I'm not as sure about the history of the AVR libraries. However, the perception was that real embedded programmers did not use printf, and it wasn't worth it to spend the time doing this optimization.

  13. #13
    Senior Member+ MichaelMeissner's Avatar
    Join Date
    Nov 2012
    Location
    Ayer Massachussetts
    Posts
    4,324
    Quote Originally Posted by sumotoy View Post
    Really interesting this journey into those mysterious c commands; now I know that exist a restrict version of sprintf. Just spent several interesting hours into c library pages to discover new stuff.
    So the equivalent restricted version of vsnprintf can be..... vsniprintf? Seems the compiler doesn't kick me off and programs fall off at 21k. RF24 library use of sprintf doesn't seems use any floating point so I can live with that!
    The only thing to make sure of if you switch one library to use the i-version, to switch all libraries. Otherwise, you might link in both sets of libraries.

    Yes, you would want to use vsniprintf instead of vsnprintf.

  14. #14
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    Common use of printf() in embedded for debugging sprintf for LCDs and so on.
    JTAG and breakpoints eliminate printf() for debugging in some situations, but even with it, debugging using printf() as a "trace of flow" is quite useful.
    Production build might omit use of printf(). But in some projects, I left some of it in so that people can plug in to serial port and get diagnostic info if an odd case shows up.

    I also originally posted printf() code that is just 200 lines of C code and can be used instead of library code.

  15. #15
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    27,057
    On my very low priority to-do list is replacing newlib's memory-hogging printf & scanf functions with smaller, more efficient versions.

    The ones in avr-libc are very small and efficient. They were written with small AVR chips in mind. The ones in newlib, which Teensy3 is currently using, were written with Linux-scale ARM systems in mind. They're big, feature-packed versions.

    So far, this just hasn't been much of a problem. It is a big depressing to see 20k of space suddenly get used up, but so far I've not heard of anyone getting anywhere near the 128k limit. I'm sure that situation will gradually change, especially over the next year as more Teensy3 specific libraries appear with awesome features like audio processing that consume significant memory.

    But for now, the Teensy3 upload is so fast that the extra 20k doesn't slow things down hardly at all (try the same on any Arduino brand board, even the Arduino Due), and I've not yet heard of any projects actually running out of code space. When it becomes a bigger problem, I'll move this up on my priority list.

    Also on my to-do list, and also at a relatively low priority, are improving the compiler settings and some of the code to reduce the amount of stuff that gets needlessly included in every compile. It's only a few kbytes... the default RAM usage is actually a bigger concern. I do have this stuff on my to-do list, but for now I'm focusing my efforts on stuff that has more immediate benefits for lots of people's projects.

  16. #16
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    very good.
    I posted some tiny printf() code earlier up if you wish to use it. Works OK. Small.

  17. #17
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    27,057
    Quote Originally Posted by stevech View Post
    I posted some tiny printf() code earlier up if you wish to use it. Works OK. Small.
    Thanks.

    One quick question, is this open source (and are you the original author), and if so, which license should it use? It's a minor detail, but important if it's going to be distributed to lots of people who will use it in their own projects which might be commercial.

  18. #18
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    I'll PM. It is open source, but...

  19. #19

    Serial.printf

    Old thread. But I think it might be good to write this here for other forum visitors.
    As of now (I don't know how long it's been available) printf is included in the Print class for Teensy 3.
    This means that one can access printf by calling:
    Code:
    Serial.printf(blablabla)
    when compiling and uploading to the teensy 3 (and 3.1).

    Simply do a find and replace in code that uses printf (like Maniacbug's RF24 library for example)

    Cheers.

  20. #20
    Senior Member
    Join Date
    Jun 2013
    Location
    So. Calif
    Posts
    2,828
    yes, and I've done this instead of editing

    #define printf Serial.printf

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •