timers.h: Remove compile-time-auto-inlining complexity

Fixes bug mentioned in #72 (oops!), also progress towards #57.
This commit is contained in:
Angus Gratton 2015-11-28 16:17:31 +11:00
parent d14d5b1aab
commit 336bad573d
5 changed files with 186 additions and 309 deletions

View file

@ -6,30 +6,122 @@
* BSD Licensed as described in the file LICENSE * BSD Licensed as described in the file LICENSE
*/ */
#include <esp/timer.h> #include <esp/timer.h>
#include <esp/dport_regs.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
/* /* Timer divisor number to maximum frequency */
* These are the runtime implementations for functions that are linked in if any of #define _FREQ_DIV1 (80*1000*1000)
* the arguments aren't known at compile time (values are evaluated at #define _FREQ_DIV16 (5*1000*1000)
* compile time otherwise.) #define _FREQ_DIV256 312500
*/
uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div) const static uint32_t IROM _TIMER_FREQS[] = { _FREQ_DIV1, _FREQ_DIV16, _FREQ_DIV256 };
/* Timer divisor index to divisor value */
const static uint32_t IROM _TIMER_DIV_VAL[] = { 1, 16, 256 };
void timer_set_interrupts(const timer_frc_t frc, bool enable)
{ {
return _timer_freq_to_count_impl(frc, freq, div); const uint32_t dp_bit = (frc == FRC1) ? DPORT_INT_ENABLE_FRC1 : DPORT_INT_ENABLE_FRC2;
const uint32_t int_mask = BIT((frc == FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
if(enable) {
DPORT.INT_ENABLE |= dp_bit;
_xt_isr_unmask(int_mask);
} else {
DPORT.INT_ENABLE &= ~dp_bit;
_xt_isr_mask(int_mask);
}
} }
uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div) uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
{ {
return _timer_time_to_count_runtime(frc, us, div); if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */
if(freq > _TIMER_FREQS[div])
return 0; /* out of range for given divisor */
uint64_t counts = _TIMER_FREQS[div]/freq;
return counts;
} }
bool _timer_set_frequency_runtime(const timer_frc_t frc, uint32_t freq) uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
{ {
return _timer_set_frequency_runtime(frc, freq); if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */
const uint32_t TIMER_MAX = timer_max_load(frc);
if(div != TIMER_CLKDIV_256) /* timer tick in MHz */
{
/* timer is either 80MHz or 5MHz, so either 80 or 5 MHz counts per us */
const uint32_t counts_per_us = ((div == TIMER_CLKDIV_1) ? _FREQ_DIV1 : _FREQ_DIV16)/1000/1000;
if(us > TIMER_MAX/counts_per_us)
return 0; /* Multiplying us by mhz_per_count will overflow TIMER_MAX */
return us*counts_per_us;
}
else /* /256 divider, 312.5kHz freq so need to scale up */
{
/* derived from naive floating point equation that we can't use:
counts = (us/1000/1000)*_FREQ_DIV256;
counts = (us/2000)*(_FREQ_DIV256/500);
counts = us*(_FREQ_DIV256/500)/2000;
*/
const uint32_t scalar = _FREQ_DIV256/500;
if(us > 1+UINT32_MAX/scalar)
return 0; /* Multiplying us by _FREQ_DIV256/500 will overflow uint32_t */
uint32_t counts = (us*scalar)/2000;
if(counts > TIMER_MAX)
return 0; /* counts value too high for timer type */
return counts;
}
} }
bool _timer_set_timeout_runtime(const timer_frc_t frc, uint32_t us) bool timer_set_frequency(const timer_frc_t frc, uint32_t freq)
{ {
return _timer_set_timeout_impl(frc, us); uint32_t counts = 0;
timer_clkdiv_t div = timer_freq_to_div(freq);
counts = timer_freq_to_count(frc, freq, div);
if(counts == 0)
{
printf("ABORT: No counter for timer %u frequency %u\r\n", frc, freq);
abort();
}
timer_set_divider(frc, div);
if(frc == FRC1)
{
timer_set_load(frc, counts);
timer_set_reload(frc, true);
}
else /* FRC2 */
{
/* assume that if this overflows it'll wrap, so we'll get desired behaviour */
TIMER(1).ALARM = counts + TIMER(1).COUNT;
}
return true;
}
bool timer_set_timeout(const timer_frc_t frc, uint32_t us)
{
uint32_t counts = 0;
timer_clkdiv_t div = timer_time_to_div(us);
counts = timer_time_to_count(frc, us, div);
if(counts == 0)
return false; /* can't set frequency */
timer_set_divider(frc, div);
if(frc == FRC1)
{
timer_set_load(frc, counts);
}
else /* FRC2 */
{
TIMER(1).ALARM = counts + TIMER(1).COUNT;
}
return true;
} }

View file

@ -23,43 +23,73 @@ typedef enum {
} timer_frc_t; } timer_frc_t;
/* Return current count value for timer. */ /* Return current count value for timer. */
INLINED uint32_t timer_get_count(const timer_frc_t frc); static inline uint32_t timer_get_count(const timer_frc_t frc)
{
return TIMER(frc).COUNT;
}
/* Return current load value for timer. */ /* Return current load value for timer. */
INLINED uint32_t timer_get_load(const timer_frc_t frc); static inline uint32_t timer_get_load(const timer_frc_t frc)
{
return TIMER(frc).LOAD;
}
/* Write load value for timer. */ /* Write load value for timer. */
INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load); static inline void timer_set_load(const timer_frc_t frc, const uint32_t load)
{
TIMER(frc).LOAD = load;
}
/* Returns maximum load value for timer. */ /* Returns maximum load value for timer. */
INLINED uint32_t timer_max_load(const timer_frc_t frc); static inline uint32_t timer_max_load(const timer_frc_t frc)
{
return (frc == FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
}
/* Set the timer divider value */ /* Set the timer divider value */
INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div); static inline void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div)
{
if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return;
TIMER(frc).CTRL = SET_FIELD(TIMER(frc).CTRL, TIMER_CTRL_CLKDIV, div);
}
/* Enable or disable timer interrupts /* Enable or disable timer interrupts
This both sets the xtensa interrupt mask and writes to the DPORT register This both sets the xtensa interrupt mask and writes to the DPORT register
that allows timer interrupts. that allows timer interrupts.
*/ */
INLINED void timer_set_interrupts(const timer_frc_t frc, bool enable); void timer_set_interrupts(const timer_frc_t frc, bool enable);
/* Turn the timer on or off */ /* Turn the timer on or off */
INLINED void timer_set_run(const timer_frc_t frc, const bool run); static inline void timer_set_run(const timer_frc_t frc, const bool run)
{
if (run)
TIMER(frc).CTRL |= TIMER_CTRL_RUN;
else
TIMER(frc).CTRL &= ~TIMER_CTRL_RUN;
}
/* Get the run state of the timer (on or off) */ /* Get the run state of the timer (on or off) */
INLINED bool timer_get_run(const timer_frc_t frc); static inline bool timer_get_run(const timer_frc_t frc)
{
return TIMER(frc).CTRL & TIMER_CTRL_RUN;
}
/* Set timer auto-reload on or off */ /* Set timer auto-reload on or off */
INLINED void timer_set_reload(const timer_frc_t frc, const bool reload); static inline void timer_set_reload(const timer_frc_t frc, const bool reload)
{
if (reload)
TIMER(frc).CTRL |= TIMER_CTRL_RELOAD;
else
TIMER(frc).CTRL &= ~TIMER_CTRL_RELOAD;
}
/* Get the auto-reload state of the timer (on or off) */ /* Get the auto-reload state of the timer (on or off) */
INLINED bool timer_get_reload(const timer_frc_t frc); static inline bool timer_get_reload(const timer_frc_t frc)
{
/* Return a suitable timer divider for the specified frequency, return TIMER(frc).CTRL & TIMER_CTRL_RELOAD;
or -1 if none is found. }
*/
INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq);
/* Return the number of timer counts to achieve the specified /* Return the number of timer counts to achieve the specified
* frequency with the specified divisor. * frequency with the specified divisor.
@ -68,14 +98,41 @@ INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq);
* *
* Returns 0 if the given freq/divisor combo cannot be achieved. * Returns 0 if the given freq/divisor combo cannot be achieved.
* *
* Compile-time evaluates if all arguments are available at compile time.
*/ */
INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, uint32_t freq, const timer_clkdiv_t div); uint32_t timer_freq_to_count(const timer_frc_t frc, uint32_t freq, const timer_clkdiv_t div);
/* Return a suitable timer divider for the specified frequency,
or -1 if none is found.
*/
static inline timer_clkdiv_t timer_freq_to_div(uint32_t freq)
{
/*
try to maintain resolution without risking overflows.
these values are a bit arbitrary at the moment! */
if(freq > 100*1000)
return TIMER_CLKDIV_1;
else if(freq > 100)
return TIMER_CLKDIV_16;
else
return TIMER_CLKDIV_256;
}
/* Return a suitable timer divider for the specified duration in /* Return a suitable timer divider for the specified duration in
microseconds or -1 if none is found. microseconds or -1 if none is found.
*/ */
INLINED timer_clkdiv_t timer_time_to_div(uint32_t us); static inline timer_clkdiv_t timer_time_to_div(uint32_t us)
{
/*
try to maintain resolution without risking overflows. Similar to
timer_freq_to_div, these values are a bit arbitrary at the
moment! */
if(us < 1000)
return TIMER_CLKDIV_1;
else if(us < 10*1000)
return TIMER_CLKDIV_16;
else
return TIMER_CLKDIV_256;
}
/* Return the number of timer counts for the specified timer duration /* Return the number of timer counts for the specified timer duration
* in microseconds, when using the specified divisor. * in microseconds, when using the specified divisor.
@ -84,9 +141,8 @@ INLINED timer_clkdiv_t timer_time_to_div(uint32_t us);
* *
* Returns 0 if the given time/divisor combo cannot be achieved. * Returns 0 if the given time/divisor combo cannot be achieved.
* *
* Compile-time evaluates if all arguments are available at compile time.
*/ */
INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div); uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div);
/* Set a target timer interrupt frequency in Hz. /* Set a target timer interrupt frequency in Hz.
@ -104,11 +160,8 @@ INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const t
timer_set_run. timer_set_run.
Returns true on success, false if given frequency could not be set. Returns true on success, false if given frequency could not be set.
Compile-time evaluates to simple register writes if all arguments
are available at compile time.
*/ */
INLINED bool timer_set_frequency(const timer_frc_t frc, uint32_t freq); bool timer_set_frequency(const timer_frc_t frc, uint32_t freq);
/* Sets the timer for a oneshot interrupt in 'us' microseconds. /* Sets the timer for a oneshot interrupt in 'us' microseconds.
@ -124,13 +177,8 @@ INLINED bool timer_set_frequency(const timer_frc_t frc, uint32_t freq);
timer_set_run(TIMER_FRC1, false); timer_set_run(TIMER_FRC1, false);
Returns true if the timeout was successfully set. Returns true if the timeout was successfully set.
Compile-time evaluates to simple register writes if all arguments
are available at compile time.
*/ */
INLINED bool timer_set_timeout(const timer_frc_t frc, uint32_t us); bool timer_set_timeout(const timer_frc_t frc, uint32_t us);
#include "timer_private.h"
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -1,266 +0,0 @@
/* Private header parts of the timer API implementation
*
* Part of esp-open-rtos
* Copyright (C) 2015 Superhouse Automation Pty Ltd
* BSD Licensed as described in the file LICENSE
*/
#ifndef _ESP_TIMER_PRIVATE_H
#define _ESP_TIMER_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include "esp/dport_regs.h"
/* Timer divisor index to max frequency */
#define _FREQ_DIV1 (80*1000*1000)
#define _FREQ_DIV16 (5*1000*1000)
#define _FREQ_DIV256 312500
const static uint32_t IROM _TIMER_FREQS[] = { _FREQ_DIV1, _FREQ_DIV16, _FREQ_DIV256 };
/* Timer divisor index to divisor value */
const static uint32_t IROM _TIMER_DIV_VAL[] = { 1, 16, 256 };
INLINED uint32_t timer_get_count(const timer_frc_t frc)
{
return TIMER(frc).COUNT;
}
INLINED uint32_t timer_get_load(const timer_frc_t frc)
{
return TIMER(frc).LOAD;
}
INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load)
{
TIMER(frc).LOAD = load;
}
INLINED uint32_t timer_max_load(const timer_frc_t frc)
{
return (frc == FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
}
INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div)
{
if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return;
TIMER(frc).CTRL = SET_FIELD(TIMER(frc).CTRL, TIMER_CTRL_CLKDIV, div);
}
INLINED void timer_set_interrupts(const timer_frc_t frc, bool enable)
{
const uint32_t dp_bit = (frc == FRC1) ? DPORT_INT_ENABLE_FRC1 : DPORT_INT_ENABLE_FRC2;
const uint32_t int_mask = BIT((frc == FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
if(enable) {
DPORT.INT_ENABLE |= dp_bit;
_xt_isr_unmask(int_mask);
} else {
DPORT.INT_ENABLE &= ~dp_bit;
_xt_isr_mask(int_mask);
}
}
INLINED void timer_set_run(const timer_frc_t frc, const bool run)
{
if (run)
TIMER(frc).CTRL |= TIMER_CTRL_RUN;
else
TIMER(frc).CTRL &= ~TIMER_CTRL_RUN;
}
INLINED bool timer_get_run(const timer_frc_t frc)
{
return TIMER(frc).CTRL & TIMER_CTRL_RUN;
}
INLINED void timer_set_reload(const timer_frc_t frc, const bool reload)
{
if (reload)
TIMER(frc).CTRL |= TIMER_CTRL_RELOAD;
else
TIMER(frc).CTRL &= ~TIMER_CTRL_RELOAD;
}
INLINED bool timer_get_reload(const timer_frc_t frc)
{
return TIMER(frc).CTRL & TIMER_CTRL_RELOAD;
}
INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq)
{
/*
try to maintain resolution without risking overflows.
these values are a bit arbitrary at the moment! */
if(freq > 100*1000)
return TIMER_CLKDIV_1;
else if(freq > 100)
return TIMER_CLKDIV_16;
else
return TIMER_CLKDIV_256;
}
/* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */
INLINED uint32_t _timer_freq_to_count_impl(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
{
if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */
if(freq > _TIMER_FREQS[div])
return 0; /* out of range for given divisor */
uint64_t counts = _TIMER_FREQS[div]/freq;
return counts;
}
uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div);
INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
{
if(__builtin_constant_p(frc) && __builtin_constant_p(freq) && __builtin_constant_p(div))
return _timer_freq_to_count_impl(frc, freq, div);
else
return _timer_freq_to_count_runtime(frc, freq, div);
}
INLINED timer_clkdiv_t timer_time_to_div(uint32_t us)
{
/*
try to maintain resolution without risking overflows. Similar to
timer_freq_to_div, these values are a bit arbitrary at the
moment! */
if(us < 1000)
return TIMER_CLKDIV_1;
else if(us < 10*1000)
return TIMER_CLKDIV_16;
else
return TIMER_CLKDIV_256;
}
/* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */
INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
{
if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */
const uint32_t TIMER_MAX = timer_max_load(frc);
if(div != TIMER_CLKDIV_256) /* timer tick in MHz */
{
/* timer is either 80MHz or 5MHz, so either 80 or 5 MHz counts per us */
const uint32_t counts_per_us = ((div == TIMER_CLKDIV_1) ? _FREQ_DIV1 : _FREQ_DIV16)/1000/1000;
if(us > TIMER_MAX/counts_per_us)
return 0; /* Multiplying us by mhz_per_count will overflow TIMER_MAX */
return us*counts_per_us;
}
else /* /256 divider, 312.5kHz freq so need to scale up */
{
/* derived from naive floating point equation that we can't use:
counts = (us/1000/1000)*_FREQ_DIV256;
counts = (us/2000)*(_FREQ_DIV256/500);
counts = us*(_FREQ_DIV256/500)/2000;
*/
const uint32_t scalar = _FREQ_DIV256/500;
if(us > 1+UINT32_MAX/scalar)
return 0; /* Multiplying us by _FREQ_DIV256/500 will overflow uint32_t */
uint32_t counts = (us*scalar)/2000;
if(counts > TIMER_MAX)
return 0; /* counts value too high for timer type */
return counts;
}
}
uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div);
INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
{
if(__builtin_constant_p(frc) && __builtin_constant_p(us) && __builtin_constant_p(div))
return _timer_time_to_count_impl(frc, us, div);
else
return _timer_time_to_count_runtime(frc, us, div);
}
/* timer_set_frequency implementation - inline if all args are constant, call normally otherwise */
INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
{
uint32_t counts = 0;
timer_clkdiv_t div = timer_freq_to_div(freq);
counts = timer_freq_to_count(frc, freq, div);
if(counts == 0)
{
printf("ABORT: No counter for timer %u frequency %u\r\n", frc, freq);
abort();
}
timer_set_divider(frc, div);
if(frc == FRC1)
{
timer_set_load(frc, counts);
timer_set_reload(frc, true);
}
else /* FRC2 */
{
/* assume that if this overflows it'll wrap, so we'll get desired behaviour */
TIMER(1).ALARM = counts + TIMER(1).COUNT;
}
return true;
}
bool _timer_set_frequency_runtime(const timer_frc_t frc, uint32_t freq);
INLINED bool timer_set_frequency(const timer_frc_t frc, uint32_t freq)
{
if(__builtin_constant_p(frc) && __builtin_constant_p(freq))
return _timer_set_frequency_impl(frc, freq);
else
return _timer_set_frequency_runtime(frc, freq);
}
/* timer_set_timeout implementation - inline if all args are constant, call normally otherwise */
INLINED bool _timer_set_timeout_impl(const timer_frc_t frc, uint32_t us)
{
uint32_t counts = 0;
timer_clkdiv_t div = timer_time_to_div(us);
counts = timer_time_to_count(frc, us, div);
if(counts == 0)
return false; /* can't set frequency */
timer_set_divider(frc, div);
if(frc == FRC1)
{
timer_set_load(frc, counts);
}
else /* FRC2 */
{
TIMER(1).ALARM = counts + TIMER(1).COUNT;
}
return true;
}
bool _timer_set_timeout_runtime(const timer_frc_t frc, uint32_t us);
INLINED bool timer_set_timeout(const timer_frc_t frc, uint32_t us)
{
if(__builtin_constant_p(frc) && __builtin_constant_p(us))
return _timer_set_timeout_impl(frc, us);
else
return _timer_set_timeout_runtime(frc, us);
}
#ifdef __cplusplus
}
#endif
#endif

View file

@ -7,10 +7,12 @@
#include <stdint.h> #include <stdint.h>
#include <sys/types.h> #include <sys/types.h>
#include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <string.h> #include <string.h>
#include <esp8266.h> #include <esp8266.h>
#include <esp/uart.h> #include <esp/uart.h>
#include <stdio.h>
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"

View file

@ -25,6 +25,7 @@
#include <esp8266.h> #include <esp8266.h>
#include <FreeRTOS.h> #include <FreeRTOS.h>
#include <semphr.h> #include <semphr.h>
#include <stdio.h>
#if (configUSE_COUNTING_SEMAPHORES == 0) #if (configUSE_COUNTING_SEMAPHORES == 0)
#error "You need to define configUSE_COUNTING_SEMAPHORES in a local FreeRTOSConfig.h, see examples/terminal/FreeRTOSConfig.h" #error "You need to define configUSE_COUNTING_SEMAPHORES in a local FreeRTOSConfig.h, see examples/terminal/FreeRTOSConfig.h"