diff --git a/components/micropython/vendor/ports/esp32/main.c b/components/micropython/vendor/ports/esp32/main.c
index 9daf20a98c903fd6f3ac7d5c361cb99111bbd73f..d73c8c92482ea3fdddacd7e96de2cab12a87b3c4 100644
--- a/components/micropython/vendor/ports/esp32/main.c
+++ b/components/micropython/vendor/ports/esp32/main.c
@@ -119,7 +119,7 @@ soft_reset:
 
     // run boot-up scripts
     pyexec_frozen_module("_boot.py");
-    pyexec_file_if_exists("boot.py");
+    int ret = pyexec_file_if_exists("boot.py");
     if (pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) {
         int ret = pyexec_file_if_exists("/flash/sys/main.py");
         if (ret & PYEXEC_FORCED_EXIT) {
@@ -127,7 +127,15 @@ soft_reset:
         }
     }
 
-    st3m_mode_set(st3m_mode_kind_repl, NULL);
+    if (ret == 1)
+    {
+      if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL)
+          st3m_mode_set(st3m_mode_kind_mpremote, NULL);
+      else
+          st3m_mode_set(st3m_mode_kind_repl, NULL);
+    }
+    else
+      st3m_mode_set(st3m_mode_kind_exception, NULL);
 
     for (;;) {
         if (pyexec_mode_kind == PYEXEC_MODE_RAW_REPL) {
diff --git a/components/micropython/vendor/ports/esp32/mphalport.c b/components/micropython/vendor/ports/esp32/mphalport.c
index 392ffe99cf9acf8ca661fd68ab50897d0044eb0c..ee227698510d5fdc4db54abb140aa990e2c0ecda 100644
--- a/components/micropython/vendor/ports/esp32/mphalport.c
+++ b/components/micropython/vendor/ports/esp32/mphalport.c
@@ -34,6 +34,7 @@
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "esp_timer.h"
+#include "st3m_term.h"
 
 #include "py/obj.h"
 #include "py/objstr.h"
@@ -122,12 +123,14 @@ int mp_hal_stdin_rx_chr(void) {
     }
 }
 
+
 void mp_hal_stdout_tx_strn(const char *str, size_t len) {
     // Only release the GIL if many characters are being sent
     bool release_gil = len > 20;
     if (release_gil) {
         MP_THREAD_GIL_EXIT();
     }
+    st3m_term_feed(str, len);
     fwrite(str, len, 1, stdout);
     if (release_gil) {
         MP_THREAD_GIL_ENTER();
diff --git a/components/st3m/CMakeLists.txt b/components/st3m/CMakeLists.txt
index 35564c8af43491c77f52bec560ecb7d4223493e3..85c25e2e98c5a60ca4d9fcea9fd5d2ec7a78c8a8 100644
--- a/components/st3m/CMakeLists.txt
+++ b/components/st3m/CMakeLists.txt
@@ -17,6 +17,7 @@ idf_component_register(
         st3m_captouch.c
         st3m_ringbuffer.c
         st3m_tar.c
+        st3m_term.c
         st3m_fs.c
         st3m_fs_flash.c
         st3m_fs_sd.c
diff --git a/components/st3m/st3m_mode.c b/components/st3m/st3m_mode.c
index 3f014161a187c41325edd18c451958deb8b110f8..e92ff8be9fcfcc56fb87edf51dd7f0201a10bbca 100644
--- a/components/st3m/st3m_mode.c
+++ b/components/st3m/st3m_mode.c
@@ -9,6 +9,7 @@
 
 #include "st3m_gfx.h"
 #include "st3m_io.h"
+#include "st3m_term.h"
 
 static st3m_mode_t _mode = {
     .kind = st3m_mode_kind_starting,
@@ -62,17 +63,27 @@ void st3m_mode_update_display(bool *restartable) {
         case st3m_mode_kind_disk:
             st3m_gfx_splash("Disk Mode");
             break;
+        case st3m_mode_kind_exception:
+            if (!_mode.shown) {
+                _mode.shown = true;
+                st3m_term_draw(6);
+            }
+            if (restartable != NULL) *restartable = true;
+            break;
         case st3m_mode_kind_repl:
+        case st3m_mode_kind_mpremote:
             if (!_mode.shown) {
                 _mode.shown = true;
                 const char *lines[] = {
-                    "Send Ctrl-D over USB",
-                    "or press left shoulder button",
+                    "Send Ctrl-D over USB or",
+                    "press left shoulder button",
                     "to restart.",
                     NULL,
                 };
                 st3m_gfx_textview_t tv = {
-                    .title = "In REPL",
+                    .title = _mode.kind == st3m_mode_kind_repl
+                                 ? "In REPL"
+                                 : "mpremote active",
                     .lines = lines,
                 };
                 st3m_gfx_show_textview(&tv);
@@ -133,4 +144,4 @@ void st3m_mode_init(void) {
     assert(_mu != NULL);
 
     xTaskCreate(_task, "mode", 4096, NULL, 1, NULL);
-}
\ No newline at end of file
+}
diff --git a/components/st3m/st3m_mode.h b/components/st3m/st3m_mode.h
index 04f2957260be507ee7f365077a4433e6f1b9f2ca..b124e8396981de30727d7aa492f36f9b9f8f2558 100644
--- a/components/st3m/st3m_mode.h
+++ b/components/st3m/st3m_mode.h
@@ -9,6 +9,8 @@ typedef enum {
     st3m_mode_kind_repl = 3,
     st3m_mode_kind_disk = 4,
     st3m_mode_kind_fatal = 5,
+    st3m_mode_kind_exception = 6,
+    st3m_mode_kind_mpremote = 7,
 } st3m_mode_kind_t;
 
 typedef struct {
@@ -33,4 +35,4 @@ void st3m_mode_set(st3m_mode_kind_t kind, const char *msg);
 
 // Update screen based on current mode immediately. Otherwise the screen gets
 // updated at a 10Hz cadence in a low-priority task.
-void st3m_mode_update_display(bool *restartable);
\ No newline at end of file
+void st3m_mode_update_display(bool *restartable);
diff --git a/components/st3m/st3m_term.c b/components/st3m/st3m_term.c
new file mode 100644
index 0000000000000000000000000000000000000000..dda0284152e53c418514aee62e9d2df40f6c6365
--- /dev/null
+++ b/components/st3m/st3m_term.c
@@ -0,0 +1,83 @@
+#include "st3m_term.h"
+
+#include "st3m_gfx.h"
+
+/* a tiny dumb terminal */
+
+#define ST3M_TERM_COLS 27
+#define ST3M_TERM_LINES 16
+
+static char st3m_term[ST3M_TERM_LINES][ST3M_TERM_COLS];
+static int st3m_term_cx = 0;
+static int st3m_term_cy = 0;
+
+void st3m_term_feed(const char *str, size_t len) {
+    for (int i = 0; i < len; i++) {
+        char c = str[i];
+        switch (c) {
+            case '\t': {
+                char *space_buf = " ";
+                do {
+                    st3m_term_feed(space_buf, 1);
+                } while ((st3m_term_cx & 7) != 0);
+            } break;
+            case '\b':
+                st3m_term_cx--;
+                if (st3m_term_cx < 0) st3m_term_cx = 0;
+                break;
+            case '\r':
+                st3m_term_cx = 0;
+                break;
+            case '\n':
+                st3m_term_cx = 0;
+                st3m_term_cy++;
+                if (st3m_term_cy >= ST3M_TERM_LINES) st3m_term_cy = 0;
+                memset(st3m_term[st3m_term_cy], 0, ST3M_TERM_COLS);
+                break;
+            default:
+                st3m_term[st3m_term_cy][st3m_term_cx] = c;
+                st3m_term_cx++;
+                if (st3m_term_cx >= ST3M_TERM_COLS) {
+                    st3m_term_cx = 0;
+                    st3m_term_cy++;
+                    if (st3m_term_cy >= ST3M_TERM_LINES) st3m_term_cy = 0;
+                    memset(st3m_term[st3m_term_cy], 0, ST3M_TERM_COLS);
+                }
+        }
+    }
+}
+
+// returns a line from terminal scrollback
+// line_no is a value 0..15
+const char *st3m_term_get_line(int line_no) {
+    return st3m_term[(st3m_term_cy - line_no - 1) & 15];
+}
+
+// draws the last bit of terminal output, avoiding the last skip_lines
+// lines added.
+void st3m_term_draw(int skip_lines) {
+    st3m_ctx_desc_t *target = st3m_gfx_drawctx_free_get(portMAX_DELAY);
+    Ctx *ctx = target->ctx;
+    float font_size = 20.0;
+    float y = 64;
+
+    ctx_save(ctx);
+
+    ctx_font_size(ctx, font_size);
+    ctx_text_align(ctx, CTX_TEXT_ALIGN_LEFT);  // XXX this should not be needed
+
+    ctx_gray(ctx, 0.0);
+    ctx_rectangle(ctx, -120, -120, 240, 240);
+    ctx_fill(ctx);
+
+    ctx_gray(ctx, 1.0);
+    for (int i = 0; i < 7 && y > -70; i++) {
+        ctx_move_to(ctx, -100, y);
+        ctx_text(ctx, st3m_term_get_line(i + skip_lines));
+        y -= font_size;
+    }
+
+    ctx_restore(ctx);
+
+    st3m_gfx_drawctx_pipe_put(target);
+}
diff --git a/components/st3m/st3m_term.h b/components/st3m/st3m_term.h
new file mode 100644
index 0000000000000000000000000000000000000000..7982a760a697fca332808ab99e03205761023498
--- /dev/null
+++ b/components/st3m/st3m_term.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "esp_err.h"
+
+#include <stdint.h>
+
+void st3m_imu_init(void);
+
+void st3m_term_feed(const char *str, size_t len);
+const char *st3m_term_get_line(int line_no);
+void st3m_term_draw(int skip_lines);