From d5c740915504135ef41ce79a6af68752e74950b9 Mon Sep 17 00:00:00 2001 From: Mike Date: Sun, 15 Sep 2019 17:00:36 +0100 Subject: [PATCH] Unified Callback Timer API (#1831) * Add `CallbackTimer` class template to support HardwareTimer, SimpleTimer and Timer. - Contains common logic and checks for all callback timer types - Templated for best performance (no VMT) - Added `TimerCallback` supporting void* arg parameter (in addition to existing `InterruptCallback`) - Templated methods added with compile-time checks on interval (so code won't compile if out of range) - Added methods to support setting/checking/querying intervals directly in timer ticksn - Timer intervals stored internally in timer ticks, so querying the value confirms the actual time in use accounting for rounding, etc. Note that delegate callbacks are supported only by the Timer class, which now also has an AutoDelete variant. Host timer queue implementation improved, handles multiple timers properly (fixes bug where all timers get cancelled instead of just one). Add `os_timer_arm_ticks()` function so software timers are programmed in ticks instead of microseconds or milliseconds - Used instead of `os_timer_arm_us` provides simpler and more flexible timer interface. - Avoids un-necessary (and potentially inaccurate) time conversions - Timers can be used with `USE_US_TIMER=0` for 3.2us tick resolution, providing basic range of 1'54" (with SimpleTimer) - Default is still `USE_US_TIMER=1` for 0.2us tick resolution and reduced 0'7"9s range (without using Timer class) * Update samples to use improved timer API and add timers module to HostTests * Templates ignore section attributes so requires entries in linker script for IRAM code. In the source code you'll see quite a bit of `__forceinline` as well as `IRAM_ATTR`, which is mainly for safety and to indicate functions/methods may be used from interrupt context. Unfortunately, marking templated code with `IRAM_ATTR` is not sufficient to get it into IRAM: https://stackoverflow.com/questions/36279162/section-attribute-of-a-function-template-is-silently-ignored-in-gcc So the linker script needs to be updated to catch all such instances. To do this requires splitting the `.text` output segment into two parts, called `.text` and `.text1`. The rBoot script also needs to know about both segments so they both end up in the ROM image it creates. Using `CallbackTimer` as an example, most of the code gets inlined anyway and as it's called from task context this actually uses very little IRAM. More details here: https://github.com/esp8266/Arduino/pull/5922 --- Sming/Arch/Esp8266/Compiler/ld/common.ld | 84 ++-- .../driver/include/driver/os_timer.h | 33 ++ .../Esp8266/Components/driver/os_timer.cpp | 109 +++++ .../esp_hal/include/esp_timer_legacy.h | 23 + .../Host/Components/esp_hal/timer_legacy.cpp | 91 ++-- Sming/Components/rboot/component.mk | 2 +- Sming/Core/CallbackTimer.h | 422 +++++++++++++++++ Sming/Core/HardwareTimer.cpp | 15 - Sming/Core/HardwareTimer.h | 226 ++++------ Sming/Core/NanoTime.h | 20 + Sming/Core/SimpleTimer.h | 126 +++--- Sming/Core/Timer.cpp | 157 ------- Sming/Core/Timer.h | 323 ++++++++----- Sming/Platform/System.cpp | 12 +- docs/source/information/timers.rst | 4 + .../HttpServer_WebSockets/app/application.cpp | 4 +- samples/LiveDebug/app/application.cpp | 33 +- samples/LiveDebug/component.mk | 9 +- samples/Wifi_Sniffer/app/application.cpp | 13 +- tests/HostTests/app/application.cpp | 6 +- tests/HostTests/app/modules.h | 3 +- tests/HostTests/app/test-timers.cpp | 425 ++++++++++++++++++ 22 files changed, 1556 insertions(+), 584 deletions(-) create mode 100644 Sming/Arch/Esp8266/Components/driver/include/driver/os_timer.h create mode 100644 Sming/Arch/Esp8266/Components/driver/os_timer.cpp create mode 100644 Sming/Core/CallbackTimer.h delete mode 100644 Sming/Core/HardwareTimer.cpp delete mode 100644 Sming/Core/Timer.cpp create mode 100644 tests/HostTests/app/test-timers.cpp 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