diff --git a/stmhal/Makefile b/stmhal/Makefile
index bf5db71867691bb75488a270a0b881dfba15a34a..b4169e3d63e75eb27e06ad9190cd8e0e1bd71790 100644
--- a/stmhal/Makefile
+++ b/stmhal/Makefile
@@ -71,6 +71,8 @@ SRC_C = \
 	math.c \
 	malloc0.c \
 	gccollect.c \
+	pybstdio.c \
+	readline.c \
 	pyexec.c \
 	help.c \
 	input.c \
diff --git a/stmhal/input.c b/stmhal/input.c
index f53018a42297642102ee8e9e68b0f540ec595008..d704c2cf762bb29c94a758d12cc432557aacb87e 100644
--- a/stmhal/input.c
+++ b/stmhal/input.c
@@ -3,10 +3,9 @@
 #include "mpconfig.h"
 #include "qstr.h"
 #include "obj.h"
+#include "readline.h"
 #include "usb.h"
 
-extern int readline(vstr_t *line, const char *prompt);
-
 STATIC mp_obj_t mp_builtin_input(uint n_args, const mp_obj_t *args) {
     if (n_args == 1) {
         mp_obj_print(args[0], PRINT_REPR);
diff --git a/stmhal/main.c b/stmhal/main.c
index 1da7a9a6491f85a946ce5a5a51f971d8f8fc03b7..e40026efddb1e5da41a823df34a86b0ef7106a2b 100644
--- a/stmhal/main.c
+++ b/stmhal/main.c
@@ -16,10 +16,10 @@
 #include "obj.h"
 #include "parsehelper.h"
 #include "compile.h"
-#include "runtime0.h"
 #include "runtime.h"
 #include "gc.h"
 #include "gccollect.h"
+#include "readline.h"
 #include "pyexec.h"
 #include "usart.h"
 #include "led.h"
@@ -210,6 +210,8 @@ soft_reset:
     def_path[2] = MP_OBJ_NEW_QSTR(MP_QSTR_0_colon__slash_lib);
     sys_path = mp_obj_new_list(3, def_path);
 
+    readline_init();
+
     exti_init();
 
 #if MICROPY_HW_HAS_SWITCH
diff --git a/stmhal/modpyb.c b/stmhal/modpyb.c
index 432dc859c1db61129042e1f31b9e26b261ee3136..45bd8b1e0f99748da0a03bdc6951a6a2a853f827 100644
--- a/stmhal/modpyb.c
+++ b/stmhal/modpyb.c
@@ -11,6 +11,7 @@
 #include "gc.h"
 #include "gccollect.h"
 #include "systick.h"
+#include "pybstdio.h"
 #include "pyexec.h"
 #include "led.h"
 #include "gpio.h"
@@ -202,8 +203,6 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_hid_send_report_obj, pyb_hid_send_report);
 MP_DEFINE_CONST_FUN_OBJ_2(pyb_I2C_obj, pyb_I2C); // TODO put this in i2c.c
 #endif
 
-extern int stdin_rx_chr(void);
-
 STATIC mp_obj_t pyb_input(void ) {
     return mp_obj_new_int(stdin_rx_chr());
 }
diff --git a/stmhal/pybstdio.c b/stmhal/pybstdio.c
new file mode 100644
index 0000000000000000000000000000000000000000..95582d330eeccd9c2679d89dcabdbfc9ca535d9e
--- /dev/null
+++ b/stmhal/pybstdio.c
@@ -0,0 +1,54 @@
+#include <stm32f4xx_hal.h>
+
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "misc.h"
+#include "obj.h"
+#include "pybstdio.h"
+#include "storage.h"
+#include "usb.h"
+#include "usart.h"
+
+void stdout_tx_str(const char *str) {
+    if (pyb_usart_global_debug != PYB_USART_NONE) {
+        usart_tx_str(pyb_usart_global_debug, str);
+    }
+#if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD
+    lcd_print_str(str);
+#endif
+    usb_vcp_send_str(str);
+}
+
+void stdout_tx_strn(const char *str, uint len) {
+    if (pyb_usart_global_debug != PYB_USART_NONE) {
+        usart_tx_strn(pyb_usart_global_debug, str, len);
+    }
+#if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD
+    lcd_print_strn(str, len);
+#endif
+    usb_vcp_send_strn(str, len);
+}
+
+int stdin_rx_chr(void) {
+    for (;;) {
+#if 0
+#ifdef USE_HOST_MODE
+        pyb_usb_host_process();
+        int c = pyb_usb_host_get_keyboard();
+        if (c != 0) {
+            return c;
+        }
+#endif
+#endif
+        if (usb_vcp_rx_num() != 0) {
+            return usb_vcp_rx_get();
+        } else if (pyb_usart_global_debug != PYB_USART_NONE && usart_rx_any(pyb_usart_global_debug)) {
+            return usart_rx_char(pyb_usart_global_debug);
+        }
+        HAL_Delay(1);
+        if (storage_needs_flush()) {
+            storage_flush();
+        }
+    }
+}
diff --git a/stmhal/pybstdio.h b/stmhal/pybstdio.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e10fc86582b84a61e36ea9b28e89b24a6c904ff
--- /dev/null
+++ b/stmhal/pybstdio.h
@@ -0,0 +1,3 @@
+void stdout_tx_str(const char *str);
+void stdout_tx_strn(const char *str, uint len);
+int stdin_rx_chr(void);
diff --git a/stmhal/pyexec.c b/stmhal/pyexec.c
index f1f28abde0788d6acc10c91ed496f2beeb7216cd..7c19d45ca692f4b326aa8341b41d6aeda4653193 100644
--- a/stmhal/pyexec.c
+++ b/stmhal/pyexec.c
@@ -1,5 +1,4 @@
 #include <stdlib.h>
-#include <string.h>
 #include <stdio.h>
 
 #include <stm32f4xx_hal.h>
@@ -19,201 +18,15 @@
 #include "gc.h"
 #include "gccollect.h"
 #include "systick.h"
+#include "pybstdio.h"
+#include "readline.h"
 #include "pyexec.h"
 #include "storage.h"
 #include "usb.h"
-#include "usart.h"
 
 pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL;
 STATIC bool repl_display_debugging_info = 0;
 
-void stdout_tx_str(const char *str) {
-    if (pyb_usart_global_debug != PYB_USART_NONE) {
-        usart_tx_str(pyb_usart_global_debug, str);
-    }
-#if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD
-    lcd_print_str(str);
-#endif
-    usb_vcp_send_str(str);
-}
-
-void stdout_tx_strn(const char *str, uint len) {
-    if (pyb_usart_global_debug != PYB_USART_NONE) {
-        usart_tx_strn(pyb_usart_global_debug, str, len);
-    }
-#if defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD
-    lcd_print_strn(str, len);
-#endif
-    usb_vcp_send_strn(str, len);
-}
-
-int stdin_rx_chr(void) {
-    for (;;) {
-#if 0
-#ifdef USE_HOST_MODE
-        pyb_usb_host_process();
-        int c = pyb_usb_host_get_keyboard();
-        if (c != 0) {
-            return c;
-        }
-#endif
-#endif
-        if (usb_vcp_rx_num() != 0) {
-            return usb_vcp_rx_get();
-        } else if (pyb_usart_global_debug != PYB_USART_NONE && usart_rx_any(pyb_usart_global_debug)) {
-            return usart_rx_char(pyb_usart_global_debug);
-        }
-        HAL_Delay(1);
-        if (storage_needs_flush()) {
-            storage_flush();
-        }
-    }
-}
-
-char *str_dup(const char *str) {
-    uint32_t len = strlen(str);
-    char *s2 = m_new(char, len + 1);
-    memcpy(s2, str, len);
-    s2[len] = 0;
-    return s2;
-}
-
-#define READLINE_HIST_SIZE (8)
-
-static const char *readline_hist[READLINE_HIST_SIZE] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
-
-int readline(vstr_t *line, const char *prompt) {
-    stdout_tx_str(prompt);
-    int orig_line_len = line->len;
-    int escape_seq = 0;
-    int hist_cur = -1;
-    int cursor_pos = orig_line_len;
-    for (;;) {
-        int c = stdin_rx_chr();
-        int last_line_len = line->len;
-        int redraw_step_back = 0;
-        bool redraw_from_cursor = false;
-        int redraw_step_forward = 0;
-        if (escape_seq == 0) {
-            if (VCP_CHAR_CTRL_A <= c && c <= VCP_CHAR_CTRL_D && vstr_len(line) == orig_line_len) {
-                // control character with empty line
-                return c;
-            } else if (c == '\r') {
-                // newline
-                stdout_tx_str("\r\n");
-                if (line->len > orig_line_len && (readline_hist[0] == NULL || strcmp(readline_hist[0], line->buf + orig_line_len) != 0)) {
-                    // a line which is not empty and different from the last one
-                    // so update the history
-                    for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
-                        readline_hist[i] = readline_hist[i - 1];
-                    }
-                    readline_hist[0] = str_dup(line->buf + orig_line_len);
-                }
-                return 0;
-            } else if (c == 27) {
-                // escape sequence
-                escape_seq = 1;
-            } else if (c == 127) {
-                // backspace
-                if (cursor_pos > orig_line_len) {
-                    vstr_cut_out_bytes(line, cursor_pos - 1, 1);
-                    // set redraw parameters
-                    redraw_step_back = 1;
-                    redraw_from_cursor = true;
-                }
-            } else if (32 <= c && c <= 126) {
-                // printable character
-                vstr_ins_char(line, cursor_pos, c);
-                // set redraw parameters
-                redraw_from_cursor = true;
-                redraw_step_forward = 1;
-            }
-        } else if (escape_seq == 1) {
-            if (c == '[') {
-                escape_seq = 2;
-            } else {
-                escape_seq = 0;
-            }
-        } else if (escape_seq == 2) {
-            escape_seq = 0;
-            if (c == 'A') {
-                // up arrow
-                if (hist_cur + 1 < READLINE_HIST_SIZE && readline_hist[hist_cur + 1] != NULL) {
-                    // increase hist num
-                    hist_cur += 1;
-                    // set line to history
-                    line->len = orig_line_len;
-                    vstr_add_str(line, readline_hist[hist_cur]);
-                    // set redraw parameters
-                    redraw_step_back = cursor_pos - orig_line_len;
-                    redraw_from_cursor = true;
-                    redraw_step_forward = line->len - orig_line_len;
-                }
-            } else if (c == 'B') {
-                // down arrow
-                if (hist_cur >= 0) {
-                    // decrease hist num
-                    hist_cur -= 1;
-                    // set line to history
-                    vstr_cut_tail_bytes(line, line->len - orig_line_len);
-                    if (hist_cur >= 0) {
-                        vstr_add_str(line, readline_hist[hist_cur]);
-                    }
-                    // set redraw parameters
-                    redraw_step_back = cursor_pos - orig_line_len;
-                    redraw_from_cursor = true;
-                    redraw_step_forward = line->len - orig_line_len;
-                }
-            } else if (c == 'C') {
-                // right arrow
-                if (cursor_pos < line->len) {
-                    redraw_step_forward = 1;
-                }
-            } else if (c == 'D') {
-                // left arrow
-                if (cursor_pos > orig_line_len) {
-                    redraw_step_back = 1;
-                }
-            }
-        } else {
-            escape_seq = 0;
-        }
-
-        // redraw command prompt, efficiently
-        if (redraw_step_back > 0) {
-            for (int i = 0; i < redraw_step_back; i++) {
-                stdout_tx_str("\b");
-            }
-            cursor_pos -= redraw_step_back;
-        }
-        if (redraw_from_cursor) {
-            if (line->len < last_line_len) {
-                // erase old chars
-                for (int i = cursor_pos; i < last_line_len; i++) {
-                    stdout_tx_str(" ");
-                }
-                // step back
-                for (int i = cursor_pos; i < last_line_len; i++) {
-                    stdout_tx_str("\b");
-                }
-            }
-            // draw new chars
-            stdout_tx_strn(line->buf + cursor_pos, line->len - cursor_pos);
-            // move cursor forward if needed (already moved forward by length of line, so move it back)
-            for (int i = cursor_pos + redraw_step_forward; i < line->len; i++) {
-                stdout_tx_str("\b");
-            }
-            cursor_pos += redraw_step_forward;
-        } else if (redraw_step_forward > 0) {
-            // draw over old chars to move cursor forwards
-            stdout_tx_strn(line->buf + cursor_pos, redraw_step_forward);
-            cursor_pos += redraw_step_forward;
-        }
-
-        HAL_Delay(1);
-    }
-}
-
 // parses, compiles and executes the code in the lexer
 // frees the lexer before returning
 bool parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, bool is_repl) {
diff --git a/stmhal/readline.c b/stmhal/readline.c
new file mode 100644
index 0000000000000000000000000000000000000000..ec13a269aca26abde51e048f801c66e3ca93f471
--- /dev/null
+++ b/stmhal/readline.c
@@ -0,0 +1,160 @@
+#include <string.h>
+
+#include <stm32f4xx_hal.h>
+
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "misc.h"
+#include "obj.h"
+#include "pybstdio.h"
+#include "readline.h"
+#include "usb.h"
+
+#define READLINE_HIST_SIZE (8)
+
+static const char *readline_hist[READLINE_HIST_SIZE] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+
+void readline_init(void) {
+    memset(readline_hist, 0, READLINE_HIST_SIZE * sizeof(const char*));
+}
+
+STATIC char *str_dup(const char *str) {
+    uint32_t len = strlen(str);
+    char *s2 = m_new(char, len + 1);
+    memcpy(s2, str, len);
+    s2[len] = 0;
+    return s2;
+}
+
+int readline(vstr_t *line, const char *prompt) {
+    stdout_tx_str(prompt);
+    int orig_line_len = line->len;
+    int escape_seq = 0;
+    int hist_cur = -1;
+    int cursor_pos = orig_line_len;
+    for (;;) {
+        int c = stdin_rx_chr();
+        int last_line_len = line->len;
+        int redraw_step_back = 0;
+        bool redraw_from_cursor = false;
+        int redraw_step_forward = 0;
+        if (escape_seq == 0) {
+            if (VCP_CHAR_CTRL_A <= c && c <= VCP_CHAR_CTRL_D && vstr_len(line) == orig_line_len) {
+                // control character with empty line
+                return c;
+            } else if (c == '\r') {
+                // newline
+                stdout_tx_str("\r\n");
+                if (line->len > orig_line_len && (readline_hist[0] == NULL || strcmp(readline_hist[0], line->buf + orig_line_len) != 0)) {
+                    // a line which is not empty and different from the last one
+                    // so update the history
+                    for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
+                        readline_hist[i] = readline_hist[i - 1];
+                    }
+                    readline_hist[0] = str_dup(line->buf + orig_line_len);
+                }
+                return 0;
+            } else if (c == 27) {
+                // escape sequence
+                escape_seq = 1;
+            } else if (c == 127) {
+                // backspace
+                if (cursor_pos > orig_line_len) {
+                    vstr_cut_out_bytes(line, cursor_pos - 1, 1);
+                    // set redraw parameters
+                    redraw_step_back = 1;
+                    redraw_from_cursor = true;
+                }
+            } else if (32 <= c && c <= 126) {
+                // printable character
+                vstr_ins_char(line, cursor_pos, c);
+                // set redraw parameters
+                redraw_from_cursor = true;
+                redraw_step_forward = 1;
+            }
+        } else if (escape_seq == 1) {
+            if (c == '[') {
+                escape_seq = 2;
+            } else {
+                escape_seq = 0;
+            }
+        } else if (escape_seq == 2) {
+            escape_seq = 0;
+            if (c == 'A') {
+                // up arrow
+                if (hist_cur + 1 < READLINE_HIST_SIZE && readline_hist[hist_cur + 1] != NULL) {
+                    // increase hist num
+                    hist_cur += 1;
+                    // set line to history
+                    line->len = orig_line_len;
+                    vstr_add_str(line, readline_hist[hist_cur]);
+                    // set redraw parameters
+                    redraw_step_back = cursor_pos - orig_line_len;
+                    redraw_from_cursor = true;
+                    redraw_step_forward = line->len - orig_line_len;
+                }
+            } else if (c == 'B') {
+                // down arrow
+                if (hist_cur >= 0) {
+                    // decrease hist num
+                    hist_cur -= 1;
+                    // set line to history
+                    vstr_cut_tail_bytes(line, line->len - orig_line_len);
+                    if (hist_cur >= 0) {
+                        vstr_add_str(line, readline_hist[hist_cur]);
+                    }
+                    // set redraw parameters
+                    redraw_step_back = cursor_pos - orig_line_len;
+                    redraw_from_cursor = true;
+                    redraw_step_forward = line->len - orig_line_len;
+                }
+            } else if (c == 'C') {
+                // right arrow
+                if (cursor_pos < line->len) {
+                    redraw_step_forward = 1;
+                }
+            } else if (c == 'D') {
+                // left arrow
+                if (cursor_pos > orig_line_len) {
+                    redraw_step_back = 1;
+                }
+            }
+        } else {
+            escape_seq = 0;
+        }
+
+        // redraw command prompt, efficiently
+        if (redraw_step_back > 0) {
+            for (int i = 0; i < redraw_step_back; i++) {
+                stdout_tx_str("\b");
+            }
+            cursor_pos -= redraw_step_back;
+        }
+        if (redraw_from_cursor) {
+            if (line->len < last_line_len) {
+                // erase old chars
+                for (int i = cursor_pos; i < last_line_len; i++) {
+                    stdout_tx_str(" ");
+                }
+                // step back
+                for (int i = cursor_pos; i < last_line_len; i++) {
+                    stdout_tx_str("\b");
+                }
+            }
+            // draw new chars
+            stdout_tx_strn(line->buf + cursor_pos, line->len - cursor_pos);
+            // move cursor forward if needed (already moved forward by length of line, so move it back)
+            for (int i = cursor_pos + redraw_step_forward; i < line->len; i++) {
+                stdout_tx_str("\b");
+            }
+            cursor_pos += redraw_step_forward;
+        } else if (redraw_step_forward > 0) {
+            // draw over old chars to move cursor forwards
+            stdout_tx_strn(line->buf + cursor_pos, redraw_step_forward);
+            cursor_pos += redraw_step_forward;
+        }
+
+        HAL_Delay(1);
+    }
+}
diff --git a/stmhal/readline.h b/stmhal/readline.h
new file mode 100644
index 0000000000000000000000000000000000000000..6b1baee32465e443c75c94bcf79917d86ed529d1
--- /dev/null
+++ b/stmhal/readline.h
@@ -0,0 +1,2 @@
+void readline_init(void);
+int readline(vstr_t *line, const char *prompt);