Overview
Operating System Abstraction layer (OSA) provides a common set of services for drivers and applications so that they can work with or without the operating system. OSA provides services that abstract most of the OS kernel functionality. These services can either be mapped to the target OS functions directly, or implemented by OSA when no OS is used (bare metal) or when the service does not exist in the target OS. Freescale Kinetis SDK implements the OS abstraction layer for MQX™ RTOS, Free RTOS, µC/OS, and for OS-less usage (bare metal). The bare metal OS abstraction implementation is selected as the default option.
OSA provides these services: task management, semaphore, mutex, event, message queue, memory allocator, critical section, and time functions.
Task Management
With OSA, applications can create and destroy tasks dynamically. These services are mapped to the task functions of RTOSes. For bare metal, a function poll mechanism simulates a task scheduler.
OSA supports task priorities 0~15, where priority 0 is the highest priority and priority 15 is the lowest priority.
To create a task, applications must prepare different resources on different RTOSes. For example, µC/OS-II and µC/OS-III need pre-allocated task stack while other RTOSes do not need this. The µC/OS-III needs pre-allocated task control block OS_TCB while other RTOSes do not. To mask the differences, OSA uses a macro OSA_TASK_DEFINE to prepare resources for task creation. Then the function
OSA_TaskCreate() creates a task based on the resources. This method makes it easy to use a copy of code on different RTOSes. However, it is not mandatory to use the OSA_TASK_DEFINE. Applications can also prepare the resources manually. There are two methods to create a task:
- Use the OSA_TASK_DEFINE macro and the function OSA_TaskCreate(). The macro OSA_TASK_DEFINE declares a task handler and task stack statically. The function OSA_TaskCreate() creates task base-on the resources declared by OSA_TASK_DEFINE.
This is an example code to create a task using method 1:
void main(void)
{
"my_task",
TASK_STACK_SIZE,
task_func_stack,
TASK_PRIO,
parameter,
false,
&task_func_task_handler);
}
- Prepare resources manually, then use the function OSA_TaskCreate() to create a task.
For example:
#if defined(FSL_RTOS_UCOSII)
"my_task",
TASK_STACK_SIZE,
task_stack,
TASK_PRIO,
parameter,
false,
&task_handler);
#elif defined(FSL_RTOS_UCOSIII)
OS_TCB TCB_task;
task_handler = &TCB_task;
"my_task",
TASK_STACK_SIZE,
task_stack,
TASK_PRIO,
parameter,
false,
&task_handler);
#else // For MQX RTOS, FreeRTOS OS and bare metal.
"my_task",
TASK_STACK_SIZE,
NULL,
TASK_PRIO,
parameter,
false,
&task_handler);
#endif
Method one is easy to use. The disadvantage is that one task function can only create one task instance. Method two can create multiple task instances using one task function, but the code must be divided by macros for different RTOSes. Applications can choose either method according to requirements.
After a task is created successfully, task handler can be used to manage the task, for example, get or set task priority, destroy task and so on.If task is not used any more, use the
OSA_TaskDestroy() function to destroy the task. If the task function does not contain an infinite loop, or, in other words, the task function returns, call the
OSA_TaskDestroy() function at the end of the task function.
Semaphore
The OSA provides the drivers and applications with a counting semaphore. It can be used either to synchronize tasks or to synchronize a task and an ISR.
A semaphore must be initialized with the
OSA_SemaCreate() function before using. The semaphore can be initialized with an initial value. When the semaphore is not used any more, use the
OSA_SemaDestroy() function to destroy it.
Note that if multiple tasks are waiting for one semaphore, different RTOSes may have different behaviors, for example, wake up the task wait first or wake up the task with highest priority. Bare metal semaphore does not support multiple tasks wait with timeout.
This is an example code to create and destroy a semaphore:
The function
OSA_SemaWait() waits a semaphore within the timeout (in milliseconds). Passing a value 0 as a timeout means return immediately and passing the OSA_WAIT_FOREVER means wait indefinitely. This function should not be used in the ISR.
The function
OSA_SemaPost() wakes up task which is waiting for the semaphore.
Mutex
A mutex is used for the mutual exclusion of tasks when they access a shared resource. OSA provides a non-recursive mutex, which means a task cannot try to lock a mutex it has already locked.
A mutex must be initialized to an unlocked status with the
OSA_MutexCreate() function before using. When the mutex is not used any more, use the
OSA_MutexDestroy() function to destroy it.
This is example code to create and destroy a mutex:
The function
OSA_MutexLock() waits to lock a mutex within the timeout (in milliseconds). Passing a value 0 as a timeout means return immediately and passing the OSA_WAIT_FOREVER means waiting indefinitely.
The function
OSA_MutexUnlock() unlocks a mutex which is locked by the current task.
Event
When using event, notice that if multiple tasks are waiting on one event, different RTOSes may have different behaviors. For example, only the first task in waiting list is woken up, or all tasks in waiting list are woken up. Bare metal event does not support multiple task waiting with timeout.
OSA provides two types of events:
- Auto-clear, which occurs when some task has get flags it is waiting for. These flags are cleared automatically.
- Manual-clear, which means that the flags could only be cleared manually.
The clear mode is a property of an event. After an event is created, the clear mode can't be changed.
An event must be initialized with the
OSA_EventCreate() function before using. When it is created, its flags are all cleared. When the event is not used any more, use the event_destory() function to destroy it.
This is example code to create and destroy an event object:
The function
OSA_EventWait() waits for specified flags of an event with the timeout (in milliseconds). Passing a value 0 as a timeout means return immediately and passing the OSA_WAIT_FOREVER means wait indefinitely. This function can be configured to wait for all specified flags or wait for any one flag in specified flags. The parameter setFlags saves the flags which wake up the waiting task. This function should not be used in the ISR.
The functions
OSA_EventSet() and
OSA_EventClear() are used to set and clear specified flags of an event. The function
OSA_EventGetFlags() is used to get current event flags.
Message Queue
OSA provides the FIFO message queue. All messages in a queue have the same size. Message queue holds an internal memory area to save messages. While putting the message, the message entity is copied to this internal memory area. While getting message, message is copied from the internal memory area.
Note that if multiple tasks are waiting on one message queue, different RTOSes may have different behaviors. Bare metal message queue does not support multiple task waiting with timeout.
To create a message queue, use
MSG_QUEUE_DECLARE() and
OSA_MsgQCreate() functions as shown here:
Note that the parameter message_size is in words and not in bytes. It means that the message queue can only transfer messages of multiple-byte size.
The function
OSA_MsgQPut() puts a message to the queue. If the queue is full, an error returns.
The function
OSA_MsgQGet() waits for a timeout in milliseconds to get the message from the queue. Passing a value 0 as a timeout means return immediately and passing the OSA_WAIT_FOREVER means wait indefinitely. This function should not be used in the ISR.
If the queue is not used any more, use the
OSA_MsgQDestroy() function to destroy it.
Critical Section
OSA provides two types of critical sections. The first type disables the interrupt while the second type only disables the scheduler to stop the task preemption.
Memory Allocator
The function
OSA_MemAlloc() allocates memory with specified size. The function
OSA_MemAllocZero() allocates and cleans the memory. The function
OSA_MemFree() frees the memory. For RTOSes that have internal memory manager, such as the MQX RTOS, OSA maps these functions directly. For other RTOSes or bare metal, the standard functions malloc/calloc/free are used.
Time Functions
OSA only provides two time functions,
OSA_TimeDelay() and
OSA_TimeGetMsec(). The function
OSA_TimeDelay() delays specified time in milliseconds, while the function
OSA_TimeGetMsec() gets the system time in milliseconds since POR.
Interrupt priority
For some RTOSes, a proper interrupt priority must be set if system services are called in this interrupt service routine.
For MQX RTOS, follow these criteria:
- Interrupt priority must be an even number.
- Interrupt priority >= 2*MQX_HARDWARE_INTERRUPT_LEVEL_MAX.
For FreeRTOS OS, the interrupt priority is defined in the configuration file FreeRTOSConfig.h. In the current configuration, priority 1~15 can be used for the ARM Cortex
®-M4. See the FreeRTOS OS' official documents for details.
OSA initialization
To initialize and start RTOSes, OSA uses abstract functions
OSA_Init() and
OSA_Start(). Call
OSA_Init() after calling hardware_init().This example shows how to use
OSA_Init() and
OSA_Start() functions:
#include <fsl_os_abstraction.h>
{
}
#if defined(FSL_RTOS_MQX)
void Main_Task(uint32_t param);
TASK_TEMPLATE_STRUCT MQX_template_list[] =
{
{ 0L, 0L, 0L, 0L, 0L, 0L }
};
#endif
#if defined(FSL_RTOS_MQX)
void Main_Task(uint32_t param)
#else
void main(void)
#endif
{
hardware_init();
"my_task",
512,
task_func_stack,
5,
false,
&task_func_task_handler);
}
Note that, for other RTOSes, the task scheduler is started up by the
OSA_Start() function. However, for MQX RTOS, the task scheduler is started before the
OSA_Init() function call. When creating tasks in the Main_Task() function with the MQX RTOS, the newly created task runs immediately if it has the highest priority. However, for other RTOSes, all newly created tasks do not run until the
OSA_Start() function executes.