Arduino_core_stm32: How to implement printf from "newlib nano + Float Printf"

Created on 6 Aug 2019  路  12Comments  路  Source: stm32duino/Arduino_Core_STM32

It became clear that compiling with newlib nano + Float Printf did not automatically extend the Stream or Print class with printf methods. It is not clear what needs to be done to enable printf though I suspect the form SerialX.printf() will never work. I'm not sure where printf "prints" to but how can it be redirected to a Serial device?

Answered Question

All 12 comments

It is possible, have used for this small patch, can paste it late evening.
As disadvantage It use static buffer, but better than nothing.

It would also be nice for STM to (also) provide an "official" solution here.

we can try to ask @fpistm to integrate it into repo

It would be good since there is an expectation that selecting the menu options will enable this functionality.

@pkourany this is also kind of idea: https://gist.github.com/ridencww/4e5d10097fee0b0f7f6b

I have working "lite" printf implementation that was developed for the STM32 Maple Core but I would rather not deviate from the STM32 Core. Worst case, we do a PR for print.x in the Core code.

can't find this "lite" version ..., ok found it in Rogers core

@uzi18, it is not in Roger's code. I added it based on a contribution from a member on STMDuino:
... added to Print.cpp

#ifdef LEAN_PRINTF
//------------------------------------------------
#ifdef toupper
#undef toupper
#endif
#ifdef tolower
#undef tolower
#endif
#ifdef islower
#undef islower
#endif
#ifdef isdigit
#undef isdigit
#endif

#define toupper(c) ((c)&=0xDF)
#define tolower(c) ((c)|=0x20)
#define islower(c) ((unsigned char)c >= (unsigned char)'a' && (unsigned char)c <= (unsigned char)'z')
#define isdigit(c) ((unsigned char)c >= (unsigned char)'0' && (unsigned char)c <= (unsigned char)'9')

typedef union {
    unsigned char  byte[5];
    long           l;
    unsigned long  ul;
    float          f;
    const char     *ptr;
} value_t;


size_t Print::printDigit(unsigned char n, bool lower_case)
{
    register unsigned char c = n + (unsigned char)'0';

    if (c > (unsigned char)'9') {
        c += (unsigned char)('A' - '0' - 10);
        if (lower_case)
            c += (unsigned char)('a' - 'A');
    }
    return write(c);
}

static void calculateDigit (value_t* value, unsigned char radix)
{
    unsigned long ul = value->ul;
    unsigned char* pb4 = &value->byte[4];
    unsigned char i = 32;

    do {
        *pb4 = (*pb4 << 1) | ((ul >> 31) & 0x01);
        ul <<= 1;

        if (radix <= *pb4 ) {
            *pb4 -= radix;
            ul |= 1;
        }
    } while (--i);
    value->ul = ul;
}

size_t Print::printf(const char *format, ...)
{
    va_list ap;
    bool   left_justify;
    bool   zero_padding;
    bool   prefix_sign;
    bool   prefix_space;
    bool   signed_argument;
    bool   char_argument;
    bool   long_argument;
    bool   lower_case;
    value_t value;
    int charsOutputted;
    bool   lsd;

    unsigned char radix;
    unsigned char  width;
    signed char decimals;
    unsigned char  length;
    char           c;
    // reset output chars
    charsOutputted = 0;

    va_start(ap, format);
    while( c=*format++ ) {
        if ( c=='%' ) {
            left_justify    = 0;
            zero_padding    = 0;
            prefix_sign     = 0;
            prefix_space    = 0;
            signed_argument = 0;
            char_argument   = 0;
            long_argument   = 0;
            radix           = 0;
            width           = 0;
            decimals        = -1;

get_conversion_spec:
            c = *format++;

            if (c=='%') {
                charsOutputted+=write(c);
                continue;
            }

            if (isdigit(c)) {
                if (decimals==-1) {
                    width = 10*width + c - '0';
                    if (width == 0) {
                        zero_padding = 1;
                    }
                } else {
                    decimals = 10*decimals + c - '0';
                }
                goto get_conversion_spec;
            }
            if (c=='.') {
                if (decimals==-1)
                    decimals=0;
                else
                    ; // duplicate, ignore
                goto get_conversion_spec;
            }
            if (islower(c)) {
                c = toupper(c);
                lower_case = 1;
            } else
                lower_case = 0;

            switch( c ) {
            case '-':
                left_justify = 1;
                goto get_conversion_spec;
            case '+':
                prefix_sign = 1;
                goto get_conversion_spec;
            case ' ':
                prefix_space = 1;
                goto get_conversion_spec;
            case 'B': /* byte */
                char_argument = 1;
                goto get_conversion_spec;
//      case '#': /* not supported */
            case 'H': /* short */
            case 'J': /* intmax_t */
            case 'T': /* ptrdiff_t */
            case 'Z': /* size_t */
                goto get_conversion_spec;
            case 'L': /* long */
                long_argument = 1;
                goto get_conversion_spec;

            case 'C':
                if( char_argument )
                    c = va_arg(ap,char);
                else
                    c = va_arg(ap,int);
                charsOutputted+=write(c);
                break;

            case 'S':
                value.ptr = va_arg(ap,const char *);

                length = strlen(value.ptr);
                if ( decimals == -1 ) {
                    decimals = length;
                }
                if ( ( !left_justify ) && (length < width) ) {
                    width -= length;
                    while( width-- != 0 ) {
                        charsOutputted+=write(' ');
                    }
                }

                while ( (c = *value.ptr)  && (decimals-- > 0)) {
                    charsOutputted+=write(c);
                    value.ptr++;
                }

                if ( left_justify && (length < width)) {
                    width -= length;
                    while( width-- != 0 ) {
                        charsOutputted+=write(' ');
                    }
                }
                break;

            case 'D':
            case 'I':
                signed_argument = 1;
                radix = 10;
                break;

            case 'O':
                radix = 8;
                break;

            case 'U':
                radix = 10;
                break;

            case 'X':
                radix = 16;
                break;

            default:
                // nothing special, just output the character
                charsOutputted+=write(c);
                break;
            }

            if (radix != 0) {
                unsigned char store[6];
                unsigned char *pstore = &store[5];

                if (char_argument) {
                    value.l = va_arg(ap, char);
                    if (!signed_argument) {
                        value.l &= 0xFF;
                    }
                } else if (long_argument) {
                    value.l = va_arg(ap, long);
                } else { // must be int
                    value.l = va_arg(ap, int);
                    if (!signed_argument) {
                        value.l &= 0xFFFF;
                    }
                }

                if ( signed_argument ) {
                    if (value.l < 0)
                        value.l = -value.l;
                    else
                        signed_argument = 0;
                }

                length=0;
                lsd = 1;

                do {
                    value.byte[4] = 0;
                    calculateDigit(&value, radix);
                    if (!lsd) {
                        *pstore = (value.byte[4] << 4) | (value.byte[4] >> 4) | *pstore;
                        pstore--;
                    } else {
                        *pstore = value.byte[4];
                    }
                    length++;
                    lsd = !lsd;
                } while( value.ul );
                if (width == 0) {
                    // default width. We set it to 1 to output
                    // at least one character in case the value itself
                    // is zero (i.e. length==0)
                    width = 1;
                }
                /* prepend spaces if needed */
                if (!zero_padding && !left_justify) {
                    while ( width > (unsigned char) (length+1) ) {
                        charsOutputted+=write(' ');
                        width--;
                    }
                }
                if (signed_argument) { // this now means the original value was negative
                    charsOutputted+=write('-');
                    // adjust width to compensate for this character
                    width--;
                } else if (length != 0) {
                    // value > 0
                    if (prefix_sign) {
                        charsOutputted+=write('+');
                        // adjust width to compensate for this character
                        width--;
                    } else if (prefix_space) {
                        charsOutputted+=write(' ');
                        // adjust width to compensate for this character
                        width--;
                    }
                }
                /* prepend zeroes/spaces if needed */
                if (!left_justify) {
                    while ( width-- > length ) {
                        charsOutputted+=write( zero_padding ? '0' : ' ');
                    }
                } else {
                    /* spaces are appended after the digits */
                    if (width > length)
                        width -= length;
                    else
                        width = 0;
                }
                 /* output the digits */
                while( length-- ) {
                    lsd = !lsd;
                    if (!lsd) {
                        pstore++;
                        value.byte[4] = *pstore >> 4;
                    } else {
                        value.byte[4] = *pstore & 0x0F;
                    }
                    charsOutputted+=printDigit(value.byte[4], lower_case);
                }
            }
        } else {
            charsOutputted+=write(c);
        }
    }
    va_end(ap);
    return (size_t)charsOutputted;
}


#endif

Hi @pkourany
your issue is to extend class with ::printf() method ? Right?

About standard C printf, it is supported thanks the newlib nano implementation.
By default there is no float support in this printf, to enable it you have to enable it explicitely using the option: newlib nano + Float Printf

See newlib-nano_readme

Newlib-nano is different from newlib in addition to the libraries' name.
Formatted input/output of floating-point number are implemented as weak symbol.
If you want to use %f, you have to pull in the symbol by explicitly specifying"-u" command option.
-u _scanf_float
-u _printf_float

e.g. to output a float, the command line is like:
$ arm-none-eabi-gcc --specs=nano.specs -u _printf_float $(OTHER_LINK_OPTIONS)

By default C printf will output on the DEBUG_UART:
https://github.com/stm32duino/Arduino_Core_STM32/blob/a8350866daf5abfd97b7cd76ec514a1f22cace39/cores/arduino/stm32/uart.c#L587
this is done thanks the _write() in syscall which can be redefined as weak:
https://github.com/stm32duino/Arduino_Core_STM32/blob/a8350866daf5abfd97b7cd76ec514a1f22cace39/cores/arduino/syscalls.c#L84-L85

It is also used by the core_debug feature to print some debug message (disabled per default).

@fpistm, I defined DEBUG_UART in my variant.h file and used printf() instead of Serial.printf() and all compiled. I will be testing and reporting back. Thanks!

my patch:

diff --git a/cores/arduino/Print.h b/cores/arduino/Print.h
index 05b585ea..68058b1f 100644
--- a/cores/arduino/Print.h
+++ b/cores/arduino/Print.h
@@ -103,6 +103,44 @@ class Print {
     void println(uint64_t, uint8_t = DEC);
     void print(uint64_t, uint8_t = DEC);
 #endif
+
+#include <stdarg.h>
+#define PRINTF_BUF 80 // define the tmp buffer size (change if desired)
+   void printf(const char *format, ...)
+   {
+   char buf[PRINTF_BUF];
+   va_list ap;
+        va_start(ap, format);
+        vsnprintf(buf, sizeof(buf), format, ap);
+        for(char *p = &buf[0]; *p; p++) // emulate cooked mode for newlines
+        {
+                if(*p == '\n')
+                        write('\r');
+                write(*p);
+        }
+        va_end(ap);
+   }
+#ifdef F // check to see if F() macro is available
+   void printf(const __FlashStringHelper *format, ...)
+   {
+   char buf[PRINTF_BUF];
+   va_list ap;
+        va_start(ap, format);
+#ifdef __AVR__
+        vsnprintf_P(buf, sizeof(buf), (const char *)format, ap); // progmem for AVR
+#else
+        vsnprintf(buf, sizeof(buf), (const char *)format, ap); // for the rest of the world
+#endif
+        for(char *p = &buf[0]; *p; p++) // emulate cooked mode for newlines
+        {
+                if(*p == '\n')
+                        write('\r');
+                write(*p);
+        }
+        va_end(ap);
+   }
+#endif
+
 };

 #endif

@fpistm, I got everything working with and without float support. Thanks!

Was this page helpful?
0 / 5 - 0 ratings