
5 changed files with 1131 additions and 0 deletions
@ -0,0 +1,453 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* |
||||
* 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_xtimer Timers |
||||
* @ingroup sys |
||||
* @brief Provides a high level timer module to register |
||||
* timers, get current system time, and let a thread sleep for |
||||
* a certain amount of time. |
||||
* |
||||
* The implementation takes one low-level timer that is supposed to run at 1MHz |
||||
* speed and multiplexes it. |
||||
* |
||||
* Insertion and removal of timers has O(n) complexity with (n) being the |
||||
* number of active timers. The reason for this is that multiplexing is |
||||
* realized by next-first singly linked lists. |
||||
* |
||||
* @{ |
||||
* @file |
||||
* @brief xtimer interface definitions |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
*/ |
||||
#ifndef XTIMER_H |
||||
#define XTIMER_H |
||||
|
||||
#include <stdint.h> |
||||
#include "msg.h" |
||||
#include "periph/timer.h" |
||||
#include "timex.h" |
||||
|
||||
#include "board.h" |
||||
#include "periph_conf.h" |
||||
|
||||
/**
|
||||
* @brief internal define to allow using variables instead of defines |
||||
*/ |
||||
#ifdef XTIMER_TRACE |
||||
#include "xtimer_trace.h" |
||||
#endif |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief xtimer callback type |
||||
*/ |
||||
typedef void (*timer_callback_t)(void*); |
||||
|
||||
/**
|
||||
* @brief xtimer timer structure |
||||
*/ |
||||
typedef struct xtimer { |
||||
struct xtimer *next; /**< reference to next timer in timer lists */ |
||||
uint32_t target; /**< lower 32bit absolute target time */ |
||||
uint32_t long_target; /**< upper 32bit absolute target time */ |
||||
timer_callback_t callback; /**< callback function to call when timer
|
||||
expires */ |
||||
void *arg; /**< argument to pass to callback function */ |
||||
} xtimer_t; |
||||
|
||||
/**
|
||||
* @brief get the current system time as 32bit microsecond value |
||||
* |
||||
* @note Overflows every ~71minutes, thus returns xtimer_now64() % 32, |
||||
* but is more efficient. |
||||
* |
||||
* @return current time as 32bit microsecond value |
||||
*/ |
||||
static inline uint32_t xtimer_now(void); |
||||
|
||||
/**
|
||||
* @brief get the current system time as 64bit microsecond value |
||||
* |
||||
* @return current time as 64bit microsecond value |
||||
*/ |
||||
uint64_t xtimer_now64(void); |
||||
|
||||
/**
|
||||
* @brief get the current system time into a timex_t |
||||
* |
||||
* @param[out] out pointer to timex_t the time will be written to |
||||
*/ |
||||
void xtimer_now_timex(timex_t *out); |
||||
|
||||
/**
|
||||
* @brief xtimer initialization function |
||||
* |
||||
* This sets up xtimer. Has to be called once at system boot. |
||||
* If @ref auto_init is enabled, it will call this for you. |
||||
*/ |
||||
void xtimer_init(void); |
||||
|
||||
/**
|
||||
* @brief Stop execution of a thread for some time |
||||
* |
||||
* When called from an ISR, this function will spin and thus block the MCU in |
||||
* interrupt context for the specified amount in *seconds*, so don't *ever* use |
||||
* it there. |
||||
* |
||||
* @param[in] seconds the amount of seconds the thread should sleep |
||||
*/ |
||||
static void xtimer_sleep(uint32_t seconds); |
||||
|
||||
/**
|
||||
* @brief Stop execution of a thread for some time |
||||
* |
||||
* When called from an ISR, this function will spin and thus block the MCU for |
||||
* the specified amount in microseconds, so only use it there for *very* short |
||||
* periods, e.g., less than XTIMER_BACKOFF. |
||||
* |
||||
* @param[in] microseconds the amount of microseconds the thread should sleep |
||||
*/ |
||||
static void xtimer_usleep(uint32_t microseconds); |
||||
|
||||
/**
|
||||
* @brief Stop execution of a thread for some time, 64bit version |
||||
* |
||||
* When called from an ISR, this function will spin and thus block the MCU for |
||||
* the specified amount in microseconds, so only use it there for *very* short |
||||
* periods, e.g., less then XTIMER_BACKOFF. |
||||
* |
||||
* @param[in] microseconds the amount of microseconds the thread should sleep |
||||
*/ |
||||
static inline void xtimer_usleep64(uint64_t microseconds); |
||||
|
||||
/**
|
||||
* @brief Stop execution of a thread for some time |
||||
* |
||||
* Don't expect nanosecond accuracy. As of now, this function just calls |
||||
* xtimer_usleep(nanoseconds/1000). |
||||
* |
||||
* When called from an ISR, this function will spin-block, so only use it there |
||||
* for *very* short periods. |
||||
* |
||||
* @param[in] nanoseconds the amount of nanoseconds the thread should sleep |
||||
*/ |
||||
static void xtimer_nanosleep(uint32_t nanoseconds); |
||||
|
||||
/**
|
||||
* @brief Stop execution of a thread for some time, blocking |
||||
* |
||||
* This function will spin-block, so only use it *very* short periods. |
||||
* |
||||
* @param[in] microseconds the amount of microseconds the thread should spin |
||||
*/ |
||||
static inline void xtimer_spin(uint32_t microseconds); |
||||
|
||||
/**
|
||||
* @brief will cause the calling thread to be suspended until the absolute |
||||
* time (@p last_wakeup + @p interval). |
||||
* |
||||
* When the function returns, @p last_wakeup is set to xtimer_now(). |
||||
* |
||||
* This function can be used to create periodic wakeups. |
||||
* @c last_wakeup should be set to xtimer_now() before first call of the |
||||
* function. |
||||
* |
||||
* If the result of (@p last_wakeup + usecs) would be in the past, the function |
||||
* sets @p last_wakeup to xtimer_now() and returns immediately. |
||||
* |
||||
* @param[in] last_wakeup base time for the wakeup |
||||
* @param[in] usecs time in microseconds that will be added to |
||||
* last_wakeup |
||||
*/ |
||||
void xtimer_usleep_until(uint32_t *last_wakeup, uint32_t usecs); |
||||
|
||||
/**
|
||||
* @brief Set a timer that sends a message |
||||
* |
||||
* This function sets a timer that will send a message @p offset microseconds |
||||
* from now. |
||||
* |
||||
* The mesage struct specified by msg parameter will not be copied, e.g., it |
||||
* needs to point to valid memory until the message has been delivered. |
||||
* |
||||
* @param[in] timer timer struct to work with |
||||
* @param[in] offset microseconds from now |
||||
* @param[in] msg ptr to msg that will be sent |
||||
* @param[in] target_pid pid the message will be sent to |
||||
*/ |
||||
void xtimer_set_msg(xtimer_t *timer, uint32_t offset, msg_t *msg, kernel_pid_t target_pid); |
||||
|
||||
/**
|
||||
* @brief Set a timer that sends a message, 64bit version |
||||
* |
||||
* This function sets a timer that will send a message @p offset microseconds |
||||
* from now. |
||||
* |
||||
* The mesage struct specified by msg parameter will not be copied, e.g., it |
||||
* needs to point to valid memory until the message has been delivered. |
||||
* |
||||
* @param[in] timer timer struct to work with |
||||
* @param[in] offset microseconds from now |
||||
* @param[in] msg ptr to msg that will be sent |
||||
* @param[in] target_pid pid the message will be sent to |
||||
*/ |
||||
void xtimer_set_msg64(xtimer_t *timer, uint64_t offset, msg_t *msg, kernel_pid_t target_pid); |
||||
|
||||
/**
|
||||
* @brief Set a timer that wakes up a thread |
||||
* |
||||
* This function sets a timer that will wake up a thread when the timer has |
||||
* expired. |
||||
* |
||||
* @param[in] timer timer struct to work with |
||||
* @param[in] offset microseconds from now |
||||
* @param[in] pid pid of the thread that will be woken up |
||||
*/ |
||||
void xtimer_set_wakeup(xtimer_t *timer, uint32_t offset, kernel_pid_t pid); |
||||
|
||||
/**
|
||||
* @brief Set a timer to execute a callback at some time in the future |
||||
* |
||||
* Expects timer->callback to be set. |
||||
* |
||||
* The callback specified in the timer struct will be executed @p offset |
||||
* microseconds in the future. |
||||
* |
||||
* @warning BEWARE! Callbacks from xtimer_set() are being executed in interrupt |
||||
* context (unless offset < XTIMER_BACKOFF). DON'T USE THIS FUNCTION unless you |
||||
* know *exactly* what that means. |
||||
* |
||||
* @param[in] timer the timer structure to use |
||||
* @param[in] offset time in microseconds from now specifying that timer's |
||||
* callback's execution time |
||||
*/ |
||||
void xtimer_set(xtimer_t *timer, uint32_t offset); |
||||
|
||||
/**
|
||||
* @brief remove a timer |
||||
* |
||||
* @note this function runs in O(n) with n being the number of active timers |
||||
* |
||||
* @param[in] timer ptr to timer structure that will be removed |
||||
* |
||||
* @return 1 on success |
||||
* @return 0 when timer was not active |
||||
*/ |
||||
int xtimer_remove(xtimer_t *timer); |
||||
|
||||
/**
|
||||
* @brief receive a message blocking but with timeout |
||||
* |
||||
* @param[out] msg pointer to a msg_t which will be filled in case of |
||||
* no timeout |
||||
* @param[in] us timeout in microseconds relative |
||||
* |
||||
* @return < 0 on error, other value otherwise |
||||
*/ |
||||
int xtimer_msg_receive_timeout(msg_t *msg, uint32_t us); |
||||
|
||||
/**
|
||||
* @brief receive a message blocking but with timeout, 64bit version |
||||
* |
||||
* @param[out] msg pointer to a msg_t which will be filled in case of no |
||||
* timeout |
||||
* @param[in] us timeout in microseconds relative |
||||
* |
||||
* @return < 0 on error, other value otherwise |
||||
*/ |
||||
int xtimer_msg_receive_timeout64(msg_t *msg, uint64_t us); |
||||
|
||||
/**
|
||||
* @brief xtimer backoff value |
||||
* |
||||
* All timers that are less than XTIMER_BACKOFF microseconds in the future will |
||||
* just spin. |
||||
* |
||||
* This is supposed to be defined per-device in e.g., periph_conf.h. |
||||
*/ |
||||
#ifndef XTIMER_BACKOFF |
||||
#define XTIMER_BACKOFF 30 |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief xtimer overhead value |
||||
* |
||||
* This value specifies the time a timer will be late if uncorrected, e.g., |
||||
* the system-specific xtimer execution time from timer ISR to executing |
||||
* a timer's callback's first instruction. |
||||
* |
||||
* E.g., with XTIMER_OVERHEAD == 0 |
||||
* start=xtimer_now(); |
||||
* xtimer_set(&timer, X); |
||||
* (in callback:) |
||||
* overhead=xtimer_now()-start-X; |
||||
* |
||||
* xtimer automatically substracts XTIMER_OVERHEAD from a timer's target time, |
||||
* but when the timer triggers, xtimer will spin-lock until a timer's target |
||||
* time is reached, so timers will never trigger early. |
||||
* |
||||
* This is supposed to be defined per-device in e.g., periph_conf.h. |
||||
*/ |
||||
#ifndef XTIMER_OVERHEAD |
||||
#define XTIMER_OVERHEAD 20 |
||||
#endif |
||||
|
||||
#ifndef XTIMER_ISR_BACKOFF |
||||
/**
|
||||
* @brief xtimer isr backoff time |
||||
* |
||||
* When scheduling the next isr, if it is less than the backoff time |
||||
* in the future, just spin. |
||||
* |
||||
* This is supposed to be defined per-device in e.g., periph_conf.h. |
||||
*/ |
||||
#define XTIMER_ISR_BACKOFF 20 |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief set xtimer default timer configuration |
||||
* @{ |
||||
*/ |
||||
#ifndef XTIMER |
||||
#define XTIMER (0) |
||||
#define XTIMER_CHAN (0) |
||||
|
||||
#if TIMER_0_MAX_VALUE == 0xffffff |
||||
#define XTIMER_MASK 0xff000000 |
||||
#elif TIMER_0_MAX_VALUE == 0xffff |
||||
#define XTIMER_MASK 0xffff0000 |
||||
#endif |
||||
|
||||
#endif |
||||
/**
|
||||
* @} |
||||
*/ |
||||
|
||||
#ifndef XTIMER_MASK |
||||
/**
|
||||
* @brief xtimer timer mask |
||||
* |
||||
* This value specifies the mask relative to 0xffffffff that the used timer |
||||
* counts to, e.g., 0xffffffff & ~TIMER_MAXVALUE. |
||||
* |
||||
* For a 16bit timer, the mask would be 0xFFFF0000, for a 24bit timer, the mask |
||||
* would be 0xFF000000. Don't set this for 32bit timers. |
||||
* |
||||
* This is supposed to be defined per-device in e.g., periph_conf.h. |
||||
*/ |
||||
#define XTIMER_MASK 0 |
||||
#endif |
||||
|
||||
#ifndef XTIMER_USLEEP_UNTIL_OVERHEAD |
||||
/**
|
||||
* @brief xtimer_usleep_until overhead value |
||||
* |
||||
* This value specifies the time a xtimer_usleep_until will be late |
||||
* if uncorrected. |
||||
* |
||||
* This is supposed to be defined per-device in e.g., periph_conf.h. |
||||
*/ |
||||
#define XTIMER_USLEEP_UNTIL_OVERHEAD 10 |
||||
#endif |
||||
|
||||
#if XTIMER_MASK |
||||
extern volatile uint32_t _high_cnt; |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief IPC message type for xtimer msg callback |
||||
*/ |
||||
#define MSG_XTIMER 12345 |
||||
|
||||
/**
|
||||
* @brief returns the (masked) low-level timer counter value. |
||||
*/ |
||||
static inline uint32_t _xtimer_now(void) |
||||
{ |
||||
#ifdef XTIMER_SHIFT |
||||
return timer_read(XTIMER) << XTIMER_SHIFT; |
||||
#else |
||||
return timer_read(XTIMER); |
||||
#endif |
||||
} |
||||
|
||||
/**
|
||||
* @brief drop bits of a value that don't fit into the low-level timer. |
||||
*/ |
||||
static inline uint32_t _mask(uint32_t val) |
||||
{ |
||||
return val & ~XTIMER_MASK; |
||||
} |
||||
|
||||
/**
|
||||
* @{ |
||||
* @brief xtimer internal stuff |
||||
* @internal |
||||
*/ |
||||
int _xtimer_set_absolute(xtimer_t *timer, uint32_t target); |
||||
void _xtimer_set64(xtimer_t *timer, uint32_t offset, uint32_t long_offset); |
||||
void _xtimer_sleep(uint32_t offset, uint32_t long_offset); |
||||
static inline void xtimer_spin_until(uint32_t value); |
||||
/** @} */ |
||||
|
||||
static inline uint32_t xtimer_now(void) |
||||
{ |
||||
#if XTIMER_MASK |
||||
return _xtimer_now() | _high_cnt; |
||||
#else |
||||
return _xtimer_now(); |
||||
#endif |
||||
} |
||||
|
||||
static inline void xtimer_spin_until(uint32_t value) { |
||||
while (_xtimer_now() > value); |
||||
while (_xtimer_now() < value); |
||||
} |
||||
|
||||
static inline void xtimer_spin(uint32_t offset) { |
||||
offset = _mask(offset + _xtimer_now()); |
||||
xtimer_spin_until(offset); |
||||
} |
||||
|
||||
static inline void xtimer_usleep(uint32_t offset) |
||||
{ |
||||
_xtimer_sleep(offset, 0); |
||||
} |
||||
|
||||
static inline void xtimer_usleep64(uint64_t microseconds) |
||||
{ |
||||
_xtimer_sleep((uint32_t) microseconds, (uint32_t) (microseconds >> 32)); |
||||
} |
||||
|
||||
static inline void xtimer_sleep(uint32_t seconds) |
||||
{ |
||||
xtimer_usleep64((uint64_t)seconds*SEC_IN_USEC); |
||||
} |
||||
|
||||
static inline void xtimer_nanosleep(uint32_t nanoseconds) |
||||
{ |
||||
_xtimer_sleep(nanoseconds/1000, 0); |
||||
} |
||||
|
||||
/** @} */ |
||||
|
||||
#if XTIMER_OVERHEAD + XTIMER_USLEEP_UNTIL_OVERHEAD > XTIMER_BACKOFF |
||||
#warning (XTIMER_OVERHEAD + XTIMER_USLEEP_UNTIL_OVERHEAD > XTIMER_BACKOFF !!) |
||||
#warning This will lead to underruns. Check if tests/xtimer_usleep_until runs through. |
||||
#endif |
||||
|
||||
/** @} */ |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
#endif /* XTIMER_H */ |
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* |
||||
* 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 xtimer |
||||
* @{ |
||||
* @file |
||||
* @brief xtimer convenience functionality |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
* @} |
||||
*/ |
||||
|
||||
#include <assert.h> |
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
|
||||
#include "xtimer.h" |
||||
#include "mutex.h" |
||||
#include "thread.h" |
||||
#include "irq.h" |
||||
|
||||
#include "timex.h" |
||||
|
||||
#define ENABLE_DEBUG 0 |
||||
#include "debug.h" |
||||
|
||||
static void _callback_unlock_mutex(void* arg) |
||||
{ |
||||
mutex_t *mutex = (mutex_t *) arg; |
||||
mutex_unlock(mutex); |
||||
} |
||||
|
||||
void _xtimer_sleep(uint32_t offset, uint32_t long_offset) |
||||
{ |
||||
if (inISR()) { |
||||
assert(!long_offset); |
||||
xtimer_spin(offset); |
||||
} |
||||
|
||||
xtimer_t timer; |
||||
mutex_t mutex = MUTEX_INIT; |
||||
|
||||
timer.callback = _callback_unlock_mutex; |
||||
timer.arg = (void*) &mutex; |
||||
|
||||
mutex_lock(&mutex); |
||||
_xtimer_set64(&timer, offset, long_offset); |
||||
mutex_lock(&mutex); |
||||
} |
||||
|
||||
void xtimer_usleep_until(uint32_t *last_wakeup, uint32_t interval) { |
||||
xtimer_t timer; |
||||
mutex_t mutex = MUTEX_INIT; |
||||
|
||||
timer.callback = _callback_unlock_mutex; |
||||
timer.arg = (void*) &mutex; |
||||
|
||||
uint32_t target = *last_wakeup + interval; |
||||
|
||||
uint32_t now = xtimer_now(); |
||||
/* make sure we're not setting a value in the past */ |
||||
if (now < *last_wakeup) { |
||||
/* base timer overflowed */ |
||||
if (!((target < *last_wakeup) && (target > now))) { |
||||
goto out; |
||||
} |
||||
} |
||||
else if (! ((target < *last_wakeup) || (target > now))) { |
||||
goto out; |
||||
} |
||||
|
||||
uint32_t offset = target - now; |
||||
|
||||
if (offset > XTIMER_BACKOFF+XTIMER_USLEEP_UNTIL_OVERHEAD+1) { |
||||
mutex_lock(&mutex); |
||||
_xtimer_set_absolute(&timer, target - XTIMER_USLEEP_UNTIL_OVERHEAD); |
||||
mutex_lock(&mutex); |
||||
} |
||||
else { |
||||
xtimer_spin_until(target); |
||||
} |
||||
|
||||
out: |
||||
*last_wakeup = target; |
||||
} |
||||
|
||||
static void _callback_msg(void* arg) |
||||
{ |
||||
msg_t *msg = (msg_t*)arg; |
||||
msg_send_int(msg, msg->sender_pid); |
||||
} |
||||
|
||||
static inline void _setup_msg(xtimer_t *timer, msg_t *msg, kernel_pid_t target_pid) |
||||
{ |
||||
timer->callback = _callback_msg; |
||||
timer->arg = (void*) msg; |
||||
|
||||
/* use sender_pid field to get target_pid into callback function */ |
||||
msg->sender_pid = target_pid; |
||||
} |
||||
|
||||
void xtimer_set_msg(xtimer_t *timer, uint32_t offset, msg_t *msg, kernel_pid_t target_pid) |
||||
{ |
||||
_setup_msg(timer, msg, target_pid); |
||||
xtimer_set(timer, offset); |
||||
} |
||||
|
||||
void xtimer_set_msg64(xtimer_t *timer, uint64_t offset, msg_t *msg, kernel_pid_t target_pid) |
||||
{ |
||||
_setup_msg(timer, msg, target_pid); |
||||
_xtimer_set64(timer, offset, offset >> 32); |
||||
} |
||||
|
||||
static void _callback_wakeup(void* arg) |
||||
{ |
||||
thread_wakeup((kernel_pid_t)((intptr_t)arg)); |
||||
} |
||||
|
||||
void xtimer_set_wakeup(xtimer_t *timer, uint32_t offset, kernel_pid_t pid) |
||||
{ |
||||
timer->callback = _callback_wakeup; |
||||
timer->arg = (void*) ((intptr_t)pid); |
||||
|
||||
xtimer_set(timer, offset); |
||||
} |
||||
|
||||
/**
|
||||
* see http://www.hackersdelight.org/magic.htm.
|
||||
* This is to avoid using long integer division functions |
||||
* the compiler otherwise links in. |
||||
*/ |
||||
static inline uint64_t _ms_to_sec(uint64_t ms) |
||||
{ |
||||
return (unsigned long long)(ms * 0x431bde83) >> (0x12 + 32); |
||||
} |
||||
|
||||
void xtimer_now_timex(timex_t *out) |
||||
{ |
||||
uint64_t now = xtimer_now64(); |
||||
|
||||
out->seconds = _ms_to_sec(now); |
||||
out->microseconds = now - (out->seconds * SEC_IN_USEC); |
||||
} |
||||
|
||||
int xtimer_msg_receive_timeout64(msg_t *m, uint64_t timeout) { |
||||
msg_t tmsg; |
||||
tmsg.type = MSG_XTIMER; |
||||
tmsg.content.ptr = (char *) &tmsg; |
||||
|
||||
xtimer_t t; |
||||
xtimer_set_msg64(&t, timeout, &tmsg, sched_active_pid); |
||||
|
||||
msg_receive(m); |
||||
if (m->type == MSG_XTIMER && m->content.ptr == (char *) &tmsg) { |
||||
/* we hit the timeout */ |
||||
return -1; |
||||
} |
||||
else { |
||||
xtimer_remove(&t); |
||||
return 1; |
||||
} |
||||
} |
@ -0,0 +1,504 @@
|
||||
/**
|
||||
* Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* |
||||
* 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 xtimer |
||||
* @{ |
||||
* @file |
||||
* @brief xtimer core functionality |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
* @} |
||||
*/ |
||||
|
||||
#include <stdint.h> |
||||
#include <string.h> |
||||
#include "board.h" |
||||
#include "periph/timer.h" |
||||
#include "periph_conf.h" |
||||
|
||||
#include "xtimer.h" |
||||
#include "irq.h" |
||||
|
||||
/* WARNING! enabling this will have side effects and can lead to timer underflows. */ |
||||
#define ENABLE_DEBUG 0 |
||||
#include "debug.h" |
||||
|
||||
static volatile uint32_t _long_cnt = 0; |
||||
#if XTIMER_MASK |
||||
volatile uint32_t _high_cnt = 0; |
||||
#endif |
||||
|
||||
static xtimer_t *timer_list_head = NULL; |
||||
static xtimer_t *overflow_list_head = NULL; |
||||
static xtimer_t *long_list_head = NULL; |
||||
|
||||
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer); |
||||
static void _add_timer_to_long_list(xtimer_t **list_head, xtimer_t *timer); |
||||
static void _shoot(xtimer_t *timer); |
||||
static inline void _lltimer_set(uint32_t target); |
||||
static uint32_t _time_left(uint32_t target, uint32_t reference); |
||||
|
||||
static void _timer_callback(void); |
||||
static void _periph_timer_callback(int chan); |
||||
|
||||
static inline int _this_high_period(uint32_t target); |
||||
|
||||
static inline int _is_set(xtimer_t *timer) |
||||
{ |
||||
return (timer->target || timer->long_target); |
||||
} |
||||
|
||||
void xtimer_init(void) |
||||
{ |
||||
/* initialize low-level timer */ |
||||
timer_init(XTIMER, 1 /* us_per_tick */, _periph_timer_callback); |
||||
|
||||
/* register initial overflow tick */ |
||||
_lltimer_set(0xFFFFFFFF); |
||||
} |
||||
|
||||
static void _xtimer_now64(uint32_t *short_term, uint32_t *long_term) |
||||
{ |
||||
uint32_t before, after, long_value; |
||||
|
||||
/* loop to cope with possible overflow of xtimer_now() */ |
||||
do { |
||||
before = xtimer_now(); |
||||
long_value = _long_cnt; |
||||
after = xtimer_now(); |
||||
|
||||
} while(before > after); |
||||
|
||||
*short_term = after; |
||||
*long_term = long_value; |
||||
} |
||||
|
||||
uint64_t xtimer_now64(void) |
||||
{ |
||||
uint32_t short_term, long_term; |
||||
_xtimer_now64(&short_term, &long_term); |
||||
|
||||
return ((uint64_t)long_term<<32) + short_term; |
||||
} |
||||
|
||||
void _xtimer_set64(xtimer_t *timer, uint32_t offset, uint32_t long_offset) |
||||
{ |
||||
DEBUG(" _xtimer_set64() offset=%" PRIu32 " long_offset=%" PRIu32 "\n", offset, long_offset); |
||||
if (!long_offset) { |
||||
/* timer fits into the short timer */ |
||||
xtimer_set(timer, (uint32_t) offset); |
||||
} |
||||
else { |
||||
xtimer_remove(timer); |
||||
|
||||
_xtimer_now64(&timer->target, &timer->long_target); |
||||
timer->target += offset; |
||||
timer->long_target += long_offset; |
||||
if (timer->target < offset) { |
||||
timer->long_target++; |
||||
} |
||||
|
||||
int state = disableIRQ(); |
||||
_add_timer_to_long_list(&long_list_head, timer); |
||||
restoreIRQ(state); |
||||
DEBUG("xtimer_set64(): added longterm timer (long_target=%" PRIu32 " target=%" PRIu32 ")\n", |
||||
timer->long_target, timer->target); |
||||
} |
||||
} |
||||
|
||||
void xtimer_set(xtimer_t *timer, uint32_t offset) |
||||
{ |
||||
DEBUG("timer_set(): offset=%" PRIu32 " now=%" PRIu32 " (%" PRIu32 ")\n", offset, xtimer_now(), _xtimer_now()); |
||||
if (!timer->callback) { |
||||
DEBUG("timer_set(): timer has no callback.\n"); |
||||
return; |
||||
} |
||||
|
||||
xtimer_remove(timer); |
||||
uint32_t target = xtimer_now() + offset; |
||||
|
||||
if (offset < XTIMER_BACKOFF) { |
||||
/* spin until timer should be run */ |
||||
xtimer_spin_until(target); |
||||
|
||||
_shoot(timer); |
||||
} |
||||
else { |
||||
_xtimer_set_absolute(timer, target); |
||||
} |
||||
} |
||||
|
||||
static void _periph_timer_callback(int chan) |
||||
{ |
||||
(void)chan; |
||||
_timer_callback(); |
||||
} |
||||
|
||||
static void _shoot(xtimer_t *timer) |
||||
{ |
||||
timer->callback(timer->arg); |
||||
} |
||||
|
||||
static inline void _lltimer_set(uint32_t target) |
||||
{ |
||||
DEBUG("__lltimer_set(): setting %" PRIu32 "\n", _mask(target)); |
||||
#ifdef XTIMER_SHIFT |
||||
target >>= XTIMER_SHIFT; |
||||
if (!target) { |
||||
target++; |
||||
} |
||||
#endif |
||||
timer_set_absolute(XTIMER, XTIMER_CHAN, _mask(target)); |
||||
} |
||||
|
||||
int _xtimer_set_absolute(xtimer_t *timer, uint32_t target) |
||||
{ |
||||
uint32_t now = xtimer_now(); |
||||
int res = 0; |
||||
|
||||
DEBUG("timer_set_absolute(): now=%" PRIu32 " target=%" PRIu32 "\n", now, target); |
||||
|
||||
timer->next = NULL; |
||||
if ((target >= now) && ((target - XTIMER_BACKOFF) < now)) { |
||||
/* backoff */ |
||||
xtimer_spin_until(target); |
||||
_shoot(timer); |
||||
return 0; |
||||
} |
||||
|
||||
timer->target = target; |
||||
|
||||
unsigned state = disableIRQ(); |
||||
if ( !_this_high_period(target) ) { |
||||
DEBUG("xtimer_set_absolute(): the timer doesn't fit into the low-level timer's mask.\n"); |
||||
timer->long_target = _long_cnt; |
||||
_add_timer_to_long_list(&long_list_head, timer); |
||||
} |
||||
else { |
||||
if (!target) { |
||||
/* set long_target != 0 so _is_set() can work */ |
||||
timer->long_target = 1; |
||||
} |
||||
|
||||
if (_mask(now) >= target) { |
||||
DEBUG("xtimer_set_absolute(): the timer will expire in the next timer period\n"); |
||||
_add_timer_to_list(&overflow_list_head, timer); |
||||
} |
||||
else { |
||||
DEBUG("timer_set_absolute(): timer will expire in this timer period.\n"); |
||||
_add_timer_to_list(&timer_list_head, timer); |
||||
|
||||
if (timer_list_head == timer) { |
||||
DEBUG("timer_set_absolute(): timer is new list head. updating lltimer.\n"); |
||||
_lltimer_set(target - XTIMER_OVERHEAD); |
||||
} |
||||
} |
||||
} |
||||
|
||||
restoreIRQ(state); |
||||
|
||||
return res; |
||||
} |
||||
|
||||
static void _add_timer_to_list(xtimer_t **list_head, xtimer_t *timer) |
||||
{ |
||||
while (*list_head && (*list_head)->target <= timer->target) { |
||||
list_head = &((*list_head)->next); |
||||
} |
||||
|
||||
timer->next = *list_head; |
||||
*list_head = timer; |
||||
} |
||||
|
||||
static void _add_timer_to_long_list(xtimer_t **list_head, xtimer_t *timer) |
||||
{ |
||||
while (*list_head |
||||
&& (*list_head)->long_target <= timer->long_target |
||||
&& (*list_head)->target <= timer->target) { |
||||
list_head = &((*list_head)->next); |
||||
} |
||||
|
||||
timer->next = *list_head; |
||||
*list_head = timer; |
||||
} |
||||
|
||||
static int _remove_timer_from_list(xtimer_t **list_head, xtimer_t *timer) |
||||
{ |
||||
while (*list_head) { |
||||
if (*list_head == timer) { |
||||
*list_head = timer->next; |
||||
return 1; |
||||
} |
||||
list_head = &((*list_head)->next); |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int xtimer_remove(xtimer_t *timer) |
||||
{ |
||||
if (!_is_set(timer)) { |
||||
return 0; |
||||
} |
||||
|
||||
unsigned state = disableIRQ(); |
||||
int res = 0; |
||||
if (timer_list_head == timer) { |
||||
uint32_t next; |
||||
timer_list_head = timer->next; |
||||
if (timer_list_head) { |
||||
/* schedule callback on next timer target time */ |
||||
next = timer_list_head->target - XTIMER_OVERHEAD; |
||||
} |
||||
else { |
||||
next = _mask(0xFFFFFFFF); |
||||
} |
||||
_lltimer_set(next); |
||||
} |
||||
else { |
||||
res = _remove_timer_from_list(&timer_list_head, timer) || |
||||
_remove_timer_from_list(&overflow_list_head, timer) || |
||||
_remove_timer_from_list(&long_list_head, timer); |
||||
} |
||||
|
||||
timer->target = 0; |
||||
timer->long_target = 0; |
||||
|
||||
restoreIRQ(state); |
||||
|
||||
return res; |
||||
} |
||||
|
||||
static uint32_t _time_left(uint32_t target, uint32_t reference) |
||||
{ |
||||
uint32_t now = _xtimer_now(); |
||||
|
||||
if (now < reference) { |
||||
return 0; |
||||
} |
||||
|
||||
if (target > now) { |
||||
return target - now; |
||||
} |
||||
else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
static inline int _this_high_period(uint32_t target) { |
||||
#if XTIMER_MASK |
||||
return (target & XTIMER_MASK) == _high_cnt; |
||||
#else |
||||
(void)target; |
||||
return 1; |
||||
#endif |
||||
} |
||||
|
||||
/**
|
||||
* @brief compare two timers' target values, return the one with lower value. |
||||
* |
||||
* if either is NULL, return the other. |
||||
* if both are NULL, return NULL. |
||||
*/ |
||||
static inline xtimer_t *_compare(xtimer_t *a, xtimer_t *b) |
||||
{ |
||||
if (a && b) { |
||||
return a->target <= b->target ? a : b; |
||||
} |
||||
else { |
||||
return a ? a : b; |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief merge two timer lists, return head of new list |
||||
*/ |
||||
static xtimer_t *_merge_lists(xtimer_t *head_a, xtimer_t *head_b) |
||||
{ |
||||
xtimer_t *result_head = _compare(head_a, head_b); |
||||
xtimer_t *pos = result_head; |
||||
|
||||
while(1) { |
||||
head_a = head_a->next; |
||||
head_b = head_b->next; |
||||
if (!head_a) { |
||||
pos->next = head_b; |
||||
break; |
||||
} |
||||
if (!head_b) { |
||||
pos->next = head_a; |
||||
break; |
||||
} |
||||
|
||||
pos->next = _compare(head_a, head_b); |
||||
pos = pos->next; |
||||
} |
||||
|
||||
return result_head; |
||||
} |
||||
|
||||
/**
|
||||
* @brief parse long timers list and copy those that will expire in the current |
||||
* short timer period |
||||
*/ |
||||
static void _select_long_timers(void) |
||||
{ |
||||
xtimer_t *select_list_start = long_list_head; |
||||
xtimer_t *select_list_last = NULL; |
||||
|
||||
/* advance long_list head so it points to the first timer of the next (not
|
||||
* just started) "long timer period" */ |
||||
while (long_list_head) { |
||||
if ((long_list_head->long_target <= _long_cnt) && _this_high_period(long_list_head->target)) { |
||||
select_list_last = long_list_head; |
||||
long_list_head = long_list_head->next; |
||||
} |
||||
else { |
||||
/* remaining long_list timers belong to later long periods */ |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/* cut the "selected long timer list" at the end */ |
||||
if (select_list_last) { |
||||
select_list_last->next = NULL; |
||||
} |
||||
|
||||
/* merge "current timer list" and "selected long timer list" */ |
||||
if (timer_list_head) { |
||||
if (select_list_last) { |
||||
/* both lists are non-empty. merge. */ |
||||
timer_list_head = _merge_lists(timer_list_head, select_list_start); |
||||
} |
||||
else { |
||||
/* "selected long timer list" is empty, nothing to do */ |
||||
} |
||||
} |
||||
else { /* current timer list is empty */ |
||||
if (select_list_last) { |
||||
/* there's no current timer list, but a non-empty "selected long
|
||||
* timer list". So just use that list as the new current timer |
||||
* list.*/ |
||||
timer_list_head = select_list_start; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/**
|
||||
* @brief handle low-level timer overflow, advance to next short timer period |
||||
*/ |
||||
static void _next_period(void) |
||||
{ |
||||
#if XTIMER_MASK |
||||
/* advance <32bit mask register */ |
||||
_high_cnt += ~XTIMER_MASK + 1; |
||||
if (! _high_cnt) { |
||||
/* high_cnt overflowed, so advance >32bit counter */ |
||||
_long_cnt++; |
||||
} |
||||
#else |
||||
/* advance >32bit counter */ |
||||
_long_cnt++; |
||||
#endif |
||||
|
||||
/* swap overflow list to current timer list */ |
||||
timer_list_head = overflow_list_head; |
||||
overflow_list_head = NULL; |
||||
|
||||
_select_long_timers(); |
||||
|
||||
} |
||||
|
||||
/**
|
||||
* @brief main xtimer callback function |
||||
*/ |
||||
static void _timer_callback(void) |
||||
{ |
||||
uint32_t next_target; |
||||
uint32_t reference; |
||||
|
||||
DEBUG("_timer_callback() now=%" PRIu32 " (%" PRIu32 ")pleft=%" PRIu32 "\n", xtimer_now(), |
||||
_mask(xtimer_now()), _mask(0xffffffff-xtimer_now())); |
||||
|
||||
if (!timer_list_head) { |
||||
DEBUG("_timer_callback(): tick\n"); |
||||
/* there's no timer for this timer period,
|
||||
* so this was a timer overflow callback. |
||||
* |
||||
* In this case, we advance to the next timer period. |
||||
*/ |
||||
_next_period(); |
||||
|
||||
reference = 0; |
||||
|
||||
/* make sure the timer counter also arrived
|
||||
* in the next timer period */ |
||||
while (_xtimer_now() == _mask(0xFFFFFFFF)); |
||||
} |
||||
else { |
||||
/* we ended up in _timer_callback and there is
|
||||
* a timer waiting. |
||||
*/ |
||||
/* set our period reference to that timer's target time. */ |
||||
reference = _mask(timer_list_head->target); |
||||
} |
||||
|
||||
overflow: |
||||
/* check if next timers are close to expiring */ |
||||
while (timer_list_head && (_time_left(_mask(timer_list_head->target), reference) < XTIMER_ISR_BACKOFF)) { |
||||
/* make sure we don't fire too early */ |
||||
while (_time_left(_mask(timer_list_head->target), 0)); |
||||
|
||||
xtimer_t *next = timer_list_head->next; |
||||
|
||||
_shoot(timer_list_head); |
||||
|
||||
/* advance to next timer in list */ |
||||
timer_list_head = next; |
||||
} |
||||
|
||||
/* possibly executing all callbacks took enough
|
||||
* time to overflow. In that case we advance to |
||||
* next timer period and check again for expired |
||||
* timers.*/ |
||||
if (reference > _xtimer_now()) { |
||||
DEBUG("_timer_callback: overflowed while executing callbacks. %i\n", timer_list_head != 0); |
||||
_next_period(); |
||||
reference = 0; |
||||
goto overflow; |
||||
} |
||||
|
||||
if (timer_list_head) { |
||||
/* schedule callback on next timer target time */ |
||||
next_target = timer_list_head->target - XTIMER_OVERHEAD; |
||||
} |
||||
else { |
||||
/* there's no timer planned for this timer period */ |
||||
/* schedule callback on next overflow */ |
||||
next_target = _mask(0xFFFFFFFF); |
||||
uint32_t now = _xtimer_now(); |
||||
|
||||
/* check for overflow again */ |
||||
if (now < reference) { |
||||
_next_period(); |
||||
reference = 0; |
||||
goto overflow; |
||||
} |
||||
else { |
||||
/* check if the end of this period is very soon */ |
||||
if (_mask(now + XTIMER_ISR_BACKOFF) < now) { |
||||
/* spin until next period, then advance */ |
||||
while (_xtimer_now() > now); |
||||
_next_period(); |
||||
reference = 0; |
||||
goto overflow; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/* set low level timer */ |
||||
_lltimer_set(next_target); |
||||
} |
Loading…
Reference in new issue