diff --git a/Makefile.dep b/Makefile.dep
index 9037185244a42030926fee4ed64a7b99495b7313..2c320dbdda38b07d91d8590a4cd560fbedbc1105 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -580,6 +580,10 @@ ifneq (,$(filter phydat,$(USEMODULE)))
   USEMODULE += fmt
 endif
 
+ifneq (,$(filter evtimer,$(USEMODULE)))
+  USEMODULE += xtimer
+endif
+
 ifneq (,$(filter random,$(USEMODULE)))
     # select default prng
     ifeq (,$(filter prng_%,$(USEMODULE)))
diff --git a/sys/evtimer/Makefile b/sys/evtimer/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/sys/evtimer/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/evtimer/evtimer.c b/sys/evtimer/evtimer.c
new file mode 100644
index 0000000000000000000000000000000000000000..4adfd426370326dca83bc67ebe42a97a472126fb
--- /dev/null
+++ b/sys/evtimer/evtimer.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *               2017 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     sys_evtimer
+ * @{
+ *
+ * @file
+ * @brief       event timer implementation
+ *
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ * @author      Martine Lenders <m.lenders@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include "div.h"
+#include "irq.h"
+#include "xtimer.h"
+
+#include "evtimer.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+/* XXX this function is intentionally non-static, since the optimizer can't
+ * handle the pointer hack in this function */
+void evtimer_add_event_to_list(evtimer_t *evtimer, evtimer_event_t *event)
+{
+    uint32_t delta_sum = 0;
+
+    /* we want list->next to point to the first list element. thus we take the
+     * *address* of evtimer->events, then cast it from (evtimer_event_t **) to
+     * (evtimer_event_t*). After that, list->next actually equals
+     * evtimer->events. */
+    evtimer_event_t *list = (evtimer_event_t *)&evtimer->events;
+
+    while (list->next) {
+        evtimer_event_t *list_entry = list->next;
+        if ((list_entry->offset + delta_sum) > event->offset) {
+            break;
+        }
+        delta_sum += list_entry->offset;
+        list = list->next;
+    }
+
+    event->next = list->next;
+    if (list->next) {
+        evtimer_event_t *next_entry = list->next;
+        next_entry->offset += delta_sum;
+        next_entry->offset -= event->offset;
+    }
+    event->offset -= delta_sum;
+
+    list->next = event;
+}
+
+static void _del_event_from_list(evtimer_t *evtimer, evtimer_event_t *event)
+{
+    evtimer_event_t *list = (evtimer_event_t *) &evtimer->events;
+
+    while (list->next) {
+        evtimer_event_t *list_entry = list->next;
+        if (list_entry == event) {
+            list->next = event->next;
+            if (list->next) {
+                list_entry = list->next;
+                list_entry->offset += event->offset;
+            }
+            break;
+        }
+        list = list->next;
+    }
+}
+
+static void _set_timer(xtimer_t *timer, uint32_t offset)
+{
+    uint64_t offset_in_us = (uint64_t)offset * 1000;
+
+    DEBUG("evtimer: now=%" PRIu32 " setting xtimer to %" PRIu32 ":%" PRIu32 "\n",
+          xtimer_now_usec(), (uint32_t)(offset_in_us >> 32),
+          (uint32_t)(offset_in_us));
+    _xtimer_set64(timer, offset_in_us, offset_in_us >> 32);
+}
+
+static void _update_timer(evtimer_t *evtimer)
+{
+    if (evtimer->events) {
+        evtimer_event_t *event = evtimer->events;
+        _set_timer(&evtimer->timer, event->offset);
+    }
+    else {
+        xtimer_remove(&evtimer->timer);
+    }
+}
+
+static uint32_t _get_offset(xtimer_t *timer)
+{
+    uint64_t now = xtimer_now_usec64();
+    uint64_t target = ((uint64_t)timer->long_target) << 32 | timer->target;
+
+    if (target <= now) {
+        return 0;
+    }
+    else {
+        target -= now;
+        /* add half of 125 so integer division rounds to nearest */
+        return div_u64_by_125((target >> 3) + 62);
+    }
+}
+
+static void _update_head_offset(evtimer_t *evtimer)
+{
+    if (evtimer->events) {
+        evtimer_event_t *event = evtimer->events;
+        event->offset = _get_offset(&evtimer->timer);
+        DEBUG("evtimer: _update_head_offset(): new head offset %" PRIu32 "\n", event->offset);
+    }
+}
+
+void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event)
+{
+    unsigned state = irq_disable();
+
+    DEBUG("evtimer_add(): adding event with offset %" PRIu32 "\n", event->offset);
+
+    _update_head_offset(evtimer);
+    evtimer_add_event_to_list(evtimer, event);
+    if (evtimer->events == event) {
+        _set_timer(&evtimer->timer, event->offset);
+    }
+    irq_restore(state);
+    if (sched_context_switch_request) {
+        thread_yield_higher();
+    }
+}
+
+void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event)
+{
+    unsigned state = irq_disable();
+
+    DEBUG("evtimer_del(): removing event with offset %" PRIu32 "\n", event->offset);
+
+    _update_head_offset(evtimer);
+    _del_event_from_list(evtimer, event);
+    _update_timer(evtimer);
+    irq_restore(state);
+}
+
+static evtimer_event_t *_get_next(evtimer_t *evtimer)
+{
+    evtimer_event_t *event = evtimer->events;
+
+    if (event && (event->offset == 0)) {
+        evtimer->events = event->next;
+        return event;
+    }
+    else {
+        return NULL;
+    }
+}
+
+static void _evtimer_handler(void *arg)
+{
+    DEBUG("_evtimer_handler()\n");
+
+    evtimer_t *evtimer = (evtimer_t *)arg;
+
+    /* this function gets called directly by xtimer if the set xtimer expired.
+     * Thus the offset of the first event is down to zero. */
+    evtimer_event_t *event = evtimer->events;
+    event->offset = 0;
+
+    /* iterate the event list */
+    while ((event = _get_next(evtimer))) {
+        evtimer->callback(event);
+    }
+
+    _update_timer(evtimer);
+}
+
+void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler)
+{
+    evtimer->callback = handler;
+    evtimer->timer.callback = _evtimer_handler;
+    evtimer->timer.arg = (void *)evtimer;
+    evtimer->events = NULL;
+}
+
+void evtimer_print(const evtimer_t *evtimer)
+{
+    evtimer_event_t *list = evtimer->events;
+
+    while (list->next) {
+        evtimer_event_t *list_entry = list->next;
+        printf("ev offset=%u\n", (unsigned)list_entry->offset);
+        list = list->next;
+    }
+}
diff --git a/sys/include/evtimer.h b/sys/include/evtimer.h
new file mode 100644
index 0000000000000000000000000000000000000000..da8dc9a1efca7b461bfd6aecf3e8d07fa1a19253
--- /dev/null
+++ b/sys/include/evtimer.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *               2017 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    sys_evtimer Millisecond interval event timers
+ * @ingroup     sys
+ * @brief       Provides timers for events up to @$2^{32}@$ milliseconds in the
+ *              future
+ *
+ * @note    Experimental and likely to replaced with unified timer API
+ *
+ * RIOT's main timer subsystem is @ref sys_xtimer "xtimer", but for many
+ * applications @ref sys_xtimer "xtimer's" 64-bit absolute time values are
+ * wasteful or clumsy to use.
+ *
+ * Compared to @ref sys_xtimer "xtimer", evtimer offers:
+ *
+ * - only relative 32-bit millisecond timer values
+ *   Events can be scheduled with a relative offset of up to ~49.7 days in the
+ *   future.
+ *   **For time-critical stuff, use @ref sys_xtimer "xtimer"!**
+ * - more flexible, "intrusive" timer type @ref evtimer_event_t only contains
+ *   the necessary fields, which can be extended as needed, and handlers define
+ *   actions taken on timer triggers. Check out @ref evtimer_msg_event_t as
+ *   example.
+ * - uses @ref sys_xtimer "xtimer" as backend
+ *
+ * @{
+ *
+ * @file
+ * @brief       evtimer API definitions
+ *
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ * @author      Martine Lenders <m.lenders@fu-berlin.de>
+ */
+
+#ifndef EVTIMER_H
+#define EVTIMER_H
+
+#include <stdint.h>
+
+#include "xtimer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Generic event
+ */
+typedef struct evtimer_event {
+    struct evtimer_event *next; /**< the next event in the queue */
+    uint32_t offset;            /**< offset in milliseconds from previous event */
+} evtimer_event_t;
+
+/**
+ * @brief   Event timer callback type
+ */
+typedef void(*evtimer_callback_t)(evtimer_event_t* event);
+
+/**
+ * @brief   Event timer
+ */
+typedef struct {
+    xtimer_t timer;                 /**< Timer */
+    evtimer_callback_t callback;    /**< Handler function for this evtimer's
+                                         event type */
+    evtimer_event_t *events;        /**< Event queue */
+} evtimer_t;
+
+/**
+ * @brief   Initializes an event timer
+ *
+ * @param[in] evtimer   An event timer
+ * @param[in] handler   An event handler function
+ */
+void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler);
+
+/**
+ * @brief   Adds event to an event timer
+ *
+ * @param[in] evtimer       An event timer
+ * @param[in] event         An event
+ */
+void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event);
+
+/**
+ * @brief   Removes an event from an event timer
+ *
+ * @param[in] evtimer       An event timer
+ * @param[in] event         An event
+ */
+void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event);
+
+/**
+ * @brief   Print overview of current state of an event timer
+ *
+ * @param[in] evtimer   An event timer
+ */
+void evtimer_print(const evtimer_t *evtimer);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVTIMER_H */
+/** @} */
diff --git a/sys/include/evtimer_msg.h b/sys/include/evtimer_msg.h
new file mode 100644
index 0000000000000000000000000000000000000000..ce15f6c3c65a0a0ab2f79a14bb2e87203ce5f7b9
--- /dev/null
+++ b/sys/include/evtimer_msg.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de>
+ *               2017 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @addtogroup  sys_evtimer
+ * @{
+ *
+ * @file
+ * @brief       IPC-based evtimer definitions
+ *
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ * @author      Martine Lenders <m.lenders@fu-berlin.de>
+ */
+#ifndef EVTIMER_MSG_H
+#define EVTIMER_MSG_H
+
+#include "msg.h"
+#include "evtimer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   IPC-message event timer
+ * @extends evtimer_t
+ */
+typedef evtimer_t evtimer_msg_t;
+
+/**
+ * @brief   IPC-message event
+ * @extends evtimer_event_t
+ */
+typedef struct {
+    evtimer_event_t event;      /**< base class */
+    msg_t msg;                  /**< the IPC message to generate on event */
+} evtimer_msg_event_t;
+
+/**
+ * @brief   Adds event to an event timer that handles events via IPC
+ *
+ * @param[in] evtimer       An event timer
+ * @param[in] event         An event
+ * @param[in] target_pid    The PID of the thread that should receive the IPC
+ *                          message
+ */
+static inline void evtimer_add_msg(evtimer_msg_t *evtimer,
+                                   evtimer_msg_event_t *event,
+                                   kernel_pid_t target_pid)
+{
+    /* use sender_pid field to get target_pid into callback function */
+    event->msg.sender_pid = target_pid;
+    evtimer_add(evtimer, &event->event);
+}
+
+/**
+ * @brief   Event handler for IPC messages
+ *
+ * @param[in] event     The event to handle
+ */
+static inline void _evtimer_msg_handler(evtimer_event_t *event)
+{
+    evtimer_msg_event_t *mevent = (evtimer_msg_event_t *)event;
+    msg_send_int(&mevent->msg, mevent->msg.sender_pid);
+}
+
+/**
+ * @brief   Initializes event timer to handle events via IPC
+ *
+ * @param[in] evtimer   An event timer
+ */
+static inline void evtimer_init_msg(evtimer_t *evtimer)
+{
+    evtimer_init(evtimer, _evtimer_msg_handler);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVTIMER_MSG_H */
+/** @} */