From fe1bc0b0294242b56fd62275742b801d5654b8c3 Mon Sep 17 00:00:00 2001 From: John Wellbelove Date: Tue, 20 Oct 2026 10:36:59 +0100 Subject: [PATCH] Initial code. Committed for reference --- include/etl/intrusive_callback_timer.h | 694 +++++++++++++++++++++++++ include/etl/timer_config.h | 588 +++++++++++++++++++++ test/test_timer_data.cpp | 319 ++++++++++++ test/vs2022/etl.vcxproj | 2 + test/vs2022/etl.vcxproj.filters | 9 + 5 files changed, 1612 insertions(+) create mode 100644 include/etl/intrusive_callback_timer.h create mode 100644 include/etl/timer_config.h create mode 100644 test/test_timer_data.cpp diff --git a/include/etl/intrusive_callback_timer.h b/include/etl/intrusive_callback_timer.h new file mode 100644 index 00000000..688dacc7 --- /dev/null +++ b/include/etl/intrusive_callback_timer.h @@ -0,0 +1,694 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2017 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_CALLBACK_TIMER_INCLUDED +#define ETL_CALLBACK_TIMER_INCLUDED + +#include "platform.h" +#include "algorithm.h" +#include "atomic.h" +#include "delegate.h" +#include "error_handler.h" +#include "function.h" +#include "nullptr.h" +#include "placement_new.h" +#include "static_assert.h" +#include "timer_data.h" + +#include + +namespace etl +{ + //*************************************************************************** + /// Interface for callback timer + //*************************************************************************** + class iintrusive_callback_timer + { + public: + + typedef etl::delegate callback_type; + + typedef etl::delegate event_callback_type; + + //******************************************* + /// Register a timer. + //******************************************* + etl::timer::id::type register_timer(void (*p_callback_)(), uint32_t period_, bool repeating_) + { + return id; + } + + //******************************************* + /// Register a timer. + //******************************************* + etl::timer::id::type register_timer(etl::ifunction& callback_, uint32_t period_, bool repeating_) + { + etl::timer::id::type id = etl::timer::id::NO_TIMER; + + bool is_space = (registered_timers < MAX_TIMERS); + + if (is_space) + { + // Search for the free space. + for (uint_least8_t i = 0U; i < MAX_TIMERS; ++i) + { + timer_data& timer = timer_array[i]; + + if (timer.id == etl::timer::id::NO_TIMER) + { + // Create in-place. + new (&timer) timer_data(i, callback_, period_, repeating_); + ++registered_timers; + id = i; + break; + } + } + } + + return id; + } + + //******************************************* + /// Register a timer. + //******************************************* +#if ETL_USING_CPP11 + etl::timer::id::type register_timer(callback_type& callback_, uint32_t period_, bool repeating_) + { + etl::timer::id::type id = etl::timer::id::NO_TIMER; + + bool is_space = (registered_timers < MAX_TIMERS); + + if (is_space) + { + // Search for the free space. + for (uint_least8_t i = 0U; i < MAX_TIMERS; ++i) + { + timer_data& timer = timer_array[i]; + + if (timer.id == etl::timer::id::NO_TIMER) + { + // Create in-place. + new (&timer) timer_data(i, callback_, period_, repeating_); + ++registered_timers; + id = i; + break; + } + } + } + + return id; + } +#endif + + //******************************************* + /// Unregister a timer. + //******************************************* + bool unregister_timer(etl::timer::id::type id_) + { + bool result = false; + + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + ETL_DISABLE_TIMER_UPDATES; + active_list.remove(timer.id, false); + remove_callback.call_if(timer.id); + ETL_ENABLE_TIMER_UPDATES; + } + + // Reset in-place. + new (&timer) timer_data(); + --registered_timers; + + result = true; + } + } + + return result; + } + + //******************************************* + /// Enable/disable the timer. + //******************************************* + void enable(bool state_) + { + enabled = state_; + } + + //******************************************* + /// Get the enable/disable state. + //******************************************* + bool is_running() const + { + return enabled; + } + + //******************************************* + /// Clears the timer of data. + //******************************************* + void clear() + { + ETL_DISABLE_TIMER_UPDATES; + active_list.clear(); + ETL_ENABLE_TIMER_UPDATES; + + for (int i = 0; i < MAX_TIMERS; ++i) + { + ::new (&timer_array[i]) timer_data(); + } + + registered_timers = 0; + } + + //******************************************* + // Called by the timer service to indicate the + // amount of time that has elapsed since the last successful call to 'tick'. + // Returns true if the tick was processed, + // false if not. + //******************************************* + bool tick(uint32_t count) + { + if (enabled) + { + if (ETL_TIMER_UPDATES_ENABLED) + { + // We have something to do? + bool has_active = !active_list.empty(); + + if (has_active) + { + while (has_active && (count >= active_list.front().delta)) + { + timer_data& timer = active_list.front(); + + count -= timer.delta; + + active_list.remove(timer.id, true); + remove_callback.call_if(timer.id); + + if (timer.repeating) + { + // Reinsert the timer. + timer.delta = timer.period; + active_list.insert(timer.id); + insert_callback.call_if(timer.id); + } + + if (timer.p_callback != ETL_NULLPTR) + { + if (timer.cbk_type == timer_data::C_CALLBACK) + { + // Call the C callback. + reinterpret_cast(timer.p_callback)(); + } + else if (timer.cbk_type == timer_data::IFUNCTION) + { + // Call the function wrapper callback. + (*reinterpret_cast*>(timer.p_callback))(); + } + else if (timer.cbk_type == timer_data::DELEGATE) + { + // Call the delegate callback. + (*reinterpret_cast(timer.p_callback))(); + } + } + + has_active = !active_list.empty(); + } + + if (has_active) + { + // Subtract any remainder from the next due timeout. + active_list.front().delta -= count; + } + } + + return true; + } + } + + return false; + } + + //******************************************* + /// Starts a timer. + //******************************************* + bool start(etl::timer::id::type id_, bool immediate_ = false) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + // Has a valid period. + if (timer.period != etl::timer::state::Inactive) + { + ETL_DISABLE_TIMER_UPDATES; + if (timer.is_active()) + { + active_list.remove(timer.id, false); + remove_callback.call_if(timer.id); + } + + timer.delta = immediate_ ? 0 : timer.period; + active_list.insert(timer.id); + insert_callback.call_if(timer.id); + ETL_ENABLE_TIMER_UPDATES; + + result = true; + } + } + } + + return result; + } + + //******************************************* + /// Stops a timer. + //******************************************* + bool stop(etl::timer::id::type id_) + { + bool result = false; + + // Valid timer id? + if (id_ != etl::timer::id::NO_TIMER) + { + timer_data& timer = timer_array[id_]; + + // Registered timer? + if (timer.id != etl::timer::id::NO_TIMER) + { + if (timer.is_active()) + { + ETL_DISABLE_TIMER_UPDATES; + active_list.remove(timer.id, false); + remove_callback.call_if(timer.id); + ETL_ENABLE_TIMER_UPDATES; + } + + result = true; + } + } + + return result; + } + + //******************************************* + /// Sets a timer's period. + //******************************************* + bool set_period(etl::timer::id::type id_, uint32_t period_) + { + if (stop(id_)) + { + timer_array[id_].period = period_; + return true; + } + + return false; + } + + //******************************************* + /// Sets a timer's mode. + //******************************************* + bool set_mode(etl::timer::id::type id_, bool repeating_) + { + if (stop(id_)) + { + timer_array[id_].repeating = repeating_; + return true; + } + + return false; + } + + //******************************************* + /// Returns true if an attempt was ever made to insert a timer into the + /// active list when it was already there. Such an insert is rejected, as + /// it would create a circular reference in the list. + //******************************************* + bool duplicate_insert_detected() const + { + return active_list.duplicate_insert_detected(); + } + + //******************************************* + /// Check if there is an active timer. + //******************************************* + bool has_active_timer() const + { + return !active_list.empty(); + } + + //******************************************* + /// Get the time to the next timer event. + /// Returns etl::timer::interval::No_Active_Interval if there is no active + /// timer. + //******************************************* + uint32_t time_to_next() const + { + uint32_t delta = static_cast(etl::timer::interval::No_Active_Interval); + + if (has_active_timer()) + { + delta = active_list.front().delta; + } + + return delta; + } + + //******************************************* + /// Checks if a timer is currently active. + /// Returns true if the timer is active, otherwise false. + //******************************************* + bool is_active(etl::timer::id::type id_) const + { + //// Valid timer id? + // if (is_valid_timer_id(id_)) + //{ + // if (has_active_timer()) + // { + // const timer_data& timer = timer_array[id_]; + + // // Registered timer? + // if (timer.id != etl::timer::id::NO_TIMER) + // { + // return timer.is_active(); + // } + // } + //} + + return false; + } + + ////******************************************* + ///// Set a callback when a timer is inserted on list + ////******************************************* + // void set_insert_callback(event_callback_type insert_) + //{ + // insert_callback = insert_; + // } + + ////******************************************* + ///// Set a callback when a timer is removed from list + ////******************************************* + // void set_remove_callback(event_callback_type remove_) + //{ + // remove_callback = remove_; + // } + + //******************************************* + void clear_insert_callback() + { + insert_callback.clear(); + } + + //******************************************* + void clear_remove_callback() + { + remove_callback.clear(); + } + + protected: + + //******************************************* + /// Constructor. + //******************************************* + icallback_timer(timer_data* const timer_array_, const uint_least8_t Max_Timers_) + : timer_array(timer_array_) + , active_list(timer_array_) + , enabled(false) + , +#if defined(ETL_CALLBACK_TIMER_USE_ATOMIC_LOCK) + process_semaphore(0) + , +#endif + registered_timers(0) + , MAX_TIMERS(Max_Timers_) + { + } + + private: + + //******************************************* + /// Check that the timer id is valid. + //******************************************* + bool is_valid_timer_id(etl::timer::id::type id_) const + { + return (id_ < MAX_TIMERS); + } + + //************************************************************************* + class timer_list + { + public: + + //******************************* + timer_list(timer_data* ptimers_) + : head(etl::timer::id::NO_TIMER) + , tail(etl::timer::id::NO_TIMER) + , ptimers(ptimers_) + , duplicate_insert(false) + { + } + + //******************************* + bool empty() const + { + return head == etl::timer::id::NO_TIMER; + } + + //******************************* + /// Returns true if an attempt was ever made to insert a timer that was + /// already in the list. + //******************************* + bool duplicate_insert_detected() const + { + return duplicate_insert; + } + + //******************************* + // Inserts the timer at the correct delta position + //******************************* + void insert(etl::timer::id::type id_) + { + timer_data& timer = ptimers[id_]; + + // A timer is in this list exactly when it is the head or has a previous + // link, as remove() clears both links. Inserting one that is already + // here would link it to itself and create a circular reference, so + // record the attempt and abandon the insert to keep the list intact. + if ((head == id_) || (timer.previous != etl::timer::id::NO_TIMER)) + { + duplicate_insert = true; + return; + } + + if (head == etl::timer::id::NO_TIMER) + { + // No entries yet. + head = id_; + tail = id_; + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + } + else + { + // We already have entries. + etl::timer::id::type test_id = begin(); + + while (test_id != etl::timer::id::NO_TIMER) + { + timer_data& test = ptimers[test_id]; + + // Find the correct place to insert. + if (timer.delta <= test.delta) + { + if (test.id == head) + { + head = timer.id; + } + + // Insert before test. + timer.previous = test.previous; + test.previous = timer.id; + timer.next = test.id; + + // Adjust the next delta to compensate. + test.delta -= timer.delta; + + if (timer.previous != etl::timer::id::NO_TIMER) + { + ptimers[timer.previous].next = timer.id; + } + break; + } + else + { + timer.delta -= test.delta; + } + + test_id = next(test_id); + } + + // Reached the end? + if (test_id == etl::timer::id::NO_TIMER) + { + // Tag on to the tail. + ptimers[tail].next = timer.id; + timer.previous = tail; + timer.next = etl::timer::id::NO_TIMER; + tail = timer.id; + } + } + } + + //******************************* + void remove(etl::timer::id::type id_, bool has_expired) + { + timer_data& timer = ptimers[id_]; + + if (head == id_) + { + head = timer.next; + } + else + { + ptimers[timer.previous].next = timer.next; + } + + if (tail == id_) + { + tail = timer.previous; + } + else + { + ptimers[timer.next].previous = timer.previous; + } + + if (!has_expired) + { + // Adjust the next delta. + if (timer.next != etl::timer::id::NO_TIMER) + { + ptimers[timer.next].delta += timer.delta; + } + } + + timer.previous = etl::timer::id::NO_TIMER; + timer.next = etl::timer::id::NO_TIMER; + timer.delta = etl::timer::state::Inactive; + } + + //******************************* + timer_data& front() + { + return ptimers[head]; + } + + //******************************* + const timer_data& front() const + { + return ptimers[head]; + } + + //******************************* + etl::timer::id::type begin() + { + return head; + } + + //******************************* + etl::timer::id::type next(etl::timer::id::type last) + { + return ptimers[last].next; + } + + //******************************* + void clear() + { + etl::timer::id::type id = begin(); + + while (id != etl::timer::id::NO_TIMER) + { + timer_data& timer = ptimers[id]; + id = next(id); + timer.next = etl::timer::id::NO_TIMER; + } + + head = etl::timer::id::NO_TIMER; + tail = etl::timer::id::NO_TIMER; + } + + private: + + etl::timer::id::type head; + etl::timer::id::type tail; + + timer_data* const ptimers; + + // Set if an attempt was ever made to insert a timer that was already + // in the list. + bool duplicate_insert; + }; + + // The array of timer data structures. + timer_data* const timer_array; + + // The list of active timers. + timer_list active_list; + + volatile bool enabled; + uint_least8_t registered_timers; + + event_callback_type timeout_callback; + + etl::timer_data terminator; + }; + + //*************************************************************************** + /// The callback timer + //*************************************************************************** + class intrusive_callback_timer : public etl::iintrusive_callback_timer + { + public: + + private: + }; +} // namespace etl + +#endif diff --git a/include/etl/timer_config.h b/include/etl/timer_config.h new file mode 100644 index 00000000..5ee34670 --- /dev/null +++ b/include/etl/timer_config.h @@ -0,0 +1,588 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#ifndef ETL_TIMER_DATA_INCLUDED +#define ETL_TIMER_DATA_INCLUDED + +#include "platform.h" +#include "nullptr.h" +#include "timer.h" + +#include + +//***************************************************************************** +// Definitions common to intrusive timers. +//***************************************************************************** + +namespace etl +{ + //*************************************************************************** + /// Common definitions for the intrusive timer framework. + //*************************************************************************** + class timer_config + { + public: + + friend class timer_register_list; + friend class timer_active_list; + friend class timer_triggered_list; + friend class intrusive_callback_timer; + + //******************************************* + timer_config() ETL_NOEXCEPT + : period(etl::timer::state::Inactive) + , delta(etl::timer::state::Inactive) + , mode(etl::timer::mode::Repeating) + , registered_next(ETL_NULLPTR) + , active_next(ETL_NULLPTR) + , triggered_next(ETL_NULLPTR) + { + } + + //******************************************* + timer_config(uint32_t period_, etl::timer::mode::type repeating_) ETL_NOEXCEPT + : period(period_) + , delta(period_) + , mode(repeating_) + , registered_next(ETL_NULLPTR) + , active_next(ETL_NULLPTR) + , triggered_next(ETL_NULLPTR) + { + } + + //******************************************* + uint32_t get_period() const ETL_NOEXCEPT + { + return period; + } + + //******************************************* + uint32_t get_delta() const ETL_NOEXCEPT + { + return delta; + } + + //******************************************* + uint32_t get_mode() const ETL_NOEXCEPT + { + return mode; + } + + //******************************************* + void clear() ETL_NOEXCEPT + { + period = etl::timer::state::Inactive; + delta = etl::timer::state::Inactive; + mode = etl::timer::mode::Repeating; + registered_next = ETL_NULLPTR; + active_next = ETL_NULLPTR; + triggered_next = ETL_NULLPTR; + } + + private: + + uint32_t period; ///< The period of the timer. + uint32_t delta; ///< The time remaining until the timer expires. + timer_config* registered_next; ///< The next timer in the registered timer list. nullptr if the timer is not registered. + timer_config* active_next; ///< The next timer in the active timer list. nullptr if the timer is not active. + timer_config* triggered_next; ///< The next timer in the triggered timer list. nullptr if the timer is not triggered. + etl::timer::mode::type mode; ///< Whether the timer is repeating or single-shot. + }; + + //*************************************************************************** + /// A timer data structure that includes a callback delegate. + //*************************************************************************** + template + struct callback_timer_config : public timer_config + { + callback_timer_config(etl::timer::id::type id_, TCallback callback_) ETL_NOEXCEPT + : timer_config() + , callback(callback_) + { + } + + TCallback callback; ///< The callback delegate for the timer. + }; + + //************************************************************************* + /// The list of timers that are registered. + //************************************************************************* + class timer_register_list + { + public: + + //******************************* + timer_register_list() ETL_NOEXCEPT + : head(ETL_NULLPTR) + , tail(ETL_NULLPTR) + { + } + + //******************************* + bool empty() const + { + return head == ETL_NULLPTR; + } + + //******************************* + // Adds the timer in to the list. + //******************************* + void insert(etl::timer_config* new_timer) ETL_NOEXCEPT + { + if (empty()) + { + head = new_timer; + } + else if (head == new_timer) + { + + } + else + { + // Remove any existing timer. + etl::timer_config* previous = head; + + // Step through the list until we find the timer to remove, or reach the end of the list. + while ((previous->registered_next != ETL_NULLPTR) && (previous->registered_next != new_timer)) + { + previous = previous->registered_next; + } + + // If we found the timer, remove it from the list. + if (previous->registered_next == new_timer) + { + } + + tail->registered_next = new_timer; + } + + tail = new_timer; + } + + //******************************* + // Remove the timer from the list. + // Returns the next timer in the list after the removed timer, or nullptr if the removed timer was the last in the list. + //******************************* + etl::timer_config* remove(etl::timer_config* timer) ETL_NOEXCEPT + { + if (timer == head) + { + head = timer->registered_next; + + if (head == ETL_NULLPTR) + { + tail = ETL_NULLPTR; + } + + timer->registered_next = ETL_NULLPTR; + + return head; + } + else + { + etl::timer_config* previous = head; + + // Step through the list until we find the timer to remove, or reach the end of the list. + while ((previous->registered_next != ETL_NULLPTR) && (previous->registered_next != timer)) + { + previous = previous->registered_next; + } + + // If we found the timer, remove it from the list. + if (previous->registered_next == timer) + { + previous->registered_next = timer->registered_next; + + if (timer == tail) + { + tail = previous; + } + + timer->registered_next = ETL_NULLPTR; + + return previous->registered_next; + } + else + { + // Timer not found in the list. + return ETL_NULLPTR; + } + } + } + + //******************************* + /// Returns the first timer in the list. + /// If the list is empty, returns nullptr. + //******************************* + etl::timer_config* first() const ETL_NOEXCEPT + { + return head; + } + + //******************************* + /// Returns the next timer in the list after the given timer. + /// May be nullptr if the given timer is the last in the list. + /// If the given timer is nullptr, returns nullptr. + //******************************* + etl::timer_config* next(etl::timer_config* timer) const ETL_NOEXCEPT + { + if (timer != ETL_NULLPTR) + { + return timer->registered_next; + } + else + { + return ETL_NULLPTR; + } + } + + //******************************* + /// Clear the list of entries. + //******************************* + void clear() + { + etl::timer_config* timer = head; + + while (timer != ETL_NULLPTR) + { + timer = pop_front(); + } + + head = ETL_NULLPTR; + tail = ETL_NULLPTR; + } + + private: + + //******************************* + // Removes the timer at the front of the list. + // Returns the new head of the list, which may be nullptr if the list is now empty. + //******************************* + etl::timer_config* pop_front() ETL_NOEXCEPT + { + etl::timer_config* timer = head; + head = timer->registered_next; + timer->registered_next = ETL_NULLPTR; + + return head; + } + + etl::timer_config* head; + etl::timer_config* tail; + }; + + //************************************************************************* + /// The list of timers that are active. + //************************************************************************* + class timer_active_list + { + public: + + //******************************* + timer_active_list() ETL_NOEXCEPT + : head(ETL_NULLPTR) + { + } + + //******************************* + bool empty() const ETL_NOEXCEPT + { + return head == ETL_NULLPTR; + } + + //******************************* + // Inserts the timer at the correct delta position + //******************************* + void insert(etl::timer_config* new_timer) ETL_NOEXCEPT + { + if (empty()) + { + // No entries yet. + head = new_timer; + new_timer->active_next = ETL_NULLPTR; + } + else if (new_timer->delta < head->delta) + { + // Goes before the head. + head->delta -= new_timer->delta; + new_timer->active_next = head; + head = new_timer; + } + else + { + // We already have entries, and new_timer doesn't go before head. + timer_config* previous_timer = head; + timer_config* timer = head->active_next; + + // Adjust the new_timer delta to take account of the head timer's delta. + new_timer->delta -= previous_timer->delta; + + bool inserted = false; + + while (!inserted && (timer != ETL_NULLPTR)) + { + // Find the correct place to insert. + // The list is sorted by delta, so we need to find the first timer with a delta greater than the new timer's delta. + if (new_timer->delta < timer->delta) + { + // Insert after the previous timer. + previous_timer->active_next = new_timer; + new_timer->active_next = timer; + + // Adjust the next delta for the new timer's delta. + timer->delta -= new_timer->delta; + + inserted = true; + } + else + { + // Adjust the new_timer delta to take account of the last timer's delta. + new_timer->delta -= timer->delta; + } + + // Move to the next timer in the list. + previous_timer = timer; + timer = timer->active_next; + } + + if (!inserted) + { + // Reached the end so attach to the tail. + previous_timer->active_next = new_timer; + new_timer->active_next = ETL_NULLPTR; + } + } + } + + //******************************* + // Removes the selected timer from the list. + // No action is taken if the timer is not found. + // returns the next timer in the list after the removed timer, or nullptr if the removed timer was the last in the list. + //******************************* + etl::timer_config* remove(etl::timer_config* timer) ETL_NOEXCEPT + { + // Find the timer in the list. + etl::timer_config* current = head; + etl::timer_config* last = ETL_NULLPTR; + + while (current != timer && current != ETL_NULLPTR) + { + last = current; + current = current->active_next; + } + + // Not found? + if (current == ETL_NULLPTR) + { + return ETL_NULLPTR; + } + + etl::timer_config* next = current->active_next; + + // Adjust the next delta. + if (next != ETL_NULLPTR) + { + next->delta += current->delta; + } + + // Remove it from the list. + if (last == ETL_NULLPTR) + { + head = next; + } + else + { + last->active_next = next; + } + + // Clear the next pointer of the removed timer. + current->active_next = ETL_NULLPTR; + + return next; + } + + //******************************* + /// Returns the first timer in the list. + /// If the list is empty, returns nullptr. + //******************************* + etl::timer_config* first() const ETL_NOEXCEPT + { + return head; + } + + //******************************* + /// Returns the next timer in the list after the given timer. + /// If the given timer is nullptr, returns nullptr. + //******************************* + etl::timer_config* next(etl::timer_config* timer) const ETL_NOEXCEPT + { + if (timer != ETL_NULLPTR) + { + return timer->active_next; + } + else + { + return ETL_NULLPTR; + } + } + + //******************************* + /// Clear the list of entries. + //******************************* + void clear() + { + etl::timer_config* timer = head; + + while (timer != ETL_NULLPTR) + { + timer = pop_front(); + } + + head = ETL_NULLPTR; + } + + private: + + //******************************* + // Removes the timer at the front of the list. + // Returns the new head of the list, which may be nullptr if the list is now empty. + //******************************* + etl::timer_config* pop_front() ETL_NOEXCEPT + { + etl::timer_config* timer = head; + head = timer->active_next; + timer->active_next = ETL_NULLPTR; + + return head; + } + + etl::timer_config* head; + }; + + //************************************************************************* + /// The list of timers that are registered. + //************************************************************************* + class timer_triggered_list + { + public: + + //******************************* + timer_triggered_list() ETL_NOEXCEPT + : head(ETL_NULLPTR) + , tail(ETL_NULLPTR) + { + } + + //******************************* + bool empty() const + { + return head == ETL_NULLPTR; + } + + //******************************* + // Adds the timer in to the list. + //******************************* + void insert(etl::timer_config* new_timer) ETL_NOEXCEPT + { + new_timer->triggered_next = ETL_NULLPTR; + + if (empty()) + { + head = new_timer; + } + else + { + tail->triggered_next = new_timer; + } + + tail = new_timer; + } + + //******************************* + /// Returns the first timer in the list. + /// If the list is empty, returns nullptr. + //******************************* + etl::timer_config* first() const ETL_NOEXCEPT + { + return head; + } + + //******************************* + /// Returns the next timer in the list after the given timer. + /// May be nullptr if the given timer is the last in the list. + /// If the given timer is nullptr, returns nullptr. + //******************************* + etl::timer_config* next(etl::timer_config* timer) const ETL_NOEXCEPT + { + if (timer != ETL_NULLPTR) + { + return timer->triggered_next; + } + else + { + return ETL_NULLPTR; + } + } + + //******************************* + /// Clear the list of entries. + //******************************* + void clear() + { + etl::timer_config* timer = head; + + while (timer != ETL_NULLPTR) + { + timer = pop_front(); + } + + head = ETL_NULLPTR; + tail = ETL_NULLPTR; + } + + private: + + //******************************* + // Removes the timer at the front of the list. + // Returns the new head of the list, which may be nullptr if the list is now empty. + //******************************* + etl::timer_config* pop_front() ETL_NOEXCEPT + { + etl::timer_config* timer = head; + head = timer->triggered_next; + timer->triggered_next = ETL_NULLPTR; + + return head; + } + + etl::timer_config* head; + etl::timer_config* tail; + }; +} // namespace etl + +#endif diff --git a/test/test_timer_data.cpp b/test/test_timer_data.cpp new file mode 100644 index 00000000..167c4a82 --- /dev/null +++ b/test/test_timer_data.cpp @@ -0,0 +1,319 @@ +/****************************************************************************** +The MIT License(MIT) + +Embedded Template Library. +https://github.com/ETLCPP/etl +https://www.etlcpp.com + +Copyright(c) 2026 John Wellbelove + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files(the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions : + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +******************************************************************************/ + +#include "unit_test_framework.h" + +#include "etl/timer_config.h" + +namespace +{ + SUITE(test_timer_config) + { + //************************************************************************* + TEST(test_register_list) + { + etl::timer_config timer1; + etl::timer_config timer2; + etl::timer_config timer3; + etl::timer_config timer4; + etl::timer_config timer5; + + etl::timer_register_list register_list; + + CHECK_TRUE(register_list.empty()); + CHECK_TRUE(register_list.first() == ETL_NULLPTR); + + // Add the timers to the list. + register_list.insert(&timer1); + register_list.insert(&timer2); + register_list.insert(&timer3); + register_list.insert(&timer4); + register_list.insert(&timer5); + + CHECK_FALSE(register_list.empty()); + CHECK_TRUE(register_list.first() == &timer1); + + etl::timer_config* timer = register_list.first(); + + // The timers should be in the order they were added. + CHECK_EQUAL(&timer1, timer); + timer = register_list.next(timer); + CHECK_EQUAL(&timer2, timer); + timer = register_list.next(timer); + CHECK_EQUAL(&timer3, timer); + timer = register_list.next(timer); + CHECK_EQUAL(&timer4, timer); + timer = register_list.next(timer); + CHECK_EQUAL(&timer5, timer); + timer = register_list.next(timer); + CHECK_EQUAL(ETL_NULLPTR, timer); + + // Remove timer3 from the list. + register_list.remove(&timer3); + + // Search the list to ensure the other timers are still present. + bool timer1_found = false; + bool timer2_found = false; + bool timer3_found = false; + bool timer4_found = false; + bool timer5_found = false; + + timer = register_list.first(); + + while (timer != ETL_NULLPTR) + { + if (timer == &timer1) + { + timer1_found = true; + } + else if (timer == &timer2) + { + timer2_found = true; + } + else if (timer == &timer3) + { + timer3_found = true; + } + else if (timer == &timer4) + { + timer4_found = true; + } + else if (timer == &timer5) + { + timer5_found = true; + } + + timer = register_list.next(timer); + } + + CHECK_TRUE(timer1_found); + CHECK_TRUE(timer2_found); + CHECK_FALSE(timer3_found); + CHECK_TRUE(timer4_found); + CHECK_TRUE(timer5_found); + + // Clear the list. + register_list.clear(); + + CHECK_TRUE(register_list.empty()); + CHECK_TRUE(register_list.first() == ETL_NULLPTR); + } + + //************************************************************************* + TEST(test_active_list) + { + etl::timer_config timer1(100, etl::timer::mode::Repeating); + etl::timer_config timer2(65, etl::timer::mode::Repeating); + etl::timer_config timer3(85, etl::timer::mode::Repeating); + etl::timer_config timer4(130, etl::timer::mode::Repeating); + etl::timer_config timer5(85, etl::timer::mode::Repeating); + etl::timer_config timer6(85, etl::timer::mode::Repeating); + + etl::timer_active_list active_list; + + CHECK_TRUE(active_list.empty()); + CHECK_TRUE(active_list.first() == ETL_NULLPTR); + + active_list.insert(&timer1); + active_list.insert(&timer2); + active_list.insert(&timer3); + active_list.insert(&timer4); + active_list.insert(&timer5); + + CHECK_FALSE(active_list.empty()); + CHECK_TRUE(active_list.first() == &timer2); + + etl::timer_config* timer = active_list.first(); + + // The timers should be in the order of their deltas. + CHECK_EQUAL(&timer2, timer); + timer = active_list.next(timer); + CHECK_EQUAL(&timer3, timer); + timer = active_list.next(timer); + CHECK_EQUAL(&timer5, timer); + timer = active_list.next(timer); + CHECK_EQUAL(&timer1, timer); + timer = active_list.next(timer); + CHECK_EQUAL(&timer4, timer); + timer = active_list.next(timer); + CHECK_EQUAL(ETL_NULLPTR, timer); + + CHECK_EQUAL(timer2.get_period(), timer2.get_delta()); + CHECK_EQUAL(timer3.get_period() - timer2.get_period(), timer3.get_delta()); + CHECK_EQUAL(timer1.get_period() - timer3.get_period(), timer1.get_delta()); + CHECK_EQUAL(timer4.get_period() - timer1.get_period(), timer4.get_delta()); + CHECK_EQUAL(timer5.get_period() - timer3.get_period(), timer5.get_delta()); + + // Remove timer3 from the list. + active_list.remove(&timer3); + + // Search the list to ensure the other timers are still present. + bool timer1_found = false; + bool timer2_found = false; + bool timer3_found = false; + bool timer4_found = false; + bool timer5_found = false; + + timer = active_list.first(); + + while (timer != ETL_NULLPTR) + { + if (timer == &timer1) + { + timer1_found = true; + } + else if (timer == &timer2) + { + timer2_found = true; + } + else if (timer == &timer3) + { + timer3_found = true; + } + else if (timer == &timer4) + { + timer4_found = true; + } + else if (timer == &timer5) + { + timer5_found = true; + } + + timer = active_list.next(timer); + } + + CHECK_TRUE(timer1_found); + CHECK_TRUE(timer2_found); + CHECK_FALSE(timer3_found); + CHECK_TRUE(timer4_found); + CHECK_TRUE(timer5_found); + + // Check the deltas are still correct. + CHECK_EQUAL(timer2.get_period(), timer2.get_delta()); + CHECK_EQUAL(timer1.get_period() - timer3.get_period(), timer1.get_delta()); + CHECK_EQUAL(timer4.get_period() - timer1.get_period(), timer4.get_delta()); + CHECK_EQUAL(timer5.get_period() - timer2.get_period(), timer5.get_delta()); + + // Remove a timer that is not in the list. + active_list.remove(&timer6); + + // Search the list to ensure the other timers are still present. + timer1_found = false; + timer2_found = false; + timer3_found = false; + timer4_found = false; + timer5_found = false; + + timer = active_list.first(); + + while (timer != ETL_NULLPTR) + { + if (timer == &timer1) + { + timer1_found = true; + } + else if (timer == &timer2) + { + timer2_found = true; + } + else if (timer == &timer3) + { + timer3_found = true; + } + else if (timer == &timer4) + { + timer4_found = true; + } + else if (timer == &timer5) + { + timer5_found = true; + } + + timer = active_list.next(timer); + } + + // Check the deltas are still correct. + CHECK_EQUAL(timer2.get_period(), timer2.get_delta()); + CHECK_EQUAL(timer1.get_period() - timer3.get_period(), timer1.get_delta()); + CHECK_EQUAL(timer4.get_period() - timer1.get_period(), timer4.get_delta()); + CHECK_EQUAL(timer5.get_period() - timer2.get_period(), timer5.get_delta()); + + // Clear the list. + active_list.clear(); + + CHECK_TRUE(active_list.empty()); + CHECK_TRUE(active_list.first() == ETL_NULLPTR); + } + + //************************************************************************* + TEST(test_triggered_list) + { + etl::timer_config timer1; + etl::timer_config timer2; + etl::timer_config timer3; + etl::timer_config timer4; + etl::timer_config timer5; + + etl::timer_triggered_list triggered_list; + + CHECK_TRUE(triggered_list.empty()); + CHECK_TRUE(triggered_list.first() == ETL_NULLPTR); + + // Add the timers to the list. + triggered_list.insert(&timer1); + triggered_list.insert(&timer2); + triggered_list.insert(&timer3); + triggered_list.insert(&timer4); + triggered_list.insert(&timer5); + + CHECK_FALSE(triggered_list.empty()); + CHECK_TRUE(triggered_list.first() == &timer1); + + // The timers should be in the order they were added. + etl::timer_config* timer = triggered_list.first(); + + CHECK_EQUAL(&timer1, timer); + timer = triggered_list.next(timer); + CHECK_EQUAL(&timer2, timer); + timer = triggered_list.next(timer); + CHECK_EQUAL(&timer3, timer); + timer = triggered_list.next(timer); + CHECK_EQUAL(&timer4, timer); + timer = triggered_list.next(timer); + CHECK_EQUAL(&timer5, timer); + timer = triggered_list.next(timer); + CHECK_EQUAL(ETL_NULLPTR, timer); + + // Clear the list. + triggered_list.clear(); + + CHECK_TRUE(triggered_list.empty()); + CHECK_TRUE(triggered_list.first() == ETL_NULLPTR); + } + } +} // namespace diff --git a/test/vs2022/etl.vcxproj b/test/vs2022/etl.vcxproj index e7bb1ff5..9b217851 100644 --- a/test/vs2022/etl.vcxproj +++ b/test/vs2022/etl.vcxproj @@ -3559,6 +3559,7 @@ + @@ -11449,6 +11450,7 @@ + diff --git a/test/vs2022/etl.vcxproj.filters b/test/vs2022/etl.vcxproj.filters index 954b7de1..7002ecda 100644 --- a/test/vs2022/etl.vcxproj.filters +++ b/test/vs2022/etl.vcxproj.filters @@ -226,6 +226,9 @@ {5b809d95-b37c-4011-af5e-b5ea6dcb354e} + + {c3b8f843-f046-4bf2-ab0a-34e9da0dcfbe} + @@ -1566,6 +1569,9 @@ UnitTest++\Header Files + + ETL\Frameworks + @@ -3836,6 +3842,9 @@ Tests\Containers + + Tests\Timer support +