diff --git a/epicardium/serial.c b/epicardium/serial.c
index 0a062c23debbdea149b1b51dd751eb1738fab79b..a891f5103911d532a732ad69103e0752b0bbe54d 100644
--- a/epicardium/serial.c
+++ b/epicardium/serial.c
@@ -5,6 +5,7 @@
 
 #include "cdcacm.h"
 #include "uart.h"
+#include "tmr_utils.h"
 
 #include "FreeRTOS.h"
 #include "task.h"
@@ -60,6 +61,11 @@ void vSerialTask(void*pvParameters)
 			continue;
 		}
 
+		if (chr == 0x3) {
+			/* Control-C */
+			TMR_TO_Start(MXC_TMR5, 1, 0);
+		}
+
 		if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
 			/* Queue overran, wait a bit */
 			vTaskDelay(portTICK_PERIOD_MS * 50);
diff --git a/pycardium/main.c b/pycardium/main.c
index 017684f215c1dde8bcb7099ec3ea3fcf0b8f10fa..68bbc2c10c1df23aec6fa1ee3bc5bf1a1aaa1dd8 100644
--- a/pycardium/main.c
+++ b/pycardium/main.c
@@ -2,6 +2,8 @@
 #include "py/gc.h"
 #include "lib/utils/pyexec.h"
 
+#include "max32665.h"
+
 static char* stack_top;
 static char heap[4096];
 
@@ -11,6 +13,9 @@ int main(void)
 	int stack_dummy;
 	stack_top = (char*)&stack_dummy;
 
+	/* TMR5 is used to notify on keyboard interrupt */
+	NVIC_EnableIRQ(TMR5_IRQn);
+
 	gc_init(heap, heap + sizeof(heap));
 
 	mp_init();
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index 0b8cba3abe45cb3676c0e9064f4ef87beccd75e9..c4e6a6cb0d86208d840d78e74b0fd1de40a3ae72 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -8,7 +8,11 @@
  * Right now, we do not support importing external modules
  * though this might change in the future.
  */
-#define MICROPY_ENABLE_EXTERNAL_IMPORT  (0)
+#define MICROPY_ENABLE_EXTERNAL_IMPORT      (0)
+
+/* We raise asynchronously from an interrupt handler */
+#define MICROPY_ASYNC_KBD_INTR              (1)
+#define MICROPY_KBD_EXCEPTION               (1)
 
 #define MICROPY_ENABLE_DOC_STRING           (1)
 #define MICROPY_ENABLE_GC                   (1)
diff --git a/pycardium/mphalport.c b/pycardium/mphalport.c
index ddaf9841bf5fc3af4568b943d82d49bec300692d..37505674af45fecbe2ac6575cef7bf2a8bb9e204 100644
--- a/pycardium/mphalport.c
+++ b/pycardium/mphalport.c
@@ -3,11 +3,15 @@
 #include "py/lexer.h"
 #include "py/mpconfig.h"
 #include "py/mperrno.h"
+#include "py/mpstate.h"
 #include "py/obj.h"
 #include "py/runtime.h"
 
-#include "epicardium.h"
 #include "mxc_delay.h"
+#include "max32665.h"
+#include "tmr.h"
+
+#include "epicardium.h"
 
 /******************************************************************************
  * Serial Communication
@@ -25,6 +29,27 @@ void mp_hal_stdout_tx_strn(const char* str, mp_uint_t len)
 	epic_uart_write_str(str, len);
 }
 
+bool do_interrupt = false;
+
+/* Timer Interrupt used for control char notification */
+void TMR5_IRQHandler(void)
+{
+	TMR_IntClear(MXC_TMR5);
+
+	if (do_interrupt) {
+		/* Taken from lib/micropython/micropython/ports/unix/unix_mphal.c */
+		mp_obj_exception_clear_traceback(
+			MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))
+		);
+		nlr_raise(MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception)));
+	}
+}
+
+void mp_hal_set_interrupt_char(char c)
+{
+	do_interrupt = (c == 0x03);
+}
+
 /******************************************************************************
  * Time & Delay
  */
diff --git a/pycardium/mphalport.h b/pycardium/mphalport.h
index cf772d7f1a1a219e786ea0fbe0b4372088da0e67..e8ad9c0d13fb49ca23ed8366fe0f2eb18d940241 100644
--- a/pycardium/mphalport.h
+++ b/pycardium/mphalport.h
@@ -3,5 +3,4 @@
 /* TODO: Replace this with a proper implementation */
 static inline mp_uint_t mp_hal_ticks_ms(void) { return 0; }
 
-/* TODO: Replace this with a proper implementation */
-static inline void mp_hal_set_interrupt_char(char c) {}
+void mp_hal_set_interrupt_char(char c);