diff --git a/esp8266/mpconfigport.h b/esp8266/mpconfigport.h
index fbf77f645c8836d471b81e1e7fc16475e6c1ff8d..7d1d6e1b7b32889ac22a1616a8b7ba0eeedf2e38 100644
--- a/esp8266/mpconfigport.h
+++ b/esp8266/mpconfigport.h
@@ -61,6 +61,11 @@ extern const struct _mp_obj_module_t pyb_module;
 #define MICROPY_PORT_BUILTIN_MODULES \
     { MP_OBJ_NEW_QSTR(MP_QSTR_pyb), (mp_obj_t)&pyb_module }, \
 
+#define MP_STATE_PORT MP_STATE_VM
+
+#define MICROPY_PORT_ROOT_POINTERS \
+    const char *readline_hist[8];
+
 // We need to provide a declaration/definition of alloca()
 #include <alloca.h>
 
diff --git a/stmhal/extint.c b/stmhal/extint.c
index 78bc17be0fd46e15cbaaff99e2ba1fbcba920f76..40254ac4ddca9c25ba06e26e317ef6d400c96eed 100644
--- a/stmhal/extint.c
+++ b/stmhal/extint.c
@@ -28,8 +28,6 @@
 #include <stddef.h>
 #include <string.h>
 
-#include <stm32f4xx_hal.h>
-
 #include "py/nlr.h"
 #include "py/runtime.h"
 #include "py/gc.h"
@@ -101,13 +99,7 @@ typedef struct {
     mp_int_t line;
 } extint_obj_t;
 
-typedef struct {
-    mp_obj_t callback_obj;
-    void *param;
-    uint32_t mode;
-} extint_vector_t;
-
-STATIC extint_vector_t extint_vector[EXTI_NUM_VECTORS];
+STATIC uint32_t pyb_extint_mode[EXTI_NUM_VECTORS];
 
 #if !defined(ETH)
 #define ETH_WKUP_IRQn   62  // The 405 doesn't have ETH, but we want a value to put in our table
@@ -123,10 +115,7 @@ STATIC const uint8_t nvic_irq_channel[EXTI_NUM_VECTORS] = {
 
 // Set override_callback_obj to true if you want to unconditionally set the
 // callback function.
-//
-// NOTE: param is for C callers. Python can use closure to get an object bound
-//       with the function.
-uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t callback_obj, bool override_callback_obj, void *param) {
+uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t callback_obj, bool override_callback_obj) {
     const pin_obj_t *pin = NULL;
     uint v_line;
 
@@ -159,22 +148,21 @@ uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t ca
         nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid ExtInt Pull: %d", pull));
     }
 
-    extint_vector_t *v = &extint_vector[v_line];
-    if (!override_callback_obj && v->callback_obj != mp_const_none && callback_obj != mp_const_none) {
+    mp_obj_t *cb = &MP_STATE_PORT(pyb_extint_callback)[v_line];
+    if (!override_callback_obj && *cb != mp_const_none && callback_obj != mp_const_none) {
         nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "ExtInt vector %d is already in use", v_line));
     }
 
-    // We need to update callback and param atomically, so we disable the line
+    // We need to update callback atomically, so we disable the line
     // before we update anything.
 
     extint_disable(v_line);
 
-    v->callback_obj = callback_obj;
-    v->param = param;
-    v->mode = (mode & 0x00010000) ? // GPIO_MODE_IT == 0x00010000
+    *cb = callback_obj;
+    pyb_extint_mode[v_line] = (mode & 0x00010000) ? // GPIO_MODE_IT == 0x00010000
         EXTI_Mode_Interrupt : EXTI_Mode_Event;
 
-    if (v->callback_obj != mp_const_none) {
+    if (*cb != mp_const_none) {
 
         GPIO_InitTypeDef exti;
         exti.Pin = pin->pin_mask;
@@ -199,7 +187,7 @@ void extint_enable(uint line) {
     // Since manipulating IMR/EMR is a read-modify-write, and we want this to
     // be atomic, we use the bit-band area to just affect the bit we're
     // interested in.
-    EXTI_MODE_BB(extint_vector[line].mode, line) = 1;
+    EXTI_MODE_BB(pyb_extint_mode[line], line) = 1;
 }
 
 void extint_disable(uint line) {
@@ -303,7 +291,7 @@ STATIC mp_obj_t extint_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n_
 
     extint_obj_t *self = m_new_obj(extint_obj_t);
     self->base.type = type_in;
-    self->line = extint_register(vals[0].u_obj, vals[1].u_int, vals[2].u_int, vals[3].u_obj, false, NULL);
+    self->line = extint_register(vals[0].u_obj, vals[1].u_int, vals[2].u_int, vals[3].u_obj, false);
 
     return self;
 }
@@ -343,11 +331,10 @@ const mp_obj_type_t extint_type = {
 };
 
 void extint_init0(void) {
-    for (extint_vector_t *v = extint_vector; v < &extint_vector[EXTI_NUM_VECTORS]; v++) {
-        v->callback_obj = mp_const_none;
-        v->param = NULL;
-        v->mode = EXTI_Mode_Interrupt;
-    }
+    for (int i = 0; i < PYB_EXTI_NUM_VECTORS; i++) {
+        MP_STATE_PORT(pyb_extint_callback)[i] = mp_const_none;
+        pyb_extint_mode[i] = EXTI_Mode_Interrupt;
+   }
 }
 
 // Interrupt handler
@@ -355,18 +342,18 @@ void Handle_EXTI_Irq(uint32_t line) {
     if (__HAL_GPIO_EXTI_GET_FLAG(1 << line)) {
         __HAL_GPIO_EXTI_CLEAR_FLAG(1 << line);
         if (line < EXTI_NUM_VECTORS) {
-            extint_vector_t *v = &extint_vector[line];
-            if (v->callback_obj != mp_const_none) {
+            mp_obj_t *cb = &MP_STATE_PORT(pyb_extint_callback)[line];
+            if (*cb != mp_const_none) {
                 // When executing code within a handler we must lock the GC to prevent
                 // any memory allocations.  We must also catch any exceptions.
                 gc_lock();
                 nlr_buf_t nlr;
                 if (nlr_push(&nlr) == 0) {
-                    mp_call_function_1(v->callback_obj, MP_OBJ_NEW_SMALL_INT(line));
+                    mp_call_function_1(*cb, MP_OBJ_NEW_SMALL_INT(line));
                     nlr_pop();
                 } else {
                     // Uncaught exception; disable the callback so it doesn't run again.
-                    v->callback_obj = mp_const_none;
+                    *cb = mp_const_none;
                     extint_disable(line);
                     printf("Uncaught exception in ExtInt interrupt handler line %lu\n", line);
                     mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val);
diff --git a/stmhal/extint.h b/stmhal/extint.h
index cae23959cf6e3322d2e6b6a370623164cda0290d..6eb21374144515d6df9044ae80a467c4f27ac83a 100644
--- a/stmhal/extint.h
+++ b/stmhal/extint.h
@@ -37,7 +37,7 @@
 #define EXTI_RTC_TIMESTAMP      (21)
 #define EXTI_RTC_WAKEUP         (22)
 
-#define EXTI_NUM_VECTORS        (23)
+#define EXTI_NUM_VECTORS        (PYB_EXTI_NUM_VECTORS)
 
 #define EXTI_MODE_INTERRUPT     (offsetof(EXTI_TypeDef, IMR))
 #define EXTI_MODE_EVENT         (offsetof(EXTI_TypeDef, EMR))
@@ -48,7 +48,7 @@
 
 void extint_init0(void);
 
-uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t callback_obj, bool override_callback_obj, void *param);
+uint extint_register(mp_obj_t pin_obj, uint32_t mode, uint32_t pull, mp_obj_t callback_obj, bool override_callback_obj);
 
 void extint_enable(uint line);
 void extint_disable(uint line);
diff --git a/stmhal/gccollect.c b/stmhal/gccollect.c
index fcbe10fec18ef87d2c07b8b4bc7b03edd670d797..de76b71ac1171e421a23f2280ce2ac6d9d78e089 100644
--- a/stmhal/gccollect.c
+++ b/stmhal/gccollect.c
@@ -30,24 +30,19 @@
 #include "py/obj.h"
 #include "py/gc.h"
 #include "gccollect.h"
-#include MICROPY_HAL_H
+#include "systick.h"
 
 mp_uint_t gc_helper_get_regs_and_sp(mp_uint_t *regs);
 
-// obsolete
-// void gc_helper_get_regs_and_clean_stack(mp_uint_t *regs, mp_uint_t heap_end);
-
 void gc_collect(void) {
     // get current time, in case we want to time the GC
-    uint32_t start = HAL_GetTick();
+    #if 0
+    uint32_t start = sys_tick_get_microseconds();
+    #endif
 
     // start the GC
     gc_collect_start();
 
-    // We need to scan everything in RAM that can hold a pointer.
-    // The data segment is used, but should not contain pointers, so we just scan the bss.
-    gc_collect_root((void**)&_sbss, ((uint32_t)&_ebss - (uint32_t)&_sbss) / sizeof(uint32_t));
-
     // get the registers and the sp
     mp_uint_t regs[10];
     mp_uint_t sp = gc_helper_get_regs_and_sp(regs);
@@ -58,14 +53,14 @@ void gc_collect(void) {
     // end the GC
     gc_collect_end();
 
-    if (0) {
-        // print GC info
-        uint32_t ticks = HAL_GetTick() - start; // TODO implement a function that does this properly
-        gc_info_t info;
-        gc_info(&info);
-        printf("GC@%lu %lums\n", start, ticks);
-        printf(" " UINT_FMT " total\n", info.total);
-        printf(" " UINT_FMT " : " UINT_FMT "\n", info.used, info.free);
-        printf(" 1=" UINT_FMT " 2=" UINT_FMT " m=" UINT_FMT "\n", info.num_1block, info.num_2block, info.max_block);
-    }
+    #if 0
+    // print GC info
+    uint32_t ticks = sys_tick_get_microseconds() - start;
+    gc_info_t info;
+    gc_info(&info);
+    printf("GC@%lu %lums\n", start, ticks);
+    printf(" " UINT_FMT " total\n", info.total);
+    printf(" " UINT_FMT " : " UINT_FMT "\n", info.used, info.free);
+    printf(" 1=" UINT_FMT " 2=" UINT_FMT " m=" UINT_FMT "\n", info.num_1block, info.num_2block, info.max_block);
+    #endif
 }
diff --git a/stmhal/main.c b/stmhal/main.c
index ae5c94b531320d5f23ac887b8a2b6aeba7532f87..d78e5529f0e08aa426ae1e3290577ea986c8048b 100644
--- a/stmhal/main.c
+++ b/stmhal/main.c
@@ -112,12 +112,9 @@ void MP_WEAK __assert_func(const char *file, int line, const char *func, const c
 }
 #endif
 
-STATIC mp_obj_t pyb_config_main = MP_OBJ_NULL;
-STATIC mp_obj_t pyb_config_usb_mode = MP_OBJ_NULL;
-
 STATIC mp_obj_t pyb_main(mp_obj_t main) {
     if (MP_OBJ_IS_STR(main)) {
-        pyb_config_main = main;
+        MP_STATE_PORT(pyb_config_main) = main;
     }
     return mp_const_none;
 }
@@ -125,7 +122,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(pyb_main_obj, pyb_main);
 
 STATIC mp_obj_t pyb_usb_mode(mp_obj_t usb_mode) {
     if (MP_OBJ_IS_STR(usb_mode)) {
-        pyb_config_usb_mode = usb_mode;
+        MP_STATE_PORT(pyb_config_usb_mode) = usb_mode;
     }
     return mp_const_none;
 }
@@ -303,7 +300,7 @@ soft_reset:
         pyb_stdio_uart = pyb_uart_type.make_new((mp_obj_t)&pyb_uart_type, MP_ARRAY_SIZE(args), 0, args);
     }
 #else
-    pyb_stdio_uart = NULL;
+    MP_STATE_PORT(pyb_stdio_uart) = NULL;
 #endif
 
     // Initialise low-level sub-systems.  Here we need to very basic things like
@@ -444,8 +441,8 @@ soft_reset:
 #endif
 
     // reset config variables; they should be set by boot.py
-    pyb_config_main = MP_OBJ_NULL;
-    pyb_config_usb_mode = MP_OBJ_NULL;
+    MP_STATE_PORT(pyb_config_main) = MP_OBJ_NULL;
+    MP_STATE_PORT(pyb_config_usb_mode) = MP_OBJ_NULL;
 
     // run boot.py, if it exists
     // TODO perhaps have pyb.reboot([bootpy]) function to soft-reboot and execute custom boot.py
@@ -479,8 +476,8 @@ soft_reset:
     // USB device
     usb_device_mode_t usb_mode = USB_DEVICE_MODE_CDC_MSC;
     // if we are not in reset_mode==1, this config variable will always be NULL
-    if (pyb_config_usb_mode != MP_OBJ_NULL) {
-        if (strcmp(mp_obj_str_get_str(pyb_config_usb_mode), "CDC+HID") == 0) {
+    if (MP_STATE_PORT(pyb_config_usb_mode) != MP_OBJ_NULL) {
+        if (strcmp(mp_obj_str_get_str(MP_STATE_PORT(pyb_config_usb_mode)), "CDC+HID") == 0) {
             usb_mode = USB_DEVICE_MODE_CDC_HID;
         }
     }
@@ -509,10 +506,10 @@ soft_reset:
     // Run the main script from the current directory.
     if (reset_mode == 1 && pyexec_mode_kind == PYEXEC_MODE_FRIENDLY_REPL) {
         const char *main_py;
-        if (pyb_config_main == MP_OBJ_NULL) {
+        if (MP_STATE_PORT(pyb_config_main) == MP_OBJ_NULL) {
             main_py = "main.py";
         } else {
-            main_py = mp_obj_str_get_str(pyb_config_main);
+            main_py = mp_obj_str_get_str(MP_STATE_PORT(pyb_config_main));
         }
         FRESULT res = f_stat(main_py, NULL);
         if (res == FR_OK) {
diff --git a/stmhal/modpyb.c b/stmhal/modpyb.c
index 91b9464713a9cc3f491334b7e423e9b01a4e275d..64712ad300a8dce33d0ccc7b5209dc4d980bf29f 100644
--- a/stmhal/modpyb.c
+++ b/stmhal/modpyb.c
@@ -29,6 +29,7 @@
 
 #include "stm32f4xx_hal.h"
 
+#include "py/mpstate.h"
 #include "py/nlr.h"
 #include "py/obj.h"
 #include "py/gc.h"
@@ -475,16 +476,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(pyb_have_cdc_obj, pyb_have_cdc);
 /// Get or set the UART object that the REPL is repeated on.
 STATIC mp_obj_t pyb_repl_uart(mp_uint_t n_args, const mp_obj_t *args) {
     if (n_args == 0) {
-        if (pyb_stdio_uart == NULL) {
+        if (MP_STATE_PORT(pyb_stdio_uart) == NULL) {
             return mp_const_none;
         } else {
-            return pyb_stdio_uart;
+            return MP_STATE_PORT(pyb_stdio_uart);
         }
     } else {
         if (args[0] == mp_const_none) {
-            pyb_stdio_uart = NULL;
+            MP_STATE_PORT(pyb_stdio_uart) = NULL;
         } else if (mp_obj_get_type(args[0]) == &pyb_uart_type) {
-            pyb_stdio_uart = args[0];
+            MP_STATE_PORT(pyb_stdio_uart) = args[0];
         } else {
             nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "need a UART object"));
         }
diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h
index d75102df36faa4f44c81dd8f4a3fa3ddf3f2c396..15d84e85fdadb26dd11bfee95f7592e462e4957d 100644
--- a/stmhal/mpconfigport.h
+++ b/stmhal/mpconfigport.h
@@ -122,6 +122,34 @@ extern const struct _mp_obj_module_t mp_module_network;
     { MP_OBJ_NEW_QSTR(MP_QSTR_pyb), (mp_obj_t)&pyb_module }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_stm), (mp_obj_t)&stm_module }, \
 
+#define PYB_EXTI_NUM_VECTORS (23)
+
+#define MP_STATE_PORT MP_STATE_VM
+
+#define MICROPY_PORT_ROOT_POINTERS \
+    const char *readline_hist[8]; \
+    \
+    mp_obj_t mp_const_vcp_interrupt; \
+    \
+    mp_obj_t pyb_config_main; \
+    mp_obj_t pyb_config_usb_mode; \
+    \
+    mp_obj_t pyb_switch_callback; \
+    \
+    mp_obj_t pin_class_mapper; \
+    mp_obj_t pin_class_map_dict; \
+    \
+    mp_obj_t pyb_extint_callback[PYB_EXTI_NUM_VECTORS]; \
+    \
+    /* Used to do callbacks to Python code on interrupt */ \
+    struct _pyb_timer_obj_t *pyb_timer_obj_all[14]; \
+    \
+    /* stdio is repeated on this UART object if it's not null */ \
+    struct _pyb_uart_obj_t *pyb_stdio_uart; \
+    \
+    /* pointers to all UART objects (if they have been created) */ \
+    struct _pyb_uart_obj_t *pyb_uart_obj_all[6]; \
+
 // type definitions for the specific machine
 
 #define BYTES_PER_WORD (4)
diff --git a/stmhal/pendsv.c b/stmhal/pendsv.c
index d7104d8518375efa3e2cf1c3b9c2c4c7108ae715..68455b2752e03adec7ccdba841b9aad73b38243a 100644
--- a/stmhal/pendsv.c
+++ b/stmhal/pendsv.c
@@ -31,7 +31,10 @@
 #include "py/runtime.h"
 #include "pendsv.h"
 
-static void *pendsv_object = NULL;
+// Note: this can contain point to the heap but is not traced by GC.
+// This is okay because we only ever set it to mp_const_vcp_interrupt
+// which is in the root-pointer set.
+STATIC void *pendsv_object;
 
 void pendsv_init(void) {
     // set PendSV interrupt at lowest priority
diff --git a/stmhal/pin.c b/stmhal/pin.c
index 91d5d916da9a149e81379d6ebf584588ca63ac79..0d28ffd54d9085982bd760b9e435808479f28236 100644
--- a/stmhal/pin.c
+++ b/stmhal/pin.c
@@ -91,13 +91,11 @@
 /// how a particular object gets mapped to a pin.
 
 // Pin class variables
-STATIC mp_obj_t pin_class_mapper;
-STATIC mp_obj_t pin_class_map_dict;
 STATIC bool pin_class_debug;
 
 void pin_init0(void) {
-    pin_class_mapper = mp_const_none;
-    pin_class_map_dict = mp_const_none;
+    MP_STATE_PORT(pin_class_mapper) = mp_const_none;
+    MP_STATE_PORT(pin_class_map_dict) = mp_const_none;
     pin_class_debug = false;
 }
 
@@ -116,8 +114,8 @@ const pin_obj_t *pin_find(mp_obj_t user_obj) {
         return pin_obj;
     }
 
-    if (pin_class_mapper != mp_const_none) {
-        pin_obj = mp_call_function_1(pin_class_mapper, user_obj);
+    if (MP_STATE_PORT(pin_class_mapper) != mp_const_none) {
+        pin_obj = mp_call_function_1(MP_STATE_PORT(pin_class_mapper), user_obj);
         if (pin_obj != mp_const_none) {
             if (!MP_OBJ_IS_TYPE(pin_obj, &pin_type)) {
                 nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "Pin.mapper didn't return a Pin object"));
@@ -135,8 +133,8 @@ const pin_obj_t *pin_find(mp_obj_t user_obj) {
         // other lookup methods.
     }
 
-    if (pin_class_map_dict != mp_const_none) {
-        mp_map_t *pin_map_map = mp_obj_dict_get_map(pin_class_map_dict);
+    if (MP_STATE_PORT(pin_class_map_dict) != mp_const_none) {
+        mp_map_t *pin_map_map = mp_obj_dict_get_map(MP_STATE_PORT(pin_class_map_dict));
         mp_map_elem_t *elem = mp_map_lookup(pin_map_map, user_obj, MP_MAP_LOOKUP);
         if (elem != NULL && elem->value != NULL) {
             pin_obj = elem->value;
@@ -266,10 +264,10 @@ STATIC mp_obj_t pin_make_new(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw,
 /// Get or set the pin mapper function.
 STATIC mp_obj_t pin_mapper(mp_uint_t n_args, const mp_obj_t *args) {
     if (n_args > 1) {
-        pin_class_mapper = args[1];
+        MP_STATE_PORT(pin_class_mapper) = args[1];
         return mp_const_none;
     }
-    return pin_class_mapper;
+    return MP_STATE_PORT(pin_class_mapper);
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_mapper_fun_obj, 1, 2, pin_mapper);
 STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_mapper_obj, (mp_obj_t)&pin_mapper_fun_obj);
@@ -278,10 +276,10 @@ STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_mapper_obj, (mp_obj_t)&pin_mapper_fun
 /// Get or set the pin mapper dictionary.
 STATIC mp_obj_t pin_map_dict(mp_uint_t n_args, const mp_obj_t *args) {
     if (n_args > 1) {
-        pin_class_map_dict = args[1];
+        MP_STATE_PORT(pin_class_map_dict) = args[1];
         return mp_const_none;
     }
-    return pin_class_map_dict;
+    return MP_STATE_PORT(pin_class_map_dict);
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pin_map_dict_fun_obj, 1, 2, pin_map_dict);
 STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(pin_map_dict_obj, (mp_obj_t)&pin_map_dict_fun_obj);
diff --git a/stmhal/pybstdio.c b/stmhal/pybstdio.c
index 5597bbd689575f1b27d456f085682b8b0ae2621d..d8ccc1be0acaa2398d61674d52d2df5eaf7da019 100644
--- a/stmhal/pybstdio.c
+++ b/stmhal/pybstdio.c
@@ -29,6 +29,7 @@
 #include <string.h>
 #include <errno.h>
 
+#include "py/mpstate.h"
 #include "py/obj.h"
 #include "py/stream.h"
 #include "usb.h"
@@ -40,16 +41,13 @@
 // be changed by Python code.  This requires some changes, as these
 // objects are in a read-only module (py/modsys.c).
 
-// stdio is repeated on this UART object if it's not null
-pyb_uart_obj_t *pyb_stdio_uart = NULL;
-
 void stdout_tx_str(const char *str) {
     stdout_tx_strn(str, strlen(str));
 }
 
 void stdout_tx_strn(const char *str, mp_uint_t len) {
-    if (pyb_stdio_uart != PYB_UART_NONE) {
-        uart_tx_strn(pyb_stdio_uart, str, len);
+    if (MP_STATE_PORT(pyb_stdio_uart) != NULL) {
+        uart_tx_strn(MP_STATE_PORT(pyb_stdio_uart), str, len);
     }
 #if 0 && defined(USE_HOST_MODE) && MICROPY_HW_HAS_LCD
     lcd_print_strn(str, len);
@@ -61,8 +59,8 @@ void stdout_tx_strn(const char *str, mp_uint_t len) {
 
 void stdout_tx_strn_cooked(const char *str, mp_uint_t len) {
     // send stdout to UART and USB CDC VCP
-    if (pyb_stdio_uart != PYB_UART_NONE) {
-        uart_tx_strn_cooked(pyb_stdio_uart, str, len);
+    if (MP_STATE_PORT(pyb_stdio_uart) != NULL) {
+        uart_tx_strn_cooked(MP_STATE_PORT(pyb_stdio_uart), str, len);
     }
     if (usb_vcp_is_enabled()) {
         usb_vcp_send_strn_cooked(str, len);
@@ -84,8 +82,8 @@ int stdin_rx_chr(void) {
         byte c;
         if (usb_vcp_recv_byte(&c) != 0) {
             return c;
-        } else if (pyb_stdio_uart != PYB_UART_NONE && uart_rx_any(pyb_stdio_uart)) {
-            return uart_rx_char(pyb_stdio_uart);
+        } else if (MP_STATE_PORT(pyb_stdio_uart) != NULL && uart_rx_any(MP_STATE_PORT(pyb_stdio_uart))) {
+            return uart_rx_char(MP_STATE_PORT(pyb_stdio_uart));
         }
         __WFI();
     }
diff --git a/stmhal/readline.c b/stmhal/readline.c
index aa9194a035c5b7af48775258ac3afdc5bd7d87a7..ee2e8ebdae7a94b5d73f299554ad0758e93c2a21 100644
--- a/stmhal/readline.c
+++ b/stmhal/readline.c
@@ -28,8 +28,7 @@
 #include <stdint.h>
 #include <string.h>
 
-#include "py/mpconfig.h"
-#include "py/misc.h"
+#include "py/mpstate.h"
 #include "readline.h"
 #include "pybstdio.h"
 
@@ -40,14 +39,12 @@
 #define DEBUG_printf(...) (void)0
 #endif
 
-#define READLINE_HIST_SIZE (8)
-
-static const char *readline_hist[READLINE_HIST_SIZE] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
+#define READLINE_HIST_SIZE (MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)))
 
 enum { ESEQ_NONE, ESEQ_ESC, ESEQ_ESC_BRACKET, ESEQ_ESC_BRACKET_DIGIT, ESEQ_ESC_O };
 
 void readline_init0(void) {
-    memset(readline_hist, 0, READLINE_HIST_SIZE * sizeof(const char*));
+    memset(MP_STATE_PORT(readline_hist), 0, READLINE_HIST_SIZE * sizeof(const char*));
 }
 
 STATIC char *str_dup_maybe(const char *str) {
@@ -89,15 +86,15 @@ int readline(vstr_t *line, const char *prompt) {
             } 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)) {
+                if (line->len > orig_line_len && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(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
                     char *most_recent_hist = str_dup_maybe(line->buf + orig_line_len);
                     if (most_recent_hist != NULL) {
                         for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
-                            readline_hist[i] = readline_hist[i - 1];
+                            MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
                         }
-                        readline_hist[0] = most_recent_hist;
+                        MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
                     }
                 }
                 return 0;
@@ -139,12 +136,12 @@ int readline(vstr_t *line, const char *prompt) {
                 escape_seq = ESEQ_NONE;
                 if (c == 'A') {
                     // up arrow
-                    if (hist_cur + 1 < READLINE_HIST_SIZE && readline_hist[hist_cur + 1] != NULL) {
+                    if (hist_cur + 1 < READLINE_HIST_SIZE && MP_STATE_PORT(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]);
+                        vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]);
                         // set redraw parameters
                         redraw_step_back = cursor_pos - orig_line_len;
                         redraw_from_cursor = true;
@@ -158,7 +155,7 @@ int readline(vstr_t *line, const char *prompt) {
                         // 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]);
+                            vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]);
                         }
                         // set redraw parameters
                         redraw_step_back = cursor_pos - orig_line_len;
diff --git a/stmhal/timer.c b/stmhal/timer.c
index 7adb98c826ec80042d4d126c2d220bdf1376642d..87239a5b8f1c4ca667fb5042595532a676f98b24 100644
--- a/stmhal/timer.c
+++ b/stmhal/timer.c
@@ -145,9 +145,7 @@ TIM_HandleTypeDef TIM6_Handle;
 // Used to divide down TIM3 and periodically call the flash storage IRQ
 STATIC uint32_t tim3_counter = 0;
 
-// Used to do callbacks to Python code on interrupt
-STATIC pyb_timer_obj_t *pyb_timer_obj_all[14];
-#define PYB_TIMER_OBJ_ALL_NUM MP_ARRAY_SIZE(pyb_timer_obj_all)
+#define PYB_TIMER_OBJ_ALL_NUM MP_ARRAY_SIZE(MP_STATE_PORT(pyb_timer_obj_all))
 
 STATIC uint32_t timer_get_source_freq(uint32_t tim_id);
 STATIC mp_obj_t pyb_timer_deinit(mp_obj_t self_in);
@@ -157,14 +155,14 @@ STATIC mp_obj_t pyb_timer_channel_callback(mp_obj_t self_in, mp_obj_t callback);
 void timer_init0(void) {
     tim3_counter = 0;
     for (uint i = 0; i < PYB_TIMER_OBJ_ALL_NUM; i++) {
-        pyb_timer_obj_all[i] = NULL;
+        MP_STATE_PORT(pyb_timer_obj_all)[i] = NULL;
     }
 }
 
 // unregister all interrupt sources
 void timer_deinit(void) {
     for (uint i = 0; i < PYB_TIMER_OBJ_ALL_NUM; i++) {
-        pyb_timer_obj_t *tim = pyb_timer_obj_all[i];
+        pyb_timer_obj_t *tim = MP_STATE_PORT(pyb_timer_obj_all)[i];
         if (tim != NULL) {
             pyb_timer_deinit(tim);
         }
@@ -659,7 +657,7 @@ STATIC mp_obj_t pyb_timer_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t
 
     // set the global variable for interrupt callbacks
     if (tim->tim_id - 1 < PYB_TIMER_OBJ_ALL_NUM) {
-        pyb_timer_obj_all[tim->tim_id - 1] = tim;
+        MP_STATE_PORT(pyb_timer_obj_all)[tim->tim_id - 1] = tim;
     }
 
     return (mp_obj_t)tim;
@@ -1253,7 +1251,7 @@ STATIC void timer_handle_irq_channel(pyb_timer_obj_t *tim, uint8_t channel, mp_o
 void timer_irq_handler(uint tim_id) {
     if (tim_id - 1 < PYB_TIMER_OBJ_ALL_NUM) {
         // get the timer object
-        pyb_timer_obj_t *tim = pyb_timer_obj_all[tim_id - 1];
+        pyb_timer_obj_t *tim = MP_STATE_PORT(pyb_timer_obj_all)[tim_id - 1];
 
         if (tim == NULL) {
             // Timer object has not been set, so we can't do anything.
diff --git a/stmhal/uart.c b/stmhal/uart.c
index 98be745938449ca7778805ebda29769f009dba7f..23eba45b87ba1679f3961c578b776c17e2a594df 100644
--- a/stmhal/uart.c
+++ b/stmhal/uart.c
@@ -90,21 +90,18 @@ struct _pyb_uart_obj_t {
     byte *read_buf;                     // byte or uint16_t, depending on char size
 };
 
-// pointers to all UART objects (if they have been created)
-STATIC pyb_uart_obj_t *pyb_uart_obj_all[6];
-
 STATIC mp_obj_t pyb_uart_deinit(mp_obj_t self_in);
 
 void uart_init0(void) {
-    for (int i = 0; i < MP_ARRAY_SIZE(pyb_uart_obj_all); i++) {
-        pyb_uart_obj_all[i] = NULL;
+    for (int i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_uart_obj_all)); i++) {
+        MP_STATE_PORT(pyb_uart_obj_all)[i] = NULL;
     }
 }
 
 // unregister all interrupt sources
 void uart_deinit(void) {
-    for (int i = 0; i < MP_ARRAY_SIZE(pyb_uart_obj_all); i++) {
-        pyb_uart_obj_t *uart_obj = pyb_uart_obj_all[i];
+    for (int i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_uart_obj_all)); i++) {
+        pyb_uart_obj_t *uart_obj = MP_STATE_PORT(pyb_uart_obj_all)[i];
         if (uart_obj != NULL) {
             pyb_uart_deinit(uart_obj);
         }
@@ -302,7 +299,7 @@ void uart_tx_strn_cooked(pyb_uart_obj_t *uart_obj, const char *str, uint len) {
 // this IRQ handler is set up to handle RXNE interrupts only
 void uart_irq_handler(mp_uint_t uart_id) {
     // get the uart object
-    pyb_uart_obj_t *self = pyb_uart_obj_all[uart_id - 1];
+    pyb_uart_obj_t *self = MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1];
 
     if (self == NULL) {
         // UART object has not been set, so we can't do anything, not
@@ -502,21 +499,21 @@ STATIC mp_obj_t pyb_uart_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t
         }
     } else {
         uart_id = mp_obj_get_int(args[0]);
-        if (uart_id < 1 || uart_id > MP_ARRAY_SIZE(pyb_uart_obj_all)) {
+        if (uart_id < 1 || uart_id > MP_ARRAY_SIZE(MP_STATE_PORT(pyb_uart_obj_all))) {
             nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "UART(%d) does not exist", uart_id));
         }
     }
 
     pyb_uart_obj_t *self;
-    if (pyb_uart_obj_all[uart_id - 1] == NULL) {
+    if (MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1] == NULL) {
         // create new UART object
         self = m_new0(pyb_uart_obj_t, 1);
         self->base.type = &pyb_uart_type;
         self->uart_id = uart_id;
-        pyb_uart_obj_all[uart_id - 1] = self;
+        MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1] = self;
     } else {
         // reference existing UART object
-        self = pyb_uart_obj_all[uart_id - 1];
+        self = MP_STATE_PORT(pyb_uart_obj_all)[uart_id - 1];
     }
 
     if (n_args > 1 || n_kw > 0) {
diff --git a/stmhal/usb.c b/stmhal/usb.c
index 50a5a7420d538eff62c66d3e18fd8e821c540ff6..0bda0aaeb20c35da6ed0f987dd1244e957bbcfef 100644
--- a/stmhal/usb.c
+++ b/stmhal/usb.c
@@ -45,12 +45,11 @@ USBD_HandleTypeDef hUSBDDevice;
 #endif
 
 STATIC int dev_is_enabled = 0;
-STATIC mp_obj_t mp_const_vcp_interrupt = MP_OBJ_NULL;
 
 void pyb_usb_init0(void) {
     // create an exception object for interrupting by VCP
-    mp_const_vcp_interrupt = mp_obj_new_exception(&mp_type_KeyboardInterrupt);
-    USBD_CDC_SetInterrupt(-1, mp_const_vcp_interrupt);
+    MP_STATE_PORT(mp_const_vcp_interrupt) = mp_obj_new_exception(&mp_type_KeyboardInterrupt);
+    USBD_CDC_SetInterrupt(-1, MP_STATE_PORT(mp_const_vcp_interrupt));
 }
 
 void pyb_usb_dev_init(usb_device_mode_t mode, usb_storage_medium_t medium) {
@@ -102,9 +101,9 @@ bool usb_vcp_is_connected(void) {
 void usb_vcp_set_interrupt_char(int c) {
     if (dev_is_enabled) {
         if (c != -1) {
-            mp_obj_exception_clear_traceback(mp_const_vcp_interrupt);
+            mp_obj_exception_clear_traceback(MP_STATE_PORT(mp_const_vcp_interrupt));
         }
-        USBD_CDC_SetInterrupt(c, mp_const_vcp_interrupt);
+        USBD_CDC_SetInterrupt(c, MP_STATE_PORT(mp_const_vcp_interrupt));
     }
 }
 
diff --git a/stmhal/usrsw.c b/stmhal/usrsw.c
index 69a636ceac57aa74667da80c98083a90c2615664..b6320054a1c35987a7b6c141d4e64af1c603d0e7 100644
--- a/stmhal/usrsw.c
+++ b/stmhal/usrsw.c
@@ -26,8 +26,6 @@
 
 #include <stdio.h>
 
-#include "stm32f4xx_hal.h"
-
 #include "py/runtime.h"
 #include "extint.h"
 #include "pin.h"
@@ -73,10 +71,9 @@ int switch_get(void) {
 
 typedef struct _pyb_switch_obj_t {
     mp_obj_base_t base;
-    mp_obj_t callback;
 } pyb_switch_obj_t;
 
-STATIC pyb_switch_obj_t pyb_switch_obj;
+STATIC const pyb_switch_obj_t pyb_switch_obj = {{&pyb_switch_type}};
 
 void pyb_switch_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
     print(env, "Switch()");
@@ -88,9 +85,6 @@ STATIC mp_obj_t pyb_switch_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_
     // check arguments
     mp_arg_check_num(n_args, n_kw, 0, 0, false);
 
-    // init the switch object
-    pyb_switch_obj.base.type = &pyb_switch_type;
-
     // No need to clear the callback member: if it's already been set and registered
     // with extint then we don't want to reset that behaviour.  If it hasn't been set,
     // then no extint will be called until it is set via the callback method.
@@ -108,8 +102,8 @@ mp_obj_t pyb_switch_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, con
 }
 
 STATIC mp_obj_t switch_callback(mp_obj_t line) {
-    if (pyb_switch_obj.callback != mp_const_none) {
-        mp_call_function_0(pyb_switch_obj.callback);
+    if (MP_STATE_PORT(pyb_switch_callback) != mp_const_none) {
+        mp_call_function_0(MP_STATE_PORT(pyb_switch_callback));
     }
     return mp_const_none;
 }
@@ -119,8 +113,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(switch_callback_obj, switch_callback);
 /// Register the given function to be called when the switch is pressed down.
 /// If `fun` is `None`, then it disables the callback.
 mp_obj_t pyb_switch_callback(mp_obj_t self_in, mp_obj_t callback) {
-    pyb_switch_obj_t *self = self_in;
-    self->callback = callback;
+    MP_STATE_PORT(pyb_switch_callback) = callback;
     // Init the EXTI each time this function is called, since the EXTI
     // may have been disabled by an exception in the interrupt, or the
     // user disabling the line explicitly.
@@ -128,7 +121,7 @@ mp_obj_t pyb_switch_callback(mp_obj_t self_in, mp_obj_t callback) {
                     MICROPY_HW_USRSW_EXTI_MODE,
                     MICROPY_HW_USRSW_PULL,
                     callback == mp_const_none ? mp_const_none : (mp_obj_t)&switch_callback_obj,
-                    true, NULL);
+                    true);
     return mp_const_none;
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_switch_callback_obj, pyb_switch_callback);
diff --git a/teensy/mpconfigport.h b/teensy/mpconfigport.h
index efe51fecda243e4374587466db0ce4cea0a883ff..63f0ee7b70c04214d95edd448709ae6f66b0ee5e 100644
--- a/teensy/mpconfigport.h
+++ b/teensy/mpconfigport.h
@@ -44,6 +44,14 @@ extern const struct _mp_obj_module_t time_module;
 #define MICROPY_PORT_CONSTANTS \
     { MP_OBJ_NEW_QSTR(MP_QSTR_pyb), (mp_obj_t)&pyb_module }, \
 
+#define MP_STATE_PORT MP_STATE_VM
+
+#define MICROPY_PORT_ROOT_POINTERS \
+    const char *readline_hist[8]; \
+    mp_obj_t pin_class_mapper; \
+    mp_obj_t pin_class_map_dict; \
+    struct _pyb_uart_obj_t *pyb_stdio_uart; \
+
 // type definitions for the specific machine
 
 #define BYTES_PER_WORD (4)