CMSIS-RTOS2  Version 2.2.0
Real-Time Operating System: API and RTX Reference Implementation
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
Theory of Operation

Many aspects of the kernel are configurable and the configuration options are mentioned where applicable.

System Startup

Since main is no longer a thread RTX5 does not interfere with the system startup until main is reached. Once the execution reaches main() there is a recommended order to initialize the hardware and start the kernel. This is also reflected in the user code template file "CMSIS-RTOS2 'main' function" supplied with the RTX5 component.

Your application's main() should implement at least the following in the given order:

  1. Initialization and configuration of hardware including peripherals, memory, pins, clocks and the interrupt system.
  2. Update the system core clock using the respective CMSIS-Core (Cortex-M) or CMSIS-Core (Cortex-A) function.
  3. Initialize the CMSIS-RTOS kernel using osKernelInitialize.
  4. Optionally, create one thread (for example app_main), which is used as a main thread using osThreadNew. This thread should take care of creating and starting objects, once it is run by the scheduler. Alternatively, threads can be created in main() directly.
  5. Start the RTOS scheduler using osKernelStart which also configures the system tick timer and initializes RTOS specific interrupts. This function does not return in case of successful execution. Therefore, any application code after osKernelStart will not be executed.
Note
  • Modifying priorities and groupings in the NVIC by the application after the above sequence is not recommended.
  • Before executing osKernelStart, only the functions osKernelGetInfo, osKernelGetState, and object creation functions (osXxxNew) may be called.

Scheduler

RTX5 implements a low-latency preemptive scheduler. Major parts of RTX5 are executed in handler mode such as

In order to be low-latency with respect to ISR execution those system exceptions are configured to use the lowest priority groups available. The priorities are configured such that no preemption happens between them. Thus no interrupt critical sections (i.e. interrupt locks) are needed to protect the scheduler.

scheduling.png
Thread scheduling and interrupt execution

The scheduler combines priority and round-robin based context switches. The example depicted in the image above contains four threads (1, 2, 3, and 4). Threads 1 and 2 share the same priority, thread 3 has a higher one and thread 4 the highest (osThreadAttr_t::priority). As long as threads 3 and 4 are blocked the scheduler switches between thread 1 and 2 on a time-slice basis (round-robin). The time-slice for round-robin scheduling can be configured, see Round-Robin Timeout in System Configuration.

Thread 2 unblocks thread 3 by an arbitrary RTOS-call (executed in SVC handler mode) at time index 2. The scheduler switches to thread 3 immediately because thread 3 has the highest priority. Thread 4 is still blocked.

At time index 4 an interrupt (ISR) occurs and preempts the SysTick_Handler. RTX does not add any latency to the interrupt service execution. The ISR routine uses an RTOS-call that unblocks thread 4. Instead of switching to thread 4 immediately the PendSV flag is set to defer the context switching. The PendSV_Handler is executed right after the SysTick_Handler returns and the deferred context switch to thread 4 is carried out. As soon as highest priority thread 4 blocks again by using a blocking RTOS-call execution is switched back to thread 3 immediately during time index 5.

At time index 5 thread 3 uses a blocking RTOS-call as well. Thus the scheduler switches back to thread 2 for time index 6. At time index 7 the scheduler uses the round-robin mechanism to switch to thread 1 and so on.

Memory Allocation

RTX5 objects (thread, mutex, semaphore, timer, message queue, thread and event flags, as well as memory pool) require dedicated RAM memory. Objects can be created using osObjectNew() calls and deleted using osObjectDelete() calls. The related object memory needs to be available during the lifetime of the object.

RTX5 offers three different memory allocation methods for objects:

  • Global Memory Pool uses a single global memory pool for all objects. It is easy to configure, but may have the disadvantage for memory fragmentation when objects with different sizes are created and destroyed.
  • Object-specific Memory Pools uses a fixed-size memory pool for each object type. The method is time deterministic and avoids memory fragmentation.
  • Static Object Memory reserves memory during compile time and completely avoids that a system can be out of memory. This is typically a required for some safety critical systems.

It's possible to intermix all the memory allocation methods in the same application.

Global Memory Pool

The global memory pool allocates all objects from a memory area. This method of memory allocation is the default configuration setting of RTX5.

MemAllocGlob.png
Global Memory Pool for all objects

When the memory pool does not provide sufficient memory, the creation of the object fails and the related osObjectNew() function returns NULL.

Enabled in System Configuration.

Object-specific Memory Pools

Object-specific memory pools avoids memory fragmentation with a dedicated fixed-size memory management for each object type. This type of memory pools are fully time deterministic, which means that object creation and destruction takes always the same fixed amount of time. As a fixed-size memory pool is specific to an object type, the handling of out-of-memory situations is simplified.

MemAllocSpec.png
One memory pool per object type

Object-specific memory pools are selectively enabled for each object type, e.g: mutex or thread using the RTX configuration file:

When the memory pool does not provide sufficient memory, the creation of the object fails and the related osObjectNew() function returns NULL.

Static Object Memory

In contrast to the dynamic memory allocations, the static memory allocation requires compile-time allocation of object memory.

MemAllocStat.png
Statically allocated memory for all objects

Static memory allocation can be achieved by providing user-defined memory using attributes at object creation, see Manual User-defined Allocation. Please take special note of the following restrictions:

Memory type Requirements
Control Block (osXxxAttr_t::cb_mem) 4-Byte alignment. Size defined by osRtxThreadCbSize, osRtxTimerCbSize, osRtxEventFlagsCbSize, osRtxMutexCbSize, osRtxSemaphoreCbSize, osRtxMemoryPoolCbSize, osRtxMessageQueueCbSize.
Thread Stack (osThreadAttr_t::stack_mem) 8-Byte alignment. Size is application specific, i.e. amount of stack variables and frames.
Memory Pool (osMemoryPoolAttr_t::mp_mem) 4-Byte alignment. Size calculated with osRtxMemoryPoolMemSize.
Message Queue (osMessageQueueAttr_t::mq_mem) 4-Byte alignment. Size calculated with osRtxMessageQueueMemSize.

In order to allow RTX5 aware debugging, i.e. Component Viewer, to recognize control blocks, they need to be placed in individual memory sections by using __attribute__((section(...))).

RTX Object Linker Section
Thread .bss.os.thread.cb
Timer .bss.os.timer.cb
Event Flags .bss.os.evflags.cb
Mutex .bss.os.mutex.cb
Semaphore .bss.os.semaphore.cb
Memory Pool .bss.os.mempool.cb
Message Queue .bss.os.msgqueue.cb

It must be assured that these sections are placed into contiguous memory. This can fail, i.e. sections end up being split over multiple memory segments, when assigning compilation units to memory segments, manually.

The following code example shows how to create an OS object using static memory.

Code Example:

/*----------------------------------------------------------------------------
* CMSIS-RTOS 'main' function template
*---------------------------------------------------------------------------*/
#include "RTE_Components.h"
#include CMSIS_device_header
#include "cmsis_os2.h"
//include rtx_os.h for types of RTX objects
#include "rtx_os.h"
//The thread function instanced in this example
void worker(void *arg)
{
while(1)
{
//work
osDelay(10000);
}
}
// Define objects that are statically allocated for worker thread 1
__attribute__((section(".bss.os.thread.cb")))
osRtxThread_t worker_thread_tcb_1;
// Reserve two areas for the stacks of worker thread 1
// uint64_t makes sure the memory alignment is 8
uint64_t worker_thread_stk_1[64];
// Define the attributes which are used for thread creation
// Optional const saves RAM memory and includes the values in periodic ROM tests
const osThreadAttr_t worker_attr_1 = {
"wrk1",
&worker_thread_tcb_1,
sizeof(worker_thread_tcb_1),
&worker_thread_stk_1[0],
sizeof(worker_thread_stk_1),
0
};
// Define ID object for thread
/*----------------------------------------------------------------------------
* Application main thread
*---------------------------------------------------------------------------*/
void app_main (void *argument) {
uint32_t param = NULL;
// Create an instance of the worker thread with static resources (TCB and stack)
th1 = osThreadNew(worker, &param, &worker_attr_1);
for (;;) {}
}
int main (void) {
// System Initialization
SystemCoreClockUpdate();
// ...
osKernelInitialize(); // Initialize CMSIS-RTOS
osThreadNew(app_main, NULL, NULL); // Create application main thread
osKernelStart(); // Start thread execution
for (;;) {}
}

Thread Stack Management

For Cortex-M processors without floating point unit the thread context requires 64 bytes on the local stack.

Note
For Cortex-M4/M7 with FP the thread context requires 200 bytes on the local stack. For these devices the default stack space should be increased to a minimum of 300 bytes.

Each thread is provided with a separate stack that holds the thread context and stack space for automatic variables and return addresses for function call nesting. The stack sizes of RTX threads are flexibly configurable as explained in the section Thread Configuration. RTX offers a configurable checking for stack overflows and stack utilization.

Low-Power Operation

The system thread osRtxIdleThread can be use to switch the system into a low-power mode. The easiest form to enter a low-power mode is the execution of the __WFE function that puts the processor into a sleep mode where it waits for an event.

Code Example:

#include "RTE_Components.h"
#include CMSIS_device_header /* Device definitions */
void osRtxIdleThread (void) {
/* The idle demon is a system thread, running when no other thread is */
/* ready to run. */
for (;;) {
__WFE(); /* Enter sleep mode */
}
}
Note
__WFE() is not available in every Cortex-M implementation. Check device manuals for availability.

RTX Kernel Timer Tick

RTX uses the generic OS Tick API to configure and control its periodic Kernel Tick.

To use an alternative timer as the Kernel Tick Timer one simply needs to implement a custom version of the OS Tick API.

Note
The OS Tick implementation provided must assure that the used timer interrupt uses the same (low) priority group as the service interrupts, i.e. interrupts used by RTX must not preempt each other. Refer to the Scheduler section for more details.

Tick-less Low-Power Operation

RTX5 provides extension for tick-less operation which is useful for applications that use extensively low-power modes where the SysTick timer is also disabled. To provide a time-tick in such power-saving modes, a wake-up timer is used to derive timer intervals. The CMSIS-RTOS2 functions osKernelSuspend and osKernelResume control the tick-less operation.

Using this functions allows the RTX5 thread scheduler to stop the periodic kernel tick interrupt. When all active threads are suspended, the system enters power-down and calculates how long it can stay in this power-down mode. In the power-down mode the processor and peripherals can be switched off. Only a wake-up timer must remain powered, because this timer is responsible to wake-up the system after the power-down period expires.

The tick-less operation is controlled from the osRtxIdleThread thread. The wake-up timeout value is set before the system enters the power-down mode. The function osKernelSuspend calculates the wake-up timeout measured in RTX Timer Ticks; this value is used to setup the wake-up timer that runs during the power-down mode of the system.

Once the system resumes operation (either by a wake-up time out or other interrupts) the RTX5 thread scheduler is started with the function osKernelResume. The parameter sleep_time specifies the time (in RTX Timer Ticks) that the system was in power-down mode.

Code Example:

#include "msp.h" // Device header
/*----------------------------------------------------------------------------
* MSP432 Low-Power Extension Functions
*---------------------------------------------------------------------------*/
static void MSP432_LP_Entry(void) {
/* Enable PCM rude mode, which allows to device to enter LPM3 without waiting for peripherals */
PCM->CTL1 = PCM_CTL1_KEY_VAL | PCM_CTL1_FORCE_LPM_ENTRY;
/* Enable all SRAM bank retentions prior to going to LPM3 */
SYSCTL->SRAM_BANKRET |= SYSCTL_SRAM_BANKRET_BNK7_RET;
__enable_interrupt();
NVIC_EnableIRQ(RTC_C_IRQn);
/* Do not wake up on exit from ISR */
SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;
/* Setting the sleep deep bit */
SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);
}
static volatile unsigned int tc;
static volatile unsigned int tc_wakeup;
void RTC_C_IRQHandler(void)
{
if (tc++ > tc_wakeup)
{
SCB->SCR &= ~SCB_SCR_SLEEPONEXIT_Msk;
NVIC_DisableIRQ(RTC_C_IRQn);
NVIC_ClearPendingIRQ(RTC_C_IRQn);
return;
}
if (RTC_C->PS0CTL & RTC_C_PS0CTL_RT0PSIFG)
{
RTC_C->CTL0 = RTC_C_KEY_VAL; // Unlock RTC key protected registers
RTC_C->PS0CTL &= ~RTC_C_PS0CTL_RT0PSIFG;
RTC_C->CTL0 = 0;
SCB->SCR |= (SCB_SCR_SLEEPDEEP_Msk);
}
}
uint32_t g_enable_sleep = 0;
void osRtxIdleThread (void) {
for (;;) {
tc_wakeup = osKernelSuspend();
/* Is there some time to sleep? */
if (tc_wakeup > 0) {
tc = 0;
/* Enter the low power state */
MSP432_LP_Entry();
__WFE();
}
/* Adjust the kernel ticks with the amount of ticks slept */
}
}
Note
__WFE() is not available in every Arm Cortex-M implementation. Check device manuals for availability. The alternative using __WFI() has other issues, please take note of https://www.keil.com/support/docs/3591.htm as well.

RTX5 Header File

Every implementation of the CMSIS-RTOS2 API can bring its own additional features. RTX5 adds a couple of functions for the idle more, for error notifications, and special system timer functions. It also is using macros for control block and memory sizes.

If you require some of the RTX specific functions in your application code, #include the header file rtx_os.h:

/*
* Copyright (c) 2013-2023 Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* -----------------------------------------------------------------------------
*
* Project: CMSIS-RTOS RTX
* Title: RTX OS definitions
*
* -----------------------------------------------------------------------------
*/
#ifndef RTX_OS_H_
#define RTX_OS_H_
#include <stdint.h>
#include <stddef.h>
#include "cmsis_os2.h"
#include "rtx_def.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// Kernel Information
#define osRtxVersionAPI 20020000 ///< API version (2.2.0)
#define osRtxVersionKernel 50070000 ///< Kernel version (5.7.0)
#define osRtxKernelId "RTX V5.7.0" ///< Kernel identification string
// ==== Common definitions ====
/// Object Identifier definitions
#define osRtxIdInvalid 0x00U
#define osRtxIdThread 0xF1U
#define osRtxIdTimer 0xF2U
#define osRtxIdEventFlags 0xF3U
#define osRtxIdMutex 0xF5U
#define osRtxIdSemaphore 0xF6U
#define osRtxIdMemoryPool 0xF7U
#define osRtxIdMessage 0xF9U
#define osRtxIdMessageQueue 0xFAU
/// Object Flags definitions
#define osRtxFlagSystemObject 0x01U
#define osRtxFlagSystemMemory 0x02U
/// Object Attribute Class definitions
#define osRtxAttrClass_Pos 4U
#define osRtxAttrClass_Msk 0xF0U
// ==== Kernel definitions ====
/// Kernel State definitions
#define osRtxKernelInactive ((uint8_t)osKernelInactive)
#define osRtxKernelReady ((uint8_t)osKernelReady)
#define osRtxKernelRunning ((uint8_t)osKernelRunning)
#define osRtxKernelLocked ((uint8_t)osKernelLocked)
#define osRtxKernelSuspended ((uint8_t)osKernelSuspended)
/// Kernel Protect definitions
#define osRtxKernelProtectPrivileged 0x01U
#define osRtxKernelProtectClass_Pos 4U
#define osRtxKernelProtectClass_Msk 0xF0U
// ==== Thread definitions ====
/// Thread State definitions (extending osThreadState)
#define osRtxThreadStateMask 0x0FU
#define osRtxThreadInactive ((uint8_t)osThreadInactive)
#define osRtxThreadReady ((uint8_t)osThreadReady)
#define osRtxThreadRunning ((uint8_t)osThreadRunning)
#define osRtxThreadBlocked ((uint8_t)osThreadBlocked)
#define osRtxThreadTerminated ((uint8_t)osThreadTerminated)
#define osRtxThreadWaitingDelay ((uint8_t)(osRtxThreadBlocked | 0x10U))
#define osRtxThreadWaitingJoin ((uint8_t)(osRtxThreadBlocked | 0x20U))
#define osRtxThreadWaitingThreadFlags ((uint8_t)(osRtxThreadBlocked | 0x30U))
#define osRtxThreadWaitingEventFlags ((uint8_t)(osRtxThreadBlocked | 0x40U))
#define osRtxThreadWaitingMutex ((uint8_t)(osRtxThreadBlocked | 0x50U))
#define osRtxThreadWaitingSemaphore ((uint8_t)(osRtxThreadBlocked | 0x60U))
#define osRtxThreadWaitingMemoryPool ((uint8_t)(osRtxThreadBlocked | 0x70U))
#define osRtxThreadWaitingMessageGet ((uint8_t)(osRtxThreadBlocked | 0x80U))
#define osRtxThreadWaitingMessagePut ((uint8_t)(osRtxThreadBlocked | 0x90U))
/// Thread Flags definitions
#define osRtxThreadFlagDefStack 0x10U ///< Default Stack flag
/// Stack Marker definitions
#define osRtxStackMagicWord 0xE25A2EA5U ///< Stack Magic Word (Stack Base)
#define osRtxStackFillPattern 0xCCCCCCCCU ///< Stack Fill Pattern
/// Thread Control Block
typedef struct osRtxThread_s {
uint8_t id; ///< Object Identifier
uint8_t state; ///< Object State
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
struct osRtxThread_s *thread_next; ///< Link pointer to next Thread in Object list
struct osRtxThread_s *thread_prev; ///< Link pointer to previous Thread in Object list
struct osRtxThread_s *delay_next; ///< Link pointer to next Thread in Delay list
struct osRtxThread_s *delay_prev; ///< Link pointer to previous Thread in Delay list
struct osRtxThread_s *thread_join; ///< Thread waiting to Join
uint32_t delay; ///< Delay Time/Round Robin Time Tick
int8_t priority; ///< Thread Priority
int8_t priority_base; ///< Base Priority
uint8_t stack_frame; ///< Stack Frame (EXC_RETURN[7..0])
uint8_t flags_options; ///< Thread/Event Flags Options
uint32_t wait_flags; ///< Waiting Thread/Event Flags
uint32_t thread_flags; ///< Thread Flags
struct osRtxMutex_s *mutex_list; ///< Link pointer to list of owned Mutexes
void *stack_mem; ///< Stack Memory
uint32_t stack_size; ///< Stack Size
uint32_t sp; ///< Current Stack Pointer
uint32_t thread_addr; ///< Thread entry address
uint32_t tz_memory; ///< TrustZone Memory Identifier
uint8_t zone; ///< Thread Zone
uint8_t reserved[3];
struct osRtxThread_s *wdog_next; ///< Link pointer to next Thread in Watchdog list
uint32_t wdog_tick; ///< Watchdog tick counter
// ==== Timer definitions ====
/// Timer State definitions
#define osRtxTimerInactive 0x00U ///< Timer Inactive
#define osRtxTimerStopped 0x01U ///< Timer Stopped
#define osRtxTimerRunning 0x02U ///< Timer Running
/// Timer attribute definitions
#define osRtxTimerPeriodic 0x01U ///< Timer Periodic mode
/// Timer Function Information
typedef struct {
osTimerFunc_t func; ///< Function Pointer
void *arg; ///< Function Argument
/// Timer Control Block
typedef struct osRtxTimer_s {
uint8_t id; ///< Object Identifier
uint8_t state; ///< Object State
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
struct osRtxTimer_s *prev; ///< Pointer to previous active Timer
struct osRtxTimer_s *next; ///< Pointer to next active Timer
uint32_t tick; ///< Timer current Tick
uint32_t load; ///< Timer Load value
osRtxTimerFinfo_t finfo; ///< Timer Function Info
// ==== Event Flags definitions ====
/// Event Flags Control Block
typedef struct {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Waiting Threads List
uint32_t event_flags; ///< Event Flags
// ==== Mutex definitions ====
/// Mutex Control Block
typedef struct osRtxMutex_s {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Waiting Threads List
osRtxThread_t *owner_thread; ///< Owner Thread
struct osRtxMutex_s *owner_prev; ///< Pointer to previous owned Mutex
struct osRtxMutex_s *owner_next; ///< Pointer to next owned Mutex
uint8_t lock; ///< Lock counter
uint8_t padding[3];
// ==== Semaphore definitions ====
/// Semaphore Control Block
typedef struct {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Waiting Threads List
uint16_t tokens; ///< Current number of tokens
uint16_t max_tokens; ///< Maximum number of tokens
// ==== Memory Pool definitions ====
/// Memory Pool Information
typedef struct {
uint32_t max_blocks; ///< Maximum number of Blocks
uint32_t used_blocks; ///< Number of used Blocks
uint32_t block_size; ///< Block Size
void *block_base; ///< Block Memory Base Address
void *block_lim; ///< Block Memory Limit Address
void *block_free; ///< First free Block Address
/// Memory Pool Control Block
typedef struct {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Waiting Threads List
osRtxMpInfo_t mp_info; ///< Memory Pool Info
// ==== Message Queue definitions ====
/// Message Control Block
typedef struct osRtxMessage_s {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t priority; ///< Message Priority
struct osRtxMessage_s *prev; ///< Pointer to previous Message
struct osRtxMessage_s *next; ///< Pointer to next Message
/// Message Queue Control Block
typedef struct {
uint8_t id; ///< Object Identifier
uint8_t reserved_state; ///< Object State (not used)
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Waiting Threads List
osRtxMpInfo_t mp_info; ///< Memory Pool Info
uint32_t msg_size; ///< Message Size
uint32_t msg_count; ///< Number of queued Messages
osRtxMessage_t *msg_first; ///< Pointer to first Message
osRtxMessage_t *msg_last; ///< Pointer to last Message
// ==== Generic Object definitions ====
/// Generic Object Control Block
typedef struct {
uint8_t id; ///< Object Identifier
uint8_t state; ///< Object State
uint8_t flags; ///< Object Flags
uint8_t attr; ///< Object Attributes
const char *name; ///< Object Name
osRtxThread_t *thread_list; ///< Threads List
// ==== OS Runtime Information definitions ====
/// OS Runtime Information structure
typedef struct {
const char *os_id; ///< OS Identification
uint32_t version; ///< OS Version
struct { ///< Kernel Info
uint8_t state; ///< State
volatile uint8_t blocked; ///< Blocked
uint8_t pendSV; ///< Pending SV
uint8_t protect; ///< Protect options
uint32_t tick; ///< Tick counter
} kernel;
int32_t tick_irqn; ///< Tick Timer IRQ Number
struct { ///< Thread Info
struct { ///< Thread Run Info
osRtxThread_t *curr; ///< Current running Thread
osRtxThread_t *next; ///< Next Thread to Run
} run;
osRtxObject_t ready; ///< Ready List Object
osRtxThread_t *idle; ///< Idle Thread
osRtxThread_t *delay_list; ///< Delay List
osRtxThread_t *wait_list; ///< Wait List (no Timeout)
osRtxThread_t *terminate_list; ///< Terminate Thread List
osRtxThread_t *wdog_list; ///< Watchdog List
struct { ///< Thread Round Robin Info
osRtxThread_t *thread; ///< Round Robin Thread
uint32_t timeout; ///< Round Robin Timeout
} robin;
} thread;
struct { ///< Timer Info
osRtxTimer_t *list; ///< Active Timer List
osRtxThread_t *thread; ///< Timer Thread
osRtxMessageQueue_t *mq; ///< Timer Message Queue
void (*tick)(void); ///< Timer Tick Function
} timer;
struct { ///< ISR Post Processing Queue
uint16_t max; ///< Maximum Items
uint16_t cnt; ///< Item Count
uint16_t in; ///< Incoming Item Index
uint16_t out; ///< Outgoing Item Index
void **data; ///< Queue Data
} isr_queue;
struct { ///< ISR Post Processing functions
void (*thread)(osRtxThread_t*); ///< Thread Post Processing function
void (*event_flags)(osRtxEventFlags_t*); ///< Event Flags Post Processing function
void (*semaphore)(osRtxSemaphore_t*); ///< Semaphore Post Processing function
void (*memory_pool)(osRtxMemoryPool_t*); ///< Memory Pool Post Processing function
void (*message)(osRtxMessage_t*); ///< Message Post Processing function
} post_process;
struct { ///< Memory Pools (Variable Block Size)
void *stack; ///< Stack Memory
void *mp_data; ///< Memory Pool Data Memory
void *mq_data; ///< Message Queue Data Memory
void *common; ///< Common Memory
} mem;
struct { ///< Memory Pools (Fixed Block Size)
osRtxMpInfo_t *stack; ///< Stack for Threads
osRtxMpInfo_t *thread; ///< Thread Control Blocks
osRtxMpInfo_t *timer; ///< Timer Control Blocks
osRtxMpInfo_t *event_flags; ///< Event Flags Control Blocks
osRtxMpInfo_t *mutex; ///< Mutex Control Blocks
osRtxMpInfo_t *semaphore; ///< Semaphore Control Blocks
osRtxMpInfo_t *memory_pool; ///< Memory Pool Control Blocks
osRtxMpInfo_t *message_queue; ///< Message Queue Control Blocks
} mpi;
extern osRtxInfo_t osRtxInfo; ///< OS Runtime Information
/// OS Runtime Object Memory Usage structure
typedef struct {
uint32_t cnt_alloc; ///< Counter for alloc
uint32_t cnt_free; ///< Counter for free
uint32_t max_used; ///< Maximum used
/// OS Runtime Object Memory Usage variables
// ==== OS API definitions ====
// Object Limits definitions
#define osRtxThreadFlagsLimit 31U ///< number of Thread Flags available per thread
#define osRtxEventFlagsLimit 31U ///< number of Event Flags available per object
#define osRtxMutexLockLimit 255U ///< maximum number of recursive mutex locks
#define osRtxSemaphoreTokenLimit 65535U ///< maximum number of tokens per semaphore
// Control Block sizes
#define osRtxThreadCbSize sizeof(osRtxThread_t)
#define osRtxTimerCbSize sizeof(osRtxTimer_t)
#define osRtxEventFlagsCbSize sizeof(osRtxEventFlags_t)
#define osRtxMutexCbSize sizeof(osRtxMutex_t)
#define osRtxSemaphoreCbSize sizeof(osRtxSemaphore_t)
#define osRtxMemoryPoolCbSize sizeof(osRtxMemoryPool_t)
#define osRtxMessageQueueCbSize sizeof(osRtxMessageQueue_t)
/// Memory size in bytes for Memory Pool storage.
/// \param block_count maximum number of memory blocks in memory pool.
/// \param block_size memory block size in bytes.
#define osRtxMemoryPoolMemSize(block_count, block_size) \
(4*(block_count)*(((block_size)+3)/4))
/// Memory size in bytes for Message Queue storage.
/// \param msg_count maximum number of messages in queue.
/// \param msg_size maximum message size in bytes.
#define osRtxMessageQueueMemSize(msg_count, msg_size) \
(4*(msg_count)*(3+(((msg_size)+3)/4)))
// ==== OS External Functions ====
// OS Error Codes
#define osRtxErrorStackUnderflow 1U ///< \deprecated Superseded by \ref osRtxErrorStackOverflow.
#define osRtxErrorStackOverflow 1U ///< Stack overflow, i.e. stack pointer below its lower memory limit for descending stacks.
#define osRtxErrorISRQueueOverflow 2U ///< ISR Queue overflow detected when inserting object.
#define osRtxErrorTimerQueueOverflow 3U ///< User Timer Callback Queue overflow detected for timer.
#define osRtxErrorClibSpace 4U ///< Standard C/C++ library libspace not available: increase \c OS_THREAD_LIBSPACE_NUM.
#define osRtxErrorClibMutex 5U ///< Standard C/C++ library mutex initialization failed.
#define osRtxErrorSVC 6U ///< Invalid SVC function called.
/// OS Error Callback function
extern uint32_t osRtxErrorNotify (uint32_t code, void *object_id);
extern uint32_t osRtxKernelErrorNotify (uint32_t code, void *object_id);
/// OS Idle Thread
extern void osRtxIdleThread (void *argument);
/// OS Exception handlers
extern void SVC_Handler (void);
extern void PendSV_Handler (void);
extern void SysTick_Handler (void);
// ==== OS External Configuration ====
/// OS Configuration flags
#define osRtxConfigPrivilegedMode (1UL<<0) ///< Threads in Privileged mode
#define osRtxConfigStackCheck (1UL<<1) ///< Stack overrun checking
#define osRtxConfigStackWatermark (1UL<<2) ///< Stack usage Watermark
#define osRtxConfigSafetyFeatures (1UL<<3) ///< Safety features enabled
#define osRtxConfigSafetyClass (1UL<<4) ///< Safety Class feature enabled
#define osRtxConfigExecutionZone (1UL<<5) ///< Execution Zone enabled
#define osRtxConfigThreadWatchdog (1UL<<6) ///< Thread Watchdog enabled
#define osRtxConfigObjPtrCheck (1UL<<7) ///< Object Pointer Checking enabled
#define osRtxConfigSVCPtrCheck (1UL<<8) ///< SVC Pointer Checking enabled
/// OS Configuration structure
typedef struct {
uint32_t flags; ///< OS Configuration Flags
uint32_t tick_freq; ///< Kernel Tick Frequency
uint32_t robin_timeout; ///< Round Robin Timeout Tick
struct { ///< ISR Post Processing Queue
void **data; ///< Queue Data
uint16_t max; ///< Maximum Items
uint16_t padding;
} isr_queue;
struct { ///< Memory Pools (Variable Block Size)
void *stack_addr; ///< Stack Memory Address
uint32_t stack_size; ///< Stack Memory Size
void *mp_data_addr; ///< Memory Pool Memory Address
uint32_t mp_data_size; ///< Memory Pool Memory Size
void *mq_data_addr; ///< Message Queue Data Memory Address
uint32_t mq_data_size; ///< Message Queue Data Memory Size
void *common_addr; ///< Common Memory Address
uint32_t common_size; ///< Common Memory Size
} mem;
struct { ///< Memory Pools (Fixed Block Size)
osRtxMpInfo_t *stack; ///< Stack for Threads
osRtxMpInfo_t *thread; ///< Thread Control Blocks
osRtxMpInfo_t *timer; ///< Timer Control Blocks
osRtxMpInfo_t *event_flags; ///< Event Flags Control Blocks
osRtxMpInfo_t *mutex; ///< Mutex Control Blocks
osRtxMpInfo_t *semaphore; ///< Semaphore Control Blocks
osRtxMpInfo_t *memory_pool; ///< Memory Pool Control Blocks
osRtxMpInfo_t *message_queue; ///< Message Queue Control Blocks
} mpi;
uint32_t thread_stack_size; ///< Default Thread Stack Size
const
osThreadAttr_t *idle_thread_attr; ///< Idle Thread Attributes
const
osThreadAttr_t *timer_thread_attr; ///< Timer Thread Attributes
void (*timer_thread)(void *); ///< Timer Thread Function
int32_t (*timer_setup)(void); ///< Timer Setup Function
const
osMessageQueueAttr_t *timer_mq_attr; ///< Timer Message Queue Attributes
uint32_t timer_mq_mcnt; ///< Timer Message Queue maximum Messages
extern const osRtxConfig_t osRtxConfig; ///< OS Configuration
#ifdef __cplusplus
}
#endif
#endif // RTX_OS_H_

Timeout Value

Timeout values are an argument to several osXxx functions to allow time for resolving a request. A timeout value of 0 means that the RTOS does not wait and the function returns instantly, even when no resource is available. A timeout value of osWaitForever means that the RTOS waits infinitely until a resource becomes available. Or one forces the thread to resume using osThreadResume which is discouraged.

The timeout value specifies the number of timer ticks until the time delay elapses. The value is an upper bound and depends on the actual time elapsed since the last timer tick.

Examples:

  • timeout value 0 : the system does not wait, even when no resource is available the RTOS function returns instantly.
  • timeout value 1 : the system waits until the next timer tick occurs; depending on the previous timer tick, it may be a very short wait time.
  • timeout value 2 : actual wait time is between 1 and 2 timer ticks.
  • timeout value osWaitForever : system waits infinite until a resource becomes available.
TimerValues.png
Example of timeout using osDelay()

Calls from Interrupt Service Routines

The following CMSIS-RTOS2 functions can be called from threads and Interrupt Service Routines (ISR):

Functions that cannot be called from an ISR are verifying the interrupt status and return the status code osErrorISR, in case they are called from an ISR context. In some implementations, this condition might be caught using the HARD_FAULT vector.

Note
  • RTX does not disable interrupts during critical sections for Armv7-M and Armv8-M architecture based devices, but rather uses atomic operations.
  • Therefore, there is no need to configure interrupt priorities of interrupt service routines that use RTOS functions.

SVC Functions

Supervisor Calls (SVC) are exceptions targeted at software and operating systems for generating system function calls. They are sometimes called software interrupts. For example, instead of allowing user programs to directly access hardware, an operating system may provide access to hardware through an SVC. So when a user program wants to use certain hardware, it generates the exception using SVC instructions. The software exception handler in the operating system executes and provides the requested service to the user application. In this way, access to hardware is under the control of the OS, which can provide a more robust system by preventing the user applications from directly accessing the hardware.

SVCs can also make software more portable because the user application does not need to know the programming details of the underlying hardware. The user program will only need to know the application programming interface (API) function ID and parameters; the actual hardware-level programming is handled by device drivers.

SVCs run in privileged handler mode of the Arm Cortex-M core. SVC functions accept arguments and can return values. The functions are used in the same way as other functions; however, they are executed indirectly through the SVC instruction. When executing SVC instructions, the controller changes to the privileged handler mode.

Interrupts are not disabled in this mode. To protect SVC functions from interrupts, you need to include the disable/enable intrinsic functions __disable_irq() and __enable_irq() in your code.

You can use SVC functions to access protected peripherals, for example, to configure NVIC and interrupts. This is required if you run threads in unprivileged (protected) mode and you need to change interrupts from the within the thread.

To implement SVC functions in your Keil RTX5 project, you need to:

  1. Add the SVC User Table file svc_user.c to your project folder and include it into your project. This file is available as a user code template.
  2. Write a function implementation. Example:
    uint32_t svc_atomic_inc32 (uint32_t *mem) {
    // A protected function to increment a counter.
    uint32_t val;
    __disable_irq();
    val = *mem;
    (*mem) = val + 1U;
    __enable_irq();
    return (val);
    }
  3. Add the function to the SVC function table in the svc_user.c module:
    void * const osRtxUserSVC[1+USER_SVC_COUNT] = {
    (void *)USER_SVC_COUNT,
    (void *)svc_atomic_inc32,
    };
  4. Increment the number of user SVC functions:
    #define USER_SVC_COUNT 1 // Number of user SVC functions
  5. Declare a function wrapper to be called by the user to execute the SVC call.
    Code Example (Arm Compiler 6)

    __STATIC_FORCEINLINE uint32_t atomic_inc32 (uint32_t *mem) {
    register uint32_t val;
    __ASM volatile (
    "svc 1" : "=l" (val) : "l" (mem) : "cc", "memory"
    );
    return (val);
    }

    Code Example (Arm Compiler 5 using __svc(x) attribute)

    uint32_t atomic_inc32 (uint32_t *mem) __svc(1);
Note
  • The SVC function 0 is reserved for the Keil RTX5 kernel.
  • Do not leave gaps when numbering SVC functions. They must occupy a continuous range of numbers starting from 1.
  • SVC functions can still be interrupted.

Arm C library multi-threading protection

RTX5 provides an interface to the Arm C libraries to ensure static data protection in a multi-threaded application.

The Arm C libraries use static data to store errno, floating-point status word for software floating-point operations, a pointer to the base of the heap, and other variables. The Arm C micro-library (i.e. microlib) does not support protection for multi-threaded applications. See the limitations and differences between microlib and the default C library.

By default, RTX5 uses the Arm C libraries multi-thread protection for:

  • all user threads if Object specific Memory allocation is enabled.
  • the number of threads defined by OS_THREAD_LIBSPACE_NUM if Object specific Memory allocation is disabled. The definition OS_THREAD_LIBSPACE_NUM defines the number of threads that can safely call Arm C library functions and can be found in "RTX_Config.h" file or can be defined on the global scope.

The default, Arm C libraries use mutex functions to protect shared resources from concurrent access. RTX5 implements these functions and uses resources from the Global Dynamic Memory to allocate mutex objects.