diff --git a/core/esp_timer.c b/core/esp_timer.c new file mode 100644 index 0000000..b00e086 --- /dev/null +++ b/core/esp_timer.c @@ -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 +#include +#include + +/* + * 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); +} diff --git a/core/include/esp/gpio.h b/core/include/esp/gpio.h index 3ad8ded..11bf9b2 100644 --- a/core/include/esp/gpio.h +++ b/core/include/esp/gpio.h @@ -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 diff --git a/core/include/esp/registers.h b/core/include/esp/registers.h index 66d6f7b..7468dfb 100644 --- a/core/include/esp/registers.h +++ b/core/include/esp/registers.h @@ -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) diff --git a/core/include/esp/timer.h b/core/include/esp/timer.h new file mode 100644 index 0000000..d1eea45 --- /dev/null +++ b/core/include/esp/timer.h @@ -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 +#include +#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 diff --git a/core/include/esp/timer_private.h b/core/include/esp/timer_private.h new file mode 100644 index 0000000..cc68a3b --- /dev/null +++ b/core/include/esp/timer_private.h @@ -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 +#include +#include + +/* 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 diff --git a/core/include/esp8266.h b/core/include/esp8266.h index f6ede41..968a522 100644 --- a/core/include/esp8266.h +++ b/core/include/esp8266.h @@ -16,5 +16,6 @@ #include "esp/cpu.h" #include "esp/iomux.h" #include "esp/gpio.h" +#include "esp/timer.h" #endif diff --git a/examples/blink_timers/Makefile b/examples/blink_timers/Makefile new file mode 100644 index 0000000..01c3e9e --- /dev/null +++ b/examples/blink_timers/Makefile @@ -0,0 +1,2 @@ +PROGRAM=blink_timers +include ../../common.mk diff --git a/examples/blink_timers/blink_timers.c b/examples/blink_timers/blink_timers.c new file mode 100644 index 0000000..c8baeb2 --- /dev/null +++ b/examples/blink_timers/blink_timers.c @@ -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); +}