diff --git a/Makefile.dep b/Makefile.dep index e6f70295b468..af39fe1142a5 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -531,6 +531,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 000000000000..48422e909a47 --- /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 000000000000..5a255e7f1fc9 --- /dev/null +++ b/sys/evtimer/evtimer.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * 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 + * + * @} + */ +#include "evtimer.h" +#include "div.h" +#include "irq.h" +#include "msg.h" +#include "xtimer.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static void _add_event_to_list(evtimer_event_t *list, evtimer_event_t *event) +{ + uint32_t delta_sum = 0; + + 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_event_t *list, evtimer_event_t *event) +{ + 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(), + (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_now64(); + 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); + _add_event_to_list(evtimer->events, event); + + if (evtimer->events == event) { + _set_timer(&evtimer->timer, event->offset); + } + irq_restore(state); +} + +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->events, event); + _update_timer(evtimer); + irq_restore(state); +} + +bool evtimer_is_event_sched(evtimer_t *evtimer, evtimer_event_t *event) +{ + evtimer_event_t *list_entry = evtimer->events; + while (list_entry) { + if (list_entry == event) { + return true; + } + list_entry = list_entry->next; + } + return false; +} + +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; + } +} + +void evtimer_msg_handler(void *arg) +{ + DEBUG("evtimer_msg_handler()\n"); + + evtimer_t *evtimer = (evtimer_t *) arg; + + evtimer_event_t *event = evtimer->events; + event->offset = 0; + + evtimer_msg_event_t *mevent; + while ((mevent = (evtimer_msg_event_t *)_get_next(evtimer))) { + msg_send_int(&mevent->msg, mevent->msg.sender_pid); + } + + _update_timer(evtimer); + + if (!irq_is_in()) { + thread_yield_higher(); + } +} + +void evtimer_init(evtimer_t *evtimer, void (*handler)(void *)) +{ + evtimer->timer.callback = 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 d55a81ac223b..e36e06f9429c 100644 --- a/sys/include/div.h +++ b/sys/include/div.h @@ -44,6 +44,33 @@ static inline uint64_t div_u64_by_15625(uint64_t val) return (val * 0x431bde83UL) >> (12 + 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 000000000000..15b8151d6667 --- /dev/null +++ b/sys/include/evtimer.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * 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 timers + * @ingroup sys + * @brief Provides timers for events up to 2**32 milliseconds in the future + * + * RIOT's main timer subsystem is xtimer, but for many applications xtimer's + * 64bit absolute time values are wasteful or clumsy to use. + * + * Compared to xtimer, evtimer offers: + * + * - only relative 32bit 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 xtimer! + * + * - more flexible, "intrusive" timer type + * evtimer_event_t only contains the necessary fields, which can be extended + * as needed, and handlers define actions taken on timer triggers. + * Check out evtimer_msg_event as example. + * + * - uses xtimer as backend + * + * @{ + * + * @file + * @brief evtimer API + * + * @author Kaspar Schleiser + */ + +#ifndef EVTIMER_H +#define EVTIMER_H + +#include + +#include "msg.h" +#include "xtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** forward declaration */ +typedef struct evtimer_event evtimer_event_t; + +typedef struct { + xtimer_t timer; + evtimer_event_t *events; +} evtimer_t; + +struct evtimer_event { + evtimer_event_t *next; + uint32_t offset; +}; + +typedef struct { + evtimer_event_t event; + msg_t msg; +} evtimer_msg_event_t; + +void evtimer_init(evtimer_t *evtimer, void(*handler)(void*)); +void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event); +void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event); +bool evtimer_is_event_sched(evtimer_t *evtimer, evtimer_event_t *event); +void evtimer_msg_handler(void *arg); +void evtimer_print(const evtimer_t *evtimer); + +#ifdef __cplusplus +} +#endif + +#endif /* EVTIMER_H */ +/** @} */