Initial Release
This commit is contained in:
2
src/rp2_common/hardware_timer/CMakeLists.txt
Normal file
2
src/rp2_common/hardware_timer/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
pico_simple_hardware_target(timer)
|
||||
target_link_libraries(hardware_timer INTERFACE hardware_claim)
|
180
src/rp2_common/hardware_timer/include/hardware/timer.h
Normal file
180
src/rp2_common/hardware_timer/include/hardware/timer.h
Normal file
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_TIMER_H
|
||||
#define _HARDWARE_TIMER_H
|
||||
|
||||
#include "pico.h"
|
||||
#include "hardware/structs/timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/timer.h
|
||||
* \defgroup hardware_timer hardware_timer
|
||||
*
|
||||
* Low-level hardware timer API
|
||||
*
|
||||
* This API provides medium level access to the timer HW.
|
||||
* See also \ref pico_time which provides higher levels functionality using the hardware timer.
|
||||
*
|
||||
* The timer peripheral on RP2040 supports the following features:
|
||||
* - single 64-bit counter, incrementing once per microsecond
|
||||
* - Latching two-stage read of counter, for race-free read over 32 bit bus
|
||||
* - Four alarms: match on the lower 32 bits of counter, IRQ on match.
|
||||
*
|
||||
* By default the timer uses a one microsecond reference that is generated in the Watchdog (see Section 4.8.2) which is derived
|
||||
* from the clk_ref.
|
||||
*
|
||||
* The timer has 4 alarms, and can output a separate interrupt for each alarm. The alarms match on the lower 32 bits of the 64
|
||||
* bit counter which means they can be fired a maximum of 2^32 microseconds into the future. This is equivalent to:
|
||||
* - 2^32 ÷ 10^6: ~4295 seconds
|
||||
* - 4295 ÷ 60: ~72 minutes
|
||||
*
|
||||
* The timer is expected to be used for short sleeps, if you want a longer alarm see the \ref hardware_rtc functions.
|
||||
*
|
||||
* \subsection timer_example Example
|
||||
* \addtogroup hardware_timer
|
||||
*
|
||||
* \include hello_timer.c
|
||||
*
|
||||
* \see pico_time
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_TIMER, Enable/disable assertions in the timer module, type=bool, default=0, group=hardware_timer
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_TIMER
|
||||
#define PARAM_ASSERTIONS_ENABLED_TIMER 0
|
||||
#endif
|
||||
|
||||
static inline void check_hardware_alarm_num_param(uint alarm_num) {
|
||||
invalid_params_if(TIMER, alarm_num >= NUM_TIMERS);
|
||||
}
|
||||
|
||||
/*! \brief Return a 32 bit timestamp value in microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* Returns the low 32 bits of the hardware timer.
|
||||
* \note This value wraps roughly every 1 hour 11 minutes and 35 seconds.
|
||||
*
|
||||
* \return the 32 bit timestamp
|
||||
*/
|
||||
static inline uint32_t time_us_32() {
|
||||
return timer_hw->timerawl;
|
||||
}
|
||||
|
||||
/*! \brief Return the current 64 bit timestamp value in microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* Returns the full 64 bits of the hardware timer. The \ref pico_time and other functions rely on the fact that this
|
||||
* value monotonically increases from power up. As such it is expected that this value counts upwards and never wraps
|
||||
* (we apologize for introducing a potential year 5851444 bug).
|
||||
*
|
||||
* \return the 64 bit timestamp
|
||||
*/
|
||||
uint64_t time_us_64();
|
||||
|
||||
/*! \brief Busy wait wasting cycles for the given (32 bit) number of microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param delay_us delay amount
|
||||
*/
|
||||
void busy_wait_us_32(uint32_t delay_us);
|
||||
|
||||
/*! \brief Busy wait wasting cycles for the given (64 bit) number of microseconds
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param delay_us delay amount
|
||||
*/
|
||||
void busy_wait_us(uint64_t delay_us);
|
||||
|
||||
/*! \brief Busy wait wasting cycles until after the specified timestamp
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param t Absolute time to wait until
|
||||
*/
|
||||
void busy_wait_until(absolute_time_t t);
|
||||
|
||||
/*! \brief Check if the specified timestamp has been reached
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param t Absolute time to compare against current time
|
||||
* \return true if it is now after the specified timestamp
|
||||
*/
|
||||
static inline bool time_reached(absolute_time_t t) {
|
||||
uint64_t target = to_us_since_boot(t);
|
||||
uint32_t hi_target = target >> 32u;
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
return (hi >= hi_target && (timer_hw->timerawl >= (uint32_t) target || hi != hi_target));
|
||||
}
|
||||
|
||||
/*! Callback function type for hardware alarms
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param alarm_num the hardware alarm number
|
||||
* \sa hardware_alarm_set_callback
|
||||
*/
|
||||
typedef void (*hardware_alarm_callback_t)(uint alarm_num);
|
||||
|
||||
/*! \brief cooperatively claim the use of this hardware alarm_num
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* This method hard asserts if the hardware alarm is currently claimed.
|
||||
*
|
||||
* \param alarm_num the hardware alarm to claim
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
void hardware_alarm_claim(uint alarm_num);
|
||||
|
||||
/*! \brief cooperatively release the claim on use of this hardware alarm_num
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* \param alarm_num the hardware alarm to unclaim
|
||||
* \sa hardware_claiming
|
||||
*/
|
||||
void hardware_alarm_unclaim(uint alarm_num);
|
||||
|
||||
/*! \brief Enable/Disable a callback for a hardware timer on this core
|
||||
* \ingroup hardware_timer
|
||||
*
|
||||
* This method enables/disables the alarm IRQ for the specified hardware alarm on the
|
||||
* calling core, and set the specified callback to be associated with that alarm.
|
||||
*
|
||||
* This callback will be used for the timeout set via hardware_alarm_set_target
|
||||
*
|
||||
* \note This will install the handler on the current core if the IRQ handler isn't already set.
|
||||
* Therefore the user has the opportunity to call this up from the core of their choice
|
||||
*
|
||||
* \param alarm_num the hardware alarm number
|
||||
* \param callback the callback to install, or NULL to unset
|
||||
*
|
||||
* \sa hardware_alarm_set_target
|
||||
*/
|
||||
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback);
|
||||
|
||||
/**
|
||||
* \brief Set the current target for the specified hardware alarm
|
||||
*
|
||||
* This will replace any existing target
|
||||
*
|
||||
* @param alarm_num the hardware alarm number
|
||||
* @param t the target timestamp
|
||||
* @return true if the target was "missed"; i.e. it was in the past, or occurred before a future hardware timeout could be set
|
||||
*/
|
||||
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t t);
|
||||
|
||||
/**
|
||||
* \brief Cancel an existing target (if any) for a given hardware_alarm
|
||||
*
|
||||
* @param alarm_num
|
||||
*/
|
||||
|
||||
void hardware_alarm_cancel(uint alarm_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
207
src/rp2_common/hardware_timer/timer.c
Normal file
207
src/rp2_common/hardware_timer/timer.c
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "hardware/timer.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/claim.h"
|
||||
|
||||
check_hw_layout(timer_hw_t, ints, TIMER_INTS_OFFSET);
|
||||
|
||||
static hardware_alarm_callback_t alarm_callbacks[NUM_TIMERS];
|
||||
static uint32_t target_hi[NUM_TIMERS];
|
||||
static uint8_t timer_callbacks_pending;
|
||||
|
||||
static_assert(NUM_TIMERS <= 4, "");
|
||||
static uint8_t claimed;
|
||||
|
||||
void hardware_alarm_claim(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
hw_claim_or_assert(&claimed, alarm_num, "Hardware alarm %d already claimed");
|
||||
}
|
||||
|
||||
void hardware_alarm_unclaim(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
hw_claim_clear(&claimed, alarm_num);
|
||||
}
|
||||
|
||||
/// tag::time_us_64[]
|
||||
uint64_t time_us_64() {
|
||||
// Need to make sure that the upper 32 bits of the timer
|
||||
// don't change, so read that first
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
uint32_t lo;
|
||||
do {
|
||||
// Read the lower 32 bits
|
||||
lo = timer_hw->timerawl;
|
||||
// Now read the upper 32 bits again and
|
||||
// check that it hasn't incremented. If it has loop around
|
||||
// and read the lower 32 bits again to get an accurate value
|
||||
uint32_t next_hi = timer_hw->timerawh;
|
||||
if (hi == next_hi) break;
|
||||
hi = next_hi;
|
||||
} while (true);
|
||||
return ((uint64_t) hi << 32u) | lo;
|
||||
}
|
||||
/// end::time_us_64[]
|
||||
|
||||
/// \tag::busy_wait[]
|
||||
void busy_wait_us_32(uint32_t delay_us) {
|
||||
if (0 <= (int32_t)delay_us) {
|
||||
// we only allow 31 bits, otherwise we could have a race in the loop below with
|
||||
// values very close to 2^32
|
||||
uint32_t start = timer_hw->timerawl;
|
||||
while (timer_hw->timerawl - start < delay_us) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
} else {
|
||||
busy_wait_us(delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
void busy_wait_us(uint64_t delay_us) {
|
||||
uint64_t base = time_us_64();
|
||||
uint64_t target = base + delay_us;
|
||||
if (target < base) {
|
||||
target = (uint64_t)-1;
|
||||
}
|
||||
absolute_time_t t;
|
||||
update_us_since_boot(&t, target);
|
||||
busy_wait_until(t);
|
||||
}
|
||||
|
||||
void busy_wait_until(absolute_time_t t) {
|
||||
uint64_t target = to_us_since_boot(t);
|
||||
uint32_t hi_target = target >> 32u;
|
||||
uint32_t hi = timer_hw->timerawh;
|
||||
while (hi < hi_target) {
|
||||
hi = timer_hw->timerawh;
|
||||
tight_loop_contents();
|
||||
}
|
||||
while (hi == hi_target && timer_hw->timerawl < (uint32_t) target) {
|
||||
hi = timer_hw->timerawh;
|
||||
tight_loop_contents();
|
||||
}
|
||||
}
|
||||
/// \end::busy_wait[]
|
||||
|
||||
static inline uint harware_alarm_irq_number(uint alarm_num) {
|
||||
return TIMER_IRQ_0 + alarm_num;
|
||||
}
|
||||
|
||||
static void hardware_alarm_irq_handler() {
|
||||
// Determine which timer this IRQ is for
|
||||
uint32_t ipsr;
|
||||
__asm volatile ("mrs %0, ipsr" : "=r" (ipsr)::);
|
||||
uint alarm_num = (ipsr & 0x3fu) - 16 - TIMER_IRQ_0;
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
|
||||
hardware_alarm_callback_t callback = NULL;
|
||||
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
// Clear the timer IRQ (inside lock, because we check whether we have handled the IRQ yet in alarm_set by looking at the interrupt status
|
||||
timer_hw->intr = 1u << alarm_num;
|
||||
|
||||
// make sure the IRQ is still valid
|
||||
if (timer_callbacks_pending & (1u << alarm_num)) {
|
||||
// Now check whether we have a timer event to handle that isn't already obsolete (this could happen if we
|
||||
// were already in the IRQ handler before someone else changed the timer setup
|
||||
if (timer_hw->timerawh >= target_hi[alarm_num]) {
|
||||
// we have reached the right high word as well as low word value
|
||||
callback = alarm_callbacks[alarm_num];
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
} else {
|
||||
// try again in 2^32 us
|
||||
timer_hw->alarm[alarm_num] = timer_hw->alarm[alarm_num]; // re-arm the timer
|
||||
}
|
||||
}
|
||||
|
||||
spin_unlock(lock, save);
|
||||
|
||||
if (callback) {
|
||||
callback(alarm_num);
|
||||
}
|
||||
}
|
||||
|
||||
void hardware_alarm_set_callback(uint alarm_num, hardware_alarm_callback_t callback) {
|
||||
// todo check current core owner
|
||||
// note this should probably be subsumed by irq_set_exclusive_handler anyway, since that
|
||||
// should disallow IRQ handlers on both cores
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
uint irq_num = harware_alarm_irq_number(alarm_num);
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
if (callback) {
|
||||
if (hardware_alarm_irq_handler != irq_get_vtable_handler(irq_num)) {
|
||||
// note that set_exclusive will silently allow you to set the handler to the same thing
|
||||
// since it is idempotent, which means we don't need to worry about locking ourselves
|
||||
irq_set_exclusive_handler(irq_num, hardware_alarm_irq_handler);
|
||||
irq_set_enabled(irq_num, true);
|
||||
// Enable interrupt in block and at processor
|
||||
hw_set_bits(&timer_hw->inte, 1u << alarm_num);
|
||||
}
|
||||
alarm_callbacks[alarm_num] = callback;
|
||||
} else {
|
||||
alarm_callbacks[alarm_num] = NULL;
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
irq_remove_handler(irq_num, hardware_alarm_irq_handler);
|
||||
irq_set_enabled(irq_num, false);
|
||||
}
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
|
||||
bool hardware_alarm_set_target(uint alarm_num, absolute_time_t target) {
|
||||
bool missed;
|
||||
uint64_t now = time_us_64();
|
||||
uint64_t t = to_us_since_boot(target);
|
||||
if (now >= t) {
|
||||
missed = true;
|
||||
} else {
|
||||
missed = false;
|
||||
|
||||
// 1) actually set the hardware timer
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
timer_hw->intr = 1u << alarm_num;
|
||||
timer_callbacks_pending |= 1u << alarm_num;
|
||||
timer_hw->alarm[alarm_num] = (uint32_t) t;
|
||||
// Set the alarm. Writing time should arm it
|
||||
target_hi[alarm_num] = t >> 32u;
|
||||
|
||||
// 2) check for races
|
||||
if (!(timer_hw->armed & 1u << alarm_num)) {
|
||||
// not armed, so has already fired .. IRQ must be pending (we are still under lock)
|
||||
assert(timer_hw->ints & 1u << alarm_num);
|
||||
} else {
|
||||
if (time_us_64() >= t) {
|
||||
// ok well it is time now; the irq isn't being handled yet because of the spin lock
|
||||
// however the other core might be in the IRQ handler itself about to do a callback
|
||||
// we do the firing ourselves (and indicate to the IRQ handler if any that it shouldn't
|
||||
missed = true;
|
||||
// disarm the timer
|
||||
timer_hw->armed = 1u << alarm_num;
|
||||
timer_hw->intr = 1u << alarm_num; // clear the IRQ too
|
||||
// and set flag in case we're already in the IRQ handler waiting on the spinlock (on the other core)
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
}
|
||||
}
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
return missed;
|
||||
}
|
||||
|
||||
void hardware_alarm_cancel(uint alarm_num) {
|
||||
check_hardware_alarm_num_param(alarm_num);
|
||||
|
||||
spin_lock_t *lock = spin_lock_instance(PICO_SPINLOCK_ID_TIMER);
|
||||
uint32_t save = spin_lock_blocking(lock);
|
||||
timer_hw->armed = 1u << alarm_num;
|
||||
timer_callbacks_pending &= ~(1u << alarm_num);
|
||||
spin_unlock(lock, save);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user