diff --git a/epicardium/epicardium.h b/epicardium/epicardium.h
index b4b72dd53f1822c5d74e11a3bab1cad9819b5e34..ac1eb09aff02856ef8eda652607dbb8e07c3599e 100644
--- a/epicardium/epicardium.h
+++ b/epicardium/epicardium.h
@@ -60,6 +60,7 @@ typedef unsigned int size_t;
 #define API_FILE_STAT          0x38
 
 #define API_RTC_GET_SECONDS    0x40
+#define API_RTC_SCHEDULE_ALARM 0x41
 /* clang-format on */
 
 typedef uint32_t api_int_id_t;
@@ -100,8 +101,11 @@ API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
 #define EPIC_INT_BHI160_TEST            2
 API_ISR(EPIC_INT_BHI160_TEST, epic_isr_bhi160_test);
 
+/** RTC Alarm interrupt.  See :c:func:`epic_isr_rtc_alarm` */
+#define EPIC_INT_RTC_ALARM              3
+
 /* Number of defined interrupts. */
-#define EPIC_INT_NUM                    3
+#define EPIC_INT_NUM                    4
 /* clang-format on */
 
 API_ISR(EPIC_INT_RESET, epic_isr_reset);
@@ -589,4 +593,23 @@ API(API_FILE_STAT, int epic_file_stat(const char* path, epic_stat_t* stat));
  */
 API(API_RTC_GET_SECONDS, uint32_t epic_rtc_get_seconds(void));
 
+/**
+ * Schedule the RTC alarm for the given timestamp.
+ *
+ * :param uint32_t timestamp: When to schedule the IRQ
+ * :return: `0` on success or a negative value if an error occured. Possible
+ *    errors:
+ *
+ *    - ``-EINVAL``: RTC is in a bad state
+ */
+API(API_RTC_SCHEDULE_ALARM, int epic_rtc_schedule_alarm(uint32_t timestamp));
+
+/**
+ * **Interrupt Service Routine**
+ *
+ * ``epic_isr_rtc_alarm()`` is called when the RTC alarm triggers.  The RTC alarm
+ * can be scheduled using :c:func:`epic_rtc_schedule_alarm`.
+ */
+API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm);
+
 #endif /* _EPICARDIUM_H */
diff --git a/epicardium/modules/rtc.c b/epicardium/modules/rtc.c
index e87b1cd9154cceca3b39a49422ff311e425be1b8..f50355e146f626ee44bff17c880259c4c8a8a010 100644
--- a/epicardium/modules/rtc.c
+++ b/epicardium/modules/rtc.c
@@ -1,3 +1,7 @@
+#include "epicardium.h"
+#include "modules/log.h"
+#include "api/interrupt-sender.h"
+
 #include "rtc.h"
 
 #include <stdint.h>
@@ -6,3 +10,33 @@ uint32_t epic_rtc_get_seconds(void)
 {
 	return RTC_GetSecond();
 }
+
+void RTC_IRQHandler(void)
+{
+	int flags = RTC_GetFlags();
+
+	if (flags & MXC_F_RTC_CTRL_ALDF) {
+		RTC_ClearFlags(MXC_F_RTC_CTRL_ALDF);
+		api_interrupt_trigger(EPIC_INT_RTC_ALARM);
+	} else {
+		LOG_WARN("rtc", "Unknown IRQ caught!");
+		/* Disable IRQ so it does not retrigger */
+		NVIC_DisableIRQ(RTC_IRQn);
+	}
+}
+
+int epic_rtc_schedule_alarm(uint32_t timestamp)
+{
+	int res;
+
+	NVIC_EnableIRQ(RTC_IRQn);
+
+	while ((res = RTC_SetTimeofdayAlarm(MXC_RTC, timestamp)) == E_BUSY)
+		;
+
+	if (res != E_SUCCESS) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/pycardium/modules/qstrdefs.h b/pycardium/modules/qstrdefs.h
index 600543abf41d20d956ffbe5466db418e65104812..ec162d219cd974edb74516c7efc27dda5b115b2b 100644
--- a/pycardium/modules/qstrdefs.h
+++ b/pycardium/modules/qstrdefs.h
@@ -13,6 +13,7 @@ Q(TOP_RIGHT)
 
 /* utime */
 Q(utime)
+Q(alarm)
 Q(sleep)
 Q(sleep_ms)
 Q(sleep_us)
diff --git a/pycardium/modules/utime.c b/pycardium/modules/utime.c
index e5f578667a2daeddebec544e14ec89900650d3a9..15fa8167a1c5ab4f7886945fabacbd55cb83a8f9 100644
--- a/pycardium/modules/utime.c
+++ b/pycardium/modules/utime.c
@@ -1,3 +1,4 @@
+#include "interrupt.h"
 #include "epicardium.h"
 
 #include "mxc_delay.h"
@@ -12,11 +13,13 @@
 // Needs to be after the stdint include ...
 #include "lib/timeutils/timeutils.h"
 
+/* MicroPython has its epoch at 2000-01-01. Our RTC is in UTC */
+#define EPOCH_OFFSET 946684800UL
+
 static mp_obj_t time_time(void)
 {
 	mp_int_t seconds;
-	/* MicroPython has its epoch at 2000-01-01. Our RTC is in UTC */
-	seconds = epic_rtc_get_seconds() - 946684800UL;
+	seconds = epic_rtc_get_seconds() - EPOCH_OFFSET;
 	return mp_obj_new_int(seconds);
 }
 MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time);
@@ -26,7 +29,7 @@ static mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args)
 	mp_int_t seconds;
 
 	if (n_args == 0 || args[0] == mp_const_none) {
-		seconds = epic_rtc_get_seconds() - 946684800UL;
+		seconds = epic_rtc_get_seconds() - EPOCH_OFFSET;
 	} else {
 		seconds = mp_obj_get_int(args[0]);
 	}
@@ -75,6 +78,26 @@ static mp_obj_t time_mktime(mp_obj_t tuple)
 }
 static MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime);
 
+/* Schedule an alarm */
+static mp_obj_t time_alarm(size_t n_args, const mp_obj_t *args)
+{
+	mp_int_t timestamp = mp_obj_get_int(args[0]) + EPOCH_OFFSET;
+	if (n_args == 2) {
+		/* If a callback was given, register it for the RTC Alarm */
+		mp_obj_t callback = args[1];
+		mp_obj_t irq_id   = MP_OBJ_NEW_SMALL_INT(EPIC_INT_RTC_ALARM);
+		mp_interrupt_set_callback(irq_id, callback);
+		mp_interrupt_enable_callback(irq_id);
+	}
+
+	int res = epic_rtc_schedule_alarm(timestamp);
+	if (res < 0) {
+		mp_raise_OSError(-res);
+	}
+	return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_alarm_obj, 1, 2, time_alarm);
+
 static const mp_rom_map_elem_t time_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime) },
 	{ MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&time_time_obj) },
@@ -83,6 +106,7 @@ static const mp_rom_map_elem_t time_module_globals_table[] = {
 	{ MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj) },
 	{ MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj) },
+	{ MP_ROM_QSTR(MP_QSTR_alarm), MP_ROM_PTR(&time_alarm_obj) },
 #if 0
 	/* TODO: Implement those */
 	{MP_ROM_QSTR(MP_QSTR_ticks_ms), MP_ROM_PTR(&mp_utime_ticks_ms_obj)},