diff --git a/Makefile.dep b/Makefile.dep
index 63e48af89a76e219163f2893986ceb2e918c724f..949924ec7281fd9acc0baf8e628b458eb2588d96 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -562,6 +562,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/div.h b/sys/include/div.h
index 1870e3e2c928e8e851229c0ed719e778394b1e42..456835ee0cae71cf81cfa3de10fd7fdb62e03271 100644
--- a/sys/include/div.h
+++ b/sys/include/div.h
@@ -74,6 +74,33 @@ static inline uint64_t div_u64_by_15625(uint64_t val)
     return (val * DIV_H_INV_15625_32) >> (DIV_H_INV_15625_SHIFT + 32);
 }
 
+/**
+ * @brief Integer divide val by 125
+ *
+ * This function can be used to convert uint64_t microsecond times (or
+ * intervals) to miliseconds and store them in uint32_t variables, with up to
+ * ~50 days worth of miliseconds ((2**32*1000) -1).
+ * Use e.g.,  ms = div_u64_by_125(microseconds >> 3)
+ *
+ * @pre val <= 536870911999 ((2**32 * 125) -1)
+ *
+ * @param[in]   val     dividend
+ * @return      (val / 125)
+ */
+static inline uint32_t div_u64_by_125(uint64_t val)
+{
+  /* a higher value would overflow the result type */
+  assert(val <= 536870911999LLU);
+
+  uint32_t hi = val >> 32;
+  uint32_t lo = val;
+  uint32_t r = (lo >> 16) + (hi << 16);
+  uint32_t res = r / 125;
+  r = ((r % 125) << 16) + (lo & 0xFFFF);
+  res = (res << 16) + r / 125;
+  return res;
+}
+
 /**
  * @brief Integer divide val by 1000000
  *
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 */
+/** @} */
diff --git a/tests/evtimer_msg/Makefile b/tests/evtimer_msg/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fff087ba1d0444a8cfc27c0eb4ce5d281906cac3
--- /dev/null
+++ b/tests/evtimer_msg/Makefile
@@ -0,0 +1,13 @@
+APPLICATION = evtimer_msg
+include ../Makefile.tests_common
+
+BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042
+
+USEMODULE += evtimer
+
+include $(RIOTBASE)/Makefile.include
+
+test:
+# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`.
+# So clears `TERMFLAGS` before run.
+	TERMFLAGS= tests/01-run.py
diff --git a/tests/evtimer_msg/main.c b/tests/evtimer_msg/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..febc60de28a335b6b8932a028cbe61576a10975b
--- /dev/null
+++ b/tests/evtimer_msg/main.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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 tests
+ * @{
+ *
+ * @file
+ * @brief    evtimer_msg test application
+ *
+ * @author   Martine Lenders <m.lenders@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+
+#include "evtimer_msg.h"
+#include "thread.h"
+#include "msg.h"
+#include "xtimer.h"
+
+static char worker_stack[THREAD_STACKSIZE_MAIN];
+static evtimer_t evtimer;
+static evtimer_msg_event_t events[] = {
+    { .event = { .offset = 1000 }, .msg = { .content = { .ptr = "supposed to be 1000" } } },
+    { .event = { .offset = 1500 }, .msg = { .content = { .ptr = "supposed to be 1500" } } },
+    { .event = { .offset = 659 }, .msg = { .content = { .ptr = "supposed to be 659" } } },
+    { .event = { .offset = 3954 }, .msg = { .content = { .ptr = "supposed to be 3954" } } },
+};
+
+#define NEVENTS ((unsigned)(sizeof(events) / sizeof(evtimer_msg_event_t)))
+
+/* This thread will print the drift to stdout once per second */
+void *worker_thread(void *arg)
+{
+    int count = 0;
+    (void) arg;
+
+    while (1) {
+        char *ctx;
+        msg_t m;
+        uint32_t now;
+
+        msg_receive(&m);
+        now = xtimer_now_usec() / US_PER_MS;
+        ctx = m.content.ptr;
+        printf("At %6" PRIu32 " ms received msg %i: \"%s\"\n", now, count++, ctx);
+    }
+}
+
+int main(void)
+{
+    uint32_t now = xtimer_now_usec() / US_PER_MS;
+
+    evtimer_init_msg(&evtimer);
+
+    /* create worker thread */
+    kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack),
+                                     THREAD_PRIORITY_MAIN - 1,
+                                     THREAD_CREATE_STACKTEST,
+                                     worker_thread, NULL, "worker");
+    printf("Testing generic evtimer (start time = %" PRIu32 " ms)\n", now);
+    for (unsigned i = 0; i < NEVENTS; i++) {
+        evtimer_add_msg(&evtimer, &events[i], pid);
+    }
+    printf("Are the reception times of all %u msgs close to the supposed values?\n",
+           NEVENTS);
+    puts("If yes, the tests were successful");
+}
diff --git a/tests/evtimer_msg/tests/01-run.py b/tests/evtimer_msg/tests/01-run.py
new file mode 100755
index 0000000000000000000000000000000000000000..721ea7615593d6d02725bc943f65d4a64a0b39a4
--- /dev/null
+++ b/tests/evtimer_msg/tests/01-run.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 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.
+
+from __future__ import print_function
+import os
+import sys
+import time
+
+sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner'))
+import testrunner
+
+ACCEPTED_ERROR = 20
+
+def testfunc(child):
+    child.expect(r"Testing generic evtimer \(start time = (\d+) ms\)")
+    timer_offset = int(child.match.group(1))
+    child.expect(r"Are the reception times of all (\d+) msgs close to the supposed values?")
+    numof = int(child.match.group(1))
+
+    for i in range(numof):
+        child.expect(r'At \s*(\d+) ms received msg %i: "supposed to be (\d+)"' % i)
+        stop = int(time.time() * 1000)
+        # check if output is correct
+        exp = int(child.match.group(2)) + timer_offset
+        assert(int(child.match.group(1)) in range(exp - ACCEPTED_ERROR, exp + ACCEPTED_ERROR + 1))
+        print(".", end="", flush=True)
+    print("")
+    print("All tests successful")
+
+if __name__ == "__main__":
+    sys.exit(testrunner.run(testfunc, echo=False))
diff --git a/tests/evtimer_underflow/Makefile b/tests/evtimer_underflow/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..fff087ba1d0444a8cfc27c0eb4ce5d281906cac3
--- /dev/null
+++ b/tests/evtimer_underflow/Makefile
@@ -0,0 +1,13 @@
+APPLICATION = evtimer_msg
+include ../Makefile.tests_common
+
+BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042
+
+USEMODULE += evtimer
+
+include $(RIOTBASE)/Makefile.include
+
+test:
+# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`.
+# So clears `TERMFLAGS` before run.
+	TERMFLAGS= tests/01-run.py
diff --git a/tests/evtimer_underflow/main.c b/tests/evtimer_underflow/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..4c8e008fa413fe6cdb14fdb7892440cd55494fef
--- /dev/null
+++ b/tests/evtimer_underflow/main.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 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 tests
+ * @{
+ *
+ * @file
+ * @brief    evtimer_msg test application
+ *
+ * @author   Martine Lenders <m.lenders@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+
+#include "evtimer_msg.h"
+#include "thread.h"
+#include "msg.h"
+
+#define WORKER_MSG_QUEUE_SIZE   (8)
+
+msg_t worker_msg_queue[WORKER_MSG_QUEUE_SIZE];
+static char worker_stack[THREAD_STACKSIZE_MAIN];
+static evtimer_t evtimer;
+static evtimer_msg_event_t events[] = {
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "1" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "2" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "3" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "4" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "5" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "6" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "7" } } },
+    { .event = { .offset = 0 }, .msg = { .content = { .ptr = "8" } } },
+};
+
+#define NEVENTS (sizeof(events) / sizeof(evtimer_msg_event_t))
+
+/* This thread will print the drift to stdout once per second */
+void *worker_thread(void *arg)
+{
+    (void) arg;
+
+    msg_init_queue(worker_msg_queue, WORKER_MSG_QUEUE_SIZE);
+    while (1) {
+        char *ctx;
+        msg_t m;
+
+        msg_receive(&m);
+        ctx = m.content.ptr;
+        printf("received msg \"%s\"\n", ctx);
+    }
+}
+
+int main(void)
+{
+    evtimer_init_msg(&evtimer);
+
+    /* create worker thread */
+    kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack),
+                                     THREAD_PRIORITY_MAIN - 1,
+                                     THREAD_CREATE_STACKTEST,
+                                     worker_thread, NULL, "worker");
+    while (1) {
+        for (unsigned i = 0; i < NEVENTS; i++) {
+            evtimer_add_msg(&evtimer, &events[i], pid);
+        }
+    }
+}
diff --git a/tests/evtimer_underflow/tests/01-run.py b/tests/evtimer_underflow/tests/01-run.py
new file mode 100755
index 0000000000000000000000000000000000000000..66c324239b0e0675e7059df30167926217aeff25
--- /dev/null
+++ b/tests/evtimer_underflow/tests/01-run.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2016 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.
+
+from __future__ import print_function
+import os
+import sys
+import time
+
+sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner'))
+import testrunner
+
+how_many = 100
+
+def testfunc(child):
+    for i in range(how_many):
+        for j in range(8):
+            child.expect(r'received msg "%i"' % (j + 1))
+        print(".", end="", flush=True)
+    print("")
+    print("Stopped after %i iterations, but should run forever." % how_many)
+    print("=> All tests successful")
+
+if __name__ == "__main__":
+    sys.exit(testrunner.run(testfunc, echo=False))