diff --git a/epicardium/api/common.h b/epicardium/api/common.h
index a929f301cabb530ed3e430a021dd4dd441acf372..c884f37c48aaa7c6b26b64d5961f99df7a07c6f2 100644
--- a/epicardium/api/common.h
+++ b/epicardium/api/common.h
@@ -1,3 +1,6 @@
+#pragma once
+#include "epicardium.h"
+
 #include <stdint.h>
 #include <stdbool.h>
 
@@ -26,6 +29,9 @@ struct api_call_mem {
 	/* ID if the ongoing API call */
 	api_id_t id;
 
+	/* ID of the current interrupt */
+	api_int_id_t int_id;
+
 	/*
 	 * Buffer for arguments/return value.  This buffer will be
 	 * *overflown*, because there is guaranteed space behind it.
diff --git a/epicardium/api/interrupt-receiver.c b/epicardium/api/interrupt-receiver.c
new file mode 100644
index 0000000000000000000000000000000000000000..95c5038d12ee10bdc53a087ea121a568bb44d86a
--- /dev/null
+++ b/epicardium/api/interrupt-receiver.c
@@ -0,0 +1,35 @@
+#include "max32665.h"
+#include "tmr.h"
+#include "api/common.h"
+#include "epicardium.h"
+
+void api_interrupt_handler_ctrl_c(api_int_id_t id)
+	__attribute__((weak, alias("api_interrupt_handler_default")));
+void api_interrupt_handler_bhi160(api_int_id_t id)
+	__attribute__((weak, alias("api_interrupt_handler_default")));
+
+/* Timer Interrupt used for control char notification */
+void TMR5_IRQHandler(void)
+{
+	TMR_IntClear(MXC_TMR5);
+
+	switch (API_CALL_MEM->int_id) {
+	case API_INT_CTRL_C:
+		api_interrupt_handler_ctrl_c(API_CALL_MEM->int_id);
+		break;
+	case API_INT_BHI160:
+		api_interrupt_handler_bhi160(API_CALL_MEM->int_id);
+		break;
+	}
+
+	API_CALL_MEM->int_id = 0;
+}
+
+__attribute__((weak)) void api_interrupt_handler_catch_all(api_int_id_t id)
+{
+}
+
+void api_interrupt_handler_default(api_int_id_t id)
+{
+	api_interrupt_handler_catch_all(id);
+}
diff --git a/epicardium/api/interrupt-sender.c b/epicardium/api/interrupt-sender.c
new file mode 100644
index 0000000000000000000000000000000000000000..fb0c65a7c189fbdfa329e8a6a8acdfb67db97c64
--- /dev/null
+++ b/epicardium/api/interrupt-sender.c
@@ -0,0 +1,50 @@
+#include "api/interrupt-sender.h"
+#include "api/common.h"
+#include "tmr_utils.h"
+
+static bool enabled[API_INT_MAX + 1];
+
+int api_interrupt_trigger(api_int_id_t id)
+{
+	if (id > API_INT_MAX) {
+		return -EINVAL;
+	}
+
+	if (enabled[id]) {
+		while (API_CALL_MEM->int_id)
+			;
+		API_CALL_MEM->int_id = id;
+		TMR_TO_Start(MXC_TMR5, 1, 0);
+	}
+	return 0;
+}
+
+void api_interrupt_init(void)
+{
+	int i;
+	API_CALL_MEM->int_id = 0;
+
+	for (i = 0; i <= API_INT_MAX; i++) {
+		enabled[i] = false;
+	}
+}
+
+int epic_interrupt_enable(api_int_id_t int_id)
+{
+	if (int_id > API_INT_MAX) {
+		return -EINVAL;
+	}
+
+	enabled[int_id] = true;
+	return 0;
+}
+
+int epic_interrupt_disable(api_int_id_t int_id)
+{
+	if (int_id > API_INT_MAX) {
+		return -EINVAL;
+	}
+
+	enabled[int_id] = false;
+	return 0;
+}
diff --git a/epicardium/api/interrupt-sender.h b/epicardium/api/interrupt-sender.h
new file mode 100644
index 0000000000000000000000000000000000000000..4f690167ceff8622418b4983da17b29085a446f4
--- /dev/null
+++ b/epicardium/api/interrupt-sender.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "api/common.h"
+void api_interrupt_init(void);
+int api_interrupt_trigger(api_int_id_t id);
diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index 4e52d85c51c38d897f38dbee03ed96cbbb7b0de1..9add57508e7aa91b3bb0f718bec2aaeddb990817 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -4,17 +4,24 @@
 #include <stddef.h>
 #include <errno.h>
 
+/* clang-format off */
+#define API_INT_CTRL_C  1
+#define API_INT_BHI160  2
+#define API_INT_MAX     API_INT_BHI160
+
 #ifndef API
 #define API(id, def) def
 #endif
 
-/* clang-format off */
 #define API_UART_WRITE         0x1
 #define API_UART_READ          0x2
 #define API_LEDS_SET           0x3
 #define API_VIBRA_SET          0x4
 #define API_VIBRA_VIBRATE      0x5
 #define API_STREAM_READ        0x6
+#define API_INTERRUPT_ENABLE   0x7
+#define API_INTERRUPT_DISABLE  0x8
+
 /* clang-format on */
 
 /**
@@ -139,4 +146,23 @@ API(API_VIBRA_SET, void epic_vibra_set(int status));
  */
 API(API_VIBRA_VIBRATE, void epic_vibra_vibrate(int millis));
 
+/**
+ * API interrupt type
+ */
+typedef uint32_t api_int_id_t;
+
+/**
+ * Enable/unmask an API interrupt
+ *
+ * :param int_id: The interrupt to be enabled
+ */
+API(API_INTERRUPT_ENABLE, int epic_interrupt_enable(api_int_id_t int_id));
+
+/**
+ * Disable/mask an API interrupt
+ *
+ * :param int_id: The interrupt to be disabled
+ */
+API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
+
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/main.c b/epicardium/main.c
index 51d365f2c0a754b1432dfe5ae14ddf95de620aee..75bb9c79537a82de067d2618d1f3071348d2b032 100644
--- a/epicardium/main.c
+++ b/epicardium/main.c
@@ -12,6 +12,7 @@
 #include "modules/modules.h"
 #include "modules/log.h"
 #include "modules/stream.h"
+#include "api/interrupt-sender.h"
 
 #include <Heart.h>
 #include "GUI_Paint.h"
@@ -54,6 +55,7 @@ int main(void)
 	}
 
 	fatfs_init();
+	api_interrupt_init();
 	stream_init();
 
 	LOG_INFO("startup", "Initializing tasks ...");
diff --git a/epicardium/meson.build b/epicardium/meson.build
index 26f36cd47e471bf658b35b6447a69489110197ea..a6ba27ce781fce91b639529351cc8b07620dbeb5 100644
--- a/epicardium/meson.build
+++ b/epicardium/meson.build
@@ -22,6 +22,7 @@ api = custom_target(
 api_caller_lib = static_library(
   'api-caller',
   'api/caller.c',
+  'api/interrupt-receiver.c',
   api[0], # Caller
   dependencies: periphdriver,
 )
@@ -35,6 +36,7 @@ api_caller = declare_dependency(
 api_dispatcher_lib = static_library(
   'api-dispatcher',
   'api/dispatcher.c',
+  'api/interrupt-sender.c',
   api[1], # Dispatcher
   dependencies: periphdriver,
 )
diff --git a/epicardium/modules/serial.c b/epicardium/modules/serial.c
index 9e41efed4e75db20fcbb56d3022b276c44b770c7..bb9c325ce3933287307093d3e4caefc28ef3a0e8 100644
--- a/epicardium/modules/serial.c
+++ b/epicardium/modules/serial.c
@@ -4,7 +4,6 @@
 #include "max32665.h"
 #include "cdcacm.h"
 #include "uart.h"
-#include "tmr_utils.h"
 
 #include "FreeRTOS.h"
 #include "task.h"
@@ -12,6 +11,7 @@
 
 #include "modules.h"
 #include "modules/log.h"
+#include "api/interrupt-sender.h"
 
 /* Task ID for the serial handler */
 TaskHandle_t serial_task_id = NULL;
@@ -24,7 +24,7 @@ static QueueHandle_t read_queue;
 /*
  * API-call to write a string.  Output goes to both CDCACM and UART
  */
-void epic_uart_write_str(char *str, intptr_t length)
+void epic_uart_write_str(const char *str, intptr_t length)
 {
 	UART_Write(ConsoleUart, (uint8_t *)str, length);
 	cdcacm_write((uint8_t *)str, length);
@@ -57,7 +57,12 @@ static void enqueue_char(char chr)
 {
 	if (chr == 0x3) {
 		/* Control-C */
-		TMR_TO_Start(MXC_TMR5, 1, 0);
+		api_interrupt_trigger(API_INT_CTRL_C);
+	}
+
+	if (chr == 0x0e) {
+		/* Control-N */
+		api_interrupt_trigger(API_INT_BHI160);
 	}
 
 	if (xQueueSend(read_queue, &chr, 100) == errQUEUE_FULL) {
diff --git a/pycardium/main.c b/pycardium/main.c
index 737fe1ad519ba373befe3700aa669779190f01b1..da08475b82820fc25fd72bb04032aa3f26fc2461 100644
--- a/pycardium/main.c
+++ b/pycardium/main.c
@@ -19,7 +19,7 @@ int main(void)
 	NVIC_EnableIRQ(TMR5_IRQn);
 
 	while (1) {
-		gc_init(&__HeapBase, &__HeapLimit);
+		gc_init(&__HeapBase + 1024 * 10, &__HeapLimit);
 
 		mp_init();
 		pyexec_friendly_repl();
diff --git a/pycardium/meson.build b/pycardium/meson.build
index 67698112ed8be52f569fec2d4d087371bbe5cb91..50398d58d43022015feb04e98bb69eee176e7f83 100644
--- a/pycardium/meson.build
+++ b/pycardium/meson.build
@@ -4,6 +4,7 @@ modsrc = files(
   'modules/utime.c',
   'modules/leds.c',
   'modules/vibra.c',
+  'modules/interrupt.c',
 )
 
 #################################
@@ -75,7 +76,7 @@ elf = executable(
   include_directories: micropython_includes,
   dependencies: [max32665_startup_core1, periphdriver, api_caller],
   link_with: upy,
-  link_whole: [max32665_startup_core1_lib],
+  link_whole: [max32665_startup_core1_lib, api_caller_lib],
   link_args: [
     '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
   ],
diff --git a/pycardium/modules/interrupt.c b/pycardium/modules/interrupt.c
new file mode 100644
index 0000000000000000000000000000000000000000..b45680c723a8150fd440665065ff8170f8e3e6bc
--- /dev/null
+++ b/pycardium/modules/interrupt.c
@@ -0,0 +1,97 @@
+#include "py/obj.h"
+#include "py/runtime.h"
+#include "py/builtin.h"
+#include "epicardium.h"
+#include "api/common.h"
+#include "mphalport.h"
+
+// TODO: these should be intialized as mp_const_none
+mp_obj_t callbacks[API_INT_MAX + 1] = {
+	0,
+};
+
+void api_interrupt_handler_catch_all(api_int_id_t id)
+{
+	// TODO: check if id is out of rante
+	// TOOD: check against mp_const_none
+	if (id <= API_INT_MAX) {
+		if (callbacks[id]) {
+			mp_sched_schedule(
+				callbacks[id], MP_OBJ_NEW_SMALL_INT(id)
+			);
+		}
+	}
+}
+
+STATIC mp_obj_t mp_interrupt_set_callback(mp_obj_t id_in, mp_obj_t callback_obj)
+{
+	api_int_id_t id = mp_obj_get_int(id_in);
+	if (callback_obj != mp_const_none &&
+	    !mp_obj_is_callable(callback_obj)) {
+		mp_raise_ValueError("handler must be None or callable");
+	}
+
+	// TODO: throw error if id is out of range
+	if (id <= API_INT_MAX) {
+		callbacks[id] = callback_obj;
+	}
+
+	return mp_const_none;
+}
+
+STATIC mp_obj_t mp_interrupt_enable_callback(mp_obj_t id_in)
+{
+	api_int_id_t id = mp_obj_get_int(id_in);
+
+	// TODO: throw error if id is out of range or epic_interrupt_enable failed
+	if (epic_interrupt_enable(id) < 0) {
+	}
+
+	return mp_const_none;
+}
+
+STATIC mp_obj_t mp_interrupt_disable_callback(mp_obj_t id_in)
+{
+	api_int_id_t id = mp_obj_get_int(id_in);
+
+	// TODO: throw error if id is out of range or epic_interrupt_enable failed
+	if (epic_interrupt_disable(id) < 0) {
+	}
+
+	return mp_const_none;
+}
+
+
+STATIC MP_DEFINE_CONST_FUN_OBJ_2(
+	interrupt_set_callback_obj, mp_interrupt_set_callback
+);
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(
+	interrupt_enable_callback_obj, mp_interrupt_enable_callback
+);
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(
+	interrupt_disable_callback_obj, mp_interrupt_disable_callback
+);
+
+STATIC const mp_rom_map_elem_t interrupt_module_globals_table[] = {
+	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_interrupt) },
+	{ MP_ROM_QSTR(MP_QSTR_set_callback),
+	  MP_ROM_PTR(&interrupt_set_callback_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_enable_callback),
+	  MP_ROM_PTR(&interrupt_enable_callback_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_disable_callback),
+	  MP_ROM_PTR(&interrupt_disable_callback_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_BHI160), MP_OBJ_NEW_SMALL_INT(2) },
+};
+STATIC MP_DEFINE_CONST_DICT(
+	interrupt_module_globals, interrupt_module_globals_table
+);
+
+// Define module object.
+const mp_obj_module_t interrupt_module = {
+	.base    = { &mp_type_module },
+	.globals = (mp_obj_dict_t *)&interrupt_module_globals,
+};
+
+/* clang-format off */
+// Register the module to make it available in Python
+MP_REGISTER_MODULE(MP_QSTR_interrupt, interrupt_module, MODULE_INTERRUPT_ENABLED);
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 2a6dcbe6beb7ebe5a4a5a29226b7eca0a6b9c901..4c7e2f753eb0f12f98f8ea1d4490297284be5408 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -25,3 +25,8 @@ Q(ticks_diff)
 /* vibra */
 Q(vibra)
 Q(vibrate)
+
+Q(set_callback)
+Q(enable_callback)
+Q(disable_callback)
+Q(BHI160)
diff --git a/pycardium/mpconfigport.h b/pycardium/mpconfigport.h
index a99c63acfe3d50a083b6a428e1b511d41edf13e5..d71bf5d4d34d8aec39eff5f027c52fa69f8df14b 100644
--- a/pycardium/mpconfigport.h
+++ b/pycardium/mpconfigport.h
@@ -19,6 +19,8 @@
 #define MICROPY_HELPER_REPL                 (1)
 #define MICROPY_LONGINT_IMPL                (MICROPY_LONGINT_IMPL_LONGLONG)
 
+#define MICROPY_ENABLE_SCHEDULER            (1)
+
 /* Builtin function and modules */
 #define MICROPY_PY_ALL_SPECIAL_METHODS      (1)
 #define MICROPY_PY_BUILTINS_HELP            (1)
@@ -38,6 +40,7 @@
 #define MODULE_UTIME_ENABLED                (1)
 #define MODULE_LEDS_ENABLED                 (1)
 #define MODULE_VIBRA_ENABLED                (1)
+#define MODULE_INTERRUPT_ENABLED            (1)
 
 /*
  * This port is intended to be 32-bit, but unfortunately, int32_t for
diff --git a/pycardium/mphalport.c b/pycardium/mphalport.c
index fdfb592d8830699f8d222952d9a4f99095b60ae3..4ca84e13027f55c434ece35cf741447284498450 100644
--- a/pycardium/mphalport.c
+++ b/pycardium/mphalport.c
@@ -14,7 +14,7 @@
 #include "tmr.h"
 
 #include "epicardium.h"
-
+#include "api/common.h"
 /******************************************************************************
  * Serial Communication
  */
@@ -62,23 +62,16 @@ long _write(int fd, const char *buf, size_t cnt)
 	return cnt;
 }
 
-bool do_interrupt = false;
-
-/* Timer Interrupt used for control char notification */
-void TMR5_IRQHandler(void)
+void api_interrupt_handler_ctrl_c(void)
 {
-	TMR_IntClear(MXC_TMR5);
-
-	if (do_interrupt) {
-		/* Taken from lib/micropython/micropython/lib/utils/interrupt_char.c */
-		MP_STATE_VM(mp_pending_exception) =
-			MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception));
+	/* Taken from lib/micropython/micropython/lib/utils/interrupt_char.c */
+	MP_STATE_VM(mp_pending_exception) =
+		MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception));
 #if MICROPY_ENABLE_SCHEDULER
-		if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
-			MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
-		}
-#endif
+	if (MP_STATE_VM(sched_state) == MP_SCHED_IDLE) {
+		MP_STATE_VM(sched_state) = MP_SCHED_PENDING;
 	}
+#endif
 }
 
 void mp_hal_set_interrupt_char(char c)
@@ -88,7 +81,12 @@ void mp_hal_set_interrupt_char(char c)
 			MP_OBJ_FROM_PTR(&MP_STATE_VM(mp_kbd_exception))
 		);
 	}
-	do_interrupt = (c == 0x03);
+
+	if (c == 0x03) {
+		epic_interrupt_enable(API_INT_CTRL_C);
+	} else {
+		epic_interrupt_disable(API_INT_CTRL_C);
+	}
 }
 
 /******************************************************************************
@@ -137,7 +135,7 @@ mp_import_stat_t mp_import_stat(const char *path)
 mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs)
 {
 	/* TODO: Once fs is implemented, get this working as well */
-	mp_raise_NotImplementedError ("FS is not yet implemented");
+	mp_raise_NotImplementedError("FS is not yet implemented");
 	return mp_const_none;
 }
 MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);