Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,24 @@

STK combines the control and transparency of bare-metal development with the structure and maintainability of modern, type-safe C++.

### For Embedded Developers

- **Deterministic execution** — no dynamic memory allocation (`malloc/free`) and no heap fragmentation. Memory usage is fully predictable at compile time.
- **Lightweight C++ architecture** — clean object-oriented design without STL dependencies, exceptions, RTTI, or heavy runtime abstractions.
- **Native C interoperability** — fully featured C API available for pure C projects and mixed C/C++ codebases.
- **Transparent implementation** — minimal wrapper macros and readable scheduler internals simplify debugging and tracing.
- **Portable design** — minimal BSP surface and straightforward architecture porting.

### For Technical Leads and Product Teams

- **Reduced hardware requirements** — the compact kernel footprint can enable the use of lower-RAM or lower-cost MCU variants.
- **Higher CPU availability for applications** — benchmarks show up to **~12% more application CPU time** compared to FreeRTOS under comparable workloads (see [Benchmark](#benchmark)).
- **Lower power potential** — reduced scheduling overhead can help meet timing requirements at lower MCU clock frequencies.
- **Simplified migration** — compatibility layers for FreeRTOS and CMSIS-RTOS2 allow existing projects to migrate with minimal application changes.
- **Predictable system behavior** — static allocation and deterministic scheduling simplify validation, debugging, and long-term maintenance.

> STK does not attempt to abstract or manage MCU peripherals. Its purpose is to provide a fast, predictable, and memory-efficient scheduling core for embedded applications.
You get:

- **C++ native RTOS** — Built so the C++ compiler can efficiently optimize your STK-based application for maximum speed and ultra-low overhead.
- **Safe code from day one** — Thoughtful OOP design enforces strict encapsulation and type safety to deliver secure, robust, and high-performance firmware.
- **Full-featured RTOS** — A comprehensive suite of thread synchronization, memory, and time management primitives. You only need to bring your own HAL.
- **Safety-critical ready** — Built for strict compliance with MISRA standards. Looking for a safe C++ RTOS for your certified device? Explore our [Services](#services).
- **Deterministic execution** — Zero dynamic memory allocation (`malloc`/`free`) and zero heap fragmentation. Memory usage is fully predictable at compile time.
- **Clean C++ design** — No STL dependencies, exceptions, RTTI, or heavy runtime abstractions. Readable internals simplify debugging and tracing.
- **Verbose-free code** — A clean C++ API makes your implementation highly concise, making it significantly easier to maintain, refactor, and debug than standard C-only APIs.
- **Portable design** — Minimal BSP (Board Support Package) footprint with complete independence from specific board and MCU peripherals.
- **Reduced hardware requirements** — Compact kernel footprint that allows you to deploy on lower-RAM, lower-cost MCU variants.
- **Higher CPU availability** — More time for your application logic. Benchmarks show up to **~12% more application CPU time** compared to FreeRTOS under comparable workloads (see [Benchmark](#benchmark)).
- **Lower power consumption** — Features ultra-low power, tickless scheduling paired with reduced overhead, enabling the use of lower-frequency MCUs to save battery of your portable design.
- **Native C support** — Includes a fully featured C API wrapper, allowing you to seamlessly use STK in pure C projects.
- **Simplified migration** — Drop-in compatibility layers for FreeRTOS and CMSIS-RTOS2 to help you migrate legacy codebases with minimal application changes.
- **B2B professional support** — Engineered for seamless integration into commercial projects. Explore our [Services](#services) for enterprise-grade support and custom engineering.

> STK does not attempt to abstract or manage MCU peripherals, similarly to FreeRTOS or CMSIS-RTOS2.

STK is an open-source project developed at https://github.com/SuperTinyKernel-RTOS.

Expand Down Expand Up @@ -326,6 +327,13 @@ A complete ultra-low power demo targeting the [STM32F407G-DISC1](https://www.st.

---

## Language

* Minimal: C++11
* Recommended: C++17 and higher

---

## Dedicated C interface

For seamless integration with C projects, STK provides a dedicated, fully-featured C API. See [interop/c](https://github.com/SuperTinyKernel-RTOS/stk/tree/main/interop/c) for the full reference and examples.
Expand Down Expand Up @@ -963,7 +971,7 @@ You may freely use it in projects of any type:

---

## 🔒 Professional Services & Commercial Licensing
## Services

While **SuperTinyKernel™ RTOS** is provided under the permissive MIT license, we offer dedicated professional services for organizations integrating STK into production-grade, mission-critical, or regulated environments.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ extern "C" void SysTick_Handler()
if (g_Enable)
++g_Ticks;

if (g_Kernel.GetState() == stk::IKernel::STATE_RUNNING)
if (g_Kernel.GetState() == stk::IKernel::KSTATE_RUNNING)
g_Kernel.GetPlatform()->ProcessTick();
}

Expand Down
8 changes: 6 additions & 2 deletions build/example/driver/led.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@ struct Led
static void Init(Id led, bool init_state);
static inline void InitAll(bool init_state)
{
for (uint8_t led = 0; led < LED_MAX; ++led)
for (uint8_t led = 0U; led < LED_MAX; ++led)
{
Led::Init(static_cast<LedId>(led), init_state);
}
}
static void Set(Id led, bool state);
static void SwitchOnExclusive(Id led)
{
for (uint8_t i = 0; i < LED_MAX; ++i)
for (uint8_t i = 0U; i < LED_MAX; ++i)
{
Led::Set(static_cast<LedId>(i), (i == led));
}
}
};

Expand Down
14 changes: 7 additions & 7 deletions interop/c/include/stk_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,18 +321,18 @@ void stk_kernel_start(stk_kernel_t *k);
\note It is a direct match for IKernel::EState enum.
\see stk_kernel_get_state()
*/
typedef enum _EKernelState {
typedef enum stk_kernel_state_t {
STK_KERNEL_STATE_INACTIVE = 0, //!< not ready, stk_kernel_init() must be called
STK_KERNEL_STATE_READY = 1, //!< ready to start, stk_kernel_start() must be called
STK_KERNEL_STATE_RUNNING = 2, //!< initialized and running, stk_kernel_start() was called successfully
STK_KERNEL_STATE_SUSPENDED = 3 //!< scheduling suspended via stk_kernel_service_suspend() (tickless idle)
} EKernelState;
} stk_kernel_state_t;

/*! \brief Get state of the scheduler.
\param[in] k: Kernel handle.
\return State value, see \a EKernelState.
\return State value, see \a stk_kernel_state_t.
*/
EKernelState stk_kernel_get_state(const stk_kernel_t *k);
stk_kernel_state_t stk_kernel_get_state(const stk_kernel_t *k);

/*! \brief Test whether currently configured task set is schedulable.
\param[in] k: Kernel handle.
Expand Down Expand Up @@ -567,7 +567,7 @@ stk_tick_t stk_ticks(void);
/*! \brief Returns how many microseconds correspond to one kernel tick.
\return Tick resolution in microseconds.
*/
int32_t stk_tick_resolution(void);
uint32_t stk_tick_resolution(void);

/*! \brief Get ticks from milliseconds using current kernel tick resolution.
\param[in] msec: Milliseconds to convert.
Expand Down Expand Up @@ -763,12 +763,12 @@ void stk_tls_set(void *ptr);
/*! \brief Typed helper for getting TLS value.
\note Expands to ((type *)stk_tls_get())
*/
#define STK_TLS_GET(type) ((type *)stk_tls_get())
#define STK_TLS_GET_T(type) ((type *)stk_tls_get())

/*! \brief Typed helper for setting TLS value.
\note Expands to stk_tls_set((void *)(ptr))
*/
#define STK_TLS_SET(ptr) stk_tls_set((void *)(ptr))
#define STK_TLS_SET_T(ptr) stk_tls_set((void *)(ptr))

// =============================================================================
// Synchronization Primitives
Expand Down
2 changes: 1 addition & 1 deletion interop/c/include/stk_c_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ extern "C" {
\note Increase if your application needs more simultaneous pools.
*/
#ifndef STK_C_BLOCKPOOL_MAX
#define STK_C_BLOCKPOOL_MAX 8
#define STK_C_BLOCKPOOL_MAX 8U
#endif

// =============================================================================
Expand Down
48 changes: 31 additions & 17 deletions interop/c/src/stk_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,25 @@ class EventOverrider final : public IPlatform::IEventOverrider
// IPlatform::IEventOverrider
bool OnSleep(Timeout sleep_ticks) override
{
bool is_handled = false;

if ((m_cb != nullptr) && (m_cb->on_sleep != nullptr))
return m_cb->on_sleep(static_cast<stk_timeout_t>(sleep_ticks), m_cb->user_data);
{
is_handled = m_cb->on_sleep(static_cast<stk_timeout_t>(sleep_ticks), m_cb->user_data);
}

return false;
return is_handled;
}
bool OnHardFault() override
{
bool is_handled = false;

if ((m_cb != nullptr) && (m_cb->on_hard_fault != nullptr))
return m_cb->on_hard_fault(m_cb->user_data);
{
is_handled = m_cb->on_hard_fault(m_cb->user_data);
}

return false;
return is_handled;
}

private:
Expand Down Expand Up @@ -320,7 +328,6 @@ void stk_kernel_destroy(stk_kernel_t *k)
// GetPlatform() paths, and we must not leave a dangling overrider pointer
// registered at that point.
UnregisterKernel(reinterpret_cast<IKernel *>(k));
reinterpret_cast<IKernel *>(k)->~IKernel();
}

// -----------------------------------------------------------------------------
Expand All @@ -340,11 +347,11 @@ void stk_kernel_start(stk_kernel_t *k)
reinterpret_cast<IKernel *>(k)->Start();
}

EKernelState stk_kernel_get_state(const stk_kernel_t *k)
stk_kernel_state_t stk_kernel_get_state(const stk_kernel_t *k)
{
STK_ASSERT(k != nullptr);

return static_cast<EKernelState>(reinterpret_cast<const stk::IKernel *>(k)->GetState());
return static_cast<stk_kernel_state_t>(reinterpret_cast<const stk::IKernel *>(k)->GetState());
}

bool stk_kernel_is_schedulable(const stk_kernel_t *k)
Expand Down Expand Up @@ -375,8 +382,8 @@ bool stk_kernel_is_started(const stk_kernel_t *k)
{
STK_ASSERT(k != nullptr);

const stk::IKernel::EState st = reinterpret_cast<const stk::IKernel *>(k)->GetState();
return ((st == stk::IKernel::STATE_RUNNING) || (st == stk::IKernel::STATE_SUSPENDED));
const stk::IKernel::EKernelState st = reinterpret_cast<const stk::IKernel *>(k)->GetState();
return ((st == stk::IKernel::KSTATE_RUNNING) || (st == stk::IKernel::KSTATE_SUSPENDED));
}

void stk_kernel_schedule_task_removal(stk_kernel_t *k, stk_task_t *task)
Expand Down Expand Up @@ -409,16 +416,23 @@ size_t stk_kernel_enumerate_tasks(stk_kernel_t *k, stk_task_t **tasks, size_t ma
STK_ASSERT(k != nullptr);
STK_ASSERT(tasks != nullptr);

// Collect ITask* pointers from the kernel, then map each back to stk_task_t*.
// TaskWrapper is the first member of stk_task_t, so ITask* == stk_task_t*.
stk::ITask *itasks[STK_C_TASKS_MAX] = {};
size_t n = reinterpret_cast<IKernel *>(k)->EnumerateTasks(
itasks, (max_count < STK_C_TASKS_MAX ? max_count : STK_C_TASKS_MAX));

// Determine the safe upper bound for the temporary buffer
const size_t requested_size = (max_count < static_cast<size_t>(STK_C_TASKS_MAX)) ? max_count :
static_cast<size_t>(STK_C_TASKS_MAX);

// Pass via ArrayView temporary object
const size_t ret_count = reinterpret_cast<IKernel *>(k)->EnumerateTasks(
ArrayView<stk::ITask*>(itasks, requested_size));

for (size_t i = 0; i < n; ++i)
tasks[i] = reinterpret_cast<stk_task_t *>(itasks[i]);
ArrayView<stk_task_t *> output_view(tasks, max_count);
for (size_t i = 0U; i < ret_count; ++i)
{
output_view[i] = reinterpret_cast<stk_task_t *>(itasks[i]);
}

return n;
return ret_count;
}

stk_timeout_t stk_kernel_suspend(stk_kernel_t *k)
Expand Down Expand Up @@ -549,7 +563,7 @@ void stk_task_destroy(stk_task_t *task)
// -----------------------------------------------------------------------------
stk_tid_t stk_tid(void) { return stk::GetTid(); }
stk_tick_t stk_ticks(void) { return stk::GetTicks(); }
int32_t stk_tick_resolution(void) { return stk::GetTickResolution(); }
uint32_t stk_tick_resolution(void) { return stk::GetTickResolution(); }
stk_time_t stk_time_now_ms(void) { return stk::GetTimeNowMs(); }
stk_tick_t stk_ticks_from_ms(stk_time_t msec) { return stk_ticks_from_ms_r(msec, stk::GetTickResolution()); }
stk_cycle_t stk_sys_timer_count(void) { return stk::GetSysTimerCount(); }
Expand Down
21 changes: 13 additions & 8 deletions interop/c/src/stk_c_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static_assert(
// Returns a size of memory in stk::Word elements required for object allocation.
template <typename T> static constexpr size_t StkGetWordCountForType()
{
return ((sizeof(T) + sizeof(stk::Word) - 1) / sizeof(stk::Word));
return ((sizeof(T) + sizeof(stk::Word) - 1U) / sizeof(stk::Word));
}

// Private memory allocators (we define malloc, free here to overcome absence of declaration in
Expand Down Expand Up @@ -92,25 +92,30 @@ s_BlockPools[STK_C_BLOCKPOOL_MAX];
// Find the slot that owns 'pool'; returns nullptr if not found.
static BlockPoolSlot *FindSlot(const stk_blockpool_t *pool)
{
for (uint32_t i = 0; i < STK_C_BLOCKPOOL_MAX; ++i)
for (size_t i = 0U; i < STK_C_BLOCKPOOL_MAX; ++i)
{
if (s_BlockPools[i].busy && (s_BlockPools[i].pool() == pool))
return &s_BlockPools[i];
if (s_BlockPools[i].busy)
{
if (s_BlockPools[i].pool() == pool)
return &s_BlockPools[i];
}
}

return nullptr;
}

// Acquire a free slot; returns nullptr when the pool is exhausted.
static BlockPoolSlot *AcquireSlot()
{
for (uint32_t i = 0; i < STK_C_BLOCKPOOL_MAX; ++i)
for (size_t i = 0U; i < STK_C_BLOCKPOOL_MAX; ++i)
{
if (!s_BlockPools[i].busy)
{
s_BlockPools[i].busy = true;
return &s_BlockPools[i];
}
}

return nullptr;
}

Expand All @@ -128,7 +133,7 @@ stk_blockpool_t *stk_blockpool_create(size_t capacity, size_t raw_block_size, co
STK_ASSERT(capacity > 0U);
STK_ASSERT(raw_block_size > 0U);

sync::ScopedCriticalSection __cs;
const sync::ScopedCriticalSection cs_;

BlockPoolSlot *slot = AcquireSlot();
if (slot == nullptr)
Expand All @@ -152,7 +157,7 @@ stk_blockpool_t *stk_blockpool_create_static(size_t capacity,
STK_ASSERT(storage != nullptr);
STK_ASSERT(storage_size >= (capacity * BlockMemoryPool::AlignBlockSize(raw_block_size)));

sync::ScopedCriticalSection __cs;
sync::ScopedCriticalSection cs_;

BlockPoolSlot *slot = AcquireSlot();
if (slot == nullptr)
Expand All @@ -170,7 +175,7 @@ void stk_blockpool_destroy(stk_blockpool_t *pool)
{
STK_ASSERT(pool != nullptr);

sync::ScopedCriticalSection __cs;
sync::ScopedCriticalSection cs_;

BlockPoolSlot *slot = FindSlot(pool);

Expand Down
33 changes: 22 additions & 11 deletions interop/cmsis/rtos2/src/cmsis_os2_stk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ struct StkThread final : public stk::ITask
}

// ---- IStackMemory ----
stk::Word *GetStack() const override { return m_stack; }
const stk::Word *GetStack() const override { return m_stack; }
size_t GetStackSize() const override { return m_stack_size; }
size_t GetStackSizeBytes() const override { return m_stack_size * sizeof(stk::Word); }

Expand Down Expand Up @@ -510,11 +510,11 @@ osKernelState_t osKernelGetState(void)

switch (g_StkKernel.GetState())
{
case stk::IKernel::STATE_INACTIVE: return osKernelInactive;
case stk::IKernel::STATE_READY: return osKernelReady;
case stk::IKernel::STATE_RUNNING: return osKernelRunning;
case stk::IKernel::STATE_SUSPENDED: return osKernelSuspended;
default: return osKernelError;
case stk::IKernel::KSTATE_INACTIVE: return osKernelInactive;
case stk::IKernel::KSTATE_READY: return osKernelReady;
case stk::IKernel::KSTATE_RUNNING: return osKernelRunning;
case stk::IKernel::KSTATE_SUSPENDED: return osKernelSuspended;
default: return osKernelError;
}
}

Expand Down Expand Up @@ -959,13 +959,24 @@ uint32_t osThreadGetCount(void)

uint32_t osThreadEnumerate(osThreadId_t *thread_array, uint32_t array_items)
{
if (osKernelGetState() == osKernelInactive)
return 0U;
uint32_t result_count = 0U;
const osKernelState_t kstate = osKernelGetState();

// osThreadId_t maps directly to stk::ITask (see StkThread)
return g_StkKernel.EnumerateTasks(reinterpret_cast<stk::ITask **>(thread_array), array_items);
}
// kernel must be active and buffer must be valid
if ((kstate != osKernelInactive) && (thread_array != nullptr) && (array_items != 0U))
{
// cast the raw pointer array to the expected ITask* destination type
stk::ITask **tasks_destination = reinterpret_cast<stk::ITask **>(thread_array);

// bind raw destination buffer into a temporary ArrayView object
const size_t count = g_StkKernel.EnumerateTasks(
stk::ArrayView<stk::ITask *>(tasks_destination, static_cast<size_t>(array_items)));

result_count = static_cast<uint32_t>(count);
}

return result_count;
}

// ===========================================================================
// ==== Thread Flags Functions ====
Expand Down
Loading
Loading