diff --git a/stmhal/Makefile b/stmhal/Makefile
index edd5e06193b7a9117b7aa2bfb842cb0431dd8770..76579d130549b3b5d746d556deebac19ee9f9377 100644
--- a/stmhal/Makefile
+++ b/stmhal/Makefile
@@ -112,6 +112,7 @@ SRC_LIB = $(addprefix lib/,\
 	netutils/netutils.c \
 	timeutils/timeutils.c \
 	utils/pyexec.c \
+	utils/interrupt_char.c \
 	)
 
 DRIVERS_SRC_C = $(addprefix drivers/,\
diff --git a/stmhal/mphalport.c b/stmhal/mphalport.c
index ca8e1c1bde5b45e8b5aa23fdb81a242c9c4465c9..64c1164cc8ed3b4f4af1a4bc570ed5fba9fcd483 100644
--- a/stmhal/mphalport.c
+++ b/stmhal/mphalport.c
@@ -21,10 +21,6 @@ NORETURN void mp_hal_raise(HAL_StatusTypeDef status) {
     mp_raise_OSError(mp_hal_status_to_errno_table[status]);
 }
 
-void mp_hal_set_interrupt_char(int c) {
-    usb_vcp_set_interrupt_char(c);
-}
-
 int mp_hal_stdin_rx_chr(void) {
     for (;;) {
 #if 0
diff --git a/stmhal/pendsv.c b/stmhal/pendsv.c
index e6df84b29a525155f0ba8fbb4aa1a7b253a4fc1b..4c2a14de1b7cb943148ac8d51ccb4ae0e939b9f1 100644
--- a/stmhal/pendsv.c
+++ b/stmhal/pendsv.c
@@ -29,6 +29,7 @@
 
 #include "py/mpstate.h"
 #include "py/runtime.h"
+#include "lib/utils/interrupt_char.h"
 #include "pendsv.h"
 #include "irq.h"
 
@@ -52,12 +53,12 @@ void pendsv_init(void) {
 // PENDSV feature.  This will wait until all interrupts are finished then raise
 // the given exception object using nlr_jump in the context of the top-level
 // thread.
-void pendsv_nlr_jump(void *o) {
+void pendsv_kbd_intr(void) {
     if (MP_STATE_VM(mp_pending_exception) == MP_OBJ_NULL) {
-        MP_STATE_VM(mp_pending_exception) = o;
+        mp_keyboard_interrupt();
     } else {
         MP_STATE_VM(mp_pending_exception) = MP_OBJ_NULL;
-        pendsv_object = o;
+        pendsv_object = &MP_STATE_VM(mp_kbd_exception);
         SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
     }
 }
diff --git a/stmhal/pendsv.h b/stmhal/pendsv.h
index 7886d9f9842bbb168015bfd3580bc1c3cad1ca32..77c78d4c18196fa6bb4f8092c82fb41e656c4951 100644
--- a/stmhal/pendsv.h
+++ b/stmhal/pendsv.h
@@ -25,7 +25,7 @@
  */
 
 void pendsv_init(void);
-void pendsv_nlr_jump(void *val);
+void pendsv_kbd_intr(void);
 
 // since we play tricks with the stack, the compiler must not generate a
 // prelude for this function
diff --git a/stmhal/usb.c b/stmhal/usb.c
index 7eb3f179a95439d7376a55565b575a94181ae8b4..c413ce4bac0faf25351537de9eb05af2a38b96f0 100644
--- a/stmhal/usb.c
+++ b/stmhal/usb.c
@@ -38,6 +38,7 @@
 #include "py/runtime.h"
 #include "py/stream.h"
 #include "py/mperrno.h"
+#include "py/mphal.h"
 #include "bufhelper.h"
 #include "usb.h"
 
@@ -96,7 +97,7 @@ const mp_obj_tuple_t pyb_usb_hid_keyboard_obj = {
 };
 
 void pyb_usb_init0(void) {
-    USBD_CDC_SetInterrupt(-1);
+    mp_hal_set_interrupt_char(-1);
     MP_STATE_PORT(pyb_hid_report_desc) = MP_OBJ_NULL;
 }
 
@@ -141,15 +142,6 @@ bool usb_vcp_is_enabled(void) {
     return (pyb_usb_flags & PYB_USB_FLAG_DEV_ENABLED) != 0;
 }
 
-void usb_vcp_set_interrupt_char(int c) {
-    if (pyb_usb_flags & PYB_USB_FLAG_DEV_ENABLED) {
-        if (c != -1) {
-            mp_obj_exception_clear_traceback(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)));
-        }
-        USBD_CDC_SetInterrupt(c);
-    }
-}
-
 int usb_vcp_recv_byte(uint8_t *c) {
     return USBD_CDC_Rx(c, 1, 0);
 }
@@ -364,7 +356,7 @@ STATIC mp_obj_t pyb_usb_vcp_make_new(const mp_obj_type_t *type, size_t n_args, s
 }
 
 STATIC mp_obj_t pyb_usb_vcp_setinterrupt(mp_obj_t self_in, mp_obj_t int_chr_in) {
-    usb_vcp_set_interrupt_char(mp_obj_get_int(int_chr_in));
+    mp_hal_set_interrupt_char(mp_obj_get_int(int_chr_in));
     return mp_const_none;
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_usb_vcp_setinterrupt_obj, pyb_usb_vcp_setinterrupt);
diff --git a/stmhal/usb.h b/stmhal/usb.h
index e153f0c6b6f8e9ec30e61b0fe30972c8ec733bd3..bc2b91c3dbd76adbefaadeadd9db80287a6c8801 100644
--- a/stmhal/usb.h
+++ b/stmhal/usb.h
@@ -61,7 +61,6 @@ void pyb_usb_init0(void);
 bool pyb_usb_dev_init(uint16_t vid, uint16_t pid, usb_device_mode_t mode, USBD_HID_ModeInfoTypeDef *hid_info);
 void pyb_usb_dev_deinit(void);
 bool usb_vcp_is_enabled(void);
-void usb_vcp_set_interrupt_char(int c);
 int usb_vcp_recv_byte(uint8_t *c); // if a byte is available, return 1 and put the byte in *c, else return 0
 void usb_vcp_send_strn(const char* str, int len);
 void usb_vcp_send_strn_cooked(const char *str, int len);
diff --git a/stmhal/usbd_cdc_interface.c b/stmhal/usbd_cdc_interface.c
index 1f46b9dcc1280d25194aae3165209d1584398910..1c12cdc1c8333f65c4c1c645b237275859c86cef 100644
--- a/stmhal/usbd_cdc_interface.c
+++ b/stmhal/usbd_cdc_interface.c
@@ -43,6 +43,7 @@
 
 #include "py/mpstate.h"
 #include "py/obj.h"
+#include "lib/utils/interrupt_char.h"
 #include "irq.h"
 #include "timer.h"
 #include "usb.h"
@@ -79,8 +80,6 @@ static uint16_t UserTxBufPtrOutShadow = 0; // shadow of above
 static uint8_t UserTxBufPtrWaitCount = 0; // used to implement a timeout waiting for low-level USB driver
 static uint8_t UserTxNeedEmptyPacket = 0; // used to flush the USB IN endpoint if the last packet was exactly the endpoint packet size
 
-static int user_interrupt_char = -1;
-
 /* Private function prototypes -----------------------------------------------*/
 static int8_t CDC_Itf_Init     (void);
 static int8_t CDC_Itf_DeInit   (void);
@@ -147,13 +146,6 @@ static int8_t CDC_Itf_Init(void)
     UserRxBufCur = 0;
     UserRxBufLen = 0;
   
-    /* NOTE: we cannot reset these here, because USBD_CDC_SetInterrupt
-     * may be called before this init function to set these values.
-     * This can happen if the USB enumeration occurs after the call to
-     * USBD_CDC_SetInterrupt.
-    user_interrupt_char = -1;
-    */
-
     return (USBD_OK);
 }
 
@@ -339,7 +331,7 @@ static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len) {
 
     uint32_t delta_len;
 
-    if (user_interrupt_char == -1) {
+    if (mp_interrupt_char == -1) {
         // no special interrupt character
         delta_len = *Len;
 
@@ -350,10 +342,10 @@ static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len) {
         uint8_t *src = Buf;
         uint8_t *buf_top = Buf + *Len;
         for (; src < buf_top; src++) {
-            if (*src == user_interrupt_char) {
+            if (*src == mp_interrupt_char) {
                 char_found = true;
-                // raise exception when interrupts are finished
-                pendsv_nlr_jump(&MP_STATE_VM(mp_kbd_exception));
+                // raise KeyboardInterrupt when interrupts are finished
+                pendsv_kbd_intr();
             } else {
                 if (char_found) {
                     *dest = *src;
@@ -385,10 +377,6 @@ int USBD_CDC_IsConnected(void) {
     return dev_is_connected;
 }
 
-void USBD_CDC_SetInterrupt(int chr) {
-    user_interrupt_char = chr;
-}
-
 int USBD_CDC_TxHalfEmpty(void) {
     int32_t tx_waiting = (int32_t)UserTxBufPtrIn - (int32_t)UserTxBufPtrOut;
     if (tx_waiting < 0) {
diff --git a/stmhal/usbd_cdc_interface.h b/stmhal/usbd_cdc_interface.h
index 2ea1a42c4a23cf2938398d4cda06d1452c19b61f..d96861a7e5fb390c4b9773e4763f4177020e21ef 100644
--- a/stmhal/usbd_cdc_interface.h
+++ b/stmhal/usbd_cdc_interface.h
@@ -32,7 +32,6 @@
 extern const USBD_CDC_ItfTypeDef USBD_CDC_fops;
 
 int USBD_CDC_IsConnected(void);
-void USBD_CDC_SetInterrupt(int chr);
 
 int USBD_CDC_TxHalfEmpty(void);
 int USBD_CDC_Tx(const uint8_t *buf, uint32_t len, uint32_t timeout);