Hardware timer support

This commit is contained in:
Angus Gratton 2015-06-18 08:59:33 +10:00
parent 5151ccc3b2
commit 1d72ed3f70
8 changed files with 527 additions and 4 deletions

35
core/esp_timer.c Normal file
View file

@ -0,0 +1,35 @@
/* Timer peripheral management functions for esp/timer.h.
*
*
* Part of esp-open-rtos
* Copyright (C) 2015 Superhouse Automation Pty Ltd
* BSD Licensed as described in the file LICENSE
*/
#include <esp/timer.h>
#include <stdio.h>
#include <stdlib.h>
/*
* These are the runtime implementations for functions that are linked in if any of
* the arguments aren't known at compile time (values are evaluated at
* compile time otherwise.)
*/
uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_div_t div)
{
return _timer_freq_to_count_impl(frc, freq, div);
}
uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_div_t div)
{
return _timer_time_to_count_runtime(frc, us, div);
}
bool _timer_set_frequency_runtime(const timer_frc_t frc, uint32_t freq)
{
return _timer_set_frequency_runtime(frc, freq);
}
bool _timer_set_timeout_runtime(const timer_frc_t frc, uint32_t us)
{
return _timer_set_timeout_impl(frc, us);
}

View file

@ -117,6 +117,8 @@ typedef enum {
extern void gpio_interrupt_handler(void);
/* Set the interrupt type for a given pin
*
* If int_type is not INT_NONE, the gpio_interrupt_handler will be attached and unmasked.
*/
INLINED void gpio_set_interrupt(const uint8_t gpio_num, const gpio_interrupt_t int_type)
{
@ -128,4 +130,10 @@ INLINED void gpio_set_interrupt(const uint8_t gpio_num, const gpio_interrupt_t i
}
}
/* Return the interrupt type set for a pin */
INLINED gpio_interrupt_t gpio_get_interrupt(const uint8_t gpio_num)
{
return (gpio_interrupt_t)(GPIO_CTRL_REG(gpio_num) & GPIO_INT_MASK);
}
#endif

View file

@ -159,15 +159,17 @@ typedef volatile uint32_t *esp_reg_t;
/* Load value for FRC1, read/write.
When TIMER_CTRL_RELOAD is cleared in TIMER_FRC1_CTRL_REG, FRC1
will reload to 0x7fffff once overflowed (unless the load value is
rewritten in the interrupt handler.)
When TIMER_CTRL_RELOAD is cleared in TIMER_FRC1_CTRL_REG, FRC1 will
reload to TIMER_FRC1_MAX_LOAD once overflowed (unless the load
value is rewritten in the interrupt handler.)
When TIMER_CTRL_RELOAD is set in TIMER_FRC1_CTRL_REG, FRC1 will reload
from the load register value once overflowed.
*/
#define TIMER_FRC1_LOAD_REG _REG(TIMER_BASE, 0x00)
#define TIMER_FRC1_MAX_LOAD 0x7fffff
/* Current count value for FRC1, read only? */
#define TIMER_FRC1_COUNT_REG _REG(TIMER_BASE, 0x04)
@ -254,7 +256,7 @@ typedef volatile uint32_t *esp_reg_t;
/* Timer auto-reload bit
This bit interacts with TIMER_FCR1_LOAD_REG & TIMER_FCR2_LOAD_REG
This bit interacts with TIMER_FRC1_LOAD_REG & TIMER_FRC2_LOAD_REG
differently, see those registers for details.
*/
#define TIMER_CTRL_RELOAD BIT(6)

138
core/include/esp/timer.h Normal file
View file

@ -0,0 +1,138 @@
/** esp/timer.h
*
* Timer (FRC1 & FRC2) functions.
*
* Part of esp-open-rtos
* Copyright (C) 2015 Superhouse Automation Pty Ltd
* BSD Licensed as described in the file LICENSE
*/
#ifndef _ESP_TIMER_H
#define _ESP_TIMER_H
#include <stdbool.h>
#include <xtensa_interrupts.h>
#include "esp/registers.h"
#include "esp/cpu.h"
typedef enum {
TIMER_FRC1,
TIMER_FRC2,
} timer_frc_t;
/* Return current count value for timer. */
INLINED uint32_t timer_get_count(const timer_frc_t frc);
/* Return current load value for timer. */
INLINED uint32_t timer_get_load(const timer_frc_t frc);
/* Write load value for timer. */
INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load);
/* Returns maximum load value for timer. */
INLINED uint32_t timer_max_load(const timer_frc_t frc);
typedef enum {
TIMER_DIV1,
TIMER_DIV16,
TIMER_DIV256,
} timer_div_t;
/* Set the timer divider value */
INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div);
/* Enable or disable timer interrupts
This both sets the xtensa interrupt mask and writes to the DPORT register
that allows timer interrupts.
*/
INLINED void timer_set_interrupts(const timer_frc_t frc, bool enable);
/* Turn the timer on or off */
INLINED void timer_set_run(const timer_frc_t frc, const bool run);
/* Get the run state of the timer (on or off) */
INLINED bool timer_get_run(const timer_frc_t frc);
/* Set timer auto-reload on or off */
INLINED void timer_set_reload(const timer_frc_t frc, const bool reload);
/* Get the auto-reload state of the timer (on or off) */
INLINED bool timer_get_reload(const timer_frc_t frc);
/* Return a suitable timer divider for the specified frequency,
or -1 if none is found.
*/
INLINED timer_div_t timer_freq_to_div(uint32_t freq);
/* Return the number of timer counts to achieve the specified
* frequency with the specified divisor.
*
* frc parameter is used to check out-of-range values for timer size.
*
* 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_div_t div);
/* Return a suitable timer divider for the specified duration in
microseconds or -1 if none is found.
*/
INLINED timer_div_t timer_time_to_div(uint32_t us);
/* Return the number of timer counts for the specified timer duration
* in microseconds, when using the specified divisor.
*
* frc paraemter is used to check out-of-range values for timer size.
*
* 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_div_t div);
/* Set a target timer interrupt frequency in Hz.
For FRC1 this sets the timer load value and enables autoreload so
the interrupt will fire regularly with the target frequency.
For FRC2 this sets the timer match value so the next interrupt
comes in line with the target frequency. However this won't repeat
automatically, you have to call timer_set_frequency again when the
timer interrupt runs.
Will change the timer divisor value to suit the target frequency.
Does not start/stop the timer, you have to do this manually via
timer_set_run.
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);
/* Sets the timer for a oneshot interrupt in 'us' microseconds.
Will change the timer divisor value to suit the target time.
Does not change the autoreload setting.
For FRC2 this sets the timer match value relative to the current
load value.
Note that for a true "one shot" timeout with FRC1 then you need to
also disable FRC1 in the timer interrupt handler by calling
timer_set_run(TIMER_FRC1, false);
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);
#include "timer_private.h"
#endif

View file

@ -0,0 +1,273 @@
/* 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
#include <limits.h>
#include <stdio.h>
#include <stdlib.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 };
/* Timer divisor to mask value */
const static uint32_t IROM _TIMER_DIV_REG[] = { TIMER_CTRL_DIV_1, TIMER_CTRL_DIV_16, TIMER_CTRL_DIV_256 };
INLINED esp_reg_t _timer_ctrl_reg(const timer_frc_t frc)
{
return (frc == TIMER_FRC1) ? &TIMER_FRC1_CTRL_REG : &TIMER_FRC2_CTRL_REG;
}
INLINED uint32_t timer_get_count(const timer_frc_t frc)
{
return (frc == TIMER_FRC1) ? TIMER_FRC1_COUNT_REG : TIMER_FRC2_COUNT_REG;
}
INLINED uint32_t timer_get_load(const timer_frc_t frc)
{
return (frc == TIMER_FRC1) ? TIMER_FRC1_LOAD_REG : TIMER_FRC2_LOAD_REG;
}
INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load)
{
if(frc == TIMER_FRC1)
TIMER_FRC1_LOAD_REG = load;
else
TIMER_FRC2_LOAD_REG = load;
}
INLINED uint32_t timer_max_load(const timer_frc_t frc)
{
return (frc == TIMER_FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
}
INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div)
{
if(div < TIMER_DIV1 || div > TIMER_DIV256)
return;
esp_reg_t ctrl = _timer_ctrl_reg(frc);
*ctrl = (*ctrl & ~TIMER_CTRL_DIV_MASK) | (_TIMER_DIV_REG[div] & TIMER_CTRL_DIV_MASK);
}
INLINED void timer_set_interrupts(const timer_frc_t frc, bool enable)
{
const uint32_t dp_bit = (frc == TIMER_FRC1) ? INT_ENABLE_FRC1 : INT_ENABLE_FRC2;
const uint32_t int_mask = BIT((frc == TIMER_FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
if(enable) {
DP_INT_ENABLE_REG |= dp_bit;
_xt_isr_unmask(int_mask);
} else {
DP_INT_ENABLE_REG &= ~dp_bit;
_xt_isr_mask(int_mask);
}
}
INLINED void timer_set_run(const timer_frc_t frc, const bool run)
{
esp_reg_t ctrl = _timer_ctrl_reg(frc);
if (run)
*ctrl |= TIMER_CTRL_RUN;
else
*ctrl &= ~TIMER_CTRL_RUN;
}
INLINED bool timer_get_run(const timer_frc_t frc)
{
return *_timer_ctrl_reg(frc) & TIMER_CTRL_RUN;
}
INLINED void timer_set_reload(const timer_frc_t frc, const bool reload)
{
esp_reg_t ctrl = _timer_ctrl_reg(frc);
if (reload)
*ctrl |= TIMER_CTRL_RELOAD;
else
*ctrl &= ~TIMER_CTRL_RELOAD;
}
INLINED bool timer_get_reload(const timer_frc_t frc)
{
return *_timer_ctrl_reg(frc) & TIMER_CTRL_RELOAD;
}
INLINED timer_div_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_DIV1;
else if(freq > 100)
return TIMER_DIV16;
else
return TIMER_DIV256;
}
/* 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_div_t div)
{
if(div < TIMER_DIV1 || div > TIMER_DIV256)
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_div_t div);
INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq, const timer_div_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_div_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_DIV1;
else if(us < 10*1000)
return TIMER_DIV16;
else
return TIMER_DIV256;
}
/* 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_div_t div)
{
if(div < TIMER_DIV1 || div > TIMER_DIV256)
return 0; /* invalid divider */
const uint32_t TIMER_MAX = timer_max_load(frc);
if(div != TIMER_DIV256) /* 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_DIV1) ? _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_div_t div);
INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const timer_div_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_div_t div = timer_freq_to_div(freq);
counts = timer_freq_to_count(frc, freq, div);
if(counts == 0)
{
printf("ABORT: No counter for timer %d frequency %d\r\n", frc, freq);
abort();
}
timer_set_divider(frc, div);
if(frc == TIMER_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_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG;
}
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_div_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 == TIMER_FRC1)
{
timer_set_load(frc, counts);
}
else /* FRC2 */
{
TIMER_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG;
}
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);
}
#endif

View file

@ -16,5 +16,6 @@
#include "esp/cpu.h"
#include "esp/iomux.h"
#include "esp/gpio.h"
#include "esp/timer.h"
#endif

View file

@ -0,0 +1,2 @@
PROGRAM=blink_timers
include ../../common.mk

View file

@ -0,0 +1,64 @@
/* The "blink" example, but writing the LED from timer interrupts
*
* This sample code is in the public domain.
*/
#include "espressif/esp_common.h"
#include "espressif/sdk_private.h"
#include "FreeRTOS.h"
#include "task.h"
#include "esp8266.h"
const int gpio_frc1 = 12;
const int freq_frc1 = 1;
const int gpio_frc2 = 14;
const int freq_frc2 = 10;
static volatile uint32_t frc1_count;
static volatile uint32_t frc2_count;
void frc1_interrupt_handler(void)
{
frc1_count++;
gpio_toggle(gpio_frc1);
}
void frc2_interrupt_handler(void)
{
/* FRC2 needs the match register updated on each timer interrupt */
timer_set_frequency(TIMER_FRC2, freq_frc2);
frc2_count++;
gpio_toggle(gpio_frc2);
}
void user_init(void)
{
sdk_uart_div_modify(0, UART_CLK_FREQ / 115200);
/* configure GPIOs */
gpio_enable(gpio_frc1, GPIO_OUTPUT);
gpio_enable(gpio_frc2, GPIO_OUTPUT);
gpio_write(gpio_frc1, 1);
/* stop both timers and mask their interrupts as a precaution */
timer_set_interrupts(TIMER_FRC1, false);
timer_set_run(TIMER_FRC1, false);
timer_set_interrupts(TIMER_FRC2, false);
timer_set_run(TIMER_FRC2, false);
/* set up ISRs */
_xt_isr_attach(INUM_TIMER_FRC1, frc1_interrupt_handler);
_xt_isr_attach(INUM_TIMER_FRC2, frc2_interrupt_handler);
/* configure timer frequencies */
timer_set_frequency(TIMER_FRC1, freq_frc1);
timer_set_frequency(TIMER_FRC2, freq_frc2);
/* unmask interrupts and start timers */
timer_set_interrupts(TIMER_FRC1, true);
timer_set_run(TIMER_FRC1, true);
timer_set_interrupts(TIMER_FRC2, true);
timer_set_run(TIMER_FRC2, true);
gpio_write(gpio_frc1, 0);
}