Added esp/timer_regs.h and esp/dport_regs.h

This commit is contained in:
Alex Stewart 2015-08-19 11:34:18 -07:00
parent b271e19b51
commit 4fa66ca391
6 changed files with 234 additions and 227 deletions

View file

@ -14,12 +14,12 @@
* the arguments aren't known at compile time (values are evaluated at * the arguments aren't known at compile time (values are evaluated at
* compile time otherwise.) * compile time otherwise.)
*/ */
uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_div_t div) uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_clkdiv_t div)
{ {
return _timer_freq_to_count_impl(frc, freq, 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) uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
{ {
return _timer_time_to_count_runtime(frc, us, div); return _timer_time_to_count_runtime(frc, us, div);
} }

View file

@ -0,0 +1,53 @@
/* esp/dport_regs.h
*
* ESP8266 DPORT0 register definitions
*
* Not compatible with ESP SDK register access code.
*/
#ifndef _ESP_DPORT_REGS_H
#define _ESP_DPORT_REGS_H
#include "esp/types.h"
#include "common_macros.h"
#define DPORT_BASE 0x3ff00000
#define DPORT (*(struct DPORT_REGS *)(DPORT_BASE))
/* DPORT registers
Control various aspects of core/peripheral interaction... Not well
documented or understood.
*/
struct DPORT_REGS {
uint32_t volatile _unknown0; // 0x00
uint32_t volatile INT_ENABLE; // 0x04
} __attribute__ (( packed ));
_Static_assert(sizeof(struct DPORT_REGS) == 0x08, "DPORT_REGS is the wrong size");
/* Details for INT_ENABLE register */
/* Set flags to enable CPU interrupts from some peripherals. Read/write.
bit 0 - Is set by RTOS SDK startup code but function is unknown.
bit 1 - INT_ENABLE_TIMER0 allows TIMER 0 (FRC1) to trigger interrupt INUM_TIMER_FRC1.
bit 2 - INT_ENABLE_TIMER1 allows TIMER 1 (FRC2) to trigger interrupt INUM_TIMER_FRC2.
Espressif calls this register "EDGE_INT_ENABLE_REG". The "edge" in
question is (I think) the interrupt line from the peripheral, as
the interrupt status bit is set. There may be a similar register
for enabling "level" interrupts instead of edge triggering
- this is unknown.
*/
#define DPORT_INT_ENABLE_TIMER0 BIT(1)
#define DPORT_INT_ENABLE_TIMER1 BIT(2)
/* Aliases for the Espressif way of referring to TIMER0 (FRC1) and TIMER1
* (FRC2).. */
#define DPORT_INT_ENABLE_FRC1 DPORT_INT_ENABLE_TIMER0
#define DPORT_INT_ENABLE_FRC2 DPORT_INT_ENABLE_TIMER1
#endif /* _ESP_DPORT_REGS_H */

View file

@ -18,6 +18,8 @@
#include "esp/iomux_regs.h" #include "esp/iomux_regs.h"
#include "esp/gpio_regs.h" #include "esp/gpio_regs.h"
#include "esp/timer_regs.h"
#include "esp/dport_regs.h"
/* Internal macro, only defined in header body */ /* Internal macro, only defined in header body */
#define _REG(BASE, OFFSET) (*(esp_reg_t)((BASE)+(OFFSET))) #define _REG(BASE, OFFSET) (*(esp_reg_t)((BASE)+(OFFSET)))
@ -27,13 +29,13 @@
You shouldn't need to use these directly. You shouldn't need to use these directly.
*/ */
#define MMIO_BASE 0x60000000 #define MMIO_BASE 0x60000000
#define DPORT_BASE 0x3ff00000 //#define DPORT_BASE 0x3ff00000
#define UART0_BASE (MMIO_BASE + 0) #define UART0_BASE (MMIO_BASE + 0)
#define SPI1_BASE (MMIO_BASE + 0x0100) #define SPI1_BASE (MMIO_BASE + 0x0100)
#define SPI_BASE (MMIO_BASE + 0x0200) #define SPI_BASE (MMIO_BASE + 0x0200)
//#define GPIO0_BASE (MMIO_BASE + 0x0300) //#define GPIO0_BASE (MMIO_BASE + 0x0300)
#define TIMER_BASE (MMIO_BASE + 0x0600) //#define TIMER_BASE (MMIO_BASE + 0x0600)
#define RTC_BASE (MMIO_BASE + 0x0700) #define RTC_BASE (MMIO_BASE + 0x0700)
//#define IOMUX_BASE (MMIO_BASE + 0x0800) //#define IOMUX_BASE (MMIO_BASE + 0x0800)
#define WDT_BASE (MMIO_BASE + 0x0900) #define WDT_BASE (MMIO_BASE + 0x0900)
@ -43,135 +45,6 @@
#define RTCS_BASE (MMIO_BASE + 0x1100) #define RTCS_BASE (MMIO_BASE + 0x1100)
#define RTCU_BASE (MMIO_BASE + 0x1200) #define RTCU_BASE (MMIO_BASE + 0x1200)
/* TIMER registers
*
* ESP8266 has two hardware(?) timer counters, FRC1 and FRC2.
*
* FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero.
* FRC2 is a 32-bit countup timer, can set a variable match value to trigger an interrupt.
*
* FreeRTOS tick timer appears to come from XTensa core tick timer0,
* not either of these. FRC2 is used in the FreeRTOS SDK however. It
* is set to free-run, interrupting periodically via updates to the
* MATCH register. sdk_ets_timer_init configures FRC2 and assigns FRC2
* interrupt handler at sdk_vApplicationTickHook+0x68
*/
/* Load value for FRC1, read/write.
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)
/* Control register for FRC1, read/write.
See the bit definitions TIMER_CTRL_xxx lower down.
*/
#define TIMER_FRC1_CTRL_REG _REG(TIMER_BASE, 0x08)
/* Reading this register always returns the value in
* TIMER_FRC1_LOAD_REG.
*
* Writing zero to this register clears the FRC1
* interrupt status.
*/
#define TIMER_FRC1_CLEAR_INT_REG _REG(TIMER_BASE, 0x0c)
/* FRC2 load register.
*
* If TIMER_CTRL_RELOAD is cleared in TIMER_FRC2_CTRL_REG, writing to
* this register will update the FRC2 COUNT value.
*
* If TIMER_CTRL_RELOAD is set in TIMER_FRC2_CTRL_REG, the behaviour
* appears to be the same except that writing 0 to the load register
* both sets the COUNT register to 0 and disables the timer, even if
* the TIMER_CTRL_RUN bit is set.
*
* Offsets 0x34, 0x38, 0x3c all seem to read back the LOAD_REG value
* also (but have no known function.)
*/
#define TIMER_FRC2_LOAD_REG _REG(TIMER_BASE, 0x20)
/* FRC2 current count value. Read only? */
#define TIMER_FRC2_COUNT_REG _REG(TIMER_BASE, 0x24)
/* Control register for FRC2. Read/write.
See the bit definitions TIMER_CTRL_xxx lower down.
*/
#define TIMER_FRC2_CTRL_REG _REG(TIMER_BASE, 0x28)
/* Reading this value returns the current value of
* TIMER_FRC2_LOAD_REG.
*
* Writing zero to this value clears the FRC2 interrupt status.
*/
#define TIMER_FRC2_CLEAR_INT_REG _REG(TIMER_BASE, 0x2c)
/* Interrupt match value for FRC2. When COUNT == MATCH,
the interrupt fires.
*/
#define TIMER_FRC2_MATCH_REG _REG(TIMER_BASE, 0x30)
/* Timer control bits to set clock divisor values.
Divider from master 80MHz APB_CLK (unconfirmed, see esp/clocks.h).
*/
#define TIMER_CTRL_DIV_1 0
#define TIMER_CTRL_DIV_16 BIT(2)
#define TIMER_CTRL_DIV_256 BIT(3)
#define TIMER_CTRL_DIV_MASK (BIT(2)|BIT(3))
/* Set timer control bits to trigger interrupt on "edge" or "level"
*
* Observed behaviour is like this:
*
* * When TIMER_CTRL_INT_LEVEL is set, the interrupt status bit
* TIMER_CTRL_INT_STATUS remains set when the timer interrupt
* triggers, unless manually cleared by writing 0 to
* TIMER_FRCx_CLEAR_INT. While the interrupt status bit stays set
* the timer will continue to run normally, but the interrupt
* (INUM_TIMER_FRC1 or INUM_TIMER_FRC2) won't trigger again.
*
* * When TIMER_CTRL_INT_EDGE (default) is set, there's no need to
* manually write to TIMER_FRCx_CLEAR_INT. The interrupt status bit
* TIMER_CTRL_INT_STATUS automatically clears after the interrupt
* triggers, and the interrupt handler will run again
* automatically.
*
*/
#define TIMER_CTRL_INT_EDGE 0
#define TIMER_CTRL_INT_LEVEL BIT(0)
#define TIMER_CTRL_INT_MASK BIT(0)
/* Timer auto-reload bit
This bit interacts with TIMER_FRC1_LOAD_REG & TIMER_FRC2_LOAD_REG
differently, see those registers for details.
*/
#define TIMER_CTRL_RELOAD BIT(6)
/* Timer run bit */
#define TIMER_CTRL_RUN BIT(7)
/* Read-only timer interrupt status.
This bit gets set on FRC1 when interrupt fires, and cleared on a
write to TIMER_FRC1_CLEAR_INT (cleared automatically if
TIMER_CTRL_INT_LEVEL is not set).
*/
#define TIMER_CTRL_INT_STATUS BIT(8)
/* WDT register(s) /* WDT register(s)
Not fully understood yet. Writing 0 here disables wdt. Not fully understood yet. Writing 0 here disables wdt.
@ -180,29 +53,4 @@
*/ */
#define WDT_CTRL _REG(WDT_BASE, 0x00) #define WDT_CTRL _REG(WDT_BASE, 0x00)
/* DPORT registers
Control various aspects of core/peripheral interaction... Not well
documented or understood.
*/
/* Set flags to enable CPU interrupts from some peripherals. Read/write.
bit 0 - Is set by RTOS SDK startup code but function is unknown.
bit 1 - INT_ENABLE_FRC1 allows TIMER FRC1 to trigger interrupt INUM_TIMER_FRC1.
bit 2 - INT_ENABLE_FRC2 allows TIMER FRC2 to trigger interrupt INUM_TIMER_FRC2.
Espressif calls this register "EDGE_INT_ENABLE_REG". The "edge" in
question is (I think) the interrupt line from the peripheral, as
the interrupt status bit is set. There may be a similar register
for enabling "level" interrupts instead of edge triggering
- this is unknown.
*/
#define DP_INT_ENABLE_REG _REG(DPORT_BASE, 0x04)
/* Set to enable interrupts from TIMER FRC1 */
#define INT_ENABLE_FRC1 BIT(1)
/* Set to enable interrupts interrupts from TIMER FRC2 */
#define INT_ENABLE_FRC2 BIT(2)
#endif #endif

View file

@ -11,12 +11,12 @@
#include <stdbool.h> #include <stdbool.h>
#include <xtensa_interrupts.h> #include <xtensa_interrupts.h>
#include "esp/registers.h" #include "esp/timer_regs.h"
#include "esp/cpu.h" #include "esp/cpu.h"
typedef enum { typedef enum {
TIMER_FRC1, FRC1 = 0,
TIMER_FRC2, FRC2 = 1,
} timer_frc_t; } timer_frc_t;
/* Return current count value for timer. */ /* Return current count value for timer. */
@ -31,14 +31,8 @@ INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load);
/* Returns maximum load value for timer. */ /* Returns maximum load value for timer. */
INLINED uint32_t timer_max_load(const timer_frc_t frc); 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 */ /* Set the timer divider value */
INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div); INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div);
/* Enable or disable timer interrupts /* Enable or disable timer interrupts
@ -62,7 +56,7 @@ INLINED bool timer_get_reload(const timer_frc_t frc);
/* Return a suitable timer divider for the specified frequency, /* Return a suitable timer divider for the specified frequency,
or -1 if none is found. or -1 if none is found.
*/ */
INLINED timer_div_t timer_freq_to_div(uint32_t freq); 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.
@ -73,12 +67,12 @@ INLINED timer_div_t timer_freq_to_div(uint32_t freq);
* *
* Compile-time evaluates if all arguments are available at compile time. * 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); INLINED 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 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_div_t timer_time_to_div(uint32_t us); INLINED timer_clkdiv_t timer_time_to_div(uint32_t us);
/* 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.
@ -89,7 +83,7 @@ INLINED timer_div_t timer_time_to_div(uint32_t us);
* *
* Compile-time evaluates if all arguments are available at compile time. * 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); INLINED 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.

View file

@ -10,6 +10,7 @@
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include "esp/dport_regs.h"
/* Timer divisor index to max frequency */ /* Timer divisor index to max frequency */
#define _FREQ_DIV1 (80*1000*1000) #define _FREQ_DIV1 (80*1000*1000)
@ -20,104 +21,90 @@ const static uint32_t IROM _TIMER_FREQS[] = { _FREQ_DIV1, _FREQ_DIV16, _FREQ_DIV
/* Timer divisor index to divisor value */ /* Timer divisor index to divisor value */
const static uint32_t IROM _TIMER_DIV_VAL[] = { 1, 16, 256 }; 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) INLINED uint32_t timer_get_count(const timer_frc_t frc)
{ {
return (frc == TIMER_FRC1) ? TIMER_FRC1_COUNT_REG : TIMER_FRC2_COUNT_REG; return TIMER(frc).COUNT;
} }
INLINED uint32_t timer_get_load(const timer_frc_t frc) INLINED uint32_t timer_get_load(const timer_frc_t frc)
{ {
return (frc == TIMER_FRC1) ? TIMER_FRC1_LOAD_REG : TIMER_FRC2_LOAD_REG; return TIMER(frc).LOAD;
} }
INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load) INLINED void timer_set_load(const timer_frc_t frc, const uint32_t load)
{ {
if(frc == TIMER_FRC1) TIMER(frc).LOAD = load;
TIMER_FRC1_LOAD_REG = load;
else
TIMER_FRC2_LOAD_REG = load;
} }
INLINED uint32_t timer_max_load(const timer_frc_t frc) INLINED uint32_t timer_max_load(const timer_frc_t frc)
{ {
return (frc == TIMER_FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX; return (frc == FRC1) ? TIMER_FRC1_MAX_LOAD : UINT32_MAX;
} }
INLINED void timer_set_divider(const timer_frc_t frc, const timer_div_t div) INLINED void timer_set_divider(const timer_frc_t frc, const timer_clkdiv_t div)
{ {
if(div < TIMER_DIV1 || div > TIMER_DIV256) if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return; return;
esp_reg_t ctrl = _timer_ctrl_reg(frc); TIMER(frc).CTRL = SET_FIELD(TIMER(frc).CTRL, TIMER_CTRL_CLKDIV, div);
*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) 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 dp_bit = (frc == FRC1) ? DPORT_INT_ENABLE_FRC1 : DPORT_INT_ENABLE_FRC2;
const uint32_t int_mask = BIT((frc == TIMER_FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2); const uint32_t int_mask = BIT((frc == FRC1) ? INUM_TIMER_FRC1 : INUM_TIMER_FRC2);
if(enable) { if(enable) {
DP_INT_ENABLE_REG |= dp_bit; DPORT.INT_ENABLE |= dp_bit;
_xt_isr_unmask(int_mask); _xt_isr_unmask(int_mask);
} else { } else {
DP_INT_ENABLE_REG &= ~dp_bit; DPORT.INT_ENABLE &= ~dp_bit;
_xt_isr_mask(int_mask); _xt_isr_mask(int_mask);
} }
} }
INLINED void timer_set_run(const timer_frc_t frc, const bool run) INLINED void timer_set_run(const timer_frc_t frc, const bool run)
{ {
esp_reg_t ctrl = _timer_ctrl_reg(frc);
if (run) if (run)
*ctrl |= TIMER_CTRL_RUN; TIMER(frc).CTRL |= TIMER_CTRL_RUN;
else else
*ctrl &= ~TIMER_CTRL_RUN; TIMER(frc).CTRL &= ~TIMER_CTRL_RUN;
} }
INLINED bool timer_get_run(const timer_frc_t frc) INLINED bool timer_get_run(const timer_frc_t frc)
{ {
return *_timer_ctrl_reg(frc) & TIMER_CTRL_RUN; return TIMER(frc).CTRL & TIMER_CTRL_RUN;
} }
INLINED void timer_set_reload(const timer_frc_t frc, const bool reload) INLINED void timer_set_reload(const timer_frc_t frc, const bool reload)
{ {
esp_reg_t ctrl = _timer_ctrl_reg(frc);
if (reload) if (reload)
*ctrl |= TIMER_CTRL_RELOAD; TIMER(frc).CTRL |= TIMER_CTRL_RELOAD;
else else
*ctrl &= ~TIMER_CTRL_RELOAD; TIMER(frc).CTRL &= ~TIMER_CTRL_RELOAD;
} }
INLINED bool timer_get_reload(const timer_frc_t frc) INLINED bool timer_get_reload(const timer_frc_t frc)
{ {
return *_timer_ctrl_reg(frc) & TIMER_CTRL_RELOAD; return TIMER(frc).CTRL & TIMER_CTRL_RELOAD;
} }
INLINED timer_div_t timer_freq_to_div(uint32_t freq) INLINED timer_clkdiv_t timer_freq_to_div(uint32_t freq)
{ {
/* /*
try to maintain resolution without risking overflows. try to maintain resolution without risking overflows.
these values are a bit arbitrary at the moment! */ these values are a bit arbitrary at the moment! */
if(freq > 100*1000) if(freq > 100*1000)
return TIMER_DIV1; return TIMER_CLKDIV_1;
else if(freq > 100) else if(freq > 100)
return TIMER_DIV16; return TIMER_CLKDIV_16;
else else
return TIMER_DIV256; return TIMER_CLKDIV_256;
} }
/* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */ /* 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) 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_DIV1 || div > TIMER_DIV256) if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */ return 0; /* invalid divider */
if(freq > _TIMER_FREQS[div]) if(freq > _TIMER_FREQS[div])
@ -127,9 +114,9 @@ INLINED uint32_t _timer_freq_to_count_impl(const timer_frc_t frc, const uint32_t
return counts; return counts;
} }
uint32_t _timer_freq_to_count_runtime(const timer_frc_t frc, const uint32_t freq, const timer_div_t div); 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_div_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)) if(__builtin_constant_p(frc) && __builtin_constant_p(freq) && __builtin_constant_p(div))
return _timer_freq_to_count_impl(frc, freq, div); return _timer_freq_to_count_impl(frc, freq, div);
@ -137,33 +124,33 @@ INLINED uint32_t timer_freq_to_count(const timer_frc_t frc, const uint32_t freq,
return _timer_freq_to_count_runtime(frc, freq, div); return _timer_freq_to_count_runtime(frc, freq, div);
} }
INLINED timer_div_t timer_time_to_div(uint32_t us) INLINED timer_clkdiv_t timer_time_to_div(uint32_t us)
{ {
/* /*
try to maintain resolution without risking overflows. Similar to try to maintain resolution without risking overflows. Similar to
timer_freq_to_div, these values are a bit arbitrary at the timer_freq_to_div, these values are a bit arbitrary at the
moment! */ moment! */
if(us < 1000) if(us < 1000)
return TIMER_DIV1; return TIMER_CLKDIV_1;
else if(us < 10*1000) else if(us < 10*1000)
return TIMER_DIV16; return TIMER_CLKDIV_16;
else else
return TIMER_DIV256; return TIMER_CLKDIV_256;
} }
/* timer_timer_to_count implementation - inline if all args are constant, call normally otherwise */ /* 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) INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, const timer_clkdiv_t div)
{ {
if(div < TIMER_DIV1 || div > TIMER_DIV256) if(div < TIMER_CLKDIV_1 || div > TIMER_CLKDIV_256)
return 0; /* invalid divider */ return 0; /* invalid divider */
const uint32_t TIMER_MAX = timer_max_load(frc); const uint32_t TIMER_MAX = timer_max_load(frc);
if(div != TIMER_DIV256) /* timer tick in MHz */ if(div != TIMER_CLKDIV_256) /* timer tick in MHz */
{ {
/* timer is either 80MHz or 5MHz, so either 80 or 5 MHz counts per us */ /* 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; const uint32_t counts_per_us = ((div == TIMER_CLKDIV_1) ? _FREQ_DIV1 : _FREQ_DIV16)/1000/1000;
if(us > TIMER_MAX/counts_per_us) if(us > TIMER_MAX/counts_per_us)
return 0; /* Multiplying us by mhz_per_count will overflow TIMER_MAX */ return 0; /* Multiplying us by mhz_per_count will overflow TIMER_MAX */
return us*counts_per_us; return us*counts_per_us;
@ -186,9 +173,9 @@ INLINED uint32_t _timer_time_to_count_impl(const timer_frc_t frc, uint32_t us, c
} }
} }
uint32_t _timer_time_to_count_runtime(const timer_frc_t frc, uint32_t us, const timer_div_t div); 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_div_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)) if(__builtin_constant_p(frc) && __builtin_constant_p(us) && __builtin_constant_p(div))
return _timer_time_to_count_impl(frc, us, div); return _timer_time_to_count_impl(frc, us, div);
@ -201,7 +188,7 @@ INLINED uint32_t timer_time_to_count(const timer_frc_t frc, uint32_t us, const t
INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq) INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
{ {
uint32_t counts = 0; uint32_t counts = 0;
timer_div_t div = timer_freq_to_div(freq); timer_clkdiv_t div = timer_freq_to_div(freq);
counts = timer_freq_to_count(frc, freq, div); counts = timer_freq_to_count(frc, freq, div);
if(counts == 0) if(counts == 0)
@ -211,7 +198,7 @@ INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
} }
timer_set_divider(frc, div); timer_set_divider(frc, div);
if(frc == TIMER_FRC1) if(frc == FRC1)
{ {
timer_set_load(frc, counts); timer_set_load(frc, counts);
timer_set_reload(frc, true); timer_set_reload(frc, true);
@ -219,7 +206,7 @@ INLINED bool _timer_set_frequency_impl(const timer_frc_t frc, uint32_t freq)
else /* FRC2 */ else /* FRC2 */
{ {
/* assume that if this overflows it'll wrap, so we'll get desired behaviour */ /* assume that if this overflows it'll wrap, so we'll get desired behaviour */
TIMER_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG; TIMER(1).ALARM = counts + TIMER(1).COUNT;
} }
return true; return true;
} }
@ -239,20 +226,20 @@ INLINED bool timer_set_frequency(const timer_frc_t frc, uint32_t freq)
INLINED bool _timer_set_timeout_impl(const timer_frc_t frc, uint32_t us) INLINED bool _timer_set_timeout_impl(const timer_frc_t frc, uint32_t us)
{ {
uint32_t counts = 0; uint32_t counts = 0;
timer_div_t div = timer_time_to_div(us); timer_clkdiv_t div = timer_time_to_div(us);
counts = timer_time_to_count(frc, us, div); counts = timer_time_to_count(frc, us, div);
if(counts == 0) if(counts == 0)
return false; /* can't set frequency */ return false; /* can't set frequency */
timer_set_divider(frc, div); timer_set_divider(frc, div);
if(frc == TIMER_FRC1) if(frc == FRC1)
{ {
timer_set_load(frc, counts); timer_set_load(frc, counts);
} }
else /* FRC2 */ else /* FRC2 */
{ {
TIMER_FRC2_MATCH_REG = counts + TIMER_FRC2_COUNT_REG; TIMER(1).ALARM = counts + TIMER(1).COUNT;
} }
return true; return true;

View file

@ -0,0 +1,125 @@
/* esp/timer_regs.h
*
* ESP8266 Timer register definitions
*
* Not compatible with ESP SDK register access code.
*/
#ifndef _ESP_TIMER_REGS_H
#define _ESP_TIMER_REGS_H
#include "esp/types.h"
#include "common_macros.h"
#define TIMER_BASE 0x60000600
#define TIMER(i) (*(struct TIMER_REGS *)(TIMER_BASE + (i)*0x20))
#define TIMER_FRC1 TIMER(0)
#define TIMER_FRC2 TIMER(1)
/* TIMER registers
*
* ESP8266 has two hardware timer counters, FRC1 and FRC2.
*
* FRC1 is a 24-bit countdown timer, triggers interrupt when reaches zero.
* FRC2 is a 32-bit countup timer, can set a variable match value to trigger an interrupt.
*
* FreeRTOS tick timer appears to come from XTensa core tick timer0,
* not either of these. FRC2 is used in the FreeRTOS SDK however. It
* is set to free-run, interrupting periodically via updates to the
* ALARM register. sdk_ets_timer_init configures FRC2 and assigns FRC2
* interrupt handler at sdk_vApplicationTickHook+0x68
*/
struct TIMER_REGS { // FRC1 FRC2
uint32_t volatile LOAD; // 0x00 0x20
uint32_t volatile COUNT; // 0x04 0x24
uint32_t volatile CTRL; // 0x08 0x28
uint32_t volatile STATUS; // 0x0c 0x2c
uint32_t volatile ALARM; // 0x30
} __attribute__ (( packed ));
_Static_assert(sizeof(struct TIMER_REGS) == 0x14, "TIMER_REGS is the wrong size");
#define TIMER_FRC1_MAX_LOAD 0x7fffff
/* Details for LOAD registers */
/* Behavior for FRC1:
*
* When TIMER_CTRL_RELOAD is cleared in TIMER(0).CTRL, FRC1 will
* reload to its max value once underflowed (unless the load
* value is rewritten in the interrupt handler.)
*
* When TIMER_CTRL_RELOAD is set in TIMER(0).CTRL, FRC1 will reload
* from the load register value once underflowed.
*
* Behavior for FRC2:
*
* If TIMER_CTRL_RELOAD is cleared in TIMER(1).CTRL, writing to
* this register will update the FRC2 COUNT value.
*
* If TIMER_CTRL_RELOAD is set in TIMER(1).CTRL, the behaviour
* appears to be the same except that writing 0 to the load register
* both sets the COUNT register to 0 and disables the timer, even if
* the TIMER_CTRL_RUN bit is set.
*
* Offsets 0x34, 0x38, 0x3c all seem to read back the LOAD_REG value
* also (but have no known function.)
*/
/* Details for CTRL registers */
/* Observed behaviour is like this:
*
* * When TIMER_CTRL_INT_HOLD is set, the interrupt status bit
* TIMER_CTRL_INT_STATUS remains set when the timer interrupt
* triggers, unless manually cleared by writing 0 to
* TIMER(x).STATUS. While the interrupt status bit stays set
* the timer will continue to run normally, but the interrupt
* (INUM_TIMER_FRC1 or INUM_TIMER_FRC2) won't trigger again.
*
* * When TIMER_CTRL_INT_HOLD is cleared (default), there's no need to
* manually write to TIMER(x).STATUS. The interrupt status bit
* TIMER_CTRL_INT_STATUS automatically clears after the interrupt
* triggers, and the interrupt handler will run again
* automatically.
*/
/* The values for TIMER_CTRL_CLKDIV control how many CPU clock cycles amount to
* one timer clock cycle. For valid values, see the timer_clkdiv_t enum below.
*/
/* TIMER_CTRL_INT_STATUS gets set when interrupt fires, and cleared on a write
* to TIMER(x).STATUS (or cleared automatically if TIMER_CTRL_INT_HOLD is not
* set).
*/
#define TIMER_CTRL_INT_HOLD BIT(0)
#define TIMER_CTRL_CLKDIV_M 0x00000003
#define TIMER_CTRL_CLKDIV_S 2
#define TIMER_CTRL_RELOAD BIT(6)
#define TIMER_CTRL_RUN BIT(7)
#define TIMER_CTRL_INT_STATUS BIT(8)
typedef enum {
TIMER_CLKDIV_1 = 0,
TIMER_CLKDIV_16 = 1,
TIMER_CLKDIV_256 = 2,
} timer_clkdiv_t;
/* Details for STATUS registers */
/* Reading this register always returns the value in
* TIMER(x).LOAD
*
* Writing zero to this register clears the FRC1
* interrupt status.
*/
/* Details for FRC2.ALARM register */
/* Interrupt match value for FRC2. When COUNT == ALARM,
the interrupt fires.
*/
#endif /* _ESP_TIMER_REGS_H */