Work in progress

Timer list tests need a workaround for ownership issues.
This commit is contained in:
John Wellbelove 2026-10-22 21:03:11 +01:00
parent b903d7759e
commit d04cc5c296
5 changed files with 290 additions and 215 deletions

View File

@ -5,7 +5,7 @@ Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2017 John Wellbelove
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
@ -30,11 +30,7 @@ SOFTWARE.
#define ETL_INTRUSIVE_TIMER_INCLUDED
#include "platform.h"
#include "algorithm.h"
#include "delegate.h"
#include "error_handler.h"
#include "nullptr.h"
#include "static_assert.h"
#include "timer_node.h"
#include "private/timer_lists.h"
@ -62,7 +58,7 @@ namespace etl
{
public:
typedef etl::delegate<void(etl::timer::id::type, uint32_t delta)> timer_update_callback_type;
typedef etl::delegate<void(etl::timer_node&, uint32_t delta)> timer_update_callback_type;
//*******************************************
/// Constructor.
@ -74,7 +70,6 @@ namespace etl
, expired_list()
, enabled(false)
, timer_count(0U)
, next_id(0U)
{
}
@ -82,43 +77,22 @@ namespace etl
/// Register a timer.
/// It will not be started until the start() method is called.
//*******************************************
bool register_timer(etl::timer_node& node) ETL_NOEXCEPT
bool register_timer(etl::timer_node& timer_) ETL_NOEXCEPT
{
bool result = false;
etl::timer::id::type id = get_next_id();
node.set_id(id);
lock_policy.lock();
// Register the new timer.
register_list.insert(&node);
result = true;
++timer_count;
return result;
}
//*******************************************
/// Unregister a timer.
/// By id.
//*******************************************
bool unregister_timer(etl::timer::id::type id) ETL_NOEXCEPT
{
bool result = false;
if (id != etl::timer::id::No_Timer)
if (!timer_.is_registered(this))
{
lock_policy.lock();
active_list.remove(id);
register_list.remove(id);
lock_policy.unlock();
register_list.insert(timer_);
timer_.set_owner(this);
++timer_count;
result = true;
--timer_count;
}
lock_policy.unlock();
return result;
}
@ -126,11 +100,29 @@ namespace etl
/// Unregister a timer.
/// By node reference.
//*******************************************
bool unregister_timer(etl::timer_node& node) ETL_NOEXCEPT
bool unregister_timer(etl::timer_node& timer_) ETL_NOEXCEPT
{
etl::timer::id::type id = node.get_id();
if (timer_.is_registered(this))
{
lock_policy.lock();
return unregister_timer(id);
if (timer_.is_active())
{
active_list.remove(&timer_);
}
register_list.remove(&timer_);
lock_policy.unlock();
--timer_count;
return true;
}
else
{
return false;
}
}
//*******************************************
@ -188,7 +180,7 @@ namespace etl
{
count -= timer->delta;
active_list.remove(timer->get_id());
active_list.remove(timer);
// If the timer is repeating then add it back to the active list.
if (timer->get_mode() == etl::timer::mode::Repeating)
@ -227,13 +219,13 @@ namespace etl
//*******************************************
/// Starts a timer.
//*******************************************
bool start(etl::timer::id::type id_, bool immediate_ = false) ETL_NOEXCEPT
bool start(etl::timer_node* timer_, bool immediate_ = false) ETL_NOEXCEPT
{
bool is_started = false;
lock_policy.lock();
etl::timer_node* timer = find_timer(register_list, id_);
etl::timer_node* timer = register_list.find(timer_);
if (timer != register_list.end())
{
@ -255,13 +247,17 @@ namespace etl
//*******************************************
/// Stops a timer.
//*******************************************
bool stop(etl::timer::id::type id_) ETL_NOEXCEPT
bool stop(etl::timer_node* timer_) ETL_NOEXCEPT
{
bool is_stopped = false;
lock_policy.lock();
is_stopped = (stop_impl(id_) != active_list.end());
if (timer_->is_active())
{
stop_impl(timer_);
is_stopped = true;
}
lock_policy.unlock();
@ -271,19 +267,52 @@ namespace etl
//*******************************************
/// Sets a timer's period.
//*******************************************
bool set_period(etl::timer::id::type id_, uint32_t period_) ETL_NOEXCEPT
bool set_period(etl::timer_node* timer_, uint32_t period_) ETL_NOEXCEPT
{
bool result = false;
lock_policy.lock();
stop_impl(id_);
etl::timer_node* timer = find_timer(register_list, id_);
if (timer != active_list.end())
if (timer_->is_registered(this))
{
timer->set_period(period_);
if (timer_->is_active())
{
stop_impl(timer_);
}
timer_->set_period(period_);
result = true;
}
lock_policy.unlock();
return result;
}
//*******************************************
/// Sets a timer's period.
//*******************************************
bool set_period(etl::timer_node& timer_, uint32_t period_) ETL_NOEXCEPT
{
return set_period(&timer_, period_);
}
//*******************************************
/// Sets a timer's mode.
//*******************************************
bool set_mode(etl::timer_node* timer_, etl::timer::mode::type mode_) ETL_NOEXCEPT
{
bool result = false;
lock_policy.lock();
if (timer_->is_registered(this))
{
if (timer_->is_active())
{
stop_impl(timer_);
}
timer_->set_mode(mode_);
result = true;
}
@ -295,25 +324,9 @@ namespace etl
//*******************************************
/// Sets a timer's mode.
//*******************************************
bool set_mode(etl::timer::id::type id_, bool mode_) ETL_NOEXCEPT
bool set_mode(etl::timer_node& timer_, etl::timer::mode::type mode_) ETL_NOEXCEPT
{
bool result = false;
lock_policy.lock();
stop_impl(id_);
etl::timer_node* timer = find_timer(register_list, id_);
if (timer != active_list.end())
{
timer->set_mode(mode_);
result = true;
}
lock_policy.unlock();
return result;
return set_mode(&timer_, mode_);
}
//*******************************************
@ -349,28 +362,10 @@ namespace etl
return delta;
}
//*******************************************
/// Checks if a timer is currently active.
/// Returns <b>true</b> if the timer is active, otherwise <b>false</b>.
//*******************************************
bool is_active(etl::timer::id::type id_) const ETL_NOEXCEPT
{
if (id_ != etl::timer::id::No_Timer)
{
lock_policy.lock();
bool is_active = (find_timer(active_list, id_) != active_list.end());
lock_policy.unlock();
return is_active;
}
return false;
}
//*******************************************
/// Returns the number of registered timers.
//*******************************************
size_t number_of_registered_timers() const ETL_NOEXCEPT
size_t number_of_timers() const ETL_NOEXCEPT
{
return timer_count;
}
@ -410,36 +405,14 @@ namespace etl
/// Stops a timer without locking.
/// Returns the timer node if it was found, otherwise returns end().
//*******************************************
etl::timer_node* stop_impl(etl::timer::id::type id_) ETL_NOEXCEPT
void stop_impl(etl::timer_node* timer_) ETL_NOEXCEPT
{
return active_list.remove(id_);
active_list.remove(timer_);
}
//*******************************************
/// Finds a timer in a list by its id.
//*******************************************
template <typename TList>
etl::timer_node* find_timer(TList& timer_list, etl::timer::id::type id_) const ETL_NOEXCEPT
{
etl::timer_node* result = timer_list.end();
if (id_ != etl::timer::id::No_Timer)
{
timer_node* timer = timer_list.first();
while ((timer != timer_list.end()) && (timer->get_config().get_id() != id_))
{
timer = timer_list.next(timer);
}
result = timer;
}
return result;
}
//*******************************************
/// Invokes the callbacks for all expired timers.
/// Invoke for all expired timers.
/// This is called after the lock has been released to avoid deadlocks.
//*******************************************
void invoke_expired_timers()
{
@ -467,7 +440,6 @@ namespace etl
bool enabled;
size_t timer_count;
etl::timer::id::type next_id;
};
} // namespace etl

View File

@ -79,21 +79,41 @@ namespace etl
if (empty())
{
head = new_timer;
tail = new_timer;
}
else if (tail->get_id() < new_timer->get_id())
{
// Common case: new id is greater than every existing id, so it goes at the tail.
tail->registered_next = new_timer;
tail = new_timer;
}
else
{
etl::timer_node* current = head;
etl::timer_node* last = head;
etl::timer_node* current = head;
etl::timer_node* previous = head;
// Find the correct place to insert the new timer in to the list.
while ((current != end()) && (current->get_id() < new_timer->get_id()))
{
last = current;
current = current->registered_next;
previous = current;
current = current->registered_next;
}
// Insert the new timer in to the list.
new_timer->registered_next = current;
last->registered_next = new_timer;
if (current == head)
{
head = new_timer;
}
else
{
previous->registered_next = new_timer;
}
}
// We are now the exclusive owner of this timer_node.
new_timer->set_owner(this);
}
//*******************************
@ -108,40 +128,45 @@ namespace etl
// 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_node* remove(etl::timer::id::type id) ETL_NOEXCEPT
etl::timer_node* remove(etl::timer_node* timer_) ETL_NOEXCEPT
{
// Find the timer in the list.
etl::timer_node* current = head;
etl::timer_node* last = end();
etl::timer_node* current = head;
etl::timer_node* previous = head;
while ((current->get_id() != id) && (current != end()))
// Find the timer in the list so we can know what timer was previous to it.
while (current != timer_)
{
last = current;
current = current->registered_next;
}
// Not found?
if (current == end())
{
return end();
previous = current;
current = current->registered_next;
}
// Get the next timer in the lister the timer to be removed.
etl::timer_node* next = current->registered_next;
// Remove it from the list.
if (last == end())
if (current == head)
{
// The timer to be removed is the head of the list.
head = next;
}
else
{
last->registered_next = next;
previous->registered_next = next;
}
if (current == tail)
{
// The timer to be removed is the tail of the list.
tail = (previous == current) ? end() : previous;
}
// Clear the id and next pointer of the removed timer.
current->set_id(etl::timer::id::No_Timer);
current->registered_next = end();
// We are no longer the owner of this timer_node.
current->set_owner(ETL_NULLPTR);
return next;
}
@ -173,11 +198,11 @@ namespace etl
//*******************************
/// Returns the end of the list.
/// Always returns &etl::timer_node::sentinal().
/// Always returns &etl::timer_node::end().
//*******************************
etl::timer_node* const end() const ETL_NOEXCEPT
{
return etl::timer_node::sentinal();
return etl::timer_node::end();
}
//*******************************
@ -197,6 +222,23 @@ namespace etl
tail = end();
}
//*******************************************
/// Finds a timer in a list by its address.
/// If in the list then returns itself.
/// If not in the list then returns end().
//*******************************************
etl::timer_node* find(etl::timer_node* timer_) const ETL_NOEXCEPT
{
timer_node* node = first();
while ((node != end()) && (node != timer_))
{
node = next(node);
}
return node;
}
private:
etl::timer_node* head;
@ -302,24 +344,19 @@ namespace etl
//*******************************
/// Removes the selected timer from the list.
//*******************************
etl::timer_node* remove(etl::timer::id::type id) ETL_NOEXCEPT
etl::timer_node* remove(etl::timer_node* timer_) ETL_NOEXCEPT
{
// Find the timer in the list.
etl::timer_node* current = head;
etl::timer_node* last = end();
etl::timer_node* current = head;
etl::timer_node* previous = head;
while ((current->get_id() != id) && (current != end()))
// Find the timer in the list so we can know what timer was previous to it.
while (current != timer_)
{
last = current;
current = current->active_next;
}
// Not found?
if (current == end())
{
return end();
previous = current;
current = current->active_next;
}
// Get the next timer in the list after the timer to be removed.
etl::timer_node* next = current->active_next;
// Adjust the next delta.
@ -329,13 +366,14 @@ namespace etl
}
// Remove it from the list.
if (last == end())
if (current == head)
{
// The timer to be removed is the head of the list.
head = next;
}
else
{
last->active_next = next;
previous->active_next = next;
}
// Clear the next pointer of the removed timer.
@ -372,11 +410,11 @@ namespace etl
//*******************************
/// Returns the end of the list.
/// Always returns &etl::timer_node::sentinal().
/// Always returns &etl::timer_node::end().
//*******************************
etl::timer_node* const end() const ETL_NOEXCEPT
{
return etl::timer_node::sentinal();
return etl::timer_node::end();
}
//*******************************
@ -394,6 +432,23 @@ namespace etl
}
}
//*******************************************
/// Finds a timer in a list by its address.
/// If in the list then returns itself.
/// If not in the list then returns end().
//*******************************************
etl::timer_node* find(etl::timer_node* timer_) const ETL_NOEXCEPT
{
timer_node* node = first();
while ((node != end()) && (node != timer_))
{
node = next(node);
}
return node;
}
private:
etl::timer_node* head;
@ -401,6 +456,7 @@ namespace etl
//*************************************************************************
/// The list of timers that are registered.
/// The list does not do any checking of parameters as it is assumed that the timer service has already done this.
//*************************************************************************
class timer_expired_list
{
@ -478,11 +534,11 @@ namespace etl
//*******************************
/// Returns the end of the list.
/// Always returns &etl::timer_node::sentinal().
/// Always returns &etl::timer_node::end().
//*******************************
etl::timer_node* const end() const ETL_NOEXCEPT
{
return etl::timer_node::sentinal();
return etl::timer_node::end();
}
//*******************************
@ -502,6 +558,23 @@ namespace etl
tail = end();
}
//*******************************************
/// Finds a timer in a list by its address.
/// If in the list then returns itself.
/// If not in the list then returns end().
//*******************************************
etl::timer_node* find(etl::timer_node* timer_) const ETL_NOEXCEPT
{
timer_node* node = first();
while ((node != end()) && (node != timer_))
{
node = next(node);
}
return node;
}
private:
etl::timer_node* head;

View File

@ -30,6 +30,7 @@ SOFTWARE.
#define ETL_TIMER_NODE_INCLUDED
#include "platform.h"
#include "delegate.h"
#include "message_router.h"
#include "nullptr.h"
#include "timer.h"
@ -122,6 +123,19 @@ namespace etl
//***************************************************************************
/// A timer data structure that can be linked into the timer lists.
/// Contains a timer configuration.
///
/// registered_next points to the next node in registered list.
/// - A value of etl::timer_node::unlinked() indicates that the timer is not in the registered list.
/// - A value of etl::timer_node::end() indicates that the timer is the last in the list.
///
/// active_next points to the next node in active list.
/// - A value of etl::timer_node::unlinked() indicates that the timer is not in the active list.
/// - A value of etl::timer_node::end() indicates that the timer is the last in the list.
///
/// expired_next points to the next node in expired list.
/// - A value of etl::timer_node::unlinked() indicates that the timer is not in the expired list.
/// - A value of etl::timer_node::end() indicates that the timer is the last in the list.
///
//***************************************************************************
class timer_node
{
@ -140,6 +154,7 @@ namespace etl
: registered_next(unlinked())
, active_next(unlinked())
, expired_next(unlinked())
, owner(ETL_NULLPTR)
, config(config_)
, delta(config_.get_period())
{
@ -152,6 +167,7 @@ namespace etl
: registered_next(unlinked())
, active_next(unlinked())
, expired_next(unlinked())
, owner(ETL_NULLPTR)
, config(node_.config)
, delta(node_.config.get_period())
{
@ -215,14 +231,6 @@ namespace etl
config.mode = mode_;
}
//*******************************************
/// Returns a nullptr to indicate that the timer is not linked into any list.
//*******************************************
static etl::timer_node* unlinked() ETL_NOEXCEPT
{
return ETL_NULLPTR;
}
//*******************************************
/// Sets the ID of the timer.
//*******************************************
@ -231,6 +239,22 @@ namespace etl
config.id = id_;
}
//*******************************************
/// Sets the owner of the timer.
//*******************************************
void set_owner(void* owner_) ETL_NOEXCEPT
{
owner = owner_;
}
//*******************************************
/// Returns a nullptr to indicate that the timer is not linked into any list.
//*******************************************
static etl::timer_node* unlinked() ETL_NOEXCEPT
{
return ETL_NULLPTR;
}
//***************************************************************************
/// Returns a reference to the sentinel timer node used to mark the end of
/// the intrusive timer lists.
@ -239,7 +263,7 @@ namespace etl
/// This avoids needing C++17 inline variables, so it remains compatible
/// with C++03 and C++11.
//***************************************************************************
static etl::timer_node* sentinal() ETL_NOEXCEPT
static etl::timer_node* end() ETL_NOEXCEPT
{
static etl::timer_node sentinel_node(etl::timer_config(0, 0));
@ -254,9 +278,35 @@ namespace etl
registered_next = unlinked();
active_next = unlinked();
expired_next = unlinked();
owner = ETL_NULLPTR;
delta = config.get_period();
}
//*******************************************
/// Returns true if the timer is registered to this owner.
/// A timer cannot be registered to more than one owner.
//*******************************************
bool is_registered(void* owner_) const ETL_NOEXCEPT
{
return (registered_next != unlinked()) && (owner == owner_);
}
//*******************************************
/// Returns true if the timer is active.
//*******************************************
bool is_active() const ETL_NOEXCEPT
{
return active_next != unlinked();
}
//*******************************************
/// Returns true if the timer has expired.
//*******************************************
bool has_expired() const ETL_NOEXCEPT
{
return expired_next != unlinked();
}
//*******************************************
/// Invokes the timer's callback.
//*******************************************
@ -267,9 +317,10 @@ namespace etl
timer_node& operator=(const timer_node&) ETL_DELETE;
timer_node* registered_next; ///< The next timer in the registered timer list. nullptr if the timer is not registered.
timer_node* active_next; ///< The next timer in the active timer list. nullptr if the timer is not active.
timer_node* expired_next; ///< The next timer in the expired timer list. nullptr if the timer is not expired.
timer_node* registered_next; ///< The next timer in the registered timer list. unlinked() if the timer is not registered.
timer_node* active_next; ///< The next timer in the active timer list. unlinked() if the timer is not active.
timer_node* expired_next; ///< The next timer in the expired timer list. unlinked() if the timer is not expired.
void* owner; ///< The owner of the timer.
timer_config config; ///< The configuration of the timer.
uint32_t delta; ///< The time remaining until the timer expires.
};

View File

@ -132,7 +132,7 @@ namespace
etl::intrusive_timer timer_controller(lock_policy);
CHECK_FALSE(timer_controller.is_running());
CHECK_EQUAL(0U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(0U, timer_controller.number_of_timers());
CHECK_EQUAL(0U, lock_policy.get_count());
}
@ -148,11 +148,11 @@ namespace
etl::timer_node node3(etl::timer_config(300, etl::timer::mode::Repeating));
CHECK_TRUE(timer_controller.register_timer(node1));
CHECK_EQUAL(1U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(1U, timer_controller.number_of_timers());
CHECK_TRUE(timer_controller.register_timer(node2));
CHECK_EQUAL(2U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(2U, timer_controller.number_of_timers());
CHECK_TRUE(timer_controller.register_timer(node3));
CHECK_EQUAL(3U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(3U, timer_controller.number_of_timers());
CHECK_FALSE(timer_controller.is_running());
CHECK_EQUAL(0U, lock_policy.get_count());
}
@ -172,40 +172,17 @@ namespace
timer_controller.register_timer(node2);
timer_controller.register_timer(node3);
CHECK_EQUAL(3U, timer_controller.number_of_timers());
CHECK_TRUE(timer_controller.unregister_timer(node1));
CHECK_EQUAL(2U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(2U, timer_controller.number_of_timers());
CHECK_TRUE(timer_controller.unregister_timer(node2));
CHECK_EQUAL(1U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(1U, timer_controller.number_of_timers());
CHECK_TRUE(timer_controller.unregister_timer(node3));
CHECK_EQUAL(0U, timer_controller.number_of_registered_timers());
CHECK_EQUAL(0U, timer_controller.number_of_timers());
CHECK_FALSE(timer_controller.is_running());
CHECK_EQUAL(0U, lock_policy.get_count());
}
//*************************************************************************
TEST(test_intrusive_timer_unregister_timers_by_id)
{
LockPolicy lock_policy;
etl::intrusive_timer timer_controller(lock_policy);
etl::timer_node node1(etl::timer_config(100, etl::timer::mode::Single_Shot));
etl::timer_node node2(etl::timer_config(200, etl::timer::mode::Repeating));
etl::timer_node node3(etl::timer_config(300, etl::timer::mode::Repeating));
timer_controller.register_timer(node1);
timer_controller.register_timer(node2);
timer_controller.register_timer(node3);
CHECK_TRUE(timer_controller.unregister_timer(node1.get_config().get_id()));
CHECK_EQUAL(2U, timer_controller.number_of_registered_timers());
CHECK_TRUE(timer_controller.unregister_timer(node2.get_config().get_id()));
CHECK_EQUAL(1U, timer_controller.number_of_registered_timers());
CHECK_TRUE(timer_controller.unregister_timer(node3.get_config().get_id()));
CHECK_EQUAL(0U, timer_controller.number_of_registered_timers());
CHECK_FALSE(timer_controller.is_running());
}
//*************************************************************************
TEST(test_intrusive_timer_register_timers_then_modify_them)
{
@ -222,14 +199,14 @@ namespace
timer_controller.register_timer(node3);
// Modify the timer periods.
timer_controller.set_period(node1.get_id(), 150U);
timer_controller.set_period(node2.get_id(), 250U);
timer_controller.set_period(node3.get_id(), 350U);
timer_controller.set_period(node1, 150U);
timer_controller.set_period(node2, 250U);
timer_controller.set_period(node3, 350U);
// Modify the timer modes.
timer_controller.set_mode(node1.get_id(), etl::timer::mode::Repeating);
timer_controller.set_mode(node2.get_id(), etl::timer::mode::Single_Shot);
timer_controller.set_mode(node3.get_id(), etl::timer::mode::Single_Shot);
timer_controller.set_mode(node1, etl::timer::mode::Repeating);
timer_controller.set_mode(node2, etl::timer::mode::Single_Shot);
timer_controller.set_mode(node3, etl::timer::mode::Single_Shot);
CHECK_EQUAL(150U, node1.get_period());
CHECK_EQUAL(250U, node2.get_period());
@ -238,12 +215,14 @@ namespace
CHECK_EQUAL(etl::timer::mode::Repeating, node1.get_mode());
CHECK_EQUAL(etl::timer::mode::Single_Shot, node2.get_mode());
CHECK_EQUAL(etl::timer::mode::Single_Shot, node3.get_mode());
CHECK_EQUAL(0U, lock_policy.get_count());
}
////*************************************************************************
//TEST(test_intrusive_timer_one_shot)
// TEST(test_intrusive_timer_one_shot)
//{
// LockPolicy lock_policy;
// LockPolicy lock_policy;
// etl::intrusive_timer timer_controller(lock_policy);
//

View File

@ -65,7 +65,7 @@ namespace
CHECK_TRUE(register_list.first() == &timer1);
// Remove timer3 from the list.
register_list.remove(timer3.get_config().get_id());
register_list.remove(&timer3);
// Search the list to ensure the other timers are still present.
bool timer1_found = false;
@ -167,7 +167,7 @@ namespace
CHECK_EQUAL(timer4.get_config().get_period() - timer1.get_config().get_period(), timer4.get_delta());
CHECK_EQUAL(timer5.get_config().get_period() - timer3.get_config().get_period(), timer5.get_delta());
// Remove timer3 from the list.
active_list.remove(timer3.get_config().get_id());
active_list.remove(&timer3);
// Search the list to ensure the other timers are still present.
bool timer1_found = false;
@ -217,7 +217,7 @@ namespace
CHECK_EQUAL(timer5.get_config().get_period() - timer2.get_config().get_period(), timer5.get_delta());
// Remove a timer that is not in the list.
active_list.remove(timer6.get_config().get_id());
active_list.remove(&timer6);
// Search the list to ensure the other timers are still present.
timer1_found = false;
@ -275,11 +275,11 @@ namespace
constexpr etl::timer_config timer3_config(85, etl::timer::mode::Repeating);
constexpr etl::timer_config timer4_config(130, etl::timer::mode::Repeating);
constexpr etl::timer_config timer5_config(85, etl::timer::mode::Repeating);
etl::timer_node timer1{timer1_config};
etl::timer_node timer2{timer2_config};
etl::timer_node timer3{timer3_config};
etl::timer_node timer4{timer4_config};
etl::timer_node timer5{timer5_config};
etl::timer_node timer1{timer1_config};
etl::timer_node timer2{timer2_config};
etl::timer_node timer3{timer3_config};
etl::timer_node timer4{timer4_config};
etl::timer_node timer5{timer5_config};
etl::private_timer::timer_expired_list expired_list;