-
Daniel Agar authoredDaniel Agar authored
drv_hrt.c 25.61 KiB
/****************************************************************************
*
* Copyright (c) 2012, 2013 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file drv_hrt.c
*
* High-resolution timer callouts and timekeeping.
*
* This can use any general or advanced STM32 timer.
*
* Note that really, this could use systick too, but that's
* monopolised by NuttX and stealing it would just be awkward.
*
* We don't use the NuttX STM32 driver per se; rather, we
* claim the timer and then drive it directly.
*/
#include <px4_config.h>
#include <px4_log.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <sys/types.h>
#include <stdbool.h>
#include <assert.h>
#include <debug.h>
#include <time.h>
#include <queue.h>
#include <errno.h>
#include <string.h>
#include <board_config.h>
#include <drivers/drv_hrt.h>
#include "chip.h"
#include "up_internal.h"
#include "up_arch.h"
#include <chip.h>
#include "sam_gpio.h"
#include "sam_tc.h"
#ifdef HRT_TIMER_CHANNEL
#if defined(HRT_TIMER)
# error "HRT_TIMER should not be defined, instead define HRT_TIMER_CHANNEL from 0-11"
#endif
#if HRT_TIMER_CHANNEL == 0 || HRT_TIMER_CHANNEL == 1 || HRT_TIMER_CHANNEL == 2
# define HRT_TIMER 0
#endif
#if HRT_TIMER_CHANNEL == 3 || HRT_TIMER_CHANNEL == 4 || HRT_TIMER_CHANNEL == 5
# define HRT_TIMER 1
#endif
#if HRT_TIMER_CHANNEL == 6 || HRT_TIMER_CHANNEL == 7 || HRT_TIMER_CHANNEL == 8
# define HRT_TIMER 2
#endif
#if HRT_TIMER_CHANNEL == 9 || HRT_TIMER_CHANNEL == 10 || HRT_TIMER_CHANNEL == 11
# define HRT_TIMER 3
#endif
/* HRT configuration */
#if HRT_TIMER == 0
# define HRT_TIMER_BASE SAM_TC012_BASE
# if !defined(CONFIG_SAMV7_TC0)
# error "HRT_TIMER_CHANNEL 0-2 Require CONFIG_SAMV7_TC0=y"
# endif
#elif HRT_TIMER == 1
# define HRT_TIMER_BASE SAM_TC345_BASE
# if !defined(CONFIG_SAMV7_TC1)
# error "HRT_TIMER_CHANNEL 3-5 Require CONFIG_SAMV7_TC1=y"
# endif
#elif HRT_TIMER == 2
# define HRT_TIMER_BASE SAM_TC678_BASE
# if !defined(CONFIG_SAMV7_TC2)
# error "HRT_TIMER_CHANNEL 6-8 Require CONFIG_SAMV7_TC2=y"
# endif
#elif HRT_TIMER == 3
# define HRT_TIMER_BASE SAM_TC901_BASE
# if !defined(CONFIG_SAMV7_TC3)
# error "HRT_TIMER_CHANNEL 9-11 Require CONFIG_SAMV7_TC3=y"
# endif
#else
# error "HRT_TIMER_CHANNEL should be defined valid value are from 0-11"
# endif
/*
* We are going to request 1 1Mhz input to our counter
* but on this platform we will most likely get a hotter value
* We will test this by assertion.
*/
# define HRT_TIMER_CLOCK (BOARD_MCK_FREQUENCY/128)
# define HRT_DESIRED_FREQUENCY 1000000
/*
* HRT clock must be a greater than 1MHz
*/
#if HRT_TIMER_CLOCK <= 1000000
# error HRT_TIMER_CLOCK must be greater than 1MHz
#endif
/*
* Scaling factor for the free-running counter; convert an input
* in counts to a time in microseconds.
*/
#define HRT_TIME_SCALE(_t) ((((_t) * HRT_TIMER_CLOCK) / USEC_PER_SEC) + 1)
/*
* Scaling factor for the free-running counter; convert an input
* in time in microseconds to counts.
*/
#define HRT_COUNTER_SCALE(_c) ((_c) * USEC_PER_SEC) / HRT_TIMER_CLOCK
/**
* Minimum/maximum deadlines.
*
* These are suitable for use with a 16-bit timer/counter clocked
* at 1MHz. The high-resolution timer need only guarantee that it
* not wrap more than once in the 50ms period for absolute time to
* be consistently maintained.
*
* The minimum deadline must be such that the time taken between
* reading a time and writing a deadline to the timer cannot
* result in missing the deadline.
*/
#define HRT_INTERVAL_MIN 50
#define HRT_INTERVAL_MAX 50000
/*
* Period of the free-running counter, in microseconds.
*/
#define HRT_COUNTER_PERIOD 65536
/*
* Timer register accessors
*/
#define REG(_reg) (*(volatile uint32_t *)(SAM_TC0_BASE + SAM_TC_CHAN_OFFSET(HRT_TIMER_CHANNEL) + _reg))
#define rCCR REG(SAM_TC_CCR_OFFSET)
#define rCMR REG(SAM_TC_CMR_OFFSET)
#define rSMMR REG(SAM_TC_SMMR_OFFSET)
#define rRAB REG(SAM_TC_RAB_OFFSET)
#define rCV REG(SAM_TC_CV_OFFSET)
#define rRA REG(SAM_TC_RA_OFFSET)
#define rRB REG(SAM_TC_RB_OFFSET)
#define rRC REG(SAM_TC_RC_OFFSET)
#define rSR REG(SAM_TC_SR_OFFSET)
#define rIER REG(SAM_TC_IER_OFFSET)
#define rIDR REG(SAM_TC_IDR_OFFSET)
#define rIMR REG(SAM_TC_IMR_OFFSET)
#define rEMR REG(SAM_TC_EMR_OFFSET)
/*
*
* Queue of callout entries.
*/
static struct sq_queue_s callout_queue;
/* latency baseline (last compare value applied) */
static uint16_t latency_baseline;
/* timer count at interrupt (for latency purposes) */
static uint16_t latency_actual;
/* latency histogram */
#define LATENCY_BUCKET_COUNT 8
__EXPORT const uint16_t latency_bucket_count = LATENCY_BUCKET_COUNT;
__EXPORT const uint16_t latency_buckets[LATENCY_BUCKET_COUNT] = { 1, 2, 5, 10, 20, 50, 100, 1000 };
__EXPORT uint32_t latency_counters[LATENCY_BUCKET_COUNT + 1];
/* timer-specific functions */
static void hrt_tim_init(void);
static void hrt_tim_isr(TC_HANDLE tch, void *arg, uint32_t sr);
static void hrt_latency_update(void);
/* callout list manipulation */
static void hrt_call_internal(struct hrt_call *entry,
hrt_abstime deadline,
hrt_abstime interval,
hrt_callout callout,
void *arg);
static void hrt_call_enter(struct hrt_call *entry);
static void hrt_call_reschedule(void);
static void hrt_call_invoke(void);
/*
* Specific registers and bits used by PPM sub-functions
*/
#undef HRT_PPM_CHANNEL //todo:implement PPM
#ifdef HRT_PPM_CHANNEL
# if HRT_PPM_CHANNEL == 1
# define rCCR_PPM rCCR /* capture register for PPM */
# define DIER_PPM TIM_DIER_CC1IE /* capture interrupt (non-DMA mode) */
# define SR_INT_PPM GTIM_SR_CC1IF /* capture interrupt (non-DMA mode) */
# define SR_OVF_PPM GTIM_SR_CC1OF /* capture overflow (non-DMA mode) */
# define CCMR1_PPM 1 /* not on TI1/TI2 */
# define CCMR2_PPM 0 /* on TI3, not on TI4 */
# define CCER_PPM (GTIM_CCER_CC1E | GTIM_CCER_CC1P | GTIM_CCER_CC1NP) /* CC1, both edges */
# define CCER_PPM_FLIP GTIM_CCER_CC1P
# elif HRT_PPM_CHANNEL == 2
# define rCCR_PPM rCCR2 /* capture register for PPM */
# define DIER_PPM GTIM_DIER_CC2IE /* capture interrupt (non-DMA mode) */
# define SR_INT_PPM GTIM_SR_CC2IF /* capture interrupt (non-DMA mode) */
# define SR_OVF_PPM GTIM_SR_CC2OF /* capture overflow (non-DMA mode) */
# define CCMR1_PPM 2 /* not on TI1/TI2 */
# define CCMR2_PPM 0 /* on TI3, not on TI4 */
# define CCER_PPM (GTIM_CCER_CC2E | GTIM_CCER_CC2P | GTIM_CCER_CC2NP) /* CC2, both edges */
# define CCER_PPM_FLIP GTIM_CCER_CC2P
# elif HRT_PPM_CHANNEL == 3
# define rCCR_PPM rCCR3 /* capture register for PPM */
# define DIER_PPM GTIM_DIER_CC3IE /* capture interrupt (non-DMA mode) */
# define SR_INT_PPM GTIM_SR_CC3IF /* capture interrupt (non-DMA mode) */
# define SR_OVF_PPM GTIM_SR_CC3OF /* capture overflow (non-DMA mode) */
# define CCMR1_PPM 0 /* not on TI1/TI2 */
# define CCMR2_PPM 1 /* on TI3, not on TI4 */
# define CCER_PPM (GTIM_CCER_CC3E | GTIM_CCER_CC3P | GTIM_CCER_CC3NP) /* CC3, both edges */
# define CCER_PPM_FLIP GTIM_CCER_CC3P
# elif HRT_PPM_CHANNEL == 4
# define rCCR_PPM rCCR4 /* capture register for PPM */
# define DIER_PPM GTIM_DIER_CC4IE /* capture interrupt (non-DMA mode) */
# define SR_INT_PPM GTIM_SR_CC4IF /* capture interrupt (non-DMA mode) */
# define SR_OVF_PPM GTIM_SR_CC4OF /* capture overflow (non-DMA mode) */
# define CCMR1_PPM 0 /* not on TI1/TI2 */
# define CCMR2_PPM 2 /* on TI3, not on TI4 */
# define CCER_PPM (GTIM_CCER_CC4E | GTIM_CCER_CC4P | GTIM_CCER_CC4NP) /* CC4, both edges */
# define CCER_PPM_FLIP GTIM_CCER_CC4P
# else
# error HRT_PPM_CHANNEL must be a value between 1 and 4
# endif
/*
* PPM decoder tuning parameters
*/
# define PPM_MIN_PULSE_WIDTH 200 /**< minimum width of a valid first pulse */
# define PPM_MAX_PULSE_WIDTH 600 /**< maximum width of a valid first pulse */
# define PPM_MIN_CHANNEL_VALUE 800 /**< shortest valid channel signal */
# define PPM_MAX_CHANNEL_VALUE 2200 /**< longest valid channel signal */
# define PPM_MIN_START 2300 /**< shortest valid start gap (only 2nd part of pulse) */
/* decoded PPM buffer */
#define PPM_MIN_CHANNELS 5
#define PPM_MAX_CHANNELS 20
/** Number of same-sized frames required to 'lock' */
#define PPM_CHANNEL_LOCK 4 /**< should be less than the input timeout */
__EXPORT uint16_t ppm_buffer[PPM_MAX_CHANNELS];
__EXPORT uint16_t ppm_frame_length = 0;
__EXPORT unsigned ppm_decoded_channels = 0;
__EXPORT uint64_t ppm_last_valid_decode = 0;
#define PPM_DEBUG 0
#if PPM_DEBUG
/* PPM edge history */
__EXPORT uint16_t ppm_edge_history[32];
unsigned ppm_edge_next;
/* PPM pulse history */
__EXPORT uint16_t ppm_pulse_history[32];
unsigned ppm_pulse_next;
#endif
static uint16_t ppm_temp_buffer[PPM_MAX_CHANNELS];
/** PPM decoder state machine */
struct {
uint16_t last_edge; /**< last capture time */
uint16_t last_mark; /**< last significant edge */
uint16_t frame_start; /**< the frame width */
unsigned next_channel; /**< next channel index */
enum {
UNSYNCH = 0,
ARM,
ACTIVE,
INACTIVE
} phase;
} ppm;
static void hrt_ppm_decode(uint32_t status);
#else
/* disable the PPM configuration */
# define rCCR_PPM 0
# define DIER_PPM 0
# define SR_INT_PPM 0
# define SR_OVF_PPM 0
# define CCMR1_PPM 0
# define CCMR2_PPM 0
# define CCER_PPM 0
#endif /* HRT_PPM_CHANNEL */
/**
* Initialise the timer we are going to use.
*
* We expect that we'll own one of the reduced-function STM32 general
* timers, and that we can use channel 1 in compare mode.
*/
static TC_HANDLE hrt_tch = (TC_HANDLE) - 1;
static void
hrt_tim_init(void)
{
uint32_t frequency;
uint32_t actual;
uint32_t cmr;
int ret;
/* We would like 1Mhz count rate */
frequency = HRT_DESIRED_FREQUENCY;
/* The pre-calculate values to use when we start the timer */
ret = sam_tc_clockselect(frequency, &cmr, &actual);
if (ret < 0) {
printf("ERROR: Failed no divisor can be found (%d),for timer channel %d\n", ret, HRT_TIMER_CHANNEL);
return;
}
/* Verify that what we got was what we expected */
ASSERT(actual == HRT_TIMER_CLOCK);
/* Allocate the timer/counter and select its mode of operation
*
* TC_CMR_TCCLKS - Returned by sam_tc_clockselect
* TC_CMR_CLKI=0 - Not inverted
* TC_CMR_BURST_NONE - Not gated by an external signal
* TC_CMR_CPCSTOP=0 - Do not Stop the clock on an RC compare event
* TC_CMR_CPCDIS=0 - Don't disable the clock on an RC compare event
* TC_CMR_EEVTEDG_NONE - No external events (and, hence, no edges
* TC_CMR_EEVT_TIOB - ???? REVISIT
* TC_CMR_ENET=0 - External event trigger disabled
* TC_CMR_WAVSEL_UP - TC_CV is incremented from 0 to 65535
* TC_CMR_WAVE - Waveform mode
* TC_CMR_ACPA_NONE - RA compare has no effect on TIOA
* TC_CMR_ACPC_NONE - RC compare has no effect on TIOA
* TC_CMR_AEEVT_NONE - No external event effect on TIOA
* TC_CMR_ASWTRG_NONE - No software trigger effect on TIOA
* TC_CMR_BCPB_NONE - RB compare has no effect on TIOB
* TC_CMR_BCPC_NONE - RC compare has no effect on TIOB
* TC_CMR_BEEVT_NONE - No external event effect on TIOB
* TC_CMR_BSWTRG_NONE - No software trigger effect on TIOB
*/
cmr |= (TC_CMR_BURST_NONE | TC_CMR_EEVTEDG_NONE | TC_CMR_EEVT_TIOB |
TC_CMR_WAVSEL_UP | TC_CMR_WAVE | TC_CMR_ACPA_NONE |
TC_CMR_ACPC_NONE | TC_CMR_AEEVT_NONE | TC_CMR_ASWTRG_NONE |
TC_CMR_BCPB_NONE | TC_CMR_BCPC_NONE | TC_CMR_BEEVT_NONE |
TC_CMR_BSWTRG_NONE);
hrt_tch = sam_tc_allocate(HRT_TIMER_CHANNEL, cmr);
if (!hrt_tch) {
printf("ERROR: Failed to allocate timer channel %d\n", HRT_TIMER_CHANNEL);
return;
}
irqstate_t flags = enter_critical_section();
/* Start with 1 ms */
uint64_t regval = HRT_TIME_SCALE(1000LL);
ASSERT(regval <= UINT16_MAX);
/* Set up to receive the callback when the Compare interrupt occurs */
(void)sam_tc_attach(hrt_tch, hrt_tim_isr, hrt_tch, TC_INT_CPCS);
/* Set RC so that an event will be triggered when TC_CV register counts
* up to RC.
*/
rRC = regval & UINT16_MAX;
/* Start the counter */
sam_tc_start(hrt_tch);
/* Enable interrupts. We should get the callback when the interrupt
* occurs.
*/
leave_critical_section(flags);
}
#ifdef HRT_PPM_CHANNEL
/**
* Handle the PPM decoder state machine.
*/
static void
hrt_ppm_decode(uint32_t status)
{
uint16_t count = rCCR_PPM;
uint16_t width;
uint16_t interval;
unsigned i;
/* if we missed an edge, we have to give up */
if (status & SR_OVF_PPM) {
goto error;
}
/* how long since the last edge? - this handles counter wrapping implicitely. */
width = count - ppm.last_edge;
#if PPM_DEBUG
ppm_edge_history[ppm_edge_next++] = width;
if (ppm_edge_next >= 32) {
ppm_edge_next = 0;
}
#endif
/*
* if this looks like a start pulse, then push the last set of values
* and reset the state machine
*/
if (width >= PPM_MIN_START) {
/*
* If the number of channels changes unexpectedly, we don't want
* to just immediately jump on the new count as it may be a result
* of noise or dropped edges. Instead, take a few frames to settle.
*/
if (ppm.next_channel != ppm_decoded_channels) {
static unsigned new_channel_count;
static unsigned new_channel_holdoff;
if (new_channel_count != ppm.next_channel) {
/* start the lock counter for the new channel count */
new_channel_count = ppm.next_channel;
new_channel_holdoff = PPM_CHANNEL_LOCK;
} else if (new_channel_holdoff > 0) {
/* this frame matched the last one, decrement the lock counter */
new_channel_holdoff--;
} else {
/* we have seen PPM_CHANNEL_LOCK frames with the new count, accept it */
ppm_decoded_channels = new_channel_count;
new_channel_count = 0;
}
} else {
/* frame channel count matches expected, let's use it */
if (ppm.next_channel > PPM_MIN_CHANNELS) {
for (i = 0; i < ppm.next_channel; i++) {
ppm_buffer[i] = ppm_temp_buffer[i];
}
ppm_last_valid_decode = hrt_absolute_time();
}
}
/* reset for the next frame */
ppm.next_channel = 0;
/* next edge is the reference for the first channel */
ppm.phase = ARM;
ppm.last_edge = count;
return;
}
switch (ppm.phase) {
case UNSYNCH:
/* we are waiting for a start pulse - nothing useful to do here */
break;
case ARM:
/* we expect a pulse giving us the first mark */
if (width < PPM_MIN_PULSE_WIDTH || width > PPM_MAX_PULSE_WIDTH) {
goto error; /* pulse was too short or too long */
}
/* record the mark timing, expect an inactive edge */
ppm.last_mark = ppm.last_edge;
/* frame length is everything including the start gap */
ppm_frame_length = (uint16_t)(ppm.last_edge - ppm.frame_start);
ppm.frame_start = ppm.last_edge;
ppm.phase = ACTIVE;
break;
case INACTIVE:
/* we expect a short pulse */
if (width < PPM_MIN_PULSE_WIDTH || width > PPM_MAX_PULSE_WIDTH) {
goto error; /* pulse was too short or too long */
}
/* this edge is not interesting, but now we are ready for the next mark */
ppm.phase = ACTIVE;
break;
case ACTIVE:
/* determine the interval from the last mark */
interval = count - ppm.last_mark;
ppm.last_mark = count;
#if PPM_DEBUG
ppm_pulse_history[ppm_pulse_next++] = interval;
if (ppm_pulse_next >= 32) {
ppm_pulse_next = 0;
}
#endif
/* if the mark-mark timing is out of bounds, abandon the frame */
if ((interval < PPM_MIN_CHANNEL_VALUE) || (interval > PPM_MAX_CHANNEL_VALUE)) {
goto error;
}
/* if we have room to store the value, do so */
if (ppm.next_channel < PPM_MAX_CHANNELS) {
ppm_temp_buffer[ppm.next_channel++] = interval;
}
ppm.phase = INACTIVE;
break;
}
ppm.last_edge = count;
return;
/* the state machine is corrupted; reset it */
error:
/* we don't like the state of the decoder, reset it and try again */
ppm.phase = UNSYNCH;
ppm_decoded_channels = 0;
}
#endif /* HRT_PPM_CHANNEL */
/**
* Handle the compare interrupt by calling the callout dispatcher
* and then re-scheduling the next deadline.
*/
void hrt_tim_isr(TC_HANDLE tch, void *arg, uint32_t sr)
{
/* grab the timer for latency tracking purposes */
latency_actual = rCV;
#ifdef HRT_PPM_CHANNEL
/* was this a PPM edge? */
if (status & (SR_INT_PPM | SR_OVF_PPM)) {
/* if required, flip edge sensitivity */
# ifdef PPM_EDGE_FLIP
rCCER ^= CCER_PPM_FLIP;
# endif
hrt_ppm_decode(status);
}
#endif
/* do latency calculations */
hrt_latency_update();
/* run any callouts that have met their deadline */
hrt_call_invoke();
/* and schedule the next interrupt */
hrt_call_reschedule();
}
/**
* Fetch a never-wrapping absolute time value in microseconds from
* some arbitrary epoch shortly after system start.
*/
hrt_abstime
hrt_absolute_time(void)
{
hrt_abstime abstime;
uint32_t count;
irqstate_t flags;
/*
* Counter state. Marked volatile as they may change
* inside this routine but outside the irqsave/restore
* pair. Discourage the compiler from moving loads/stores
* to these outside of the protected range.
*/
static volatile hrt_abstime base_time;
static volatile uint32_t last_count;
/* prevent re-entry */
flags = enter_critical_section();
/* get the current counter value */
count = rCV;
/*
* Determine whether the counter has wrapped since the
* last time we're called.
*
* This simple test is sufficient due to the guarantee that
* we are always called at least once per counter period.
*/
if (count < last_count) {
base_time += HRT_COUNTER_PERIOD;
}
/* save the count for next time */
last_count = count;
/* compute the current time */
abstime = HRT_COUNTER_SCALE(base_time + count);
leave_critical_section(flags);
return abstime;
}
/**
* Convert a timespec to absolute time
*/
hrt_abstime
ts_to_abstime(struct timespec *ts)
{
hrt_abstime result;
result = (hrt_abstime)(ts->tv_sec) * 1000000;
result += ts->tv_nsec / 1000;
return result;
}
/**
* Convert absolute time to a timespec.
*/
void
abstime_to_ts(struct timespec *ts, hrt_abstime abstime)
{
ts->tv_sec = abstime / 1000000;
abstime -= ts->tv_sec * 1000000;
ts->tv_nsec = abstime * 1000;
}
/**
* Compare a time value with the current time.
*/
hrt_abstime
hrt_elapsed_time(const volatile hrt_abstime *then)
{
irqstate_t flags = enter_critical_section();
hrt_abstime delta = hrt_absolute_time() - *then;
leave_critical_section(flags);
return delta;
}
/**
* Store the absolute time in an interrupt-safe fashion
*/
hrt_abstime
hrt_store_absolute_time(volatile hrt_abstime *now)
{
irqstate_t flags = enter_critical_section();
hrt_abstime ts = hrt_absolute_time();
leave_critical_section(flags);
return ts;
}
/**
* Initalise the high-resolution timing module.
*/
void
hrt_init(void)
{
sq_init(&callout_queue);
hrt_tim_init();
#ifdef HRT_PPM_CHANNEL
/* configure the PPM input pin */
stm32_configgpio(GPIO_PPM_IN);
#endif
}
/**
* Call callout(arg) after interval has elapsed.
*/
void
hrt_call_after(struct hrt_call *entry, hrt_abstime delay, hrt_callout callout, void *arg)
{
hrt_call_internal(entry,
hrt_absolute_time() + delay,
0,
callout,
arg);
}
/**
* Call callout(arg) at calltime.
*/
void
hrt_call_at(struct hrt_call *entry, hrt_abstime calltime, hrt_callout callout, void *arg)
{
hrt_call_internal(entry, calltime, 0, callout, arg);
}
/**
* Call callout(arg) every period.
*/
void
hrt_call_every(struct hrt_call *entry, hrt_abstime delay, hrt_abstime interval, hrt_callout callout, void *arg)
{
hrt_call_internal(entry,
hrt_absolute_time() + delay,
interval,
callout,
arg);
}
static void
hrt_call_internal(struct hrt_call *entry, hrt_abstime deadline, hrt_abstime interval, hrt_callout callout, void *arg)
{
irqstate_t flags = enter_critical_section();
/* if the entry is currently queued, remove it */
/* note that we are using a potentially uninitialised
entry->link here, but it is safe as sq_rem() doesn't
dereference the passed node unless it is found in the
list. So we potentially waste a bit of time searching the
queue for the uninitialised entry->link but we don't do
anything actually unsafe.
*/
if (entry->deadline != 0) {
sq_rem(&entry->link, &callout_queue);
}
entry->deadline = deadline;
entry->period = interval;
entry->callout = callout;
entry->arg = arg;
hrt_call_enter(entry);
leave_critical_section(flags);
}
/**
* If this returns true, the call has been invoked and removed from the callout list.
*
* Always returns false for repeating callouts.
*/
bool
hrt_called(struct hrt_call *entry)
{
return (entry->deadline == 0);
}
/**
* Remove the entry from the callout list.
*/
void
hrt_cancel(struct hrt_call *entry)
{
irqstate_t flags = enter_critical_section();
sq_rem(&entry->link, &callout_queue);
entry->deadline = 0;
/* if this is a periodic call being removed by the callout, prevent it from
* being re-entered when the callout returns.
*/
entry->period = 0;
leave_critical_section(flags);
}
static void
hrt_call_enter(struct hrt_call *entry)
{
struct hrt_call *call, *next;
call = (struct hrt_call *)sq_peek(&callout_queue);
if ((call == NULL) || (entry->deadline < call->deadline)) {
sq_addfirst(&entry->link, &callout_queue);
//lldbg("call enter at head, reschedule\n");
/* we changed the next deadline, reschedule the timer event */
hrt_call_reschedule();
} else {
do {
next = (struct hrt_call *)sq_next(&call->link);
if ((next == NULL) || (entry->deadline < next->deadline)) {
//lldbg("call enter after head\n");
sq_addafter(&call->link, &entry->link, &callout_queue);
break;
}
} while ((call = next) != NULL);
}
//lldbg("scheduled\n");
}
static void
hrt_call_invoke(void)
{
struct hrt_call *call;
hrt_abstime deadline;
while (true) {
/* get the current time */
hrt_abstime now = hrt_absolute_time();
call = (struct hrt_call *)sq_peek(&callout_queue);
if (call == NULL) {
break;
}
if (call->deadline > now) {
break;
}
sq_rem(&call->link, &callout_queue);
//lldbg("call pop\n");
/* save the intended deadline for periodic calls */
deadline = call->deadline;
/* zero the deadline, as the call has occurred */
call->deadline = 0;
/* invoke the callout (if there is one) */
if (call->callout) {
//lldbg("call %p: %p(%p)\n", call, call->callout, call->arg);
call->callout(call->arg);
}
/* if the callout has a non-zero period, it has to be re-entered */
if (call->period != 0) {
// re-check call->deadline to allow for
// callouts to re-schedule themselves
// using hrt_call_delay()
if (call->deadline <= now) {
call->deadline = deadline + call->period;
}
hrt_call_enter(call);
}
}
}
/**
* Reschedule the next timer interrupt.
*
* This routine must be called with interrupts disabled.
*/
static void
hrt_call_reschedule()
{
hrt_abstime now = hrt_absolute_time();
struct hrt_call *next = (struct hrt_call *)sq_peek(&callout_queue);
hrt_abstime deadline = now + HRT_INTERVAL_MAX;
/*
* Determine what the next deadline will be.
*
* Note that we ensure that this will be within the counter
* period, so that when we truncate all but the low 16 bits
* the next time the compare matches it will be the deadline
* we want.
*
* It is important for accurate timekeeping that the compare
* interrupt fires sufficiently often that the base_time update in
* hrt_absolute_time runs at least once per timer period.
*/
if (next != NULL) {
//lldbg("entry in queue\n");
if (next->deadline <= (now + HRT_INTERVAL_MIN)) {
//lldbg("pre-expired\n");
/* set a minimal deadline so that we call ASAP */
deadline = now + HRT_INTERVAL_MIN;
} else if (next->deadline < deadline) {
//lldbg("due soon\n");
deadline = next->deadline;
}
}
//lldbg("schedule for %u at %u\n", (unsigned)(deadline & 0xffffffff), (unsigned)(now & 0xffffffff));
/* set the new compare value and remember it for latency tracking */
rRC = latency_baseline = HRT_TIME_SCALE(deadline) & UINT16_MAX;
}
static void
hrt_latency_update(void)
{
uint16_t latency = latency_actual - latency_baseline;
unsigned index;
/* bounded buckets */
for (index = 0; index < LATENCY_BUCKET_COUNT; index++) {
if (latency <= latency_buckets[index]) {
latency_counters[index]++;
return;
}
}
/* catch-all at the end */
latency_counters[index]++;
}
void
hrt_call_init(struct hrt_call *entry)
{
memset(entry, 0, sizeof(*entry));
}
void
hrt_call_delay(struct hrt_call *entry, hrt_abstime delay)
{
entry->deadline = hrt_absolute_time() + delay;
}
#endif /* HRT_TIMER_CHANNEL */