diff --git a/py/formatfloat.c b/py/formatfloat.c
index 83aee1ef7494c10af3227225387f8a40f795cd1a..b1c62fa6d5e4915657500734eb21cb35c3887a08 100644
--- a/py/formatfloat.c
+++ b/py/formatfloat.c
@@ -14,6 +14,7 @@
 ***********************************************************************/
 
 #include <stdlib.h>
+#include <stdint.h>
 
 #include "mpconfig.h"
 
diff --git a/py/obj.c b/py/obj.c
index 95052d16d2730f00dfaf871cb9e3e426e153e48d..43863667ca802bd464fcc7bfec9bb036d50f5ead 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -198,6 +198,10 @@ machine_int_t mp_obj_get_int(mp_obj_t arg) {
         return MP_OBJ_SMALL_INT_VALUE(arg);
     } else if (MP_OBJ_IS_TYPE(arg, &mp_type_int)) {
         return mp_obj_int_get_checked(arg);
+#if MICROPY_ENABLE_FLOAT
+    } else if (MP_OBJ_IS_TYPE(arg, &mp_type_float)) {
+        return mp_obj_float_get(arg);
+#endif
     } else {
         nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
     }
diff --git a/py/objstr.c b/py/objstr.c
index 936542b0e991193aa003242bb3d07996b30a695f..8389bb0bdfb3043ec2fff88ba92a46e189a07ddb 100644
--- a/py/objstr.c
+++ b/py/objstr.c
@@ -9,6 +9,7 @@
 #include "obj.h"
 #include "runtime0.h"
 #include "runtime.h"
+#include "pfenv.h"
 
 typedef struct _mp_obj_str_t {
     mp_obj_base_t base;
@@ -492,28 +493,389 @@ STATIC mp_obj_t str_strip(uint n_args, const mp_obj_t *args) {
     return mp_obj_new_str(orig_str + first_good_char_pos, stripped_len, false);
 }
 
+// Takes an int arg, but only parses unsigned numbers, and only changes
+// *num if at least one digit was parsed.
+static int str_to_int(const char *str, int *num) {
+    const char *s = str;
+    if (unichar_isdigit(*s)) {
+        *num = 0;
+        do {
+            *num = *num * 10 + (*s - '0');
+            s++;
+        }
+        while (unichar_isdigit(*s));
+    }
+    return s - str;
+}
+
+static bool isalignment(char ch) {
+    return ch && strchr("<>=^", ch) != NULL;
+}
+
+static bool istype(char ch) {
+    return ch && strchr("bcdeEfFgGnosxX%", ch) != NULL;
+}
+
+static bool arg_looks_integer(mp_obj_t arg) {
+    return MP_OBJ_IS_TYPE(arg, &mp_type_bool) || MP_OBJ_IS_INT(arg);
+}
+
+static bool arg_looks_numeric(mp_obj_t arg) {
+    return arg_looks_integer(arg)
+#if MICROPY_ENABLE_FLOAT
+        || MP_OBJ_IS_TYPE(arg, &mp_type_float)
+#endif
+    ;
+}
+
 mp_obj_t str_format(uint n_args, const mp_obj_t *args) {
     assert(MP_OBJ_IS_STR(args[0]));
 
     GET_STR_DATA_LEN(args[0], str, len);
-    int arg_i = 1;
+    int arg_i = 0;
     vstr_t *vstr = vstr_new();
+    pfenv_t pfenv_vstr;
+    pfenv_vstr.data = vstr;
+    pfenv_vstr.print_strn = pfenv_vstr_add_strn;
+
     for (const byte *top = str + len; str < top; str++) {
-        if (*str == '{') {
+        if (*str == '}') {
             str++;
-            if (str < top && *str == '{') {
-                vstr_add_char(vstr, '{');
+            if (str < top && *str == '}') {
+                vstr_add_char(vstr, '}');
+                continue;
+            }
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Single '}' encountered in format string"));
+        }
+        if (*str != '{') {
+            vstr_add_char(vstr, *str);
+            continue;
+        }
+
+        str++;
+        if (str < top && *str == '{') {
+            vstr_add_char(vstr, '{');
+            continue;
+        }
+
+        // replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"
+
+        vstr_t *field_name = NULL;
+        char conversion = '\0';
+        vstr_t *format_spec = NULL;
+
+        if (str < top && *str != '}' && *str != '!' && *str != ':') {
+            field_name = vstr_new();
+            while (str < top && *str != '}' && *str != '!' && *str != ':') {
+                vstr_add_char(field_name, *str++);
+            }
+            vstr_add_char(field_name, '\0');
+        }
+
+        // conversion ::=  "r" | "s"
+
+        if (str < top && *str == '!') {
+            str++;
+            if (str < top && (*str == 'r' || *str == 's')) {
+                conversion = *str++;
             } else {
-                while (str < top && *str != '}') str++;
-                if (arg_i >= n_args) {
-                    nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "end of format while looking for conversion specifier"));
+            }
+        }
+
+        if (str < top && *str == ':') {
+            str++;
+            // {:} is the same as {}, which is the same as {!s}
+            // This makes a difference when passing in a True or False
+            // '{}'.format(True) returns 'True'
+            // '{:d}'.format(True) returns '1'
+            // So we treat {:} as {} and this later gets treated to be {!s}
+            if (*str != '}') {
+                format_spec = vstr_new(); 
+                while (str < top && *str != '}') {
+                    vstr_add_char(format_spec, *str++);
                 }
-                // TODO: may be PRINT_REPR depending on formatting code
-                mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_STR);
-                arg_i++;
+                vstr_add_char(format_spec, '\0');
             }
+        }
+        if (str >= top) {
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "unmatched '{' in format"));
+        }
+        if (*str != '}') {
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "expected ':' after format specifier"));
+        }
+
+        mp_obj_t arg = mp_const_none;
+
+        if (field_name) {
+            if (arg_i > 0) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "cannot switch from automatic field numbering to manual field specification"));
+            }
+            int index;
+            if (str_to_int(vstr_str(field_name), &index) != vstr_len(field_name) - 1) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "attributes not supported yet"));
+            }
+            if (index >= n_args - 1) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
+            }
+            arg = args[index + 1];
+            arg_i = -1;
+            vstr_free(field_name);
+            field_name = NULL;
         } else {
-            vstr_add_char(vstr, *str);
+            if (arg_i < 0) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "cannot switch from manual field specification to automatic field numbering"));
+            }
+            if (arg_i >= n_args - 1) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
+            }
+            arg = args[arg_i + 1];
+            arg_i++;
+        }
+        if (!format_spec && !conversion) {
+            conversion = 's';
+        }
+        if (conversion) {
+            mp_print_kind_t print_kind;
+            if (conversion == 's') {
+                print_kind = PRINT_STR;
+            } else if (conversion == 'r') {
+                print_kind = PRINT_REPR;
+            } else {
+                nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "Unknown conversion specifier %c", conversion));
+            }
+            vstr_t *arg_vstr = vstr_new();
+            mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, arg_vstr, arg, print_kind);
+            arg = mp_obj_new_str((const byte *)vstr_str(arg_vstr), vstr_len(arg_vstr), false);
+            vstr_free(arg_vstr);
+        }
+
+        char sign = '\0';
+        char fill = '\0';
+        char align = '\0';
+        int width = -1;
+        int precision = -1;
+        char type = '\0';
+        int flags = 0;
+
+        if (format_spec) {
+            // The format specifier (from http://docs.python.org/2/library/string.html#formatspec)
+            //
+            // [[fill]align][sign][#][0][width][,][.precision][type]
+            // fill        ::=  <any character>
+            // align       ::=  "<" | ">" | "=" | "^"
+            // sign        ::=  "+" | "-" | " "
+            // width       ::=  integer
+            // precision   ::=  integer
+            // type        ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
+
+            const char *s = vstr_str(format_spec);
+            if (isalignment(*s)) {
+                align = *s++;
+            } else if (*s && isalignment(s[1])) {
+                fill = *s++;
+                align = *s++;
+            }
+            if (*s == '+' || *s == '-' || *s == ' ') {
+                if (*s == '+') {
+                    flags |= PF_FLAG_SHOW_SIGN;
+                } else if (*s == ' ') {
+                    flags |= PF_FLAG_SPACE_SIGN;
+                }
+                sign = *s++;
+            }
+            if (*s == '#') {
+                flags |= PF_FLAG_SHOW_PREFIX;
+                s++;
+            }
+            if (*s == '0') {
+                if (!align) {
+                    align = '=';
+                }
+                if (!fill) {
+                    fill = '0';
+                }
+            }
+            s += str_to_int(s, &width);
+            if (*s == ',') {
+                flags |= PF_FLAG_SHOW_COMMA;
+                s++;
+            }
+            if (*s == '.') {
+                s++;
+                s += str_to_int(s, &precision);
+            }
+            if (istype(*s)) {
+                type = *s++;
+            }
+            if (*s) {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "Invalid conversion specification"));
+            }
+            vstr_free(format_spec);
+            format_spec = NULL;
+        }
+        if (!align) {
+            if (arg_looks_numeric(arg)) {
+                align = '>';
+            } else {
+                align = '<';
+            }
+        }
+        if (!fill) {
+            fill = ' ';
+        }
+
+        if (sign) {
+            if (type == 's') {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Sign not allowed in string format specifier"));
+            }
+            if (type == 'c') {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "Sign not allowed with integer format specifier 'c'"));
+            }
+        } else {
+            sign = '-';
+        }
+
+        switch (align) {
+            case '<': flags |= PF_FLAG_LEFT_ADJUST;     break;
+            case '=': flags |= PF_FLAG_PAD_AFTER_SIGN;  break;
+            case '^': flags |= PF_FLAG_CENTER_ADJUST;   break;
+        }
+
+        if (arg_looks_integer(arg)) {
+            switch (type) {
+                case 'b':
+                    pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 2, 'a', flags, fill, width);
+                    continue;
+
+                case 'c':
+                {
+                    char ch = mp_obj_get_int(arg);
+                    pfenv_print_strn(&pfenv_vstr, &ch, 1, flags, fill, width);
+                    continue;
+                }
+
+                case '\0':  // No explicit format type implies 'd'
+                case 'n':   // I don't think we support locales in uPy so use 'd'
+                case 'd':
+                    pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 10, 'a', flags, fill, width);
+                    continue;
+
+                case 'o':
+                    pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 8, 'a', flags, fill, width);
+                    continue;
+
+                case 'x':
+                    pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 16, 'a', flags, fill, width);
+                    continue;
+
+                case 'X':
+                    pfenv_print_int(&pfenv_vstr, mp_obj_get_int(arg), 1, 16, 'A', flags, fill, width);
+                    continue;
+
+                case 'e':
+                case 'E':
+                case 'f':
+                case 'F':
+                case 'g':
+                case 'G':
+                case '%':
+                    // The floating point formatters all work with anything that
+                    // looks like an integer
+                    break;
+
+                default:
+                    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
+                        "Unknown format code '%c' for object of type '%s'", type, mp_obj_get_type_str(arg)));
+            }
+        }
+#if MICROPY_ENABLE_FLOAT
+        if (arg_looks_numeric(arg)) {
+            if (!type) {
+
+                // Even though the docs say that an unspecified type is the same
+                // as 'g', there is one subtle difference, when the exponent
+                // is one less than the precision.
+                //  
+                // '{:10.1}'.format(0.0) ==> '0e+00'
+                // '{:10.1g}'.format(0.0) ==> '0'
+                //
+                // TODO: Figure out how to deal with this.
+                //
+                // A proper solution would involve adding a special flag
+                // or something to format_float, and create a format_double
+                // to deal with doubles. In order to fix this when using
+                // sprintf, we'd need to use the e format and tweak the
+                // returned result to strip trailing zeros like the g format
+                // does.
+                //
+                // {:10.3} and {:10.2e} with 1.23e2 both produce 1.23e+02
+                // but with 1.e2 you get 1e+02 and 1.00e+02
+                //
+                // Stripping the trailing 0's (like g) does would make the
+                // e format give us the right format.
+                //
+                // CPython sources say:
+                //   Omitted type specifier.  Behaves in the same way as repr(x)
+                //   and str(x) if no precision is given, else like 'g', but with
+                //   at least one digit after the decimal point. */
+
+                type = 'g';
+            }
+            if (type == 'n') {
+                type = 'g';
+            }
+
+            flags |= PF_FLAG_PAD_NAN_INF; // '{:06e}'.format(float('-inf')) should give '-00inf'
+            switch (type) {
+                case 'e':
+                case 'E':
+                case 'f':
+                case 'F':
+                case 'g':
+                case 'G':
+                    pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg), type, flags, fill, width, precision); 
+                    break;
+
+                case '%':
+                    flags |= PF_FLAG_ADD_PERCENT;
+                    pfenv_print_float(&pfenv_vstr, mp_obj_get_float(arg) * 100.0F, 'f', flags, fill, width, precision);
+                    break;
+
+                default:
+                    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
+                        "Unknown format code '%c' for object of type 'float'",
+                        type, mp_obj_get_type_str(arg)));
+            }
+#endif
+        } else {
+            if (align == '=') {
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "'=' alignment not allowed in string format specifier"));
+            }
+            switch (type) {
+                case '\0':
+                    mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, arg, PRINT_STR);
+                    break;
+
+                case 's':
+                {
+                    uint len;
+                    const char *s = mp_obj_str_get_data(arg, &len);
+                    if (precision < 0) {
+                        precision = len;
+                    }
+                    if (len > precision) {
+                        len = precision;
+                    }
+                    pfenv_print_strn(&pfenv_vstr, s, len, flags, fill, width);
+                    break;
+                }
+
+                default:
+                    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError,
+                        "Unknown format code '%c' for object of type 'str'",
+                        type, mp_obj_get_type_str(arg)));
+            }
         }
     }
 
diff --git a/py/pfenv.c b/py/pfenv.c
new file mode 100644
index 0000000000000000000000000000000000000000..07f35c335cf53a7761ed6dedaecfab2fdea97948
--- /dev/null
+++ b/py/pfenv.c
@@ -0,0 +1,208 @@
+#include <stdint.h>
+#include <string.h>
+
+///#include "std.h"
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "obj.h"
+#include "pfenv.h"
+
+#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+#include <stdio.h>
+#endif
+
+#if MICROPY_ENABLE_FLOAT
+#include "formatfloat.h"
+#endif
+
+#define PF_PAD_SIZE 16
+static const char *pad_spaces = "                ";
+static const char *pad_zeroes = "0000000000000000";
+
+void pfenv_vstr_add_strn(void *data, const char *str, unsigned int len){
+    vstr_add_strn(data, str, len);
+}
+
+int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, char fill, int width) {
+    int left_pad = 0;
+    int right_pad = 0;
+    int pad = width - len;
+    char pad_fill[PF_PAD_SIZE];
+    const char *pad_chars;
+
+    if (!fill || fill == ' ' ) {
+        pad_chars = pad_spaces;
+    } else if (fill == '0') {
+        pad_chars = pad_zeroes;
+    } else {
+        memset(pad_fill, fill, PF_PAD_SIZE);
+        pad_chars = pad_fill;
+    }
+
+    if (flags & PF_FLAG_CENTER_ADJUST) {
+        left_pad = pad / 2;
+        right_pad = pad - left_pad;
+    } else if (flags & PF_FLAG_LEFT_ADJUST) {
+        right_pad = pad;
+    } else {
+        left_pad = pad;
+    }
+
+    if (left_pad) {
+        while (left_pad > 0) {
+            int p = left_pad;
+            if (p > PF_PAD_SIZE)
+                p = PF_PAD_SIZE;
+            pfenv->print_strn(pfenv->data, pad_chars, p);
+            left_pad -= p;
+        }
+    }
+    pfenv->print_strn(pfenv->data, str, len);
+    if (right_pad) {
+        while (right_pad > 0) {
+            int p = right_pad;
+            if (p > PF_PAD_SIZE)
+                p = PF_PAD_SIZE;
+            pfenv->print_strn(pfenv->data, pad_chars, p);
+            right_pad -= p;
+        }
+    }
+    return len;
+}
+
+// enough room for 32 signed number
+#define INT_BUF_SIZE (16)
+
+int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, char fill, int width) {
+    char sign = 0;
+    if (sgn) {
+        if ((int)x < 0) {
+            sign = '-';
+            x = -x;
+        } else if (flags & PF_FLAG_SHOW_SIGN) {
+            sign = '+';
+        } else if (flags & PF_FLAG_SPACE_SIGN) {
+            sign = ' ';
+        }
+    }
+
+    char buf[INT_BUF_SIZE];
+    char *b = buf + INT_BUF_SIZE;
+
+    if (x == 0) {
+        *(--b) = '0';
+    } else {
+        do {
+            int c = x % base;
+            x /= base;
+            if (c >= 10) {
+                c += base_char - 10;
+            } else {
+                c += '0';
+            }
+            *(--b) = c;
+        } while (b > buf && x != 0);
+    }
+
+    char prefix_char = '\0';
+
+    if (flags & PF_FLAG_SHOW_PREFIX) {
+        if (base == 2) {
+            prefix_char = base_char + 'b' - 'a';
+        } else if (base == 8) {
+            prefix_char = base_char + 'o' - 'a';
+        } else if (base == 16) {
+            prefix_char = base_char + 'x' - 'a';
+        }
+    }
+
+    int len = 0;
+    if (flags & PF_FLAG_PAD_AFTER_SIGN) {
+        if (sign) {
+            len += pfenv_print_strn(pfenv, &sign, 1, flags, fill, 1);
+            width--;
+        }
+        if (prefix_char) {
+            len += pfenv_print_strn(pfenv, "0", 1, flags, fill, 1);
+            len += pfenv_print_strn(pfenv, &prefix_char, 1, flags, fill, 1);
+            width -= 2;
+        }
+    } else {
+        if (prefix_char && b > &buf[1]) {
+            *(--b) = prefix_char;
+            *(--b) = '0';
+        }
+        if (sign && b > buf) {
+            *(--b) = sign;
+        }
+    }
+
+    len += pfenv_print_strn(pfenv, b, buf + INT_BUF_SIZE - b, flags, fill, width);
+    return len;
+}
+
+#if MICROPY_ENABLE_FLOAT
+int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec) {
+    char buf[32];
+    char sign = '\0';
+    int chrs = 0;
+
+    if (flags & PF_FLAG_SHOW_SIGN) {
+        sign = '+';
+    }
+    else
+    if (flags & PF_FLAG_SPACE_SIGN) {
+        sign = ' ';
+    }
+    int len;
+#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
+    len = format_float(f, buf, sizeof(buf), fmt, prec, sign);
+#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+    char fmt_buf[6];
+    char *fmt_s = fmt_buf;
+
+    *fmt_s++ = '%';
+    if (sign) {
+        *fmt_s++ = sign;
+    }
+    *fmt_s++ = '.';
+    *fmt_s++ = '*';
+    *fmt_s++ = fmt;
+    *fmt_s = '\0';
+
+    len = snprintf(buf, sizeof(buf), fmt_buf, prec, f);
+#else
+#error Unknown MICROPY FLOAT IMPL
+#endif
+    char *s = buf;
+
+    if ((flags & PF_FLAG_ADD_PERCENT) && (len + 1) < sizeof(buf)) {
+        buf[len++] = '%';
+        buf[len] = '\0';
+    }
+
+    // buf[0] < '0' returns true if the first character is space, + or -
+    if ((flags & PF_FLAG_PAD_AFTER_SIGN) && buf[0] < '0') {
+        // We have a sign character
+        s++;
+        if (*s <= '9' || (flags & PF_FLAG_PAD_NAN_INF)) {
+            // We have a number, or we have a inf/nan and PAD_NAN_INF is set
+            // With '{:06e}'.format(float('-inf')) you get '-00inf'
+            chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 0, 1);
+            width--;
+            len--;
+        }
+    }
+
+    if (*s > 'A' && (flags & PF_FLAG_PAD_NAN_INF) == 0) {
+        // We have one of the inf or nan variants, suppress zero fill.
+        // With printf, if you use: printf("%06e", -inf) then you get "  -inf"
+        // so suppress the zero fill.
+        fill = ' ';
+    }
+    chrs += pfenv_print_strn(pfenv, s, len, flags, fill, width); 
+
+    return chrs;
+}
+#endif
diff --git a/py/pfenv.h b/py/pfenv.h
new file mode 100644
index 0000000000000000000000000000000000000000..edceaf3e4e4b9314a3a4980e50b1dc4a7329b11a
--- /dev/null
+++ b/py/pfenv.h
@@ -0,0 +1,23 @@
+#define PF_FLAG_LEFT_ADJUST     (0x001)
+#define PF_FLAG_SHOW_SIGN       (0x002)
+#define PF_FLAG_SPACE_SIGN      (0x004)
+#define PF_FLAG_NO_TRAILZ       (0x008)
+#define PF_FLAG_SHOW_PREFIX     (0x010)
+#define PF_FLAG_SHOW_COMMA      (0x020)
+#define PF_FLAG_PAD_AFTER_SIGN  (0x040)
+#define PF_FLAG_CENTER_ADJUST   (0x080)
+#define PF_FLAG_ADD_PERCENT     (0x100)
+#define PF_FLAG_PAD_NAN_INF     (0x200)
+
+typedef struct _pfenv_t {
+    void *data;
+    void (*print_strn)(void *, const char *str, unsigned int len);
+} pfenv_t;
+
+void pfenv_vstr_add_strn(void *data, const char *str, unsigned int len);
+
+int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, char fill, int width);
+int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, char fill, int width);
+#if MICROPY_ENABLE_FLOAT
+int pfenv_print_float(const pfenv_t *pfenv, mp_float_t f, char fmt, int flags, char fill, int width, int prec);
+#endif
diff --git a/py/py.mk b/py/py.mk
index 3905aa77df7a464e4346fed3fcdf7fbffcdf1c32..e2e83eb36abbfec7f9652b977bdd417c7801dbb2 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -84,6 +84,7 @@ PY_O_BASENAME = \
 	showbc.o \
 	repl.o \
 	intdivmod.o \
+	pfenv.o \
 
 # prepend the build destination prefix to the py object files
 PY_O = $(addprefix $(PY_BUILD)/, $(PY_O_BASENAME))
diff --git a/stmhal/printf.c b/stmhal/printf.c
index 6fd06508ea698b2a7605237eeca3f675cd7c36bf..b1eace28de0d06a5951902c1999cf54b0d8e35be 100644
--- a/stmhal/printf.c
+++ b/stmhal/printf.c
@@ -8,6 +8,7 @@
 #include "mpconfig.h"
 #include "qstr.h"
 #include "obj.h"
+#include "pfenv.h"
 #if 0
 #include "lcd.h"
 #endif
@@ -18,89 +19,6 @@
 #include "formatfloat.h"
 #endif
 
-#define PF_FLAG_LEFT_ADJUST (0x01)
-#define PF_FLAG_SHOW_SIGN   (0x02)
-#define PF_FLAG_SPACE_SIGN  (0x04)
-#define PF_FLAG_NO_TRAILZ   (0x08)
-#define PF_FLAG_ZERO_PAD    (0x10)
-
-// tricky; we compute pad string by: pad_chars + (flags & PF_FLAG_ZERO_PAD)
-#define PF_PAD_SIZE PF_FLAG_ZERO_PAD
-static const char *pad_chars = "                0000000000000000";
-
-typedef struct _pfenv_t {
-    void *data;
-    void (*print_strn)(void *, const char *str, unsigned int len);
-} pfenv_t;
-
-static void print_str_dummy(void *data, const char *str, unsigned int len) {
-}
-
-const pfenv_t pfenv_dummy = {0, print_str_dummy};
-
-static int pfenv_print_strn(const pfenv_t *pfenv, const char *str, unsigned int len, int flags, int width) {
-    int pad = width - len;
-    if (pad > 0 && (flags & PF_FLAG_LEFT_ADJUST) == 0) {
-        while (pad > 0) {
-            int p = pad;
-            if (p > PF_PAD_SIZE)
-                p = PF_PAD_SIZE;
-            pfenv->print_strn(pfenv->data, pad_chars + (flags & PF_FLAG_ZERO_PAD), p);
-            pad -= p;
-        }
-    }
-    pfenv->print_strn(pfenv->data, str, len);
-    while (pad > 0) {
-        int p = pad;
-        if (p > PF_PAD_SIZE)
-            p = PF_PAD_SIZE;
-        pfenv->print_strn(pfenv->data, pad_chars, p);
-        pad -= p;
-    }
-    return len;
-}
-
-// enough room for 32 signed number
-#define INT_BUF_SIZE (12)
-
-static int pfenv_print_int(const pfenv_t *pfenv, unsigned int x, int sgn, int base, int base_char, int flags, int width) {
-    char sign = 0;
-    if (sgn) {
-        if ((int)x < 0) {
-            sign = '-';
-            x = -x;
-        } else if (flags & PF_FLAG_SHOW_SIGN) {
-            sign = '+';
-        } else if (flags & PF_FLAG_SPACE_SIGN) {
-            sign = ' ';
-        }
-    }
-
-    char buf[INT_BUF_SIZE];
-    char *b = buf + INT_BUF_SIZE;
-
-    if (x == 0) {
-        *(--b) = '0';
-    } else {
-        do {
-            int c = x % base;
-            x /= base;
-            if (c >= 10) {
-                c += base_char - 10;
-            } else {
-                c += '0';
-            }
-            *(--b) = c;
-        } while (b > buf && x != 0);
-    }
-
-    if (b > buf && sign != 0) {
-        *(--b) = sign;
-    }
-
-    return pfenv_print_strn(pfenv, b, buf + INT_BUF_SIZE - b, flags, width);
-}
-
 void pfenv_prints(const pfenv_t *pfenv, const char *str) {
     pfenv->print_strn(pfenv->data, str, strlen(str));
 }
@@ -129,13 +47,16 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
 
         // parse flags, if they exist
         int flags = 0;
+        char fill = ' ';
         while (*fmt != '\0') {
             if (*fmt == '-') flags |= PF_FLAG_LEFT_ADJUST;
             else if (*fmt == '+') flags |= PF_FLAG_SHOW_SIGN;
             else if (*fmt == ' ') flags |= PF_FLAG_SPACE_SIGN;
             else if (*fmt == '!') flags |= PF_FLAG_NO_TRAILZ;
-            else if (*fmt == '0') flags |= PF_FLAG_ZERO_PAD;
-            else break;
+            else if (*fmt == '0') {
+                flags |= PF_FLAG_PAD_AFTER_SIGN;
+                fill = '0';
+            } else break;
             ++fmt;
         }
 
@@ -177,15 +98,15 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
         switch (*fmt) {
             case 'b':
                 if (va_arg(args, int)) {
-                    chrs += pfenv_print_strn(pfenv, "true", 4, flags, width);
+                    chrs += pfenv_print_strn(pfenv, "true", 4, flags, fill, width);
                 } else {
-                    chrs += pfenv_print_strn(pfenv, "false", 5, flags, width);
+                    chrs += pfenv_print_strn(pfenv, "false", 5, flags, fill, width);
                 }
                 break;
             case 'c':
             {
                 char str = va_arg(args, int);
-                chrs += pfenv_print_strn(pfenv, &str, 1, flags, width);
+                chrs += pfenv_print_strn(pfenv, &str, 1, flags, fill, width);
                 break;
             }
             case 's':
@@ -195,25 +116,25 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
                     if (prec < 0) {
                         prec = strlen(str);
                     }
-                    chrs += pfenv_print_strn(pfenv, str, prec, flags, width);
+                    chrs += pfenv_print_strn(pfenv, str, prec, flags, fill, width);
                 } else {
-                    chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, width);
+                    chrs += pfenv_print_strn(pfenv, "(null)", 6, flags, fill, width);
                 }
                 break;
             }
             case 'u':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, width);
+                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 10, 'a', flags, fill, width);
                 break;
             case 'd':
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, width);
+                chrs += pfenv_print_int(pfenv, va_arg(args, int), 1, 10, 'a', flags, fill, width);
                 break;
             case 'x':
             case 'p': // ?
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, width);
+                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'a', flags, fill, width);
                 break;
             case 'X':
             case 'P': // ?
-                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, width);
+                chrs += pfenv_print_int(pfenv, va_arg(args, int), 0, 16, 'A', flags, fill, width);
                 break;
 #if MICROPY_ENABLE_FLOAT
             case 'e':
@@ -223,33 +144,18 @@ int pfenv_printf(const pfenv_t *pfenv, const char *fmt, va_list args) {
             case 'g':
             case 'G':
             {
-                char buf[32];
-                char sign = '\0';
-
-                if (flags & PF_FLAG_SHOW_SIGN) {
-                    sign = '+';
-                }
-                else
-                if (flags & PF_FLAG_SPACE_SIGN) {
-                    sign = ' ';
-                }
-                float f = va_arg(args, double);
-                int len = format_float(f, buf, sizeof(buf), *fmt, prec, sign);
-                char *s = buf;
-
-                // buf[0] < '0' returns true if the first character is space, + or -
-                // buf[1] < '9' matches a digit, and doesn't match when we get back +nan or +inf
-                if (buf[0] < '0' && buf[1] <= '9' && (flags & PF_FLAG_ZERO_PAD)) {
-                    chrs += pfenv_print_strn(pfenv, &buf[0], 1, 0, 1);
-                    s++;
-                    width--;
-                    len--;
-                }
-                if (*s < '0' || *s >= '9') {
-                    // For inf or nan, we don't want to zero pad.
-                    flags &= ~PF_FLAG_ZERO_PAD;
-                }
-                chrs += pfenv_print_strn(pfenv, s, len, flags, width); 
+#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_FLOAT
+                mp_float_t f = va_arg(args, double);
+                chrs += pfenv_print_float(pfenv, f, *fmt, flags, fill, width, prec);
+#elif MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+                // Currently pfenv_print_float uses snprintf, so if you want
+                // to use pfenv_print_float with doubles then you'll need
+                // fix it to not use snprintf first. Otherwise you'll have
+                // inifinite recursion.
+#error Calling pfenv_print_float with double not supported from within printf
+#else
+#error Unknown MICROPY FLOAT IMPL
+#endif
                 break;
             }
 #endif
@@ -338,7 +244,7 @@ void strn_print_strn(void *data, const char *str, unsigned int len) {
     strn_pfenv->cur += len;
     strn_pfenv->remain -= len;
 }
-
+    
 int vsnprintf(char *str, size_t size, const char *fmt, va_list ap) {
     strn_pfenv_t strn_pfenv;
     strn_pfenv.cur = str;
diff --git a/tests/basics/string-format.py b/tests/basics/string-format.py
index ba51e0890b50605b8631036be9d891f395333083..0a9764bf25e57e20e6f8fc1e4d20d58b4bc0c427 100644
--- a/tests/basics/string-format.py
+++ b/tests/basics/string-format.py
@@ -1,8 +1,138 @@
-print("{}-{}".format(1, [4, 5]))
-print("{0}-{1}".format(1, [4, 5]))
-print("{:x}".format(1))
-print("{!r}".format(2))
-# TODO
-#print("{1}-{0}".format(1, [4, 5]))
-#print("{:x}".format(0x10))
-#print("{!r}".format("foo"))
+def test(fmt, *args):
+    print('{:8s}'.format(fmt) + '>' +  fmt.format(*args) + '<')
+
+test("{}-{}", 1, [4, 5])
+test("{0}-{1}", 1, [4, 5])
+test("{1}-{0}", 1, [4, 5])
+test("{:x}", 1)
+test("{!r}", 2)
+test("{1}-{0}", 1, [4, 5])
+test("{:x}", 0x10)
+test("{!r}", "foo")
+test("{!s}", "foo")
+
+def test_fmt(conv, fill, alignment, sign, prefix, width, precision, type, arg):
+    fmt = '{'
+    if conv:
+        fmt += '!'
+        fmt += conv
+    fmt += ':'
+    if alignment:
+        fmt += fill
+        fmt += alignment
+    fmt += sign
+    fmt += prefix
+    fmt += width
+    if precision:
+        fmt += '.'
+        fmt += precision
+    fmt += type
+    fmt += '}'
+    test(fmt,  arg)
+    if fill == '0' and alignment == '=':
+        fmt = '{:'
+        fmt += sign
+        fmt += prefix
+        fmt += width
+        if precision:
+            fmt += '.'
+            fmt += precision
+        fmt += type
+        fmt += '}'
+        test(fmt, arg)
+
+int_nums = (-1234, -123, -12, -1, 0, 1, 12, 123, 1234, True, False)
+int_nums2 = (-12, -1, 0, 1, 12, True, False)
+
+if True:
+    for type in ('', 'b', 'd', 'o', 'x', 'X'):
+        for width in ('', '1', '3', '5', '7'):
+            for alignment in ('', '<', '>', '=', '^'):
+                for fill in ('', ' ', '0', '@'):
+                    for sign in ('', '+', '-', ' '):
+                        for prefix in ('', '#'):
+                            for num in int_nums:
+                                test_fmt('', fill, alignment, sign, prefix, width, '', type, num)
+
+if True:
+    for width in ('', '1', '2'):
+        for alignment in ('', '<', '>', '^'):
+            for fill in ('', ' ', '0', '@'):
+                test_fmt('', fill, alignment, '', '', width, '', 'c', 48)
+
+if True:
+    for conv in ('', 'r', 's'):
+        for width in ('', '1', '4', '10'):
+            for alignment in ('', '<', '>', '^'):
+                for fill in ('', ' ', '0', '@'):
+                    for str in ('', 'a', 'bcd', 'This is a test with a longer string'):
+                        test_fmt(conv, fill, alignment, '', '', width, '', 's', str)
+
+eg_nums = (0.0, -0.0, 0.1, 1.234, 12.3459, 1.23456789, 123456789.0, -0.0,
+    -0.1, -1.234, -12.3459, 1e4, 1e-4, 1e5, 1e-5, 1e6, 1e-6, 1e10,
+    1e37, -1e37, 1e-37, -1e-37,
+    1.23456e8, 1.23456e7, 1.23456e6, 1.23456e5, 1.23456e4, 1.23456e3, 1.23456e2, 1.23456e1, 1.23456e0,
+    1.23456e-1, 1.23456e-2, 1.23456e-3, 1.23456e-4, 1.23456e-5, 1.23456e-6, 1.23456e-7, 1.23456e-8,
+    -1.23456e8, -1.23456e7, -1.23456e6, -1.23456e5, -1.23456e4, -1.23456e3, -1.23456e2, -1.23456e1, -1.23456e0,
+    -1.23456e-1, -1.23456e-2, -1.23456e-3, -1.23456e-4, -1.23456e-5, -1.23456e-6, -1.23456e-7, -1.23456e-8)
+
+if True:
+    for type in ('e', 'E', 'g', 'G', 'n'):
+        for width in ('', '4', '6', '8', '10'):
+            for alignment in ('', '<', '>', '=', '^'):
+                for fill in ('', '@', '0', ' '):
+                    for sign in ('', '+', '-', ' '):
+                        for prec in ('', '1', '3', '6'):
+                            for num in eg_nums:
+                                test_fmt('', fill, alignment, sign, '', width, prec, type, num)
+
+# Note: We use 1.23459 rather than 1.2345 because '{:3f}'.format(1.2345)
+#       rounds differently than print("%.3f", 1.2345);
+
+f_nums = (0.0, -0.0, 0.0001, 0.001, 0.01, 0.1, 1.0, 10.0,
+     0.0012,  0.0123,  0.1234,  1.23459,  12.3456,
+    -0.0001, -0.001,  -0.01,   -0.1,      -1.0, -10.0,
+    -0.0012, -0.0123, -0.1234, -1.23459, -12.3456)
+
+if True:
+    for type in ('f', 'F'):
+        for width in ('', '4', '6', '8', '10'):
+            for alignment in ('', '<', '>', '=', '^'):
+                for fill in ('', ' ', '0', '@'):
+                    for sign in ('', '+', '-', ' '):
+                        # An empty precision defaults to 6, but when uPy is
+                        # configured to use a float, we can only use a
+                        # precision of 6 with numbers less than 10 and still
+                        # get results that compare to CPython (which uses
+                        # long doubles).
+                        for prec in ('1', '2', '3'):
+                            for num in f_nums:
+                                test_fmt('', fill, alignment, sign, '', width, prec, type, num)
+                        for num in int_nums2:
+                            test_fmt('', fill, alignment, sign, '', width, '', type, num)
+
+pct_nums1 = (0.1, 0.58, 0.99, -0.1, -0.58, -0.99)
+pct_nums2 = (True, False, 1, 0, -1)
+
+if True:
+    type = '%'
+    for width in ('', '4', '6', '8', '10'):
+        for alignment in ('', '<', '>', '=', '^'):
+            for fill in ('', ' ', '0', '@'):
+                for sign in ('', '+', '-', ' '):
+                    # An empty precision defaults to 6, but when uPy is
+                    # configured to use a float, we can only use a
+                    # precision of 6 with numbers less than 10 and still
+                    # get results that compare to CPython (which uses
+                    # long doubles).
+                    for prec in ('1', '2', '3'):
+                        for num in pct_nums1:
+                            test_fmt('', fill, alignment, sign, '', width, prec, type, num)
+                    for num in pct_nums2:
+                        test_fmt('', fill, alignment, sign, '', width, '', type, num)
+
+# We don't currently test a type of '' with floats (see the detailed comment
+# in  objstr.c)
+
+# TODO Add tests for erroneous format strings.
+