mirror of
https://github.com/betaflight/betaflight.git
synced 2025-07-25 01:05:27 +03:00
Adding RP2350 SDK and target framework (#13988)
* Adding RP2350 SDK and target framework * Spacing * Removing board definitions
This commit is contained in:
parent
462cb05930
commit
2dd6f95aad
576 changed files with 435012 additions and 0 deletions
844
lib/main/pico-sdk/common/pico_time/include/pico/time.h
Normal file
844
lib/main/pico-sdk/common/pico_time/include/pico/time.h
Normal file
|
@ -0,0 +1,844 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PICO_TIME_H
|
||||
#define _PICO_TIME_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file time.h
|
||||
* \defgroup pico_time pico_time
|
||||
*
|
||||
* \brief API for accurate timestamps, sleeping, and time based callbacks
|
||||
*
|
||||
* \note The functions defined here provide a much more powerful and user friendly wrapping around the
|
||||
* low level hardware timer functionality. For these functions (and any other SDK functionality
|
||||
* e.g. timeouts, that relies on them) to work correctly, the hardware timer should not be modified. i.e. it is expected
|
||||
* to be monotonically increasing once per microsecond. Fortunately there is no need to modify the hardware
|
||||
* timer as any functionality you can think of that isn't already covered here can easily be modelled
|
||||
* by adding or subtracting a constant value from the unmodified hardware timer.
|
||||
*
|
||||
* \sa \ref hardware_timer
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PICO_TIME, Enable/disable assertions in the pico_time module, type=bool, default=0, group=pico_time
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_PICO_TIME
|
||||
#ifdef PARAM_ASSERTIONS_ENABLED_PICO_TIME // backwards compatibility with SDK < 2.0.0
|
||||
#define PARAM_ASSERTIONS_ENABLED_PICO_TIME PARAM_ASSERTIONS_ENABLED_TIME
|
||||
#else
|
||||
#define PARAM_ASSERTIONS_ENABLED_PICO_TIME 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_TIME_SLEEP_OVERHEAD_ADJUST_US, How many microseconds to wake up early (and then busy_wait) to account for timer overhead when sleeping in low power mode, type=int, default=6, group=pico_time
|
||||
#ifndef PICO_TIME_SLEEP_OVERHEAD_ADJUST_US
|
||||
#define PICO_TIME_SLEEP_OVERHEAD_ADJUST_US 6
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \defgroup timestamp timestamp
|
||||
* \ingroup pico_time
|
||||
* \brief Timestamp functions relating to points in time (including the current time).
|
||||
*
|
||||
* These are functions for dealing with timestamps (i.e. instants in time) represented by the type absolute_time_t. This opaque
|
||||
* type is provided to help prevent accidental mixing of timestamps and relative time values.
|
||||
*/
|
||||
|
||||
/*! \brief Return a representation of the current time.
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* Returns an opaque high fidelity representation of the current time sampled during the call.
|
||||
*
|
||||
* \return the absolute time (now) of the hardware timer
|
||||
*
|
||||
* \sa absolute_time_t
|
||||
* \sa sleep_until()
|
||||
* \sa time_us_64()
|
||||
*/
|
||||
static inline absolute_time_t get_absolute_time(void) {
|
||||
absolute_time_t t;
|
||||
update_us_since_boot(&t, time_us_64());
|
||||
return t;
|
||||
}
|
||||
|
||||
static inline uint32_t us_to_ms(uint64_t us) {
|
||||
if (us >> 32u) {
|
||||
return (uint32_t)(us / 1000u);
|
||||
} else {
|
||||
return ((uint32_t)us) / 1000u;
|
||||
}
|
||||
}
|
||||
|
||||
/*! fn to_ms_since_boot
|
||||
* \ingroup timestamp
|
||||
* \brief Convert a timestamp into a number of milliseconds since boot.
|
||||
* \param t an absolute_time_t value to convert
|
||||
* \return the number of milliseconds since boot represented by t
|
||||
* \sa to_us_since_boot()
|
||||
*/
|
||||
static inline uint32_t to_ms_since_boot(absolute_time_t t) {
|
||||
uint64_t us = to_us_since_boot(t);
|
||||
return us_to_ms(us);
|
||||
}
|
||||
|
||||
/*! \brief Return a timestamp value obtained by adding a number of microseconds to another timestamp
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \param t the base timestamp
|
||||
* \param us the number of microseconds to add
|
||||
* \return the timestamp representing the resulting time
|
||||
*/
|
||||
static inline absolute_time_t delayed_by_us(const absolute_time_t t, uint64_t us) {
|
||||
absolute_time_t t2;
|
||||
uint64_t base = to_us_since_boot(t);
|
||||
uint64_t delayed = base + us;
|
||||
if ((int64_t)delayed < 0) {
|
||||
// absolute_time_t (to allow for signed time deltas) is never greater than INT64_MAX which == at_the_end_of_time
|
||||
delayed = INT64_MAX;
|
||||
}
|
||||
update_us_since_boot(&t2, delayed);
|
||||
return t2;
|
||||
}
|
||||
|
||||
/*! \brief Return a timestamp value obtained by adding a number of milliseconds to another timestamp
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \param t the base timestamp
|
||||
* \param ms the number of milliseconds to add
|
||||
* \return the timestamp representing the resulting time
|
||||
*/
|
||||
static inline absolute_time_t delayed_by_ms(const absolute_time_t t, uint32_t ms) {
|
||||
absolute_time_t t2;
|
||||
uint64_t base = to_us_since_boot(t);
|
||||
uint64_t delayed = base + ms * 1000ull;
|
||||
if ((int64_t)delayed < 0) {
|
||||
// absolute_time_t (to allow for signed time deltas) is never greater than INT64_MAX which == at_the_end_of_time
|
||||
delayed = INT64_MAX;
|
||||
}
|
||||
update_us_since_boot(&t2, delayed);
|
||||
return t2;
|
||||
}
|
||||
|
||||
/*! \brief Convenience method to get the timestamp a number of microseconds from the current time
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \param us the number of microseconds to add to the current timestamp
|
||||
* \return the future timestamp
|
||||
*/
|
||||
static inline absolute_time_t make_timeout_time_us(uint64_t us) {
|
||||
return delayed_by_us(get_absolute_time(), us);
|
||||
}
|
||||
|
||||
/*! \brief Convenience method to get the timestamp a number of milliseconds from the current time
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \param ms the number of milliseconds to add to the current timestamp
|
||||
* \return the future timestamp
|
||||
*/
|
||||
static inline absolute_time_t make_timeout_time_ms(uint32_t ms) {
|
||||
return delayed_by_ms(get_absolute_time(), ms);
|
||||
}
|
||||
|
||||
/*! \brief Return the difference in microseconds between two timestamps
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \note be careful when diffing against large timestamps (e.g. \ref at_the_end_of_time)
|
||||
* as the signed integer may overflow.
|
||||
*
|
||||
* \param from the first timestamp
|
||||
* \param to the second timestamp
|
||||
* \return the number of microseconds between the two timestamps (positive if `to` is after `from` except
|
||||
* in case of overflow)
|
||||
*/
|
||||
static inline int64_t absolute_time_diff_us(absolute_time_t from, absolute_time_t to) {
|
||||
return (int64_t)(to_us_since_boot(to) - to_us_since_boot(from));
|
||||
}
|
||||
|
||||
/*! \brief Return the earlier of two timestamps
|
||||
* \ingroup timestamp
|
||||
*
|
||||
* \param a the first timestamp
|
||||
* \param b the second timestamp
|
||||
* \return the earlier of the two timestamps
|
||||
*/
|
||||
static inline absolute_time_t absolute_time_min(absolute_time_t a, absolute_time_t b) {
|
||||
return to_us_since_boot(a) < to_us_since_boot(b) ? a : b;
|
||||
}
|
||||
|
||||
/*! \brief The timestamp representing the end of time; this is actually not the maximum possible
|
||||
* timestamp, but is set to 0x7fffffff_ffffffff microseconds to avoid sign overflows with time
|
||||
* arithmetic. This is almost 300,000 years, so should be sufficient.
|
||||
* \ingroup timestamp
|
||||
*/
|
||||
extern const absolute_time_t at_the_end_of_time;
|
||||
|
||||
/*! \brief Determine if the given timestamp is "at_the_end_of_time"
|
||||
* \ingroup timestamp
|
||||
* \param t the timestamp
|
||||
* \return true if the timestamp is at_the_end_of_time
|
||||
* \sa at_the_end_of_time
|
||||
*/
|
||||
static inline bool is_at_the_end_of_time(absolute_time_t t) {
|
||||
return to_us_since_boot(t) == to_us_since_boot(at_the_end_of_time);
|
||||
}
|
||||
|
||||
/*! \brief The timestamp representing a null timestamp
|
||||
* \ingroup timestamp
|
||||
*/
|
||||
extern const absolute_time_t nil_time;
|
||||
|
||||
/*! \brief Determine if the given timestamp is nil
|
||||
* \ingroup timestamp
|
||||
* \param t the timestamp
|
||||
* \return true if the timestamp is nil
|
||||
* \sa nil_time
|
||||
*/
|
||||
static inline bool is_nil_time(absolute_time_t t) {
|
||||
return !to_us_since_boot(t);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \defgroup sleep sleep
|
||||
* \ingroup pico_time
|
||||
* \brief Sleep functions for delaying execution in a lower power state.
|
||||
*
|
||||
* These functions allow the calling core to sleep. This is a lower powered sleep; waking and re-checking time on every processor
|
||||
* event (WFE)
|
||||
*
|
||||
* \note These functions should not be called from an IRQ handler.
|
||||
*
|
||||
* \note Lower powered sleep requires use of the \link alarm_pool_get_default default alarm pool\endlink which may
|
||||
* be disabled by the PICO_TIME_DEFAULT_ALARM_POOL_DISABLED #define or currently full in which case these functions
|
||||
* become busy waits instead.
|
||||
*
|
||||
* \note Whilst \a sleep_ functions are preferable to \a busy_wait functions from a power perspective, the \a busy_wait equivalent function
|
||||
* may return slightly sooner after the target is reached.
|
||||
*
|
||||
* \sa busy_wait_until() \sa busy_wait_us() \sa busy_wait_us_32()
|
||||
*/
|
||||
|
||||
/*! \brief Wait until after the given timestamp to return
|
||||
* \ingroup sleep
|
||||
*
|
||||
* \note This method attempts to perform a lower power (WFE) sleep
|
||||
*
|
||||
* \param target the time after which to return
|
||||
* \sa sleep_us()
|
||||
* \sa busy_wait_until()
|
||||
* */
|
||||
void sleep_until(absolute_time_t target);
|
||||
|
||||
/*! \brief Wait for the given number of microseconds before returning
|
||||
* \ingroup sleep
|
||||
*
|
||||
* \note This method attempts to perform a lower power (WFE) sleep
|
||||
*
|
||||
* \param us the number of microseconds to sleep
|
||||
* \sa busy_wait_us()
|
||||
*/
|
||||
void sleep_us(uint64_t us);
|
||||
|
||||
/*! \brief Wait for the given number of milliseconds before returning
|
||||
* \ingroup sleep
|
||||
*
|
||||
* \note This method attempts to perform a lower power sleep (using WFE) as much as possible.
|
||||
*
|
||||
* \param ms the number of milliseconds to sleep
|
||||
*/
|
||||
void sleep_ms(uint32_t ms);
|
||||
|
||||
/*! \brief Helper method for blocking on a timeout
|
||||
* \ingroup sleep
|
||||
*
|
||||
* This method will return in response to an event (as per __wfe) or
|
||||
* when the target time is reached, or at any point before.
|
||||
*
|
||||
* This method can be used to implement a lower power polling loop waiting on
|
||||
* some condition signalled by an event (__sev()).
|
||||
*
|
||||
* This is called \a best_effort because under certain circumstances (notably the default timer pool
|
||||
* being disabled or full) the best effort is simply to return immediately without a __wfe, thus turning the calling
|
||||
* code into a busy wait.
|
||||
*
|
||||
* Example usage:
|
||||
* ```c
|
||||
* bool my_function_with_timeout_us(uint64_t timeout_us) {
|
||||
* absolute_time_t timeout_time = make_timeout_time_us(timeout_us);
|
||||
* do {
|
||||
* // each time round the loop, we check to see if the condition
|
||||
* // we are waiting on has happened
|
||||
* if (my_check_done()) {
|
||||
* // do something
|
||||
* return true;
|
||||
* }
|
||||
* // will try to sleep until timeout or the next processor event
|
||||
* } while (!best_effort_wfe_or_timeout(timeout_time));
|
||||
* return false; // timed out
|
||||
* }
|
||||
* ```
|
||||
* NOTE: This method should always be used in a loop associated with checking another "event" variable, since
|
||||
* processor events are a shared resource and can happen for a large number of reasons.
|
||||
*
|
||||
* @param timeout_timestamp the timeout time
|
||||
* @return true if the target time is reached, false otherwise
|
||||
*/
|
||||
bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp);
|
||||
|
||||
/*!
|
||||
* \defgroup alarm alarm
|
||||
* \ingroup pico_time
|
||||
* \brief Alarm functions for scheduling future execution
|
||||
*
|
||||
* Alarms are added to alarm pools, which may hold a certain fixed number of active alarms. Each alarm pool
|
||||
* utilizes one of four underlying timer_alarms, thus you may have up to four alarm pools. An alarm pool
|
||||
* calls (except when the callback would happen before or during being set) the callback on the core from which
|
||||
* the alarm pool was created. Callbacks are called from the timer_alarm IRQ handler, so care must
|
||||
* be taken in their implementation.
|
||||
*
|
||||
* A default pool is created the core specified by PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
|
||||
* on core 0, and may be used by the method variants that take no alarm pool parameter.
|
||||
*
|
||||
* \sa struct alarm_pool
|
||||
* \sa hardware_timer
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PICO_TIME_DEFAULT_ALARM_POOL_DISABLED, Disable the default alarm pool, type=bool, default=0, advanced=true, group=pico_time
|
||||
#ifndef PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
/*!
|
||||
* \brief If 1 then the default alarm pool is disabled (so no timer_alarm is claimed for the pool)
|
||||
*
|
||||
* \note Setting to 1 may cause some code not to compile as default timer pool related methods are removed
|
||||
*
|
||||
* \note When the default alarm pool is disabled, \a sleep_ methods and timeouts are no longer lower powered
|
||||
* (they become \a busy_wait_)
|
||||
*
|
||||
* \ingroup alarm
|
||||
* \sa #PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
|
||||
* \sa alarm_pool_get_default()
|
||||
*/
|
||||
#define PICO_TIME_DEFAULT_ALARM_POOL_DISABLED 0
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM, Select which HW alarm is used for the default alarm pool, min=0, max=3, default=3, advanced=true, group=pico_time
|
||||
#ifndef PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
|
||||
/*!
|
||||
* \brief Selects which timer_alarm is used for the default alarm pool
|
||||
* \ingroup alarm
|
||||
* \sa alarm_pool_get_default()
|
||||
*/
|
||||
#define PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM 3
|
||||
#endif
|
||||
|
||||
// PICO_CONFIG: PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS, Selects the maximum number of concurrent timers in the default alarm pool, min=0, max=255, default=16, advanced=true, group=pico_time
|
||||
#ifndef PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS
|
||||
/*!
|
||||
* \brief Selects the maximum number of concurrent timers in the default alarm pool
|
||||
* \ingroup alarm
|
||||
*
|
||||
* \note For implementation reasons this is limited to PICO_PHEAP_MAX_ENTRIES which defaults to 255
|
||||
* \sa #PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
|
||||
* \sa alarm_pool_get_default()
|
||||
*/
|
||||
#define PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS 16
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief The identifier for an alarm
|
||||
*
|
||||
* \note this identifier is signed because <0 is used as an error condition when creating alarms
|
||||
*
|
||||
* \note alarm ids may be reused, however for convenience the implementation makes an attempt to defer
|
||||
* reusing as long as possible. You should certainly expect it to be hundreds of ids before one is
|
||||
* reused, although in most cases it is more. Nonetheless care must still be taken when cancelling
|
||||
* alarms or other functionality based on alarms when the alarm may have expired, as eventually
|
||||
* the alarm id may be reused for another alarm.
|
||||
*
|
||||
* \see pico_error_codes
|
||||
* \ingroup alarm
|
||||
*/
|
||||
typedef int32_t alarm_id_t; // note this is signed because we use <0 as a meaningful error value
|
||||
|
||||
/**
|
||||
* \brief User alarm callback
|
||||
* \ingroup alarm
|
||||
* \param id the alarm_id as returned when the alarm was added
|
||||
* \param user_data the user data passed when the alarm was added
|
||||
* \return <0 to reschedule the same alarm this many us from the time the alarm was previously scheduled to fire
|
||||
* \return >0 to reschedule the same alarm this many us from the time this method returns
|
||||
* \return 0 to not reschedule the alarm
|
||||
*/
|
||||
typedef int64_t (*alarm_callback_t)(alarm_id_t id, void *user_data);
|
||||
|
||||
typedef struct alarm_pool alarm_pool_t;
|
||||
typedef void alarm_pool_timer_t;
|
||||
|
||||
/**
|
||||
* \brief Create the default alarm pool (if not already created or disabled)
|
||||
* \ingroup alarm
|
||||
*/
|
||||
void alarm_pool_init_default(void);
|
||||
void runtime_init_default_alarm_pool(void);
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
/*!
|
||||
* \brief The default alarm pool used when alarms are added without specifying an alarm pool,
|
||||
* and also used by the SDK to support lower power sleeps and timeouts.
|
||||
*
|
||||
* \ingroup alarm
|
||||
* \sa #PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM
|
||||
*/
|
||||
alarm_pool_t *alarm_pool_get_default(void);
|
||||
#endif
|
||||
|
||||
alarm_pool_t *alarm_pool_create_on_timer(alarm_pool_timer_t *timer, uint timer_alarm_num, uint max_timers);
|
||||
|
||||
alarm_pool_timer_t *alarm_pool_timer_for_timer_num(uint timer_num);
|
||||
|
||||
alarm_pool_timer_t *alarm_pool_get_default_timer(void);
|
||||
|
||||
/**
|
||||
* \brief Create an alarm pool
|
||||
*
|
||||
* The alarm pool will call callbacks from an alarm IRQ Handler on the core of this function is called from.
|
||||
*
|
||||
* In many situations there is never any need for anything other than the default alarm pool, however you
|
||||
* might want to create another if you want alarm callbacks on core 1 or require alarm pools of
|
||||
* different priority (IRQ priority based preemption of callbacks)
|
||||
*
|
||||
* \note This method will hard assert if the timer_alarm is already claimed.
|
||||
*
|
||||
* \ingroup alarm
|
||||
* \param timer_alarm_num the timer_alarm to use to back this pool
|
||||
* \param max_timers the maximum number of timers
|
||||
* \note For implementation reasons this is limited to PICO_PHEAP_MAX_ENTRIES which defaults to 255
|
||||
* \sa alarm_pool_get_default()
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
static inline alarm_pool_t *alarm_pool_create(uint timer_alarm_num, uint max_timers) {
|
||||
return alarm_pool_create_on_timer(alarm_pool_get_default_timer(), timer_alarm_num, max_timers);
|
||||
}
|
||||
|
||||
alarm_pool_t *alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_timer_t *timer, uint max_timers);
|
||||
|
||||
/**
|
||||
* \brief Create an alarm pool, claiming an used timer_alarm to back it.
|
||||
*
|
||||
* The alarm pool will call callbacks from an alarm IRQ Handler on the core of this function is called from.
|
||||
*
|
||||
* In many situations there is never any need for anything other than the default alarm pool, however you
|
||||
* might want to create another if you want alarm callbacks on core 1 or require alarm pools of
|
||||
* different priority (IRQ priority based preemption of callbacks)
|
||||
*
|
||||
* \note This method will hard assert if the there is no free hardware to claim.
|
||||
*
|
||||
* \ingroup alarm
|
||||
* \param max_timers the maximum number of timers
|
||||
* \note For implementation reasons this is limited to PICO_PHEAP_MAX_ENTRIES which defaults to 255
|
||||
* \sa alarm_pool_get_default()
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
static inline alarm_pool_t *alarm_pool_create_with_unused_hardware_alarm(uint max_timers) {
|
||||
return alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_get_default_timer(), max_timers);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the timer alarm used by an alarm pool
|
||||
* \ingroup alarm
|
||||
* \param pool the pool
|
||||
* \return the timer_alarm used by the pool
|
||||
*/
|
||||
uint alarm_pool_timer_alarm_num(alarm_pool_t *pool);
|
||||
// backwards compatibility
|
||||
static inline uint alarm_pool_hardware_alarm_num(alarm_pool_t *pool) {
|
||||
return alarm_pool_timer_alarm_num(pool);
|
||||
}
|
||||
/**
|
||||
* \brief Return the core number the alarm pool was initialized on (and hence callbacks are called on)
|
||||
* \ingroup alarm
|
||||
* \param pool the pool
|
||||
* \return the core used by the pool
|
||||
*/
|
||||
uint alarm_pool_core_num(alarm_pool_t *pool);
|
||||
|
||||
/**
|
||||
* \brief Destroy the alarm pool, cancelling all alarms and freeing up the underlying timer_alarm
|
||||
* \ingroup alarm
|
||||
* \param pool the pool
|
||||
*/
|
||||
void alarm_pool_destroy(alarm_pool_t *pool);
|
||||
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called at a specific time
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the callback (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param time the timestamp when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls before or during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id for an active (at the time of return) alarm
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback, void *user_data, bool fire_if_past);
|
||||
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called at or after a specific time
|
||||
* \ingroup alarm
|
||||
*
|
||||
* The callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. Unlike \ref alarm_pool_add_alarm_at, this method
|
||||
* guarantees to call the callback from that core even if the time is during this method call or in the past.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the callback (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param time the timestamp when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @return >0 the alarm id for an active (at the time of return) alarm
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
|
||||
void *user_data);
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called after a delay specified in microseconds
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the callback (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param us the delay (from now) in microseconds when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
static inline alarm_id_t alarm_pool_add_alarm_in_us(alarm_pool_t *pool, uint64_t us, alarm_callback_t callback, void *user_data, bool fire_if_past) {
|
||||
return alarm_pool_add_alarm_at(pool, delayed_by_us(get_absolute_time(), us), callback, user_data, fire_if_past);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called after a delay specified in milliseconds
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the callback (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param ms the delay (from now) in milliseconds when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls before or during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
static inline alarm_id_t alarm_pool_add_alarm_in_ms(alarm_pool_t *pool, uint32_t ms, alarm_callback_t callback, void *user_data, bool fire_if_past) {
|
||||
return alarm_pool_add_alarm_at(pool, delayed_by_ms(get_absolute_time(), ms), callback, user_data, fire_if_past);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Return the time remaining before the next trigger of an alarm
|
||||
* \ingroup alarm
|
||||
*
|
||||
* @param pool the alarm_pool containing the alarm
|
||||
* @param alarm_id the alarm
|
||||
*
|
||||
* @return >=0 the number of microseconds before the next trigger
|
||||
* @return <0 if either the given alarm is not in progress or it has passed
|
||||
*/
|
||||
int64_t alarm_pool_remaining_alarm_time_us(alarm_pool_t *pool, alarm_id_t alarm_id);
|
||||
|
||||
/*!
|
||||
* \brief Return the time remaining before the next trigger of an alarm
|
||||
* \ingroup alarm
|
||||
*
|
||||
* @param pool the alarm_pool containing the alarm
|
||||
* @param alarm_id the alarm
|
||||
*
|
||||
* @return >=0 the number of milliseconds before the next trigger (INT32_MAX if the number of ms is higher than can be represented0
|
||||
* @return <0 if either the given alarm is not in progress or it has passed
|
||||
*/
|
||||
int32_t alarm_pool_remaining_alarm_time_ms(alarm_pool_t *pool, alarm_id_t alarm_id);
|
||||
|
||||
/*!
|
||||
* \brief Cancel an alarm
|
||||
* \ingroup alarm
|
||||
* \param pool the alarm_pool containing the alarm
|
||||
* \param alarm_id the alarm
|
||||
* \return true if the alarm was cancelled, false if it didn't exist
|
||||
* \sa alarm_id_t for a note on reuse of IDs
|
||||
*/
|
||||
bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id);
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called at a specific time
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core of the default alarm pool (generally core 0). If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param time the timestamp when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls before or during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
static inline alarm_id_t add_alarm_at(absolute_time_t time, alarm_callback_t callback, void *user_data, bool fire_if_past) {
|
||||
return alarm_pool_add_alarm_at(alarm_pool_get_default(), time, callback, user_data, fire_if_past);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called after a delay specified in microseconds
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core of the default alarm pool (generally core 0). If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param us the delay (from now) in microseconds when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
static inline alarm_id_t add_alarm_in_us(uint64_t us, alarm_callback_t callback, void *user_data, bool fire_if_past) {
|
||||
return alarm_pool_add_alarm_in_us(alarm_pool_get_default(), us, callback, user_data, fire_if_past);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Add an alarm callback to be called after a delay specified in milliseconds
|
||||
* \ingroup alarm
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core of the default alarm pool (generally core 0). If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param ms the delay (from now) in milliseconds when (after which) the callback should fire
|
||||
* @param callback the callback function
|
||||
* @param user_data user data to pass to the callback function
|
||||
* @param fire_if_past if true, and the alarm time falls during this call before the alarm can be set,
|
||||
* then the callback should be called during (by) this function instead
|
||||
* @return >0 the alarm id
|
||||
* @return 0 if the alarm time passed before or during the call and fire_if_past was false
|
||||
* @return <0 if there were no alarm slots available, or other error occurred
|
||||
*/
|
||||
static inline alarm_id_t add_alarm_in_ms(uint32_t ms, alarm_callback_t callback, void *user_data, bool fire_if_past) {
|
||||
return alarm_pool_add_alarm_in_ms(alarm_pool_get_default(), ms, callback, user_data, fire_if_past);
|
||||
}
|
||||
/*!
|
||||
* \brief Cancel an alarm from the default alarm pool
|
||||
* \ingroup alarm
|
||||
* \param alarm_id the alarm
|
||||
* \return true if the alarm was cancelled, false if it didn't exist
|
||||
* \sa alarm_id_t for a note on reuse of IDs
|
||||
*/
|
||||
static inline bool cancel_alarm(alarm_id_t alarm_id) {
|
||||
return alarm_pool_cancel_alarm(alarm_pool_get_default(), alarm_id);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Return the time remaining before the next trigger of an alarm
|
||||
* \ingroup alarm
|
||||
*
|
||||
* @param pool the alarm_pool containing the alarm
|
||||
* @param alarm_id the alarm
|
||||
*
|
||||
* @return >=0 the number of microseconds before the next trigger
|
||||
* @return <0 if either the given alarm is not in progress or it has passed
|
||||
*/
|
||||
int64_t remaining_alarm_time_us(alarm_id_t alarm_id);
|
||||
|
||||
/*!
|
||||
* \brief Return the time remaining before the next trigger of an alarm
|
||||
* \ingroup alarm
|
||||
*
|
||||
* @param alarm_id the alarm
|
||||
*
|
||||
* @return >=0 the number of milliseconds before the next trigger (INT32_MAX if the number of ms is higher than can be represented0
|
||||
* @return <0 if either the given alarm is not in progress or it has passed
|
||||
*/
|
||||
int32_t remaining_alarm_time_ms(alarm_id_t alarm_id);
|
||||
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \defgroup repeating_timer repeating_timer
|
||||
* \ingroup pico_time
|
||||
* \brief Repeating Timer functions for simple scheduling of repeated execution
|
||||
*
|
||||
* \note The regular \a alarm_ functionality can be used to make repeating alarms (by return non zero from the callback),
|
||||
* however these methods abstract that further (at the cost of a user structure to store the repeat delay in (which
|
||||
* the alarm framework does not have space for).
|
||||
*/
|
||||
|
||||
typedef struct repeating_timer repeating_timer_t;
|
||||
|
||||
/**
|
||||
* \brief Callback for a repeating timer
|
||||
* \ingroup repeating_timer
|
||||
* \param rt repeating time structure containing information about the repeating time. user_data is of primary important to the user
|
||||
* \return true to continue repeating, false to stop.
|
||||
*/
|
||||
typedef bool (*repeating_timer_callback_t)(repeating_timer_t *rt);
|
||||
|
||||
/**
|
||||
* \brief Information about a repeating timer
|
||||
* \ingroup repeating_timer
|
||||
* \return
|
||||
*/
|
||||
struct repeating_timer {
|
||||
int64_t delay_us;
|
||||
alarm_pool_t *pool;
|
||||
alarm_id_t alarm_id;
|
||||
repeating_timer_callback_t callback;
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Add a repeating timer that is called repeatedly at the specified interval in microseconds
|
||||
* \ingroup repeating_timer
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the repeating timer (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param delay_us the repeat delay in microseconds; if >0 then this is the delay between one callback ending and the next starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
|
||||
* @param callback the repeating timer callback function
|
||||
* @param user_data user data to pass to store in the repeating_timer structure for use by the callback.
|
||||
* @param out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must outlive the repeating timer, so be careful of using stack space
|
||||
* @return false if there were no alarm slots available to create the timer, true otherwise.
|
||||
*/
|
||||
bool alarm_pool_add_repeating_timer_us(alarm_pool_t *pool, int64_t delay_us, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out);
|
||||
|
||||
/*!
|
||||
* \brief Add a repeating timer that is called repeatedly at the specified interval in milliseconds
|
||||
* \ingroup repeating_timer
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core the alarm pool was created on. If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param pool the alarm pool to use for scheduling the repeating timer (this determines which timer_alarm is used, and which core calls the callback)
|
||||
* @param delay_ms the repeat delay in milliseconds; if >0 then this is the delay between one callback ending and the next starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1 microsecond
|
||||
* @param callback the repeating timer callback function
|
||||
* @param user_data user data to pass to store in the repeating_timer structure for use by the callback.
|
||||
* @param out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must outlive the repeating timer, so be careful of using stack space
|
||||
* @return false if there were no alarm slots available to create the timer, true otherwise.
|
||||
*/
|
||||
static inline bool alarm_pool_add_repeating_timer_ms(alarm_pool_t *pool, int32_t delay_ms, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out) {
|
||||
return alarm_pool_add_repeating_timer_us(pool, delay_ms * (int64_t)1000, callback, user_data, out);
|
||||
}
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
/*!
|
||||
* \brief Add a repeating timer that is called repeatedly at the specified interval in microseconds
|
||||
* \ingroup repeating_timer
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core of the default alarm pool (generally core 0). If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param delay_us the repeat delay in microseconds; if >0 then this is the delay between one callback ending and the next starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1
|
||||
* @param callback the repeating timer callback function
|
||||
* @param user_data user data to pass to store in the repeating_timer structure for use by the callback.
|
||||
* @param out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must outlive the repeating timer, so be careful of using stack space
|
||||
* @return false if there were no alarm slots available to create the timer, true otherwise.
|
||||
*/
|
||||
static inline bool add_repeating_timer_us(int64_t delay_us, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out) {
|
||||
return alarm_pool_add_repeating_timer_us(alarm_pool_get_default(), delay_us, callback, user_data, out);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Add a repeating timer that is called repeatedly at the specified interval in milliseconds
|
||||
* \ingroup repeating_timer
|
||||
*
|
||||
* Generally the callback is called as soon as possible after the time specified from an IRQ handler
|
||||
* on the core of the default alarm pool (generally core 0). If the callback is in the past or happens before
|
||||
* the alarm setup could be completed, then this method will optionally call the callback itself
|
||||
* and then return a return code to indicate that the target time has passed.
|
||||
*
|
||||
* \note It is safe to call this method from an IRQ handler (including alarm callbacks), and from either core.
|
||||
*
|
||||
* @param delay_ms the repeat delay in milliseconds; if >0 then this is the delay between one callback ending and the next starting; if <0 then this is the negative of the time between the starts of the callbacks. The value of 0 is treated as 1 microsecond
|
||||
* @param callback the repeating timer callback function
|
||||
* @param user_data user data to pass to store in the repeating_timer structure for use by the callback.
|
||||
* @param out the pointer to the user owned structure to store the repeating timer info in. BEWARE this storage location must outlive the repeating timer, so be careful of using stack space
|
||||
* @return false if there were no alarm slots available to create the timer, true otherwise.
|
||||
*/
|
||||
static inline bool add_repeating_timer_ms(int32_t delay_ms, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out) {
|
||||
return alarm_pool_add_repeating_timer_us(alarm_pool_get_default(), delay_ms * (int64_t)1000, callback, user_data, out);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Cancel a repeating timer
|
||||
* \ingroup repeating_timer
|
||||
* \param timer the repeating timer to cancel
|
||||
* \return true if the repeating timer was cancelled, false if it didn't exist
|
||||
* \sa alarm_id_t for a note on reuse of IDs
|
||||
*/
|
||||
bool cancel_repeating_timer(repeating_timer_t *timer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _PICO_TIMEOUT_HELPER_H
|
||||
#define _PICO_TIMEOUT_HELPER_H
|
||||
|
||||
#include "pico/time.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct timeout_state {
|
||||
absolute_time_t next_timeout;
|
||||
uint64_t param;
|
||||
} timeout_state_t;
|
||||
|
||||
typedef bool (*check_timeout_fn)(timeout_state_t *ts, bool reset);
|
||||
|
||||
check_timeout_fn init_single_timeout_until(timeout_state_t *ts, absolute_time_t target);
|
||||
check_timeout_fn init_per_iteration_timeout_us(timeout_state_t *ts, uint64_t per_iteration_timeout_us);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
570
lib/main/pico-sdk/common/pico_time/time.c
Normal file
570
lib/main/pico-sdk/common/pico_time/time.c
Normal file
|
@ -0,0 +1,570 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "pico.h"
|
||||
#include "pico/time.h"
|
||||
#include "pico/sync.h"
|
||||
#include "pico/runtime_init.h"
|
||||
|
||||
const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(nil_time, 0);
|
||||
const absolute_time_t ABSOLUTE_TIME_INITIALIZED_VAR(at_the_end_of_time, INT64_MAX);
|
||||
|
||||
typedef struct alarm_pool_entry {
|
||||
// next entry link or -1
|
||||
int16_t next;
|
||||
// low 15 bits are a sequence number used in the low word of the alarm_id so that
|
||||
// the alarm_id for this entry only repeats every 32767 adds (note this value is never zero)
|
||||
// the top bit is a cancellation flag.
|
||||
volatile uint16_t sequence;
|
||||
int64_t target;
|
||||
alarm_callback_t callback;
|
||||
void *user_data;
|
||||
} alarm_pool_entry_t;
|
||||
|
||||
struct alarm_pool {
|
||||
uint8_t timer_alarm_num;
|
||||
uint8_t core_num;
|
||||
// this is protected by the lock (threads allocate from it, and the IRQ handler adds back to it)
|
||||
int16_t free_head;
|
||||
// this is protected by the lock (threads add to it, the IRQ handler removes from it)
|
||||
volatile int16_t new_head;
|
||||
volatile bool has_pending_cancellations;
|
||||
|
||||
// this is owned by the IRQ handler so doesn't need additional locking
|
||||
int16_t ordered_head;
|
||||
uint16_t num_entries;
|
||||
alarm_pool_timer_t *timer;
|
||||
spin_lock_t *lock;
|
||||
alarm_pool_entry_t *entries;
|
||||
};
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
// To avoid bringing in calloc, we statically allocate the arrays and the heap
|
||||
static alarm_pool_entry_t default_alarm_pool_entries[PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS];
|
||||
|
||||
static alarm_pool_t default_alarm_pool = {
|
||||
.entries = default_alarm_pool_entries,
|
||||
};
|
||||
|
||||
static inline bool default_alarm_pool_initialized(void) {
|
||||
return default_alarm_pool.lock != NULL;
|
||||
}
|
||||
|
||||
static lock_core_t sleep_notifier;
|
||||
#endif
|
||||
|
||||
#include "pico/time_adapter.h"
|
||||
|
||||
static alarm_pool_t *pools[TA_NUM_TIMERS][TA_NUM_TIMER_ALARMS];
|
||||
|
||||
static void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers);
|
||||
|
||||
static inline int16_t alarm_index(alarm_id_t id) {
|
||||
return (int16_t)(id >> 16);
|
||||
}
|
||||
|
||||
static inline uint16_t alarm_sequence(alarm_id_t id) {
|
||||
return (uint16_t)id;
|
||||
}
|
||||
|
||||
static alarm_id_t make_alarm_id(int index, uint16_t counter) {
|
||||
return index << 16 | counter;
|
||||
}
|
||||
|
||||
#if !PICO_RUNTIME_NO_INIT_DEFAULT_ALARM_POOL
|
||||
void __weak runtime_init_default_alarm_pool(void) {
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
// allow multiple calls for ease of use from host tests
|
||||
if (!default_alarm_pool_initialized()) {
|
||||
alarm_pool_timer_t *timer = alarm_pool_get_default_timer();
|
||||
ta_hardware_alarm_claim(timer, PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM);
|
||||
alarm_pool_post_alloc_init(&default_alarm_pool,
|
||||
timer,
|
||||
PICO_TIME_DEFAULT_ALARM_POOL_HARDWARE_ALARM_NUM,
|
||||
PICO_TIME_DEFAULT_ALARM_POOL_MAX_TIMERS);
|
||||
}
|
||||
lock_init(&sleep_notifier, PICO_SPINLOCK_ID_TIMER);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
void alarm_pool_init_default(void) {
|
||||
runtime_init_default_alarm_pool();
|
||||
}
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
alarm_pool_t *alarm_pool_get_default(void) {
|
||||
assert(default_alarm_pool_initialized());
|
||||
return &default_alarm_pool;
|
||||
}
|
||||
|
||||
#if defined(PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL) && !PICO_RUNTIME_SKIP_INIT_DEFAULT_ALARM_POOL
|
||||
PICO_RUNTIME_INIT_FUNC_RUNTIME(runtime_init_default_alarm_pool, PICO_RUNTIME_INIT_DEFAULT_ALARM_POOL);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// note the timer is created with IRQs on this core
|
||||
alarm_pool_t *alarm_pool_create_on_timer(alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) {
|
||||
alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
|
||||
if (pool) {
|
||||
pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t));
|
||||
ta_hardware_alarm_claim(timer, hardware_alarm_num);
|
||||
alarm_pool_post_alloc_init(pool, timer, hardware_alarm_num, max_timers);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
alarm_pool_t *alarm_pool_create_on_timer_with_unused_hardware_alarm(alarm_pool_timer_t *timer, uint max_timers) {
|
||||
alarm_pool_t *pool = (alarm_pool_t *) malloc(sizeof(alarm_pool_t));
|
||||
if (pool) {
|
||||
pool->entries = (alarm_pool_entry_t *) calloc(max_timers, sizeof(alarm_pool_entry_t));
|
||||
alarm_pool_post_alloc_init(pool, timer, (uint) ta_hardware_alarm_claim_unused(timer, true), max_timers);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
static void alarm_pool_irq_handler(void);
|
||||
|
||||
// marker which we can use in place of handler function to indicate we are a repeating timer
|
||||
|
||||
#define repeating_timer_marker ((alarm_callback_t)alarm_pool_irq_handler)
|
||||
#include "hardware/gpio.h"
|
||||
static void alarm_pool_irq_handler(void) {
|
||||
// This IRQ handler does the main work, as it always (assuming the IRQ hasn't been enabled on both cores
|
||||
// which is unsupported) run on the alarm pool's core, and can't be preempted by itself, meaning
|
||||
// that it doesn't need locks except to protect against linked list, or other state access.
|
||||
// This simplifies the code considerably, and makes it much faster in general, even though we are forced to take
|
||||
// two IRQs per alarm.
|
||||
uint timer_alarm_num;
|
||||
alarm_pool_timer_t *timer = ta_from_current_irq(&timer_alarm_num);
|
||||
uint timer_num = ta_timer_num(timer);
|
||||
alarm_pool_t *pool = pools[timer_num][timer_alarm_num];
|
||||
assert(pool->timer_alarm_num == timer_alarm_num);
|
||||
int64_t earliest_target;
|
||||
// 1. clear force bits if we were forced (do this outside the loop, as forcing is hopefully rare)
|
||||
ta_clear_force_irq(timer, timer_alarm_num);
|
||||
do {
|
||||
// 2. clear the IRQ if it was fired
|
||||
ta_clear_irq(timer, timer_alarm_num);
|
||||
// 3. we look at the earliest existing alarm first; the reasoning here is that we
|
||||
// don't want to delay an existing callback because a later one is added, and
|
||||
// if both are due now, then we have a race anyway (but we prefer to fire existing
|
||||
// timers before new ones anyway.
|
||||
int16_t earliest_index = pool->ordered_head;
|
||||
// by default, we loop if there was any event pending (we will mark it false
|
||||
// later if there is no work to do)
|
||||
if (earliest_index >= 0) {
|
||||
alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
|
||||
earliest_target = earliest_entry->target;
|
||||
if (((int64_t)ta_time_us_64(timer) - earliest_target) >= 0) {
|
||||
// time to call the callback now (or in the past)
|
||||
// note that an entry->target of < 0 means the entry has been canceled (not this is set
|
||||
// by this function, in response to the entry having been queued by the cancel_alarm API
|
||||
// meaning that we don't need to worry about tearing of the 64 bit value)
|
||||
int64_t delta;
|
||||
if (earliest_target >= 0) {
|
||||
// special case repeating timer without making another function call which adds overhead
|
||||
if (earliest_entry->callback == repeating_timer_marker) {
|
||||
repeating_timer_t *rpt = (repeating_timer_t *)earliest_entry->user_data;
|
||||
delta = rpt->callback(rpt) ? rpt->delay_us : 0;
|
||||
} else {
|
||||
alarm_id_t id = make_alarm_id(pool->ordered_head, earliest_entry->sequence);
|
||||
delta = earliest_entry->callback(id, earliest_entry->user_data);
|
||||
}
|
||||
} else {
|
||||
// negative target means cancel alarm
|
||||
delta = 0;
|
||||
}
|
||||
if (delta) {
|
||||
int64_t next_time;
|
||||
if (delta < 0) {
|
||||
// delta is (positive) delta from last fire time
|
||||
next_time = earliest_target - delta;
|
||||
} else {
|
||||
// delta is relative to now
|
||||
next_time = (int64_t) ta_time_us_64(timer) + delta;
|
||||
}
|
||||
earliest_entry->target = next_time;
|
||||
// need to re-add, unless we are the only entry or already at the front
|
||||
if (earliest_entry->next >= 0 && next_time - pool->entries[earliest_entry->next].target >= 0) {
|
||||
// unlink this item
|
||||
pool->ordered_head = earliest_entry->next;
|
||||
int16_t *prev = &pool->ordered_head;
|
||||
// find insertion point; note >= as if we add a new item for the same time as another, then it follows
|
||||
while (*prev >= 0 && (next_time - pool->entries[*prev].target) >= 0) {
|
||||
prev = &pool->entries[*prev].next;
|
||||
}
|
||||
earliest_entry->next = *prev;
|
||||
*prev = earliest_index;
|
||||
}
|
||||
} else {
|
||||
// need to remove the item
|
||||
pool->ordered_head = earliest_entry->next;
|
||||
// and add it back to the free list (under lock)
|
||||
uint32_t save = spin_lock_blocking(pool->lock);
|
||||
earliest_entry->next = pool->free_head;
|
||||
pool->free_head = earliest_index;
|
||||
spin_unlock(pool->lock, save);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we have any new alarms, add them to the ordered list
|
||||
if (pool->new_head >= 0) {
|
||||
uint32_t save = spin_lock_blocking(pool->lock);
|
||||
// must re-read new head under lock
|
||||
int16_t new_index = pool->new_head;
|
||||
// clear the list
|
||||
pool->new_head = -1;
|
||||
spin_unlock(pool->lock, save);
|
||||
// insert each of the new items
|
||||
while (new_index >= 0) {
|
||||
alarm_pool_entry_t *new_entry = &pool->entries[new_index];
|
||||
int64_t new_entry_time = new_entry->target;
|
||||
int16_t *prev = &pool->ordered_head;
|
||||
// find insertion point; note >= as if we add a new item for the same time as another, then it follows
|
||||
while (*prev >= 0 && (new_entry_time - pool->entries[*prev].target) >= 0) {
|
||||
prev = &pool->entries[*prev].next;
|
||||
}
|
||||
int16_t next = *prev;
|
||||
*prev = new_index;
|
||||
new_index = new_entry->next;
|
||||
new_entry->next = next;
|
||||
}
|
||||
}
|
||||
// if we have any canceled alarms, then mark them for removal by setting their due time to -1 (which will
|
||||
// cause them to be handled the next time round and removed)
|
||||
if (pool->has_pending_cancellations) {
|
||||
pool->has_pending_cancellations = false;
|
||||
__compiler_memory_barrier();
|
||||
int16_t *prev = &pool->ordered_head;
|
||||
// set target for canceled items to -1, and move to front of the list
|
||||
for(int16_t index = pool->ordered_head; index != -1; ) {
|
||||
alarm_pool_entry_t *entry = &pool->entries[index];
|
||||
int16_t next = entry->next;
|
||||
if ((int16_t)entry->sequence < 0) {
|
||||
// mark for deletion
|
||||
entry->target = -1;
|
||||
if (index != pool->ordered_head) {
|
||||
// move to start of queue
|
||||
*prev = entry->next;
|
||||
entry->next = pool->ordered_head;
|
||||
pool->ordered_head = index;
|
||||
}
|
||||
} else {
|
||||
prev = &entry->next;
|
||||
}
|
||||
index = next;
|
||||
}
|
||||
}
|
||||
earliest_index = pool->ordered_head;
|
||||
if (earliest_index < 0) break;
|
||||
// need to wait
|
||||
alarm_pool_entry_t *earliest_entry = &pool->entries[earliest_index];
|
||||
earliest_target = earliest_entry->target;
|
||||
// we are leaving a timeout every 2^32 microseconds anyway if there is no valid target, so we can choose any value.
|
||||
// best_effort_wfe_or_timeout now relies on it being the last value set, and arguably this is the
|
||||
// best value anyway, as it is the furthest away from the last fire.
|
||||
if (earliest_target != -1) { // cancelled alarm has target of -1
|
||||
ta_set_timeout(timer, timer_alarm_num, earliest_target);
|
||||
}
|
||||
// check we haven't now passed the target time; if not we don't want to loop again
|
||||
} while ((earliest_target - (int64_t)ta_time_us_64(timer)) <= 0);
|
||||
// We always want the timer IRQ to wake a WFE so that best_effort_wfe_or_timeout() will wake up. It will wake
|
||||
// a WFE on its own core by nature of having taken an IRQ, but we do an explicit SEV so it wakes the other core
|
||||
__sev();
|
||||
}
|
||||
|
||||
void alarm_pool_post_alloc_init(alarm_pool_t *pool, alarm_pool_timer_t *timer, uint hardware_alarm_num, uint max_timers) {
|
||||
pool->timer = timer;
|
||||
pool->lock = spin_lock_instance(next_striped_spin_lock_num());
|
||||
pool->timer_alarm_num = (uint8_t) hardware_alarm_num;
|
||||
invalid_params_if(PICO_TIME, max_timers > 65536);
|
||||
pool->num_entries = (uint16_t)max_timers;
|
||||
pool->core_num = (uint8_t) get_core_num();
|
||||
pool->new_head = pool->ordered_head = -1;
|
||||
pool->free_head = (int16_t)(max_timers - 1);
|
||||
for(uint i=0;i<max_timers;i++) {
|
||||
pool->entries[i].next = (int16_t)(i-1);
|
||||
}
|
||||
pools[ta_timer_num(timer)][hardware_alarm_num] = pool;
|
||||
|
||||
ta_enable_irq_handler(timer, hardware_alarm_num, alarm_pool_irq_handler);
|
||||
}
|
||||
|
||||
void alarm_pool_destroy(alarm_pool_t *pool) {
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
if (pool == &default_alarm_pool) {
|
||||
assert(false); // attempt to delete default alarm pool
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
ta_disable_irq_handler(pool->timer, pool->timer_alarm_num, alarm_pool_irq_handler);
|
||||
assert(pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] == pool);
|
||||
pools[ta_timer_num(pool->timer)][pool->timer_alarm_num] = NULL;
|
||||
free(pool->entries);
|
||||
free(pool);
|
||||
}
|
||||
|
||||
alarm_id_t alarm_pool_add_alarm_at(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
|
||||
void *user_data, bool fire_if_past) {
|
||||
if (!fire_if_past) {
|
||||
absolute_time_t t = get_absolute_time();
|
||||
if (absolute_time_diff_us(t, time) < 0) return 0;
|
||||
}
|
||||
return alarm_pool_add_alarm_at_force_in_context(pool, time, callback, user_data);
|
||||
}
|
||||
|
||||
alarm_id_t alarm_pool_add_alarm_at_force_in_context(alarm_pool_t *pool, absolute_time_t time, alarm_callback_t callback,
|
||||
void *user_data) {
|
||||
// ---- take a free pool entry
|
||||
uint32_t save = spin_lock_blocking(pool->lock);
|
||||
int16_t index = pool->free_head;
|
||||
alarm_pool_entry_t *entry = &pool->entries[index];
|
||||
if (index >= 0) {
|
||||
// remove from free list
|
||||
pool->free_head = entry->next;
|
||||
}
|
||||
spin_unlock(pool->lock, save);
|
||||
if (index < 0) return PICO_ERROR_GENERIC; // PICO_ERROR_INSUFFICIENT_RESOURCES - not using to preserve previous -1 return code
|
||||
|
||||
// ---- initialize the pool entry
|
||||
entry->callback = callback;
|
||||
entry->user_data = user_data;
|
||||
entry->target = (int64_t)to_us_since_boot(time);
|
||||
uint16_t next_sequence = (entry->sequence + 1) & 0x7fff;
|
||||
if (!next_sequence) next_sequence = 1; // zero is not allowed
|
||||
entry->sequence = next_sequence;
|
||||
alarm_id_t id = make_alarm_id(index, next_sequence);
|
||||
|
||||
// ---- and add it to the new list
|
||||
save = spin_lock_blocking(pool->lock);
|
||||
entry->next = pool->new_head;
|
||||
pool->new_head = index;
|
||||
spin_unlock(pool->lock, save);
|
||||
|
||||
// force the IRQ
|
||||
ta_force_irq(pool->timer, pool->timer_alarm_num);
|
||||
return id;
|
||||
}
|
||||
|
||||
bool alarm_pool_cancel_alarm(alarm_pool_t *pool, alarm_id_t alarm_id) {
|
||||
int16_t index = alarm_index(alarm_id);
|
||||
if (index >= pool->num_entries) return false;
|
||||
uint16_t sequence = alarm_sequence(alarm_id);
|
||||
bool canceled = false;
|
||||
alarm_pool_entry_t *entry = &pool->entries[index];
|
||||
uint32_t save = spin_lock_blocking(pool->lock);
|
||||
// note this will not be true if the entry is already canceled (as the entry->sequence
|
||||
// will have the top bit set)
|
||||
uint current_sequence = entry->sequence;
|
||||
if (sequence == current_sequence) {
|
||||
entry->sequence = (uint16_t)(current_sequence | 0x8000);
|
||||
__compiler_memory_barrier();
|
||||
pool->has_pending_cancellations = true;
|
||||
canceled = true;
|
||||
}
|
||||
spin_unlock(pool->lock, save);
|
||||
// force the IRQ if we need to clean up an alarm id
|
||||
if (canceled) ta_force_irq(pool->timer, pool->timer_alarm_num);
|
||||
return canceled;
|
||||
}
|
||||
|
||||
uint alarm_pool_timer_alarm_num(alarm_pool_t *pool) {
|
||||
return pool->timer_alarm_num;
|
||||
}
|
||||
|
||||
uint alarm_pool_core_num(alarm_pool_t *pool) {
|
||||
return pool->core_num;
|
||||
}
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
static int64_t sleep_until_callback(__unused alarm_id_t id, __unused void *user_data) {
|
||||
uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
|
||||
lock_internal_spin_unlock_with_notify(&sleep_notifier, save);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void sleep_until(absolute_time_t t) {
|
||||
#if PICO_ON_DEVICE && !defined(NDEBUG)
|
||||
if (__get_current_exception()) {
|
||||
panic("Attempted to sleep inside of an exception handler; use busy_wait if you must");
|
||||
}
|
||||
#endif
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
uint64_t t_us = to_us_since_boot(t);
|
||||
uint64_t t_before_us = t_us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US;
|
||||
// needs to work in the first PICO_TIME_SLEEP_OVERHEAD_ADJUST_US of boot
|
||||
if (t_before_us > t_us) t_before_us = 0;
|
||||
absolute_time_t t_before;
|
||||
update_us_since_boot(&t_before, t_before_us);
|
||||
if (absolute_time_diff_us(get_absolute_time(), t_before) > 0) {
|
||||
if (add_alarm_at(t_before, sleep_until_callback, NULL, false) >= 0) {
|
||||
// able to add alarm for just before the time
|
||||
while (!time_reached(t_before)) {
|
||||
uint32_t save = spin_lock_blocking(sleep_notifier.spin_lock);
|
||||
lock_internal_spin_unlock_with_wait(&sleep_notifier, save);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
// hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
|
||||
sync_internal_yield_until_before(t);
|
||||
#endif
|
||||
// now wait until the exact time
|
||||
busy_wait_until(t);
|
||||
}
|
||||
|
||||
void sleep_us(uint64_t us) {
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
sleep_until(make_timeout_time_us(us));
|
||||
#else
|
||||
if (us < PICO_TIME_SLEEP_OVERHEAD_ADJUST_US) {
|
||||
busy_wait_us(us);
|
||||
} else {
|
||||
// hook in case we're in RTOS; note we assume using the alarm pool is better always if available.
|
||||
absolute_time_t t = make_timeout_time_us(us - PICO_TIME_SLEEP_OVERHEAD_ADJUST_US);
|
||||
sync_internal_yield_until_before(t);
|
||||
|
||||
// then wait the rest of the way
|
||||
busy_wait_until(t);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sleep_ms(uint32_t ms) {
|
||||
sleep_us(ms * 1000ull);
|
||||
}
|
||||
|
||||
bool best_effort_wfe_or_timeout(absolute_time_t timeout_timestamp) {
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
if (__get_current_exception()) {
|
||||
tight_loop_contents();
|
||||
return time_reached(timeout_timestamp);
|
||||
} else {
|
||||
alarm_id_t id;
|
||||
// note that as of SDK 2.0.0 calling add_alarm_at always causes a SEV. What we really
|
||||
// want to do is cause an IRQ at the specified time in the future if there is not
|
||||
// an IRQ already happening before then. The problem is that the IRQ may be happening on the
|
||||
// other core, so taking an IRQ is the only way to get the state protection.
|
||||
//
|
||||
// Therefore, we make a compromise; we will set the alarm, if we won't wake up before the right time
|
||||
// already. This means that repeated calls to this function with the same timeout will work correctly
|
||||
// after the first one! This is fine, because we ask callers to use a polling loop on another
|
||||
// event variable when using this function.
|
||||
//
|
||||
// For this to work, we require that once we have set an alarm, an SEV happens no later than that, even
|
||||
// if we cancel the alarm as we do below. Therefore, the IRQ handler (which is always enabled) will
|
||||
// never set its wakeup time to a later value, but instead wake up once and then wake up again.
|
||||
//
|
||||
// This overhead when canceling alarms is a small price to pay for the much simpler/faster/cleaner
|
||||
// implementation that relies on the IRQ handler (on a single core) being the only state accessor.
|
||||
//
|
||||
// Note also, that the use of software spin locks on RP2350 to access state would always cause a SEV
|
||||
// due to use of LDREX etc., so actually using spin locks to protect the state would be worse.
|
||||
if (ta_wakes_up_on_or_before(alarm_pool_get_default()->timer, alarm_pool_get_default()->timer_alarm_num,
|
||||
(int64_t)to_us_since_boot(timeout_timestamp))) {
|
||||
// we already are waking up at or before when we want to (possibly due to us having been called
|
||||
// before in a loop), so we can do an actual WFE. Note we rely on the fact that the alarm pool IRQ
|
||||
// handler always does an explicit SEV, since it may be on the other core.
|
||||
__wfe();
|
||||
return time_reached(timeout_timestamp);
|
||||
} else {
|
||||
id = add_alarm_at(timeout_timestamp, sleep_until_callback, NULL, false);
|
||||
if (id <= 0) {
|
||||
tight_loop_contents();
|
||||
return time_reached(timeout_timestamp);
|
||||
} else {
|
||||
if (!time_reached(timeout_timestamp)) {
|
||||
// ^ at the point above the timer hadn't fired, so it is safe
|
||||
// to wait; the event will happen due to IRQ at some point between
|
||||
// then and the correct wakeup time
|
||||
__wfe();
|
||||
}
|
||||
// we need to clean up if it wasn't us that caused the wfe; if it was this will be a noop.
|
||||
cancel_alarm(id);
|
||||
return time_reached(timeout_timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
tight_loop_contents();
|
||||
return time_reached(timeout_timestamp);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool alarm_pool_add_repeating_timer_us(alarm_pool_t *pool, int64_t delay_us, repeating_timer_callback_t callback, void *user_data, repeating_timer_t *out) {
|
||||
if (!delay_us) delay_us = 1;
|
||||
out->pool = pool;
|
||||
out->callback = callback;
|
||||
out->delay_us = delay_us;
|
||||
out->user_data = user_data;
|
||||
out->alarm_id = alarm_pool_add_alarm_at(pool, make_timeout_time_us((uint64_t)(delay_us >= 0 ? delay_us : -delay_us)),
|
||||
repeating_timer_marker, out, true);
|
||||
return out->alarm_id > 0;
|
||||
}
|
||||
|
||||
bool cancel_repeating_timer(repeating_timer_t *timer) {
|
||||
bool rc = false;
|
||||
if (timer->alarm_id) {
|
||||
rc = alarm_pool_cancel_alarm(timer->pool, timer->alarm_id);
|
||||
timer->alarm_id = 0;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
alarm_pool_timer_t *alarm_pool_timer_for_timer_num(uint timer_num) {
|
||||
return ta_timer_instance(timer_num);
|
||||
}
|
||||
|
||||
alarm_pool_timer_t *alarm_pool_get_default_timer(void) {
|
||||
return ta_default_timer_instance();
|
||||
}
|
||||
|
||||
int64_t alarm_pool_remaining_alarm_time_us(alarm_pool_t *pool, alarm_id_t alarm_id) {
|
||||
// note there is no point distinguishing between invalid alarm_id and timer passed,
|
||||
// since an alarm_id that has fired without being re-enabled becomes logically invalid after
|
||||
// that point anyway
|
||||
int64_t rc = -1;
|
||||
int16_t index = alarm_index(alarm_id);
|
||||
if ((uint16_t)index < pool->num_entries) {
|
||||
uint16_t sequence = alarm_sequence(alarm_id);
|
||||
alarm_pool_entry_t *entry = &pool->entries[index];
|
||||
if (entry->sequence == sequence) {
|
||||
uint32_t save = spin_lock_blocking(pool->lock);
|
||||
int16_t search_index = pool->ordered_head;
|
||||
while (search_index >= 0) {
|
||||
entry = &pool->entries[search_index];
|
||||
if (index == search_index) {
|
||||
if (entry->sequence == sequence) {
|
||||
rc = entry->target - (int64_t) ta_time_us_64(pool->timer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
search_index = entry->next;
|
||||
}
|
||||
spin_unlock(pool->lock, save);
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int32_t alarm_pool_remaining_alarm_time_ms(alarm_pool_t *pool, alarm_id_t alarm_id) {
|
||||
int64_t rc = alarm_pool_remaining_alarm_time_us(pool, alarm_id);
|
||||
if (rc >= 0) rc /= 1000;
|
||||
return rc >= INT32_MAX ? INT32_MAX : (int32_t) rc;
|
||||
}
|
||||
|
||||
#if !PICO_TIME_DEFAULT_ALARM_POOL_DISABLED
|
||||
int64_t remaining_alarm_time_us(alarm_id_t alarm_id) {
|
||||
return alarm_pool_remaining_alarm_time_us(alarm_pool_get_default(), alarm_id);
|
||||
}
|
||||
|
||||
int32_t remaining_alarm_time_ms(alarm_id_t alarm_id) {
|
||||
return alarm_pool_remaining_alarm_time_ms(alarm_pool_get_default(), alarm_id);
|
||||
}
|
||||
#endif
|
32
lib/main/pico-sdk/common/pico_time/timeout_helper.c
Normal file
32
lib/main/pico-sdk/common/pico_time/timeout_helper.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "pico/timeout_helper.h"
|
||||
|
||||
static bool check_single_timeout_us(timeout_state_t *ts, __unused bool reset) {
|
||||
return time_reached(ts->next_timeout);
|
||||
}
|
||||
|
||||
check_timeout_fn init_single_timeout_until(timeout_state_t *ts, absolute_time_t target) {
|
||||
ts->next_timeout = target;
|
||||
return check_single_timeout_us;
|
||||
}
|
||||
|
||||
static bool check_per_iteration_timeout_us(timeout_state_t *ts, bool reset) {
|
||||
if (reset) {
|
||||
ts->next_timeout = make_timeout_time_us(ts->param);
|
||||
}
|
||||
if (time_reached(ts->next_timeout)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
check_timeout_fn init_per_iteration_timeout_us(timeout_state_t *ts, uint64_t per_iteration_timeout_us) {
|
||||
ts->next_timeout = make_timeout_time_us(per_iteration_timeout_us);
|
||||
ts->param = per_iteration_timeout_us;
|
||||
return check_per_iteration_timeout_us;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue