You are on page 1of 11

Real-Time Operating Systems (RTOS) basics

by Mats Pettersson, IAR Systems

This article will explain some Real-Time Operating Systems (RTOS) basics. The material presented here is not intended as a complete coverage of different RTOSes and their features. For the purposes of simplification, I will briefly cover the most important features of a representative RTOS. After we explore what constitutes an RTOS and why we might want to use one, Ill explain each of the basic components of a typical RTOS and show how these building blocks are integrated into the system.

Task-oriented design
Designing applications for an embedded application is almost always challenging, to say the least. One way to decrease the complexity of your application is to use a task-oriented design and divide a project into different modules (or tasks). Each module is then responsible for some part of the application. With such a system you would like to be able to specify that some modules (or tasks) are more important than others. That is, some tasks have real-time requirements. They have to respond quickly and correctly. If your system employs a professional RTOS, features that prioritize tasks are already part of the package. In addition to task prioritization, a clean and well-tested API is included that eases communicate between different tasks. So if we think about using an RTOS we will have the tools to:

Ensure that time-critical parts of the code execute in time. Make complex applications easier to develop and maintain. Its easier to develop and maintain smaller tasks, than to have to deal with the entire application as a whole. Distribute different parts of the application among several developers. Each developer can be responsible for one or more tasks within the application and a clean Application Programming Interface (API) will be available for communication between the different modules/tasks as they are developed.

Applications can be divided into different tasks (modules) with or without the use of an RTOS. For example, one task can be responsible for reading the keyboard, another for checking temperature, a third for printing messages on a LCD screen, and so on. With an RTOS you not only get the tool to create tasks, but also tools to communicate between the tasks, and tools to ensure that tasks that are time critical are executed in time. Since the interface between the different tasks becomes very clean when using an RTOS you will save time both in development and in maintenance of the application.

How does an RTOS work?

The core of an RTOS is known as the

kernel. An API is provided to allow access to the kernel

for the creation of tasks, among other things. A

task is like a function that has its own stack,

and a Task Control Block (TCB). In addition to the stack, which is private to a task, each task control block holds information about the state of that task.

The kernel also contains a

scheduler. The scheduler is responsible for executing tasks in

accordance with a scheduling mechanism. The main difference among schedulers is how they distribute execution time among the various tasks they are managing.

The heart beat of the kernel is the system tick, often called just

systick. For every system

tick the kernel will check to determine if a task switch needs to be performed. The system tick is often implemented using one of the hardware timers within the microcontroller. Using a timer in this way, may not be the optimum solution for all applications. If the application is energy-sensitive, you might not want the system tick handler to run each time a timer times out. For example, you might have an application that goes into sleep mode to save power. The device wakes up only if an external event occurs. If the triggering event is an external interrupt, the system tick handler can be called from within that external interrupt. This way, the application will run the RTOS only when something actually happens. The rest of the time, the application can remain in sleep mode.

Closely associated with the system tick are

software timers. Software timers ticks once for

each system tick, and is a convenient way to setup for example delays in an RTOS. For this to work properly you should call the system tick handler with regular intervals. There will be further discussion about scheduling algorithms and system ticks later in this article.

Think modules
Perhaps the best way to get started with an RTOS application is to think about how the application can be divided into distinct modules. For example, a somewhat simple engine input control application could be divided into the following modules:

Engine temperature Oil pressure RPM User input

These modules can then be set up as tasks, or can be divided into sub-modules. For example:

Engine temperature

- Read engine temperature - Update LCD with current temperature Oil pressure - Read current oil pressure - Conduct emergency engine shutdown RPM - Read RPM - Update LCD with current RPM User input - Get gas pedal angle - Get current gear

This division into sub-modules can continue until each module is suitable to be handled by a single task.

RTOS components
Lets see what features an RTOS has to offer and how each of these features might come in handy in different applications. Tasks Tasks are like functions, each with its own stack and task control block (TCB). Unlike most functions, however, a task is almost always an infinite loop. That is, once it has been created, it will never exit.

void TaskSendData( void ) { while (1) { // Send the data } }

A task is always in one of several

states. A task can be ready to be executed, that is, in the

READY state. Or the task may be suspended (pending), that is, the task is waiting for something to happen before it goes into the READY state. This is called the WAITING state. Here is a short description of the states we will use in our RTOS.
State Description


This task is not available for scheduling This task is ready to run This is the currently-running task This task is waiting for something. This could be an event, a message or maybe for the RTOS clock to reach a specific value (delayed).

Note: Different RTOSes may have different names for each of these states.


There are two major types of scheduler from which you can choose:

1. Time-sharing:
The most common time-sharing algorithm is called

Round-Robin. With round-robin

scheduling, the scheduler has a list of the tasks that make up the system and it uses this list to check for the next task that is ready to execute. If a task is READY, that task will execute. Associated with each task is its time-slice. This time-slice is the maximum time a task can execute for each round the scheduler makes.

2. Event-driven: (Priority-Controlled Scheduling Algorithm)

Usually different tasks have differing response requirement. For example, in an application that controls a motor, a keyboard and a display, the motor usually requires faster reaction time than the keyboard and display. This makes an event-driven scheduler a must. In event-driven systems, every task is assigned a priority and the task with the highest priority is executed. The order of execution depends on this priority. The rule is very simple:

The scheduler activates the task that has the highest priority of all tasks that are ready to run.

preemptive or non-preemptive. In a preemptive system each task can be interrupted by a task which has a higher priority. In a nonpreemptive system the executing task will finish its execution, even if a task with higher
An event-driven scheduler can be either priority becomes ready for execution during execution of a task.

Task communications
We also need to be able to communicate between the different tasks in an RTOS. Communication can take the form of an event, a semaphore (flag), or it can be in the form of a message sent to another task.

The most basic communication is via an

event. An event is a way for a single task to

communicate with another task. An interrupt service routine (ISR) can also send an event to a task. Some RTOS can also send one (1) event to more than one task.

Semaphores are usually used to protect shared resources, for example if there is more than
one task that needs to read/write to the same memory (variable). This is done so that a variable does not change through the actions of another task while it is being addressed by the active task. The principle is that you need to obtain a semaphore associated with a variable protected in this way before reading/writing to this memory. Once you have obtained the semaphore, no one else can read/write to this memory until you release the semaphore. This way you can ensure that only one task at the time reads/writes to a memory location or variable.

Messages allow you to send data to one or more tasks. These messages can be of almost any size and are usually implemented as a mailbox or a queue. A mailbox is a FIFO (first-in, first-out) buffer and each item in the mailbox has a predefined
fixed size. A

queue is a FIFO (first-in, first-out) buffer and each item in the queue has a dynamic size.

There are three major differences between queues and mailboxes:

1. Queues accept messages of various sizes. 2. Retrieving a message from the queue does not copy the message, but returns a pointer to the message along with its size. 3. For queues, the retrieving function has to delete every queue message after processing it. Otherwise the message will remain in the queue.

Lets recap what we have discussed so far.

Now then, lets see what we can do with the components we have so far. Imagine that we need to create an engine control application and we have a microcontroller and a LCD screen. We would also like to have the current time displayed on the LCD. We will reuse the engine control example, where we divided this application into different modules and tasks. (We will ignore the User input to make the example clearer.) For this exercise, we will create the following tasks:

1. // Read the temperature and oil pressure of the engine void T_OilRead(void) and void T_TempRead(void) 2. // Print the temperature and oil pressure on the LCD void T_OilLCD(void) and void T_TempLCD(void) 3. // Controls the engine void T_EngineCTRL(void)
Graphically, the system will look like this:

And the different symbols used are defined as follows...

For this system, we will need a semaphore to control writes to the LCD. We need this because we have two different tasks that are writing to the LCD and if one of these tasks was interrupted by the other, the output would most likely be corrupted. To summarize, we need the following signaling mechanisms.

The mailboxes M_TempLCD and M_OilLCD. These mailboxes contain messages that will be printed on the LCD.

The mailboxes M_TempCTRL and M_OilCTRL. These mailboxes contain messages that are sent to the engine control task.

The semaphore S_LCD. This semaphore will ensure that one and only one task at a time prints to

the LCD.
System ticks: We now have a system that will update the LCD with motor commands and show the current time on the LCD. There is, however, one very important thing missing and that is the RTOS tick function. As stated earlier, the kernel needs to gain control over the system every now and then to be able to, for example, switch tasks according to the RTOS scheduler. This is normally done by calling a tick-handling API function driven by one of the microcontroller timers. Without system ticks, nothing will happen in the system. Connection to the microcontroller hardware: The last piece in the puzzle is to connect all this to the hardware. In our case we will assume that we have access to a firmware library. We assume that we have the following firmware API functions:

// Set text X,Y coordinate in characters void GLCD_TextSetPos(int X, int Y);

// Set draw window XY coordinate in pixels void GLCD_SetWindow(int X_Left, int Y_Up, int X_Right, int Y_Down);

// Function that reads the engine temperature char Engine_ReadTemp(void);

// Function that reads the engine oil pressure char Engine_ReadOilPressure(void);

// Function that controls the engine char Engine_Control(int CtrlValue);

Now we only have to put the pieces together to see if our system will work as intended. The easiest way to get started is, if available, to use a BSP from the RTOS vendor. For IAR PowerPac and C/OS-II there are numerous different BSPs for various devices. To build your application without a BSP you have to:

Add compiler/assembler include paths to the build tool. We need to make sure that the compiler and assembler can see the header

files used in our system.

Add the generic RTOS library or source files. We need to add the kernel. The kernel can come either as source or as library files.

Add the target-specific RTOS files. Many RTOSes have a Board Support Package (BSP) for different boards/devices. In such a case, you only have to make sure that you include these target-specific files. In such a BSP you would, for example, have code that configures the clock so that the RTOS tick is configured correctly. Your application has to initialize the RTOS and create at least one task before starting the RTOS. This is usually done in the main() function, but can be done anywhere in your application.

Assuming that we have added all of the files and have set the build options according to the RTOS specifications, we are ready to start creating tasks in our application. But before we can create our tasks, we need to define their Task Control Blocks (TCB), and specify their stacks. The code snippets below are examples for how task stacks can be defined for the IAR PowerPac RTOS and for Micrium's C/OS-II RTOS:
IAR PowerPac Micrium C/OS-II

OS_STACKPTR int StackEngineCTRL[128]; OS_STACKPTR int StackOilLCD[128]; OS_STACKPTR int StackTempLCD[128]; OS_STACKPTR int StackOilRead[128]; OS_STACKPTR int StackTempRead[128];

static OS_STK StackEngineCTRL[128]; static OS_STK StackOilLCD[128]; static OS_STK StackTempLCD[128]; static OS_STK StackOilRead[128]; static OS_STK StackTempRead[128];

An example of a definition of the TCB (Task Control Block) for IAR PowerPac is seen here:



For the Micrium C/OS-II RTOS, these TCBs are created by the kernel. Now, what remains is the main function. In our main(), we will start by disabling interrupts to make sure we are not interrupted during the setup of our system. After this we will initialize the hardware, both for the RTOS and for our application and BSP. Then we will create our tasks, events, semaphores and mailboxes/queues. When all of this is done we can start our RTOS and let the scheduler take care of the problem of making sure that the correct task is executed at the right time. Below are the pieces we have in our main function, both for IAR PowerPac and for C/OS-II.
IAR PowerPac

int main(void) { BSP_IntDisAll(); OS_InitHW(); BSP_Init(); board

// initially disable interrupts // initialize OS // initialize Hardware for OS and the

OSTaskCreate(...); // create at least one task before starting the RTOS // create your events, mailboxes and semaphores ... OSStart(); // Start your RTOS }

Micrium C/OS-II

int main(void) { BSP_IntDisAll(); // initially disable interrupts OSInit(); // initialize OS BSP_Init(); // initialize Hardware for OS and the board OS_CPU_SysTickInit(); OSTaskCreateExt(...); // create at least one task before starting the RTOS // create your events, mailboxes and semaphores ... OSStart(); // Start your RTOS
This article explains the concepts of kernel, tasks, messaging (like events and mailboxes), scheduler and the main pieces to put an RTOS application together. To do this in a real application will require more details, for example, how to create mailboxes and events. If you are a novice RTOS user, the best way to get started is to bring up one of the many examples that usually come with different RTOSes. In this article I have used IAR PowerPac and Micriums C/OS-II as examples when showing code snippets. Both IAR Systems and Micrium provide many example applications and have BSPs for many different boards and devices.

I would also recommend obtaining a copy of the

MicroC/OS-II book from Micrium. This is

an excellent way to learn what constitutes an RTOS and how to use it.