SuperTinyKernel™ RTOS 1.05.3
Lightweight, high-performance, deterministic, bare-metal C++ RTOS for resource-constrained embedded systems. MIT Open Source License.
Loading...
Searching...
No Matches
stk_time_timer.h
Go to the documentation of this file.
1/*
2 * SuperTinyKernel™ (STK): Lightweight High-Performance Deterministic C++ RTOS for Embedded Systems.
3 *
4 * Source: https://github.com/SuperTinyKernel-RTOS
5 *
6 * Copyright (c) 2022-2026 Neutron Code Limited <stk@neutroncode.com>. All Rights Reserved.
7 * License: MIT License, see LICENSE for a full text.
8 */
9
10#ifndef STK_TIME_TIMER_H_
11#define STK_TIME_TIMER_H_
12
13#include "sync/stk_sync_event.h"
14#include "sync/stk_sync_pipe.h"
15
20
21namespace stk {
22namespace time {
23
27#ifndef STK_TIMER_THREADS_COUNT
28 #define STK_TIMER_THREADS_COUNT 1
29#endif
30
34#ifndef STK_TIMER_HANDLER_STACK_SIZE
35 #define STK_TIMER_HANDLER_STACK_SIZE 256
36#endif
37
41#ifndef STK_TIMER_COUNT_MAX
42 #define STK_TIMER_COUNT_MAX 32
43#endif
44
112{
113public:
125
137 class Timer : private util::DListEntry<Timer, false>
138 {
139 friend class TimerHost;
140
141 public:
142 Timer() : m_deadline(0), m_timestamp(0), m_period(0), m_active(false), m_pending(false)
143 {}
144
149 {}
150
151 virtual void OnExpired(TimerHost *host) = 0;
152
153 bool IsActive() const { return m_active; }
154 Ticks GetDeadline() const { return m_deadline; }
155 Ticks GetTimestamp() const { return m_timestamp; }
156 uint32_t GetPeriod() const { return m_period; }
157
164 uint32_t GetRemainingTime() const;
165
166 private:
168
171 uint32_t m_period;
172 volatile bool m_active;
173 volatile bool m_pending;
174 };
175
179
184 {}
185
190 void Initialize(IKernel *kernel, EAccessMode mode);
191
198 bool Start(Timer &timer, uint32_t delay, uint32_t period = 0);
199
204 bool Stop(Timer &timer);
205
210 bool Reset(Timer &timer);
211
223 bool Restart(Timer &timer, uint32_t delay, uint32_t period = 0);
224
238 bool StartOrReset(Timer &timer, uint32_t delay, uint32_t period = 0);
239
249 bool SetPeriod(Timer &timer, uint32_t period);
250
255 bool IsEmpty() const { return m_active.IsEmpty(); }
256
261 size_t GetSize() const { return m_active.GetSize(); }
262
266 bool Shutdown();
267
272
273private:
275
279 typedef void (*TimerFuncType) (TimerHost *host);
280
284 class TimerWorkerTask : public ITask
285 {
286 public:
288 : m_func(nullptr), m_host(nullptr), m_stack(nullptr), m_stack_size(0), m_mode(ACCESS_USER), m_weight(0)
289 {}
290
291 // ITask
292 EAccessMode GetAccessMode() const { return m_mode; }
293 void OnDeadlineMissed(uint32_t duration) { (void)duration; }
294 int32_t GetWeight() const { return m_weight; }
295 TId GetId() const { return hw::PtrToWord(this); }
296 const char *GetTraceName() const { return nullptr; }
297
298 // IStackMemory
299 Word *GetStack() const { return m_stack; }
300 size_t GetStackSize() const { return m_stack_size; }
301 size_t GetStackSizeBytes() const { return m_stack_size * sizeof(Word); }
302
303 void Initialize(TimerHost *host, Word *stack, size_t stack_size, EAccessMode mode, TimerFuncType func)
304 {
305 m_host = host;
306 m_func = func;
307 m_stack = stack;
308 m_stack_size = stack_size;
309 m_mode = mode;
310 m_weight = 1;
311 }
312
313 void SetWeight(int32_t weight) { m_weight = weight; }
314
315 private:
316 void Run() { m_func(m_host); }
317
323 int32_t m_weight;
324 };
325
345
346 void UpdateTime();
347 void ProcessTimers();
348 bool ProcessCommands(Timeout next_sleep);
349 bool PushCommand(TimerCommand cmd);
350
355
364};
365
366// ---------------------------------------------------------------------------
367// Timer::GetTimeRemaining
368// ---------------------------------------------------------------------------
369
371{
372 if (!m_active)
373 return 0;
374
375 // if deadline has passed but not yet been processed by the tick task,
376 // remaining would be negative, result fits in uint32_t because delay and
377 // period are uint32_t, so remaining time is bounded by uint32_t max
378 Ticks remaining = m_deadline - GetTicks();
379 return (remaining > 0 ? static_cast<uint32_t>(remaining) : 0);
380}
381
382// ---------------------------------------------------------------------------
383// Initialize
384// ---------------------------------------------------------------------------
385
386inline void TimerHost::Initialize(IKernel *kernel, EAccessMode mode)
387{
388 for (int32_t i = 0; i < STK_TIMER_THREADS_COUNT; ++i)
389 {
390 m_task_process[i].Initialize(this, m_task_handler_memory[i], TASK_HANDLER_STACK_SIZE, mode, [](TimerHost *host) {
391 host->ProcessTimers();
392 });
393 kernel->AddTask(&m_task_process[i]);
394 }
395
397 host->UpdateTime();
398 });
399 kernel->AddTask(&m_task_tick);
400}
401
402// ---------------------------------------------------------------------------
403// Start
404// ---------------------------------------------------------------------------
405
406inline bool TimerHost::Start(Timer &timer, uint32_t delay, uint32_t period)
407{
408 STK_ASSERT(delay <= static_cast<uint32_t>(WAIT_INFINITE));
409 STK_ASSERT((period == 0U) || (period <= static_cast<uint32_t>(WAIT_INFINITE)));
410
411 // timer must not already be active
412 if (timer.m_active)
413 return false;
414
415 return PushCommand({
417 .timer = &timer,
418 .timestamp = GetTicks(),
419 .delay = delay,
420 .period = period
421 });
422}
423
424// ---------------------------------------------------------------------------
425// Stop
426// ---------------------------------------------------------------------------
427
428inline bool TimerHost::Stop(Timer &timer)
429{
430 // timer must be active
431 if (!timer.m_active)
432 return false;
433
434 return PushCommand({
436 .timer = &timer,
437 .timestamp = 0,
438 .delay = 0U,
439 .period = 0U
440 });
441}
442
443// ---------------------------------------------------------------------------
444// Reset
445// ---------------------------------------------------------------------------
446
447inline bool TimerHost::Reset(Timer &timer)
448{
449 // timer must be active and periodic
450 if (!timer.m_active || (timer.m_period == 0))
451 return false;
452
453 return PushCommand({
455 .timer = &timer,
456 .timestamp = GetTicks(),
457 .delay = 0U,
458 .period = 0U
459 });
460}
461
462// ---------------------------------------------------------------------------
463// Restart
464// ---------------------------------------------------------------------------
465
466inline bool TimerHost::Restart(Timer &timer, uint32_t delay, uint32_t period)
467{
468 STK_ASSERT(delay <= static_cast<uint32_t>(WAIT_INFINITE));
469 STK_ASSERT((period == 0U) || (period <= static_cast<uint32_t>(WAIT_INFINITE)));
470
471 return PushCommand({
473 .timer = &timer,
474 .timestamp = GetTicks(),
475 .delay = delay,
476 .period = period
477 });
478}
479
480// ---------------------------------------------------------------------------
481// StartOrReset
482// ---------------------------------------------------------------------------
483
484inline bool TimerHost::StartOrReset(Timer &timer, uint32_t delay, uint32_t period)
485{
486 STK_ASSERT(delay <= static_cast<uint32_t>(WAIT_INFINITE));
487 STK_ASSERT((period == 0U) || (period <= static_cast<uint32_t>(WAIT_INFINITE)));
488
489 return PushCommand({
491 .timer = &timer,
492 .timestamp = GetTicks(),
493 .delay = delay,
494 .period = period
495 });
496}
497
498// ---------------------------------------------------------------------------
499// SetPeriod
500// ---------------------------------------------------------------------------
501
502inline bool TimerHost::SetPeriod(Timer &timer, uint32_t period)
503{
504 // period == 0 is rejected: it would silently convert a periodic timer
505 // to one-shot semantics, which is better expressed via Stop() + Start()
506 if (!timer.m_active || (timer.m_period == 0U) || (period == 0U) ||
507 (period > static_cast<uint32_t>(WAIT_INFINITE)))
508 {
509 return false;
510 }
511
512 return PushCommand({
514 .timer = &timer,
515 .timestamp = 0,
516 .delay = 0U,
517 .period = period
518 });
519}
520
521// ---------------------------------------------------------------------------
522// Shutdown
523// ---------------------------------------------------------------------------
524
526{
527 return PushCommand({
529 .timer = nullptr,
530 .timestamp = 0,
531 .delay = 0U,
532 .period = 0U
533 });
534}
535
536// ---------------------------------------------------------------------------
537// UpdateTime (tick task body)
538// ---------------------------------------------------------------------------
539
541{
542 Timeout next_sleep = NO_WAIT;
543
544 while (ProcessCommands(next_sleep))
545 {
546 next_sleep = WAIT_INFINITE;
547
548 Ticks now = GetTicks();
549
550 // using WriteVolatile64() to guarantee correct lockless reading order by ReadVolatile64
552
553 Timer *timer = static_cast<Timer *>(m_active.GetFirst());
554 while (timer != nullptr)
555 {
556 Timer *next = static_cast<Timer *>(timer->GetNext());
557
558 if (timer->m_active)
559 {
560 // check if still pending to be handled
561 if (timer->m_pending)
562 {
563 timer = next;
564 continue;
565 }
566
567 bool one_shot = false;
568 Ticks diff = now - timer->m_deadline;
569
570 if (diff >= 0)
571 {
572 // set timestamp at which timer expired
573 timer->m_timestamp = now;
574
575 // avoid updating timer again before it was handled
576 timer->m_pending = true;
577
578 // periodic
579 if (timer->m_period != 0)
580 {
581 // reload (use now to avoid drift accumulation)
582 timer->m_deadline = now + static_cast<Ticks>(timer->m_period) - diff;
583 }
584 // one-shot
585 else
586 {
587 one_shot = true;
588
589 // remove from active timers
590 m_active.Unlink(timer);
591
592 // mark as inactive (must follow Unlink)
593 timer->m_active = false;
594 }
595
596 __stk_full_memfence();
597
598 // push to the handling queue
599 m_queue.Write(timer);
600 }
601
602 // one-shot timer does not affect next_sleep
603 if (!one_shot)
604 {
605 Timeout next_deadline = static_cast<Timeout>(timer->m_deadline - now);
606 STK_ASSERT(next_deadline > 0);
607
608 if ((next_deadline > 0) && (next_deadline < next_sleep))
609 next_sleep = next_deadline;
610 }
611 }
612 else
613 {
614 // could be stopped externally, remove from active timers
615 m_active.Unlink(timer);
616 }
617
618 timer = next;
619 }
620 }
621
622 // unlink all timers on shutdown
623 while (Timer::DLEntryType *timer = m_active.GetFirst())
624 m_active.Unlink(timer);
625}
626
627// ---------------------------------------------------------------------------
628// ProcessTimers (handler task body)
629// ---------------------------------------------------------------------------
630
632{
633 Timer *timer;
634 while (m_queue.Read(timer))
635 {
636 // nullptr is the shutdown sentinel pushed by CMD_SHUTDOWN
637 if (timer == nullptr)
638 break;
639
640 if (timer->m_pending)
641 {
642 timer->m_pending = false;
643 timer->OnExpired(this);
644 }
645 }
646}
647
648// ---------------------------------------------------------------------------
649// ProcessCommands (tick task only)
650// ---------------------------------------------------------------------------
651
652inline bool TimerHost::ProcessCommands(Timeout next_sleep)
653{
654 // if nothing is active, sleep indefinitely until a command arrives
655 next_sleep = (m_active.IsEmpty() ? WAIT_INFINITE : next_sleep);
656
657 TimerCommand cmd;
658 while (m_commands.Read(cmd, next_sleep))
659 {
660 switch (cmd.cmd)
661 {
663 Timer *timer = cmd.timer;
664 STK_ASSERT(timer != nullptr);
665
666 // reject if already active or linked (double-start)
667 if (timer->m_active)
668 continue;
669
670 STK_ASSERT(!timer->IsLinked());
671
672 timer->m_deadline = cmd.timestamp + cmd.delay;
673 timer->m_period = cmd.period;
674 timer->m_active = true;
675 timer->m_pending = false;
676
677 m_active.LinkBack(timer);
678 next_sleep = NO_WAIT;
679 break; }
680
682 Timer *timer = cmd.timer;
683 STK_ASSERT(timer != nullptr);
684
685 timer->m_active = false;
686 timer->m_pending = false;
687
688 // allow possibly duplicate CMD_STOP as it is harmless for the logic
689 if (timer->IsLinked())
690 m_active.Unlink(timer);
691 break; }
692
694 Timer *timer = cmd.timer;
695 STK_ASSERT(timer != nullptr);
696
697 // only reset if still active and periodic
698 if (!timer->m_active || (timer->m_period == 0))
699 continue;
700
701 STK_ASSERT(timer->GetHead() == &m_active);
702
703 timer->m_deadline = cmd.timestamp + timer->m_period;
704 timer->m_pending = false;
705
706 next_sleep = NO_WAIT;
707 break; }
708
710 // atomic stop + re-start: no precondition on current timer state
711 Timer *timer = cmd.timer;
712 STK_ASSERT(timer != nullptr);
713
714 // unlink if currently in the active list
715 if (timer->IsLinked())
716 m_active.Unlink(timer);
717
718 // re-arm with fresh parameters
719 timer->m_deadline = cmd.timestamp + cmd.delay;
720 timer->m_period = cmd.period;
721 timer->m_active = true;
722 timer->m_pending = false;
723
724 // re-link to the back of the list
725 m_active.LinkBack(timer);
726 next_sleep = NO_WAIT;
727 break; }
728
730 Timer *timer = cmd.timer;
731 STK_ASSERT(timer != nullptr);
732
733 // not currently active: start with supplied parameters
734 if (!timer->m_active)
735 {
736 STK_ASSERT(!timer->IsLinked());
737
738 timer->m_deadline = cmd.timestamp + cmd.delay;
739 timer->m_period = cmd.period;
740 timer->m_active = true;
741 timer->m_pending = false;
742
743 m_active.LinkBack(timer);
744 }
745 else
746 // active and periodic: reset deadline anchored to call-site timestamp
747 if (timer->m_period != 0)
748 {
749 STK_ASSERT(timer->GetHead() == &m_active);
750
751 timer->m_deadline = cmd.timestamp + timer->m_period;
752 timer->m_pending = false;
753 }
754
755 // active one-shot: no action — cannot reset a one-shot mid-flight,
756 // caller should use Restart() if unconditional re-arm is needed.
757
758 next_sleep = NO_WAIT;
759 break; }
760
762 Timer *timer = cmd.timer;
763 STK_ASSERT(timer != nullptr);
764 STK_ASSERT(cmd.period != 0U);
765
766 // guard: only apply if still active and periodic
767 if (!timer->m_active || (timer->m_period == 0U))
768 continue;
769
770 STK_ASSERT(timer->GetHead() == &m_active);
771
772 // new period takes effect on the next reload, current deadline is
773 // intentionally left unchanged so the in-flight interval is not
774 // truncated or extended, caller can follow up with Reset() if
775 // immediate application is required
776 timer->m_period = cmd.period;
777 break; }
778
780 // wake all handler tasks with shutdown sentinels
781 for (int32_t i = 0; i < STK_TIMER_THREADS_COUNT; ++i)
782 m_queue.Write(nullptr, NO_WAIT);
783
784 // signal UpdateTime() to exit its loop
785 return false; }
786
787 default: {
788 STK_ASSERT(false);
789 break; }
790 }
791 }
792
793 return true;
794}
795
796// ---------------------------------------------------------------------------
797// PushCommand
798// ---------------------------------------------------------------------------
799
801{
802 if (!m_commands.Write(cmd, NO_WAIT))
803 {
804 // queue full: this indicates a usage error — more commands are being
805 // issued than the tick task can drain. Loud in debug, recoverable in
806 // release (caller receives false and can retry or escalate).
807 STK_ASSERT(false);
808 return false;
809 }
810
811 return true;
812}
813
814} // namespace time
815} // namespace stk
816
817#endif /* STK_TIME_TIMER_H_ */
#define STK_ASSERT(e)
Runtime assertion. Halts execution if the expression e evaluates to false.
Definition stk_defs.h:330
#define STK_STACK_SIZE_MIN
Minimum stack size in elements of Word, shared by all stack allocation lower-bound checks.
Definition stk_defs.h:454
Implementation of synchronization primitive: stk::sync::Event.
Implementation of synchronization primitive: stk::sync::Pipe.
#define STK_TIMER_THREADS_COUNT
Number of threads handling timers in TimerHost (default: 1).
#define STK_TIMER_HANDLER_STACK_SIZE
Stack size of the timer handler, increase if your timers consume more (default: 256).
Namespace of STK package.
uintptr_t Word
Native processor word type.
Definition stk_common.h:112
const Timeout WAIT_INFINITE
Timeout value: block indefinitely until the synchronization object is signaled.
Definition stk_common.h:139
Ticks GetTicks()
Get number of ticks elapsed since kernel start.
Definition stk_helper.h:248
int64_t Ticks
Ticks value.
Definition stk_common.h:150
int32_t Timeout
Timeout time (ticks).
Definition stk_common.h:133
constexpr T Max(T a, T b) noexcept
Compile-time maximum of two values.
Definition stk_defs.h:536
const Timeout NO_WAIT
Timeout value: return immediately if the synchronization object is not yet signaled (non-blocking pol...
Definition stk_common.h:145
Word TId
Definition stk_common.h:117
EAccessMode
Hardware access mode by the user task.
Definition stk_common.h:31
@ ACCESS_USER
Unprivileged access mode (access to some hardware is restricted, see CPU manual for details).
Definition stk_common.h:32
__stk_forceinline void WriteVolatile64(volatile T *addr, T value)
Atomically write a 64-bit volatile value.
Definition stk_arch.h:411
__stk_forceinline Word PtrToWord(T *ptr) noexcept
Cast a pointer to a CPU register-width integer.
Definition stk_arch.h:94
__stk_forceinline T ReadVolatile64(volatile const T *addr)
Atomically read a 64-bit volatile value.
Definition stk_arch.h:357
Time-related primitives.
Word Type[_StackSize]
Stack memory type.
Definition stk_common.h:174
Interface for a user task.
Definition stk_common.h:433
Interface for the implementation of the kernel of the scheduler. It supports Soft and Hard Real-Time ...
Definition stk_common.h:854
virtual void AddTask(ITask *user_task)=0
Add user task.
Intrusive doubly-linked list container. Manages a collection of DListEntry nodes embedded in host obj...
Intrusive doubly-linked list node. Embed this as a base class in any object (T) that needs to partici...
DLHeadType * GetHead() const
Get the list head this entry currently belongs to.
bool IsLinked() const
Check whether this entry is currently a member of any list.
DLEntryType * GetNext() const
Get the next entry in the list.
DListEntry< Timer, _ClosedLoop > DLEntryType
Thread-safe FIFO communication pipe for inter-task data passing.
bool Reset(Timer &timer)
Reset periodic timer's deadline.
Ticks m_now
last known current time (ticks)
util::DListHead< Timer, false > m_active
active timers (tick task only)
bool SetPeriod(Timer &timer, uint32_t period)
Change the period of a running periodic timer without affecting its current deadline.
void Initialize(IKernel *kernel, EAccessMode mode)
Initialize timer host instance.
bool IsEmpty() const
Return true if no timers are currently active.
Ticks GetTimeNow() const
Get current time.
sync::Pipe< Timer *, 32 > ReadyQueue
TimerWorkerTask m_task_tick
timer task
TimerWorkerTask m_task_process[1]
handler tasks
size_t GetSize() const
Return number of currently active timers.
STK_NONCOPYABLE_CLASS(TimerHost)
TaskTickMemory m_task_tick_memory
tick task memory
void(* TimerFuncType)(TimerHost *host)
Timer task function prototype.
ReadyQueue m_queue
queue of timers ready for handling
bool Shutdown()
Shutdown host instance. All timers are stopped and removed from the host.
sync::Pipe< TimerCommand, 32 > CommandQueue
bool Restart(Timer &timer, uint32_t delay, uint32_t period=0)
Atomically stop and re-start timer.
bool StartOrReset(Timer &timer, uint32_t delay, uint32_t period=0)
Start timer if inactive, or reset its deadline if already active and periodic.
bool Stop(Timer &timer)
Stop running timer.
@ TASK_TICK_MEMORY_SIZE
stack memory size of the timer handler task
@ TASK_COUNT
total number of tasks serving this instance
StackMemoryDef< TASK_TICK_MEMORY_SIZE >::Type TaskTickMemory
bool ProcessCommands(Timeout next_sleep)
bool Start(Timer &timer, uint32_t delay, uint32_t period=0)
Start timer.
bool PushCommand(TimerCommand cmd)
TimerHostMemory m_task_handler_memory[1]
handler task memory
CommandQueue m_commands
command queue
StackMemoryDef< TASK_HANDLER_STACK_SIZE >::Type TimerHostMemory
Abstract base class for a timer managed by TimerHost.
volatile bool m_active
true if active
Ticks m_deadline
absolute expiration time (ticks)
Ticks m_timestamp
time at which timer expired (ticks), updated by TimerHost
uint32_t m_period
reload period in ticks (0 = one-shot)
uint32_t GetRemainingTime() const
Get remaining ticks until the timer next expires.
volatile bool m_pending
true if pending to be handled
virtual void OnExpired(TimerHost *host)=0
The actual task that executes timer callback.
void Run()
Entry point of the user task.
void OnDeadlineMissed(uint32_t duration)
Called by the scheduler if deadline of the task is missed when Kernel is operating in Hard Real-Time ...
Word * GetStack() const
Get pointer to the stack memory.
int32_t GetWeight() const
Get static base weight of the task.
const char * GetTraceName() const
Get task trace name set by application.
size_t GetStackSize() const
Get number of elements of the stack memory array.
void Initialize(TimerHost *host, Word *stack, size_t stack_size, EAccessMode mode, TimerFuncType func)
TId GetId() const
Get task Id set by application.
size_t GetStackSizeBytes() const
Get size of the memory in bytes.
EAccessMode GetAccessMode() const
Get hardware access mode of the user task.
@ CMD_SET_PERIOD
change period of a running periodic timer
@ CMD_START_OR_RESET
start if inactive, reset deadline if active and periodic
@ CMD_RESTART
atomic stop + re-start
uint32_t period
reload period ticks (CMD_START / CMD_RESTART / CMD_START_OR_RESET / CMD_SET_PERIOD)
Ticks timestamp
hw tick count captured at call site
uint32_t delay
initial delay ticks (CMD_START / CMD_RESTART / CMD_START_OR_RESET)