/* * Copyright (C) 2019 Gunar Schorcht * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup cpu_esp8266 * @ingroup drivers_periph_timer * @{ * * @file * @brief Low-level timer driver implementation using ESP8266 SDK * * @author Gunar Schorcht * @} */ /* WARNING! enable debugging will have timing side effects and can lead * to timer underflows, system crashes or system dead locks in worst case. */ #define ENABLE_DEBUG 0 #include "debug.h" #include "log.h" #include "xtimer.h" #include "periph/timer.h" #include "esp/common_macros.h" #include "esp_common.h" #include "irq_arch.h" #include "rom/ets_sys.h" #include "sdk/sdk.h" #include "xtensa/hal.h" #if !defined(MODULE_ESP_SW_TIMER) /* hardware timer used */ #define HW_TIMER_NUMOF 1 #define HW_TIMER_CHANNELS 1 #define HW_TIMER_MASK 0xffffffff #define HW_TIMER_DELTA_MAX 0x00ffffff /* in us */ #define HW_TIMER_DELTA_MIN 0x00000001 /* in us */ #define HW_TIMER_DELTA_MASK 0x00ffffff #define HW_TIMER_DELTA_RSHIFT 24 #define HW_TIMER_CORRECTION 2 /* overhead in us */ #define HW_TIMER_CLOCK (APB_CLK_FREQ) #define US_TO_HW_TIMER_TICKS(t) (t * system_get_cpu_freq()) #define HW_TIMER_TICKS_TO_US(t) (t / system_get_cpu_freq()) struct hw_channel_t { bool used; /* indicates whether the channel is used */ uint32_t start_time; /* physical time when the timer channel has been started */ uint32_t delta_time; /* timer delta value (delta = cycles * timer_max + remainder) */ uint32_t cycles; /* number of complete max timer cycles */ uint32_t remainder; /* remainder timer value */ }; struct hw_timer_t { bool initialized; /* indicates whether timer is already initialized */ bool started; /* indicates whether timer is already started */ timer_isr_ctx_t isr_ctx; struct hw_channel_t channels[HW_TIMER_CHANNELS]; }; static struct hw_timer_t timers[HW_TIMER_NUMOF] = { }; static void __timer_channel_start (struct hw_timer_t* timer, struct hw_channel_t* channel); static void __timer_channel_stop (struct hw_timer_t* timer, struct hw_channel_t* channel); static uint32_t __hw_timer_ticks_max; static uint32_t __hw_timer_ticks_min; void IRAM hw_timer_handler(void* arg) { uint32_t dev = (uint32_t)arg >> 4; uint32_t chn = (uint32_t)arg & 0xf; if (dev >= HW_TIMER_NUMOF && chn >= HW_TIMER_CHANNELS) { return; } irq_isr_enter(); DEBUG("%s arg=%p\n", __func__, arg); struct hw_timer_t* timer = &timers[dev]; struct hw_channel_t* channel = &timer->channels[chn]; if (channel->cycles) { channel->cycles--; xthal_set_ccompare(0, __hw_timer_ticks_max + xthal_get_ccount()); } else if (channel->remainder >= HW_TIMER_DELTA_MIN) { xthal_set_ccompare (0, US_TO_HW_TIMER_TICKS(channel->remainder) + xthal_get_ccount()); channel->remainder = 0; } else { channel->remainder = 0; channel->used = false; ets_isr_mask (BIT(ETS_CCOM_INUM)); xthal_set_ccompare (0, 0); timer->isr_ctx.cb(timer->isr_ctx.arg, chn); } irq_isr_exit(); } int timer_init (tim_t dev, unsigned long freq, timer_cb_t cb, void *arg) { DEBUG("%s dev=%u freq=%lu cb=%p arg=%p\n", __func__, dev, freq, cb, arg); CHECK_PARAM_RET (dev < HW_TIMER_NUMOF, -1); CHECK_PARAM_RET (freq == XTIMER_HZ_BASE, -1); CHECK_PARAM_RET (cb != NULL, -1); if (timers[dev].initialized) { DEBUG("%s timer dev=%u is already initialized (used)\n", __func__, dev); return -1; } timers[dev].initialized = true; timers[dev].started = false; timers[dev].isr_ctx.cb = cb; timers[dev].isr_ctx.arg = arg; ets_isr_attach (ETS_CCOM_INUM, hw_timer_handler, NULL); for (int i = 0; i < HW_TIMER_CHANNELS; i++) { timers[dev].channels[i].used = false; timers[dev].channels[i].cycles = 0; timers[dev].channels[i].remainder = 0; } timer_start(dev); return 0; } int IRAM timer_set(tim_t dev, int chn, unsigned int delta) { DEBUG("%s dev=%u channel=%d delta=%u\n", __func__, dev, chn, delta); CHECK_PARAM_RET (dev < HW_TIMER_NUMOF, -1); CHECK_PARAM_RET (chn < HW_TIMER_CHANNELS, -1); int state = irq_disable (); struct hw_timer_t* timer = &timers[dev]; struct hw_channel_t* channel = &timer->channels[chn]; /* set delta time and channel used flag */ channel->delta_time = delta > HW_TIMER_CORRECTION ? delta - HW_TIMER_CORRECTION : 0; channel->used = true; /* start channel with new delta time */ __timer_channel_start (timer, channel); irq_restore (state); return 0; } int IRAM timer_set_absolute(tim_t dev, int chn, unsigned int value) { DEBUG("%s dev=%u channel=%d value=%u\n", __func__, dev, chn, value); return timer_set (dev, chn, value - timer_read(dev)); } int timer_clear(tim_t dev, int chn) { DEBUG("%s dev=%u channel=%d\n", __func__, dev, chn); CHECK_PARAM_RET (dev < HW_TIMER_NUMOF, -1); CHECK_PARAM_RET (chn < HW_TIMER_CHANNELS, -1); int state = irq_disable (); /* stop running timer channel */ __timer_channel_stop (&timers[dev], &timers[dev].channels[chn]); irq_restore (state); return 0; } unsigned int IRAM timer_read(tim_t dev) { (void)dev; return phy_get_mactime (); } void IRAM timer_start(tim_t dev) { DEBUG("%s dev=%u @%u\n", __func__, dev, phy_get_mactime()); CHECK_PARAM (dev < HW_TIMER_NUMOF); CHECK_PARAM (!timers[dev].started); int state = irq_disable (); __hw_timer_ticks_max = US_TO_HW_TIMER_TICKS(HW_TIMER_DELTA_MAX); __hw_timer_ticks_min = US_TO_HW_TIMER_TICKS(HW_TIMER_DELTA_MIN); struct hw_timer_t* timer = &timers[dev]; timer->started = true; for (int i = 0; i < HW_TIMER_CHANNELS; i++) { __timer_channel_start (timer, &timer->channels[i]); } irq_restore (state); } void IRAM timer_stop(tim_t dev) { DEBUG("%s dev=%u\n", __func__, dev); CHECK_PARAM (dev < HW_TIMER_NUMOF); int state = irq_disable (); struct hw_timer_t* timer = &timers[dev]; timer->started = false; for (int i = 0; i < HW_TIMER_CHANNELS; i++) { __timer_channel_stop (timer, &timer->channels[i]); } irq_restore (state); } static void IRAM __timer_channel_start (struct hw_timer_t* timer, struct hw_channel_t* channel) { if (!timer->started || !channel->used) { return; } /* save channel starting time */ channel->start_time = timer_read (0); channel->cycles = channel->delta_time >> HW_TIMER_DELTA_RSHIFT; channel->remainder = channel->delta_time & HW_TIMER_DELTA_MASK; DEBUG("%s cycles=%u remainder=%u @%u\n", __func__, channel->cycles, channel->remainder, phy_get_mactime()); /* start timer either with full cycles, remaining or minimum time */ if (channel->cycles) { channel->cycles--; xthal_set_ccompare(0, __hw_timer_ticks_max + xthal_get_ccount()); } else if (channel->remainder >= HW_TIMER_DELTA_MIN) { xthal_set_ccompare (0, US_TO_HW_TIMER_TICKS(channel->remainder) + xthal_get_ccount()); channel->remainder = 0; } else { channel->remainder = 0; xthal_set_ccompare(0, __hw_timer_ticks_min + xthal_get_ccount()); } ets_isr_unmask (BIT(ETS_CCOM_INUM)); } static void IRAM __timer_channel_stop (struct hw_timer_t* timer, struct hw_channel_t* channel) { if (!channel->used) { return; } ets_isr_mask (BIT(ETS_CCOM_INUM)); /* compute elapsed time */ uint32_t elapsed_time = timer_read (0) - channel->start_time; if (channel->delta_time > elapsed_time) { /* compute new delta time if the timer has no been expired */ channel->delta_time -= elapsed_time; } else { /* otherwise deactivate the channel */ channel->used = false; } } void timer_print_config(void) { for (unsigned i = 0; i < HW_TIMER_NUMOF; i++) { printf("\tTIMER_DEV(%u)\t%d channel(s)\n", i, ARRAY_SIZE(timers[i].channels)); } } #else /* MODULE_ESP_SW_TIMER */ /* software timer based on os_timer_arm functions */ #define OS_TIMER_NUMOF 1 #define OS_TIMER_CHANNELS 10 #define OS_TIMER_MASK 0xffffffff #define OS_TIMER_DELTA_MAX 0x0000ffff #define OS_TIMER_DELTA_MIN 0x00000064 #define OS_TIMER_DELTA_MASK 0x0000ffff #define OS_TIMER_DELTA_RSHIFT 16 #define OS_TIMER_CORRECTION 4 extern void os_timer_arm_us(os_timer_t *ptimer, uint32_t time, bool repeat_flag); /* Since hardware timer FRC1 is needed to implement PWM, we have to map our */ /* timer using the exsting ETS timer with 1 us clock rate */ struct phy_channel_t { bool used; /* indicates whether the channel is used */ uint32_t start_time; /* physical time when the timer channel has been started */ uint32_t delta_time; /* timer delta value (delta = cycles * timer_max + remainder) */ uint32_t cycles; /* number of complete max timer cycles */ uint32_t remainder; /* remainder timer value */ os_timer_t os_timer; /* used system software timer */ }; struct phy_timer_t { bool initialized; /* indicates whether timer is already initialized */ bool started; /* indicates whether timer is already started */ timer_isr_ctx_t isr_ctx; struct phy_channel_t channels[OS_TIMER_CHANNELS]; }; static struct phy_timer_t timers[OS_TIMER_NUMOF] = { }; static void __timer_channel_start (struct phy_timer_t* timer, struct phy_channel_t* channel); static void __timer_channel_stop (struct phy_timer_t* timer, struct phy_channel_t* channel); /* Since we use ETS software timers, it is not really an ISR. Therefore */ /* we don't need to run in interrupt context. */ void IRAM os_timer_handler (void* arg) { uint32_t dev = (uint32_t)arg >> 4; uint32_t chn = (uint32_t)arg & 0xf; if (dev >= OS_TIMER_NUMOF && chn >= OS_TIMER_CHANNELS) { return; } irq_isr_enter (); struct phy_timer_t* timer = &timers[dev]; struct phy_channel_t* channel = &timer->channels[chn]; if (channel->cycles) { channel->cycles--; os_timer_arm_us (&channel->os_timer, OS_TIMER_DELTA_MAX, false); } else if (channel->remainder >= OS_TIMER_DELTA_MIN) { os_timer_arm_us (&channel->os_timer, channel->remainder, false); channel->remainder = 0; } else { channel->remainder = 0; channel->used = false; timer->isr_ctx.cb(timer->isr_ctx.arg, chn); } irq_isr_exit (); } int timer_init (tim_t dev, unsigned long freq, timer_cb_t cb, void *arg) { DEBUG("%s dev=%u freq=%lu cb=%p arg=%p\n", __func__, dev, freq, cb, arg); CHECK_PARAM_RET (dev < OS_TIMER_NUMOF, -1); CHECK_PARAM_RET (freq == XTIMER_HZ_BASE, -1); CHECK_PARAM_RET (cb != NULL, -1); if (timers[dev].initialized) { DEBUG("%s timer dev=%u is already initialized (used)\n", __func__, dev); return -1; } timers[dev].initialized = true; timers[dev].started = false; timers[dev].isr_ctx.cb = cb; timers[dev].isr_ctx.arg = arg; for (int i = 0; i < OS_TIMER_CHANNELS; i++) { os_timer_setfn(&timers[dev].channels[i].os_timer, os_timer_handler, (void*)((dev << 4) | i)); timers[dev].channels[i].used = false; timers[dev].channels[i].cycles = 0; timers[dev].channels[i].remainder = 0; } timer_start(dev); return 0; } int IRAM timer_set(tim_t dev, int chn, unsigned int delta) { DEBUG("%s dev=%u channel=%d delta=%u\n", __func__, dev, chn, delta); CHECK_PARAM_RET (dev < OS_TIMER_NUMOF, -1); CHECK_PARAM_RET (chn < OS_TIMER_CHANNELS, -1); int state = irq_disable (); struct phy_timer_t* timer = &timers[dev]; struct phy_channel_t* channel = &timer->channels[chn]; /* set delta time and channel used flag */ channel->delta_time = delta > OS_TIMER_CORRECTION ? delta - OS_TIMER_CORRECTION : 0; channel->used = true; /* start channel with new delta time */ __timer_channel_start (timer, channel); irq_restore (state); return 0; } int IRAM timer_set_absolute(tim_t dev, int chn, unsigned int value) { DEBUG("%s dev=%u channel=%d value=%u\n", __func__, dev, chn, value); return timer_set (dev, chn, value - timer_read(dev)); } int timer_clear(tim_t dev, int chn) { DEBUG("%s dev=%u channel=%d\n", __func__, dev, chn); CHECK_PARAM_RET (dev < OS_TIMER_NUMOF, -1); CHECK_PARAM_RET (chn < OS_TIMER_CHANNELS, -1); int state = irq_disable (); /* stop running timer channel */ __timer_channel_stop (&timers[dev], &timers[dev].channels[chn]); irq_restore (state); return 0; } unsigned int IRAM timer_read(tim_t dev) { (void)dev; return phy_get_mactime (); } void IRAM timer_start(tim_t dev) { DEBUG("%s dev=%u\n", __func__, dev); CHECK_PARAM (dev < OS_TIMER_NUMOF); CHECK_PARAM (!timers[dev].started); int state = irq_disable (); struct phy_timer_t* timer = &timers[dev]; timer->started = true; for (int i = 0; i < OS_TIMER_CHANNELS; i++) { __timer_channel_start (timer, &timer->channels[i]); } irq_restore (state); } void IRAM timer_stop(tim_t dev) { DEBUG("%s dev=%u\n", __func__, dev); CHECK_PARAM (dev < OS_TIMER_NUMOF); int state = irq_disable (); struct phy_timer_t* timer = &timers[dev]; timer->started = false; for (int i = 0; i < OS_TIMER_CHANNELS; i++) { __timer_channel_stop (timer, &timer->channels[i]); } irq_restore (state); } static void IRAM __timer_channel_start (struct phy_timer_t* timer, struct phy_channel_t* channel) { if (!timer->started || !channel->used) { return; } /* disarm old timer if already started */ os_timer_disarm (&channel->os_timer); /* save channel starting time */ channel->start_time = timer_read (0); channel->cycles = channel->delta_time >> OS_TIMER_DELTA_RSHIFT; channel->remainder = channel->delta_time & OS_TIMER_DELTA_MASK; DEBUG("%s cycles=%u remainder=%u @%u\n", __func__, channel->cycles, channel->remainder, phy_get_mactime()); /* start timer either with full cycles, remainder or minimum time */ if (channel->cycles) { channel->cycles--; os_timer_arm_us (&channel->os_timer, OS_TIMER_DELTA_MAX, false); } else if (channel->remainder > OS_TIMER_DELTA_MIN) { os_timer_arm_us (&channel->os_timer, channel->remainder, false); channel->remainder = 0; } else { channel->remainder = 0; os_timer_arm_us (&channel->os_timer, OS_TIMER_DELTA_MIN, false); } } static void IRAM __timer_channel_stop (struct phy_timer_t* timer, struct phy_channel_t* channel) { if (!channel->used) { return; } os_timer_disarm (&channel->os_timer); /* compute elapsed time */ uint32_t elapsed_time = timer_read (0) - channel->start_time; if (channel->delta_time > elapsed_time) { /* compute new delta time if the timer has no been expired */ channel->delta_time -= elapsed_time; } else { /* otherwise deactivate the channel */ channel->used = false; } } void timer_print_config(void) { for (unsigned i = 0; i < OS_TIMER_NUMOF; i++) { printf("\tTIMER_DEV(%u)\t%d channel(s)\n", i, ARRAY_SIZE(timers[i].channels)); } } #endif /* MODULE_ESP_SW_TIMER */