From 548c4f02fcafc6af5acc0973543b38e1ad3b23fd Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Wed, 16 Sep 2020 08:28:03 +0200 Subject: [PATCH 1/2] sys/ztimer: add ztimer_periodic --- sys/include/ztimer/periodic.h | 137 ++++++++++++++++++++++++++++++++++ sys/ztimer/Makefile | 2 +- sys/ztimer/periodic.c | 77 +++++++++++++++++++ sys/ztimer/util.c | 1 + 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 sys/include/ztimer/periodic.h create mode 100644 sys/ztimer/periodic.c diff --git a/sys/include/ztimer/periodic.h b/sys/include/ztimer/periodic.h new file mode 100644 index 0000000000..797c323351 --- /dev/null +++ b/sys/include/ztimer/periodic.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 Kaspar Schleiser + * 2020 Freie Universität Berlin + * 2020 Inria + * + * 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_ztimer + * @brief Periodic ztimer API + * + * Once started, the periodic timer will call the configured callback function + * once each interval until the timer is either stopped using + * ztimer_periodic_stop or the callback function returns a non-zero value. + * + * Should the timer underflow ((time_at_interrupt + interval) % 2**32 > + * interval), the next timer will be scheduled with an offset of zero, thus + * fire right away. This leads to a callback for each missed tick, until the + * original period can be honoured again. + * + * Example: + * + * ``` + * #include "ztimer/periodic.h" + * + * static void callback(void *arg) + * { + * puts(arg); + * } + * + * + * int main(void) + * { + * // allocate timer in .bss with static, so that timer can keep + * // running when leaving function scope + * static ztimer_periodic_t timer; + * // initialize timer + * ztimer_periodic_init(ZTIMER_SEC, &timer, callback, "test", 1); + * + * // start timer + * ztimer_periodic_start(&timer); + * // timer will tick every 1 second + * + * // call again to reset timer (will abort current period and trigger next + * // after full interval) + * ztimer_periodic_start(&timer); + * + * // stop timer (will not trigger anymore) + * ztimer_periodic_stop(&timer); + * + * // NOTE: if using a stack allocated timer, *it must be stopped before + * // going out of scope*! + * } + * ``` + * + * @{ + * + * @file + * @brief ztimer periodic API + * + * @author Kaspar Schleiser + */ + +#ifndef ZTIMER_PERIODIC_H +#define ZTIMER_PERIODIC_H + +#include + +#include "ztimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Periodic timer stop unless it returns this value + */ +#define ZTIMER_PERIODIC_KEEP_GOING 0 + +/** + * @brief ztimer periodic structure + */ +typedef struct { + ztimer_t timer; /**< timer object used for this timer */ + ztimer_clock_t *clock; /**< clock for this timer */ + uint32_t interval; /**< interval of this timer */ + ztimer_now_t last; /**< last trigger time */ + int (*callback)(void *); /**< called on each trigger */ + void *arg; /**< argument for callback */ +} ztimer_periodic_t; + +/** + * @brief Initialize a periodic timer structure + * + * This sets up the underlying structure of a periodic timer. + * After initializing, use @ref ztimer_periodic_start() to start the timer. + * + * @param[in] clock the clock to configure this timer on + * @param[inout] timer periodic timer object to initialize + * @param[in] callback function to call on each trigger + * @param[in] arg argument to pass to callback function + * @param[in] interval period length of this timer instance + */ +void ztimer_periodic_init(ztimer_clock_t *clock, ztimer_periodic_t *timer, + int (*callback)(void *), + void *arg, uint32_t interval); + +/** + * @brief Start or restart a periodic timer + * + * When called on a newly initialized timer, the timer will start. + * + * When called on an already running timer, the current interval is reset to its + * start (thus the next callback will be called after the configured interval + * has passed). + * + * @param[in] timer periodic timer object to work on + */ +void ztimer_periodic_start(ztimer_periodic_t *timer); + +/** + * @brief Stop a periodic timer + * + * The periodic timer will not trigger anymore. + * + * @param[in] timer periodic timer object to stop + */ +void ztimer_periodic_stop(ztimer_periodic_t *timer); + +#ifdef __cplusplus +} +#endif + +#endif /* ZTIMER_PERIODIC_H */ +/** @} */ diff --git a/sys/ztimer/Makefile b/sys/ztimer/Makefile index 2b26882483..d23154b353 100644 --- a/sys/ztimer/Makefile +++ b/sys/ztimer/Makefile @@ -5,7 +5,7 @@ MODULE := ztimer_core BASE_MODULE := ztimer # ztimer_core files -SRC := core.c util.c +SRC := core.c util.c periodic.c # enable submodules SUBMODULES := 1 diff --git a/sys/ztimer/periodic.c b/sys/ztimer/periodic.c new file mode 100644 index 0000000000..5c38fdfd5a --- /dev/null +++ b/sys/ztimer/periodic.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Kaspar Schleiser + * 2020 Inria + * 2020 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_ztimer + * @{ + * + * @file + * @brief ztimer periodic timer implementation + * + * @author Kaspar Schleiser + * + * @} + */ +#include +#include +#include + +#include "ztimer.h" +#include "ztimer/periodic.h" + +static void _ztimer_periodic_reset(ztimer_periodic_t *timer, ztimer_now_t now) +{ + ztimer_now_t target = timer->last + timer->interval; + ztimer_now_t offset = target - now; + + if (offset > timer->interval) { + offset = 0; + } + + timer->last = target; + + ztimer_set(timer->clock, &timer->timer, offset); +} + +static void _ztimer_periodic_callback(void *arg) +{ + ztimer_periodic_t *timer = arg; + ztimer_now_t now = ztimer_now(timer->clock); + + if (timer->callback(timer->arg) == ZTIMER_PERIODIC_KEEP_GOING) { + _ztimer_periodic_reset(timer, now); + } +} + +void ztimer_periodic_init(ztimer_clock_t *clock, ztimer_periodic_t *timer, + int (*callback)( + void *), void *arg, uint32_t interval) +{ + *timer = + (ztimer_periodic_t){ .clock = clock, .interval = interval, + .callback = callback, .arg = arg, + .timer = { + .callback = _ztimer_periodic_callback, + .arg = timer + } }; +} + +void ztimer_periodic_start(ztimer_periodic_t *timer) +{ + uint32_t now = ztimer_now(timer->clock); + + timer->last = now; + _ztimer_periodic_reset(timer, now); +} + +void ztimer_periodic_stop(ztimer_periodic_t *timer) +{ + ztimer_remove(timer->clock, &timer->timer); +} diff --git a/sys/ztimer/util.c b/sys/ztimer/util.c index 115af57d40..9f6e416df1 100644 --- a/sys/ztimer/util.c +++ b/sys/ztimer/util.c @@ -62,6 +62,7 @@ void ztimer_periodic_wakeup(ztimer_clock_t *clock, uint32_t *last_wakeup, uint32_t now = ztimer_now(clock); uint32_t target = *last_wakeup + period; uint32_t offset = target - now; + irq_restore(state); if (offset <= period) { From 7b30777429b7a2b083739e66e56cfb93e33367e7 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Fri, 18 Sep 2020 14:51:08 +0200 Subject: [PATCH 2/2] tests/ztimer_periodic: initial commit --- tests/ztimer_periodic/Makefile | 7 ++ tests/ztimer_periodic/README.md | 3 + tests/ztimer_periodic/main.c | 94 +++++++++++++++++++++++++++ tests/ztimer_periodic/tests/01-run.py | 18 +++++ 4 files changed, 122 insertions(+) create mode 100644 tests/ztimer_periodic/Makefile create mode 100644 tests/ztimer_periodic/README.md create mode 100644 tests/ztimer_periodic/main.c create mode 100755 tests/ztimer_periodic/tests/01-run.py diff --git a/tests/ztimer_periodic/Makefile b/tests/ztimer_periodic/Makefile new file mode 100644 index 0000000000..e3ee9ea098 --- /dev/null +++ b/tests/ztimer_periodic/Makefile @@ -0,0 +1,7 @@ +DEVELHELP ?= 0 +include ../Makefile.tests_common + +USEMODULE += fmt +USEMODULE += ztimer_usec ztimer_msec + +include $(RIOTBASE)/Makefile.include diff --git a/tests/ztimer_periodic/README.md b/tests/ztimer_periodic/README.md new file mode 100644 index 0000000000..7619d16ee5 --- /dev/null +++ b/tests/ztimer_periodic/README.md @@ -0,0 +1,3 @@ +# Introduction + +This test application does some basic functionality testing of ztimer_periodic. diff --git a/tests/ztimer_periodic/main.c b/tests/ztimer_periodic/main.c new file mode 100644 index 0000000000..ce2fb557c9 --- /dev/null +++ b/tests/ztimer_periodic/main.c @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 Kaspar Schleiser + * 2020 Freie Universität Berlin + * 2020 Inria + * + * 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 test + * @{ + * + * @file + * @brief ztimer periodic test application + * + * @author Kaspar Schleiser + * + * @} + */ + +#include +#include +#include +#include + +#include "ztimer.h" +#include "ztimer/periodic.h" +#include "fmt.h" +#include "mutex.h" + +static mutex_t _mutex = MUTEX_INIT_LOCKED; + +#define MAX_OFFSET 2 +#define N 5 +#define INTERVAL 100LU + +static uint32_t _times[N]; + +static int callback(void *arg) +{ + (void)arg; + static int count = 0; + + _times[count] = ztimer_now(ZTIMER_MSEC); + + count += 1; + + /* enable this to test underflow behavior */ +#if 0 + if (count == 2) { + ztimer_spin(ZTIMER_MSEC, INTERVAL*2); + } +#endif + + if (count == N) { + mutex_unlock(&_mutex); + } + + return (count == N); +} + +int main(void) +{ + ztimer_periodic_t t; + + ztimer_periodic_init(ZTIMER_MSEC, &t, callback, NULL, INTERVAL); + uint32_t last = ztimer_now(ZTIMER_MSEC); + + ztimer_periodic_start(&t); + + /* wait for periodic to trigger N times */ + mutex_lock(&_mutex); + + int failed = 0; + + for (unsigned i = 0; i < N; i++) { + uint32_t offset = labs((int32_t)(_times[i] - INTERVAL - last)); + printf("i: %u time: %" PRIu32 " offset: %" PRIu32 "\n", + i, _times[i], offset); + if (offset > MAX_OFFSET) { + failed = 1; + } + last = _times[i]; + } + + if (!failed) { + print_str("Test successful.\n"); + } + else { + print_str("Test failed!\n"); + } +} diff --git a/tests/ztimer_periodic/tests/01-run.py b/tests/ztimer_periodic/tests/01-run.py new file mode 100755 index 0000000000..eec0244c60 --- /dev/null +++ b/tests/ztimer_periodic/tests/01-run.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2019 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. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("Test successful.") + + +if __name__ == "__main__": + sys.exit(run(testfunc))