diff --git a/Sming/Arch/Esp8266/Compiler/ld/common.ld b/Sming/Arch/Esp8266/Compiler/ld/common.ld index 1e06f2f3c6..de1354a3f8 100644 --- a/Sming/Arch/Esp8266/Compiler/ld/common.ld +++ b/Sming/Arch/Esp8266/Compiler/ld/common.ld @@ -82,6 +82,55 @@ SECTIONS _data_end = ABSOLUTE(.); } >dram0_0_seg :dram0_0_phdr + + /* + IRAM is split into .text and .text1 to allow for moving specific + functions into IRAM that would be matched by the irom0.text matcher + */ + .text : ALIGN(4) + { + _stext = .; + _text_start = ABSOLUTE(.); + *(.UserEnter.text) + . = ALIGN(16); + *(.DebugExceptionVector.text) + . = ALIGN(16); + *(.NMIExceptionVector.text) + . = ALIGN(16); + *(.KernelExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.UserExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN(16); + *(.DoubleExceptionVector.text) + LONG(0) + LONG(0) + LONG(0) + LONG(0) + . = ALIGN (16); + *(.entry.text) + *(.init.literal) + *(.init) + + *(.iram.literal .iram.text.literal .iram.text .iram.text.*) + + /* + GCC silently ignores section attributes on templated code. + The only practical workaround is enforcing sections in the linker script. + */ + *(.literal._ZN13CallbackTimer*) + *(.text._ZN13CallbackTimer*) + *(.text._ZNKSt8functionIF*EE*) /* std::function::operator()() const */ + + } >iram1_0_seg :iram1_0_phdr + .irom0.text : ALIGN(4) { _irom0_text_start = ABSOLUTE(.); @@ -128,39 +177,12 @@ SECTIONS _flash_code_end = ABSOLUTE(.); } >irom0_0_seg :irom0_0_phdr - .text : ALIGN(4) + + /* Pick up any remaining IRAM objects and close the text segment */ + .text1 : ALIGN(4) { - _stext = .; - _text_start = ABSOLUTE(.); - *(.UserEnter.text) - . = ALIGN(16); - *(.DebugExceptionVector.text) - . = ALIGN(16); - *(.NMIExceptionVector.text) - . = ALIGN(16); - *(.KernelExceptionVector.text) - LONG(0) - LONG(0) - LONG(0) - LONG(0) - . = ALIGN(16); - *(.UserExceptionVector.text) - LONG(0) - LONG(0) - LONG(0) - LONG(0) - . = ALIGN(16); - *(.DoubleExceptionVector.text) - LONG(0) - LONG(0) - LONG(0) - LONG(0) - . = ALIGN (16); - *(.entry.text) - *(.init.literal) - *(.init) *(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*) - *(.iram.literal .iram.text.literal .iram.text .iram.text.*) + *(.fini.literal) *(.fini) *(.gnu.version) diff --git a/Sming/Arch/Esp8266/Components/driver/include/driver/os_timer.h b/Sming/Arch/Esp8266/Components/driver/include/driver/os_timer.h new file mode 100644 index 0000000000..1cfa2f55c3 --- /dev/null +++ b/Sming/Arch/Esp8266/Components/driver/include/driver/os_timer.h @@ -0,0 +1,33 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * os_timer.h + * + * @author: 13 August 2018 - mikee47 + * + * An alternative method for setting software timers based on the tick count. + * + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set a software timer using the Timer2 tick value + * @param ptimer Timer structure + * @param ticks Tick count duration for the timer + * @param repeat_flag true if timer will automatically repeat + */ +void IRAM_ATTR os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag); + +#ifdef __cplusplus +} +#endif diff --git a/Sming/Arch/Esp8266/Components/driver/os_timer.cpp b/Sming/Arch/Esp8266/Components/driver/os_timer.cpp new file mode 100644 index 0000000000..c4de6b17ca --- /dev/null +++ b/Sming/Arch/Esp8266/Components/driver/os_timer.cpp @@ -0,0 +1,109 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * os_timer.cpp + * + * @author: 13 August 2018 - mikee47 + * + * An alternative method for setting software timers based on the tick count. + * + * Technical notes + * --------------- + * + * This information was obtained by examining the SDK timer function assembly code + * from SDK versions 1.5.4, 2.2 and 3.0. + * + * Software timers for the ESP8266 are defined by an `os_timer_t` structure. + * When armed, the structure is contained in a queue ordered by expiry time. + * Thus, the first timer is the next one to expire and the expiry time is programmed + * into the Timer2 alarm register (counter compare). The timer alarm interrupt simply + * queues a task to handle the event. + * + * The timer task dequeues the timer (setting `timer_next` to -1) and invokes the + * user-provided callback routine. If it is a repeating timer then it is re-queued. + * The queue is implemented as a linked list so adding and removing items is very efficient + * and requires no additional memory allocation. + * + * Because the Sming Clock API handles all time/tick conversions, a new `os_timer_arm_ticks()` + * function is used which replaces the existing `ets_timer_arm_new()` function. This makes + * timer operation more transparent, faster. + * + * `ets_timer_arm_new` + * ------------------- + * + * This is the SDK function which implements `os_timer_arm_us` and `os_timer_arm`. + * + * With ms_flag = false, the maximum value for `time` is 428496729us. The SDK documentation + * for `os_timer_arm_us` states a maximum value of 0x0FFFFFFF, which is incorrect; it probably + * applies to earlier SDK releases. + * + * Note: If `system_timer_reinit()` hasn't been called then calling with `ms_flag = false` will fail. + * + * This figure can be derived as follows, where 0x7FFFFFFF is the maximum tick range + * (signed comparison) and 5000000 is the Timer2 frequency with /16 prescale: + * + * 0x7FFFFFFF / 5000000 = 429496729.4us = 0' 7" 9.5s + * + * With ms_flag = true, the limit is 428496ms which agrees with the value stated in the SDK documentation. + * + * Timer2 frequencies for two prescaler settings are: + * Prescale Frequency Period Range (0x7FFFFFFF ticks) + * -------- --------- ------ ------------------------- + * - /1 80000000 12.5ns 0' 0" 26.84s + * - /16 5000000 200ns 0' 7" 9.5s + * - /256 312500 3.2us 1' 54" 31.95s + * + * @see See also `drivers/hw_timer.h` + * + */ + +#include "include/driver/os_timer.h" +#include + +/* + * This variable points to the first timer in the queue. + * It's a global variable defined in the Espressif SDK. + */ +extern "C" os_timer_t* timer_list; + +/** + * @brief Insert a timer into the queue + * @param ptimer The timer to insert + * @param expire The Timer2 tick value when this timer is due + * @note Timer is inserted into queue according to its expiry time, and _after_ any + * existing timers with the same expiry time. If it's inserted at the head of the + * queue (i.e. it's the new value for `timer_list`) then the Timer2 alarm register + * is updated. + */ +static void IRAM_ATTR timer_insert(os_timer_t* ptimer, uint32_t expire) +{ + os_timer_t* t_prev = nullptr; + auto t = timer_list; + while(t != nullptr) { + if(int(t->timer_expire - expire) > 0) { + break; + } + t_prev = t; + t = t->timer_next; + } + if(t_prev == nullptr) { + timer_list = ptimer; + hw_timer2_set_alarm(expire); + } else { + t_prev->timer_next = ptimer; + } + ptimer->timer_next = t; + ptimer->timer_expire = expire; +} + +void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag) +{ + os_timer_disarm(ptimer); + ptimer->timer_period = repeat_flag ? ticks : 0; + ets_intr_lock(); + timer_insert(ptimer, hw_timer2_read() + ticks); + ets_intr_unlock(); +} diff --git a/Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h b/Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h index 6d66843fe0..f11b34fb10 100644 --- a/Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h +++ b/Sming/Arch/Host/Components/esp_hal/include/esp_timer_legacy.h @@ -1,3 +1,11 @@ +/* + * This implementation mimics the behaviour of the ESP8266 Non-OS SDK timers, + * using Timer2 as the reference (which is _not_ in microseconds!) + * + * The ESP32 IDF contains more sophisticated timer implementations, but also + * this same API which it refers to as the 'legacy' timer API. + */ + #pragma once #include "c_types.h" @@ -7,16 +15,31 @@ extern "C" { typedef void os_timer_func_t(void* timer_arg); +/** + * @brief This is the structure used by the Espressif timer API + * @note This is used as an element in a linked list + * The Espressif implementation orders the list according to next expiry time. + * os_timer_setfn and os_timer_disarm set timer_next to -1 + * When expired, timer_next is 0 + */ struct os_timer_t { + /// If disarmed, set to -1, otherwise points to the next queued timer (or NULL if last in the list) struct os_timer_t* timer_next; + /// Set to the next Timer2 count value when the timer will expire uint32_t timer_expire; + /// 0 if this is a one-shot timer, otherwise defines the interval in Timer2 ticks uint32_t timer_period; + /// User-provided callback function pointer os_timer_func_t* timer_func; + /// Argument passed to the callback function void* timer_arg; }; +void os_timer_arm_ticks(struct os_timer_t* ptimer, uint32_t ticks, bool repeat_flag); + void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag); void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag); + void os_timer_disarm(struct os_timer_t* ptimer); void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* parg); diff --git a/Sming/Arch/Host/Components/esp_hal/timer_legacy.cpp b/Sming/Arch/Host/Components/esp_hal/timer_legacy.cpp index ea5aca35a8..b24aace4db 100644 --- a/Sming/Arch/Host/Components/esp_hal/timer_legacy.cpp +++ b/Sming/Arch/Host/Components/esp_hal/timer_legacy.cpp @@ -1,42 +1,62 @@ - #include "include/esp_system.h" #include "include/esp_timer_legacy.h" #include +#include +#include +#include static os_timer_t* timer_head; static CMutex mutex; -void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag) +static void timer_insert(uint32_t expire, os_timer_t* ptimer) { - os_timer_arm_us(ptimer, time * 1000UL, repeat_flag); + os_timer_t* t_prev = nullptr; + auto t = timer_head; + while(t != nullptr) { + if(int(t->timer_expire - expire) > 0) { + break; + } + t_prev = t; + t = t->timer_next; + } + if(t_prev == nullptr) { + timer_head = ptimer; + } else { + t_prev->timer_next = ptimer; + } + ptimer->timer_next = t; + ptimer->timer_expire = expire; } -void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag) +void os_timer_arm_ticks(os_timer_t* ptimer, uint32_t ticks, bool repeat_flag) { - os_timer_disarm(ptimer); - ptimer->timer_next = nullptr; - ptimer->timer_expire = system_get_time() + time; - ptimer->timer_period = repeat_flag ? time : 0; + assert(ptimer != nullptr); + // assert(time <= MAX_OS_TIMER_INTERVAL_US); - // Append to list + os_timer_disarm(ptimer); + ptimer->timer_period = repeat_flag ? ticks : 0; mutex.lock(); - if(timer_head == nullptr) { - timer_head = ptimer; - } else { - auto t = timer_head; - while(t->timer_next != nullptr) { - t = t->timer_next; - } - t->timer_next = ptimer; - } + timer_insert(hw_timer2_read() + ticks, ptimer); mutex.unlock(); } +void os_timer_arm(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag) +{ + using R = std::ratio; + auto ticks = muldiv(time); + os_timer_arm_ticks(ptimer, ticks, repeat_flag); +} + +void os_timer_arm_us(struct os_timer_t* ptimer, uint32_t time, bool repeat_flag) +{ + using R = std::ratio; + auto ticks = muldiv(time); + os_timer_arm_ticks(ptimer, ticks, repeat_flag); +} + void os_timer_disarm(struct os_timer_t* ptimer) { - if(ptimer == nullptr) { - return; - } + assert(ptimer != nullptr); mutex.lock(); if(timer_head != nullptr) { @@ -53,6 +73,7 @@ void os_timer_disarm(struct os_timer_t* ptimer) t = t->timer_next; } } + ptimer->timer_next = reinterpret_cast(-1); } mutex.unlock(); } @@ -62,6 +83,7 @@ void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* if(ptimer != nullptr) { ptimer->timer_func = pfunction; ptimer->timer_arg = parg; + ptimer->timer_next = reinterpret_cast(-1); } } @@ -72,23 +94,24 @@ static os_timer_t* find_expired_timer() return nullptr; } - auto time_now = system_get_time(); + auto ticks_now = hw_timer2_read(); os_timer_t* t_prev = nullptr; for(auto t = timer_head; t != nullptr; t_prev = t, t = t->timer_next) { - if(int(t->timer_expire - time_now) > 0) { - continue; + if(int(t->timer_expire - ticks_now) > 0) { + // No timers due + break; } - // Found an expired timer - if(t->timer_period == 0) { - // Non-repeating timer, remove now - if(t == timer_head) { - timer_head = nullptr; - } else if(t_prev != nullptr) { - t_prev->timer_next = t->timer_next; - } - } else { - t->timer_expire = time_now + t->timer_period; + // Found an expired timer, so remove from queue + if(t == timer_head) { + timer_head = t->timer_next; + } else if(t_prev != nullptr) { + t_prev->timer_next = t->timer_next; + } + + // Repeating timer? + if(t->timer_period != 0) { + timer_insert(t->timer_expire + t->timer_period, t); } return t; diff --git a/Sming/Components/rboot/component.mk b/Sming/Components/rboot/component.mk index 7d69e96407..196b50289e 100644 --- a/Sming/Components/rboot/component.mk +++ b/Sming/Components/rboot/component.mk @@ -64,7 +64,7 @@ APP_CFLAGS += -DRBOOT_SPIFFS_1=$(RBOOT_SPIFFS_1) ROM_0_ADDR := 0x002000 # filenames and options for generating rBoot rom images with esptool2 -RBOOT_E2_SECTS ?= .text .data .rodata +RBOOT_E2_SECTS ?= .text .text1 .data .rodata RBOOT_E2_USER_ARGS ?= -quiet -bin -boot2 RBOOT_ROM_0_BIN := $(FW_BASE)/$(RBOOT_ROM_0).bin diff --git a/Sming/Core/CallbackTimer.h b/Sming/Core/CallbackTimer.h new file mode 100644 index 0000000000..bd9e690606 --- /dev/null +++ b/Sming/Core/CallbackTimer.h @@ -0,0 +1,422 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * CallbackTimer.h - Template classes to implement callback timers + * + ****/ + +#pragma once + +#include "Interrupts.h" +#include "NanoTime.h" + +/** @defgroup callback_timer Callback timers + * @brief Callback interval timers + * @ingroup timer + * @{ +*/ + +typedef void (*TimerCallback)(void* arg); ///< Interrupt-compatible C callback function pointer +using TimerDelegate = Delegate; ///< Delegate callback + +/** + * @brief Callback timer API class template + * @note Used to define the basic programming interface for physical callback-capable timers + */ +template struct CallbackTimerApi { + static constexpr const char* typeName() + { + return ApiDef::typeName(); + } + + String name() const + { + String s; + s += typeName(); + s += '@'; + s += String(uint32_t(this), HEX); + return s; + } + + String toString() const + { + String s; + s += name(); + s += ": interval = "; + s += static_cast(this)->getInterval(); + s += ", ticks = "; + s += static_cast(this)->ticks(); + // s += ApiDef::Clock::ticks(); + return s; + } + + operator String() const + { + return toString(); + } +}; + +/** + * @brief Callback timer class template + * @tparam TimerApi The physical timer implementation + * @note Methods return object reference for Method Chaining + * http://en.wikipedia.org/wiki/Method_chaining + * This class template provides basic C-style callbacks for best performance + */ +template class CallbackTimer : protected TimerApi +{ +public: + using typename TimerApi::Clock; + using typename TimerApi::TickType; + using typename TimerApi::TimeType; + using Millis = NanoTime::TimeSource; + using Micros = NanoTime::TimeSource; + + using TimerApi::maxTicks; + using TimerApi::minTicks; + using TimerApi::toString; + using TimerApi::typeName; + using TimerApi::operator String; + + /** @brief Get a millisecond time source */ + static constexpr Millis millis() + { + return Millis(); + } + + /** @brief Get a microsecond time source */ + static constexpr Micros micros() + { + return Micros(); + } + + /** @brief Convert microsecond count into timer ticks */ + template static constexpr uint64_t usToTicks() + { + return Micros::template timeToTicks(); + } + + /** @brief Convert microsecond count into timer ticks */ + static TickType usToTicks(TimeType time) + { + return Micros::timeToTicks(time); + } + + /** @brief Convert timer ticks into microseconds */ + template static constexpr uint64_t ticksToUs() + { + return Micros::template ticksToTime(); + } + + /** @brief Convert timer ticks into microseconds */ + static TimeType ticksToUs(TickType ticks) + { + return Micros::ticksToTime(ticks); + } + + /** @brief Initialise timer with an interval (static check) and callback + * @param unit Time unit for interval + * @param time Timer interval + * @param callback Callback function to call when timer triggers + * @param arg Optional argument passed to callback + * @retval CallbackTimer& Reference to timer + * @note If interval out of range compilation will fail with error + */ + template + CallbackTimer& IRAM_ATTR initialize(TimerCallback callback, void* arg = nullptr) + { + setCallback(callback, arg); + setInterval(); + return *this; + } + + /** @brief Initialise timer with an interval and callback + * @param time Timer interval + * @param callback Callback function to call when timer triggers + * @param arg Optional argument passed to callback + * @retval CallbackTimer& Reference to timer + */ + template + CallbackTimer& IRAM_ATTR initialize(TimeType time, TimerCallback callback, void* arg = nullptr) + { + setCallback(callback, arg); + setInterval(time); + return *this; + } + + /** @brief Initialise timer in microseconds (static check) with Timer Callback and optional argument */ + template + __forceinline CallbackTimer& IRAM_ATTR initializeUs(TimerCallback callback, void* arg = nullptr) + { + return initialize(callback, arg); + } + + /** @brief Initialise timer in microseconds (static check) with optional Interrupt Callback (no argument) */ + template + __forceinline CallbackTimer& IRAM_ATTR initializeUs(InterruptCallback callback = nullptr) + { + return initializeUs(TimerCallback(callback)); + } + + /** @brief Initialise timer in microseconds with Timer Callback and optional argument */ + __forceinline CallbackTimer& IRAM_ATTR initializeUs(TimeType microseconds, TimerCallback callback, + void* arg = nullptr) + { + return initialize(microseconds, callback, arg); + } + + /** @brief Initialise timer in microseconds with optional Interrupt Callback (no arg) */ + __forceinline CallbackTimer& IRAM_ATTR initializeUs(TimeType microseconds, InterruptCallback callback = nullptr) + { + return initializeUs(microseconds, TimerCallback(callback)); + } + + /** @brief Initialise hardware timer in milliseconds (static check) with Timer Callback and optional argument */ + template + __forceinline CallbackTimer& IRAM_ATTR initializeMs(TimerCallback callback, void* arg = nullptr) + { + return initialize(callback, arg); + } + + /** @brief Initialise hardware timer in milliseconds (static check) and optional Interrupt Callback (no arg) */ + template + __forceinline CallbackTimer& IRAM_ATTR initializeMs(InterruptCallback callback = nullptr) + { + return initializeMs(TimerCallback(callback)); + } + + /** @brief Initialise hardware timer in milliseconds with Timer Callback and optional argument */ + __forceinline CallbackTimer& IRAM_ATTR initializeMs(uint32_t milliseconds, TimerCallback callback, + void* arg = nullptr) + { + return initialize(milliseconds, callback, arg); + } + + /** @brief Initialise hardware timer in milliseconds with optional Interrupt Callback (no arg) */ + __forceinline CallbackTimer& IRAM_ATTR initializeMs(uint32_t milliseconds, InterruptCallback callback = nullptr) + { + return initializeMs(milliseconds, TimerCallback(callback)); + } + + /** @brief Start timer running + * @param repeating True to restart timer when it triggers, false for one-shot (Default: true) + * @retval bool True if timer started + */ + IRAM_ATTR bool start(bool repeating = true); + + /** @brief Start one-shot timer + * @retval bool True if timer started + * @note Timer starts and will run for configured period then stop + */ + __forceinline bool IRAM_ATTR startOnce() + { + return start(false); + } + + /** @brief Stops timer + */ + __forceinline void IRAM_ATTR stop() + { + if(started) { + TimerApi::disarm(); + started = false; + } + } + + /** @brief Restart timer + * @retval bool True if timer started + * @note Timer is stopped then started with current configuration + */ + __forceinline bool IRAM_ATTR restart() + { + return start(repeating); + } + + /** @brief Check if timer is started + * @retval bool True if started + */ + __forceinline bool isStarted() const + { + return started; + } + + /** @brief Get timer interval in microseconds */ + NanoTime::Time getIntervalUs() const + { + return Micros::ticksToTime(getInterval()); + } + + /** @brief Get timer interval in milliseconds */ + NanoTime::Time getIntervalMs() const + { + return Millis::ticksToTime(getInterval()); + } + + /** @brief Get timer interval in clock ticks */ + __forceinline TickType getInterval() const + { + return TimerApi::getInterval(); + } + + /** @brief Check timer interval is valid + * @param ticks Interval to check + * @retval bool true if interval is within acceptable range for this timer + */ + bool IRAM_ATTR checkInterval(TickType ticks) const + { + return ticks >= minTicks() && ticks <= maxTicks(); + } + + /** @brief Check timer interval in ticks is valid (static check) + * @tparam ticks Timer interval to check + * @note On error, compilation fails with error message + */ + template static constexpr void checkInterval() + { + static_assert(ticks >= minTicks() && ticks <= maxTicks(), "Timer interval out of range"); + } + + /** @brief Check timer interval in specific time unit is valid (static check) + * @tparam unit Time unit for interval + * @tparam time Interval to check + * @note On error, compilation fails with error message + */ + template static constexpr void checkInterval() + { + checkInterval::ticks()>(); + } + + /** @brief Check timer interval in milliseconds is valid (static check) */ + template static constexpr void checkIntervalMs() + { + checkInterval(); + } + + /** @brief Check timer interval in microseconds is valid (static check) */ + template static constexpr void checkIntervalUs() + { + checkInterval(); + } + + /** @brief Set timer interval in timer ticks + * @param ticks Interval in timer ticks + */ + bool IRAM_ATTR setInterval(TickType ticks) + { + if(checkInterval(ticks)) { + internalSetInterval(ticks); + } else { + stop(); + intervalSet = false; + } + return started; + } + + /** @brief Set timer interval in timer ticks (static check) + * @tparam ticks Interval in ticks + * @note On error, compilation fails with error message + */ + template void IRAM_ATTR setInterval() + { + checkInterval(); + internalSetInterval(ticks); + } + + /** @brief Set timer interval in specific time unit (static check) + * @tparam unit + * @tparam time Interval to set + * @note On error, compilation fails with error message + */ + template __forceinline void IRAM_ATTR setInterval() + { + setInterval::ticks()>(); + } + + /** @brief Set timer interval in timer ticks + * @param ticks Interval in timer ticks + */ + template __forceinline bool IRAM_ATTR setInterval(TimeType time) + { + return setInterval(Clock::template timeToTicks(time)); + } + + /** @brief Set timer interval in microseconds */ + __forceinline bool IRAM_ATTR setIntervalUs(TimeType microseconds) + { + return setInterval(microseconds); + } + + /** @brief Set timer interval in microseconds (static check) */ + template __forceinline void IRAM_ATTR setIntervalUs() + { + return setInterval(); + } + + /** @brief Set timer interval in milliseconds */ + __forceinline bool IRAM_ATTR setIntervalMs(uint32_t milliseconds) + { + return setInterval(milliseconds); + } + + /** @brief Set timer interval in milliseconds (static check) */ + template __forceinline void IRAM_ATTR setIntervalMs() + { + return setInterval(); + } + + /** @brief Set timer trigger callback + * @param callback Function to call when timer triggers + * @param arg Optional argument passed to callback + */ + __forceinline void IRAM_ATTR setCallback(TimerCallback callback, void* arg = nullptr) + { + // Always disarm before setting the callback + stop(); + TimerApi::setCallback(callback, arg); + callbackSet = (callback != nullptr); + } + + /** + * @brief Set timer trigger callback + * @param callback Function to call when timer triggers + * @note Provided for convenience where callback argument not required + */ + __forceinline void IRAM_ATTR setCallback(InterruptCallback callback) + { + setCallback(reinterpret_cast(callback), nullptr); + } + +private: + __forceinline void IRAM_ATTR internalSetInterval(TickType ticks) + { + TimerApi::disarm(); + TimerApi::setInterval(ticks); + intervalSet = true; + if(started) { + restart(); + } + } + +protected: + bool callbackSet = false; ///< User has provided callback function + bool intervalSet = false; ///< User has set valid time interval + bool repeating = false; ///< Timer is auto-repeat + bool started = false; ///< Timer is active, or has fired +}; + +template IRAM_ATTR bool CallbackTimer::start(bool repeating) +{ + stop(); + if(!callbackSet || !intervalSet) { + return false; + } + + TimerApi::arm(repeating); + started = true; + return true; +} + +/** @} */ diff --git a/Sming/Core/HardwareTimer.cpp b/Sming/Core/HardwareTimer.cpp deleted file mode 100644 index 53d37de9ec..0000000000 --- a/Sming/Core/HardwareTimer.cpp +++ /dev/null @@ -1,15 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * HWTimer.cpp - * - * Created 23.11.2015 by johndoe - * - ****/ - -#include "HardwareTimer.h" - -uint8_t HardwareTimer::state; diff --git a/Sming/Core/HardwareTimer.h b/Sming/Core/HardwareTimer.h index c4fcc448a4..7846c20d9e 100644 --- a/Sming/Core/HardwareTimer.h +++ b/Sming/Core/HardwareTimer.h @@ -10,229 +10,153 @@ * ****/ -/** @defgroup hwtimer Hardware timer - * @brief Access Hardware timer +/** @ingroup callback_timer * @{ */ #pragma once -#include "Interrupts.h" -#include -#include +#include "CallbackTimer.h" +#include -// Hardware Timer operating mode +/** @ingroup callback_timer + * @{ + */ + +/** + * @brief Hardware Timer interrupt mode + */ enum HardwareTimerMode { eHWT_Maskable, eHWT_NonMaskable, }; /** - * @brief Hardware timer class + * @brief Class template for Timer1 API + * @note Provides low-level interface for timer access */ -class HardwareTimer +template +class Timer1Api : public CallbackTimerApi> { public: - /** @brief Convert microseconds into timer ticks. - */ - static uint32_t IRAM_ATTR usToTicks(uint32_t us) - { - return muldiv(us); - } - - /** @brief Convert timer ticks into microseconds - */ - static uint32_t IRAM_ATTR ticksToUs(uint32_t ticks) - { - return muldiv<1000000, frequency>(ticks); - } + using Clock = Timer1Clock; + using TickType = uint32_t; + using TimeType = uint32_t; - /** @brief Hardware timer - * @param mode - * @note NMI has highest interrupt priority on system and can therefore occur within - * any other interrupt service routine. Similarly, the NMI service routine cannot - * itself be interrupted. This provides the most stable and reliable timing possible, - * and is therefore the default behaviour. - */ - HardwareTimer(HardwareTimerMode mode = eHWT_NonMaskable) : mode(mode) + static constexpr const char* typeName() { - assert(state == eTS_CallbackNotSet); + return "Timer1Api"; } - ~HardwareTimer() + static constexpr TickType minTicks() { - stop(); - hw_timer1_detach_interrupt(); - state = eTS_CallbackNotSet; + return Clock::template TimeConst::ticks(); } - /** @brief Initialise hardware timer - * @param microseconds Timer interval in microseconds - * @param callback Callback function to call when timer triggers (Default: none) - * @retval HardwareTimer& Reference to timer - */ - HardwareTimer& IRAM_ATTR initializeUs(uint32_t microseconds, InterruptCallback callback = nullptr) + static constexpr TickType maxTicks() { - setCallback(callback); - setIntervalUs(microseconds); - return *this; + return Clock::maxTicks(); } - /** @brief Initialise hardware timer - * @param milliseconds Timer interval in milliseconds - * @param callback Callback function to call when timer triggers (Default: none) - * @retval HardwareTimer& Reference to timer - */ - HardwareTimer& IRAM_ATTR initializeMs(uint32_t milliseconds, InterruptCallback callback = nullptr) + static TickType ticks() { - setCallback(callback); - setIntervalMs(milliseconds); - return *this; + return Clock::ticks(); } - /** @brief Start timer running - * @param repeating True to restart timer when it triggers, false for one-shot (Default: true) - * @retval bool True if timer started - */ - bool IRAM_ATTR start(bool repeating = true) + Timer1Api() { - if(interval == 0 || state == eTS_CallbackNotSet) { - stop(); - return false; - } - - hw_timer1_enable(clkdiv, TIMER_EDGE_INT, repeating); - this->repeating = repeating; - hw_timer1_write(interval); - return true; + assert(state == eTS_CallbackNotSet); } - /** @brief Start one-shot timer - * @retval bool True if timer started - * @note Timer starts and will run for configured period then stop - */ - __forceinline bool IRAM_ATTR startOnce() + ~Timer1Api() { - return start(false); + detach_interrupt(); } - /** @brief Stops timer - */ - void IRAM_ATTR stop() + __forceinline static void IRAM_ATTR setCallback(TimerCallback callback, void* arg) { - if(state == eTS_Armed) { - hw_timer1_disable(); + if(callback == nullptr) { + detach_interrupt(); + } else { + assert(state <= eTS_Disarmed); + hw_timer1_attach_interrupt(mode == eHWT_NonMaskable ? TIMER_NMI_SOURCE : TIMER_FRC1_SOURCE, + reinterpret_cast(callback), arg); state = eTS_Disarmed; } } - /** @brief Restart timer - * @retval bool True if timer started - * @note Timer is stopped then started with current configuration - */ - bool IRAM_ATTR restart() - { - stop(); - return start(repeating); - } - - /** @brief Check if timer is started - * @retval bool True if started - */ - __forceinline bool isStarted() + __forceinline static void IRAM_ATTR setInterval(TickType interval) { - return state == eTS_Armed; + Timer1Api::interval = interval; } - /** @brief Get timer interval - * @retval uint32_t Timer interval in microseconds - */ - __forceinline uint32_t getIntervalUs() + __forceinline static TickType IRAM_ATTR getInterval() { - return ticksToUs(interval); + return interval; } - /** @brief Get timer interval - * @retval uint32_t Timer interval in milliseconds - */ - uint32_t getIntervalMs() + __forceinline static bool IRAM_ATTR isArmed() { - return getIntervalUs() / 1000; + return state >= eTS_Armed; } - /** @brief Set timer interval in timer ticks - * @param ticks Interval in ticks - */ - bool IRAM_ATTR setInterval(uint32_t ticks) + __forceinline static void IRAM_ATTR arm(bool repeating) { - constexpr uint32_t minTicks = MIN_HW_TIMER1_INTERVAL_US * frequency / 1000000; - if(ticks < MAX_HW_TIMER1_INTERVAL && ticks > minTicks) { - interval = ticks; - if(state == eTS_Armed) { - return restart(); - } - } else { - stop(); + State newState = repeating ? eTS_ArmedAutoLoad : eTS_Armed; + if(state != newState) { + hw_timer1_enable(clkdiv, TIMER_EDGE_INT, repeating); + state = newState; } - return false; - } - - /** @brief Set timer interval - * @param microseconds Interval time in microseconds (Default: 1ms) - */ - __forceinline bool IRAM_ATTR setIntervalUs(uint32_t microseconds) - { - return setInterval(usToTicks(microseconds)); + hw_timer1_write(interval); } - /** @brief Set timer interval - * @param milliseconds Interval time in milliseconds - */ - __forceinline bool IRAM_ATTR setIntervalMs(uint32_t milliseconds) + __forceinline static void IRAM_ATTR disarm() { - return setIntervalUs(milliseconds * 1000); + if(isArmed()) { + hw_timer1_disable(); + state = eTS_Disarmed; + } } - /** @brief Set timer trigger callback - * @param callback Function to call when timer triggers - */ - void IRAM_ATTR setCallback(InterruptCallback callback) +private: + __forceinline static void detach_interrupt() { - if(callback == nullptr) { - stop(); + disarm(); + if(state > eTS_CallbackNotSet) { hw_timer1_detach_interrupt(); state = eTS_CallbackNotSet; - } else { - hw_timer1_attach_interrupt(mode == eHWT_NonMaskable ? TIMER_NMI_SOURCE : TIMER_FRC1_SOURCE, - reinterpret_cast(callback), nullptr); - state = eTS_Disarmed; } } private: - HardwareTimerMode mode = eHWT_NonMaskable; - - /// Fixed prescale value - static constexpr unsigned prescale = 16; - static constexpr hw_timer_clkdiv_t clkdiv = TIMER_CLKDIV_16; - static constexpr uint32_t frequency = HW_TIMER_BASE_CLK / prescale; - - uint32_t interval = 0; ///< Actual timer tick interval - bool repeating = false; - enum State { eTS_CallbackNotSet, eTS_Disarmed, eTS_Armed, + eTS_ArmedAutoLoad, }; static uint8_t state; + static TickType interval; }; +template uint8_t Timer1Api::state; +template uint32_t Timer1Api::interval; + +template + +/** + * @brief Hardware Timer class template with selectable divider and interrupt mode + */ +using HardwareTimer1 = CallbackTimer>; + +/** + * @brief Default hardware Timer class + */ +using HardwareTimer = HardwareTimer1<>; + /** * @deprecated Use HardwareTimer class instead */ -class Hardware_Timer : public HardwareTimer -{ -}; +typedef HardwareTimer Hardware_Timer SMING_DEPRECATED; /** @} */ diff --git a/Sming/Core/NanoTime.h b/Sming/Core/NanoTime.h index 143e0b16b1..de78e4c8cc 100644 --- a/Sming/Core/NanoTime.h +++ b/Sming/Core/NanoTime.h @@ -691,6 +691,26 @@ template struct TimeSource : publ return muldiv(time); } + /** + * @brief Get the number of ticks for a given time + * @tparam time + * @retval uint64_t Tick count, rounded to the nearest tick + */ + template static constexpr uint64_t timeToTicks() + { + return TimeConst