diff --git a/esp8266/esp_mphal.c b/esp8266/esp_mphal.c
index cc0eb3eae1d00b6ec956c5ed43b6a5fb370d6b51..c78b3600f04f1f45e18c686675e06fb04464fad8 100644
--- a/esp8266/esp_mphal.c
+++ b/esp8266/esp_mphal.c
@@ -98,7 +98,11 @@ void mp_hal_delay_ms(uint32_t delay) {
 }
 
 void mp_hal_set_interrupt_char(int c) {
-    // TODO
+    if (c != -1) {
+        mp_obj_exception_clear_traceback(MP_STATE_PORT(mp_kbd_exception));
+    }
+    extern int interrupt_char;
+    interrupt_char = c;
 }
 
 void __assert_func(const char *file, int line, const char *func, const char *expr) {
diff --git a/esp8266/main.c b/esp8266/main.c
index 4fd1189288b14eb7ebb1c683af557ea193ade4ef..3e12008ce649c7e6e081dfeecd02a0617f8562be 100644
--- a/esp8266/main.c
+++ b/esp8266/main.c
@@ -47,6 +47,7 @@ STATIC void mp_reset(void) {
     mp_init();
     mp_obj_list_init(mp_sys_path, 0);
     mp_obj_list_init(mp_sys_argv, 0);
+    MP_STATE_PORT(mp_kbd_exception) = mp_obj_new_exception(&mp_type_KeyboardInterrupt);
 #if MICROPY_MODULE_FROZEN
     pyexec_frozen_module("main");
 #endif
@@ -83,6 +84,10 @@ mp_obj_t mp_builtin_open(uint n_args, const mp_obj_t *args, mp_map_t *kwargs) {
 }
 MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
 
+void mp_keyboard_interrupt(void) {
+    MP_STATE_VM(mp_pending_exception) = MP_STATE_PORT(mp_kbd_exception);
+}
+
 void nlr_jump_fail(void *val) {
     printf("NLR jump failed\n");
     for (;;) {
diff --git a/esp8266/mpconfigport.h b/esp8266/mpconfigport.h
index 1f7ab09e27cdbd4db812cb9a572ddfe6ce14b731..75e3f46c5af273d223c4302fec7fbd0c0884a533 100644
--- a/esp8266/mpconfigport.h
+++ b/esp8266/mpconfigport.h
@@ -99,6 +99,7 @@ extern const struct _mp_obj_module_t mp_module_machine;
 
 #define MICROPY_PORT_ROOT_POINTERS \
     const char *readline_hist[8]; \
+    mp_obj_t mp_kbd_exception; \
     \
     /* Singleton instance of scan callback, meaning that there can
        be only one concurrent AP scan. */ \
diff --git a/esp8266/uart.c b/esp8266/uart.c
index 49fc9bb87cbcdd8d2b15f3f6ed86fb82bb4c0440..aa3d368b7c03dcc443572de499488c6ac4dc0d4c 100644
--- a/esp8266/uart.c
+++ b/esp8266/uart.c
@@ -210,10 +210,15 @@ void ICACHE_FLASH_ATTR uart_reattach() {
 #include "lib/utils/pyexec.h"
 
 void soft_reset(void);
+void mp_keyboard_interrupt(void);
 
+int interrupt_char;
 void uart_task_handler(os_event_t *evt) {
     int c, ret = 0;
     while ((c = uart_rx_one_char(UART_REPL)) >= 0) {
+        if (c == interrupt_char) {
+            mp_keyboard_interrupt();
+        }
         ret = pyexec_event_repl_process_char(c);
         if (ret & PYEXEC_FORCED_EXIT) {
             break;