embOS & embOS-MPU
Real-Time Operating System User Guide & Reference Manual
Software Version: 5.12.0
Document revision: 0
Introduction and basic concepts
What is embOS?
embOS is a priority-controlled multitasking system, designed to be used as an
embedded operating system for the development of real-time applications for a variety of
embOS is a high-performance tool that has been optimized for minimal memory consumption
in both RAM and ROM, as well as high speed and versatility.
Throughout the development process of embOS, the limited resources of microcontrollers
have always been kept in mind. The internal structure of the real-time operating
system (RTOS) has been optimized in a variety of applications with different
customers, to fit the needs of industry. Fully source-compatible implementations of
embOS are available for a variety of microcontrollers, making it well worth the time
and effort to learn how to structure real-time programs with real-time operating systems.
embOS is highly modular. This means that only those functions that are required are
linked into an application, keeping the ROM size very small. The minimum memory
consumption is little more than 1.7 Kbyte of ROM and about 70 bytes of RAM (plus
memory for stacks). A couple of files are supplied in source code to make sure that
you do not loose any flexibility by using embOS libraries and that you can customize
the system to fully fit your needs.
The tasks you create can easily and safely communicate with each other using a
number of communication mechanisms such as semaphores, mailboxes, and events.
Some features of embOS include:
- Preemptive scheduling:
Guarantees that of all tasks in READY state the one with the highest priority executes,
except for situations in which priority inheritance applies.
- Round-robin scheduling for tasks with identical priorities.
- Preemptions can be disabled for entire tasks or for sections of a program.
- Up to 4,294,967,296 priorities.
- Every task can have an individual priority, which means that the response of
tasks can be precisely defined according to the requirements of the application.
- Unlimited number of tasks
(limited only by the amount of available memory).
- Unlimited number of semaphores
(limited only by the amount of available memory).
- Two types of semaphores: Mutex and counting semaphores.
- Unlimited number of mailboxes
(limited only by the amount of available memory).
- Size and number of messages can be freely defined when initializing mailboxes.
- Unlimited number of software timers
(limited only by the amount of available memory).
- Up to 32-bit events for every task.
- Time resolution can be freely selected (default is 1 millisecond).
- Easily accessible time variable.
- Power management.
- Calculation time in which embOS is idle can automatically be spent in power save mode.
Power-consumption is minimized.
- Full interrupt support:
Interrupts may call any function except those that require waiting for data,
as well as create, delete or change the priority of a task.
Interrupts can wake up or suspend tasks and directly communicate with tasks
using all available communication methods (mailboxes, semaphores, events).
- Disabling interrupts for very short periods allows minimal interrupt latency.
- Nested interrupts are permitted.
- embOS has its own, optional interrupt stack.
- Application samples for an easy start.
- Debug build performs runtime checks that catch common programming errors early on.
- Profiling and stack-check may be implemented by choosing specified libraries.
- Monitoring during runtime is available using embOSView via UART, Debug Communications
Channel (DCC) and memory read/write, or else via Ethernet.
- Very fast and efficient, yet small code.
- Minimal RAM usage.
- API can be called from assembly, C or C++ code.
- Board support packages (BSP) as source code available.
embOS is available for many core and compiler combinations.
The embOS sources are written in C but a small part is written in assembler and therefore core and compiler specific.
Hence, an embOS port is always technically limited to one core or core family and one compiler.
An embOS port includes several board support packages for different devices and evalboards.
Each board support package includes a project for a specific IDE.
In most embOS ports the same IDE is used for all board support packages.
Some embOS aspects are core and compiler specific and explained in a separate embOS manual which is shipped in the according embOS port shipment.
All embOS ports use the same naming convention: embOS_<core>_<compiler>.
For example: embOS_CortexM_ES, embOS for Cortex-M and Embedded Studio
Version number convention
SEGGER releases new embOS versions with new features and bug fixes.
As soon as a new embOS version is released embOS ports are updated to this version.
Each new embOS version gets a unique version number: V Major.Minor.Patch
Major and minor values are used for new features.
The patch value is used for bug fixes only.
An updated embOS port gets the same version number as the used embOS sources.
Because an embOS port can be updated with the same embOS sources but with e.g. changes in the CPU/compiler specific part a revision number is additionally used.
The complete version number for a specific embOS port is defined as: V Major.Minor.Patch.Revision
In this context, a task is a program running on the CPU core of a microcontroller.
Without a multitasking kernel (an RTOS), only one task can be executed by the CPU.
This is called a single-task system. A real-time operating system, on the other hand,
allows the execution of multiple tasks on a single CPU. All tasks execute as if they completely
“owned” the entire CPU. The tasks are scheduled for execution, meaning that the RTOS
can activate and deactivate each task according to its priority, with the highest priority
task being executed in general.
Threads vs. Processes
Threads are tasks that share the same memory layout, hence any two threads can access
the same memory locations. If virtual memory is used, the same virtual to physical
translation and access rights are used.
With embOS, all tasks are threads: they all have the same memory access rights and
translation (in systems with virtual memory).
Processes are tasks with their own memory layout. Two processes cannot normally
access the same memory locations. Different processes typically have different
access rights and (in case of MMUs) different translation tables.
Processes are not supported with the current version of embOS.
Single-task systems (superloop)
The classic way of designing embedded systems does not use the services of an
RTOS, which is also called “superloop design”. Typically, no real time kernel is used,
so interrupt service routines (ISRs) are used for the real-time parts of the application
and for critical operations (at interrupt level). This type of system is typically used in
small, simple systems or if real-time behavior is not critical.
Typically, since no real-time kernel and only one stack is used, both program
(ROM) size and RAM size are smaller for simple applications when compared to using an
RTOS. Obviously, there are no inter-task synchronization problems with a superloop
application. However, superloops can become difficult to maintain if the program
becomes too large or uses complex interactions. As sequential processes cannot
interrupt themselves, reaction times depend on the execution time of the entire
sequence, resulting in a poor real-time behavior.
Advantages & disadvantages
- Simple structure (for small applications)
- Low stack usage (only one stack required)
- No “delay” capability
- Higher power consumption due to the lack of a power save mode in most architectures
- Difficult to maintain as program grows
- Timing of all software components depends on all other software components:
Small change in one place can have major side effects in other places
- Defeats modular programming
- Real time behavior only with interrupts
Using embOS in superloop applications
In a true superloop application, no tasks are used, hence the biggest advantage of using
an RTOS cannot be utilized unless the application is re-written for multitasking.
However, even with just one single task, using embOS offers the following advantages:
- Software timers are available
- Power saving: Idle mode can be used
- Future extensions can be put in a separate task
Migrating from superloop to multi-tasking
A common situation is that an application exists for some time and has been
designed as a single-task super-loop-application. At some point, the disadvantages
of this approach result in a decision to use an RTOS. The typical question now usually
is: How do I do this?
The easiest way is to start with one of the sample applications that come with embOS and to add
the existing “super-loop code” into one task. At this point, you should also ensure
that the stack size of this task is sufficient. Later, additional functionality is
added to the software and can be put in one or more additional tasks; the functionality
of the super-loop can also be distributed over multiple tasks.
In a multitasking system, there are different ways to distribute CPU time amongst
different tasks. This process is called scheduling.
There are two types of task switches, also called context switches: Cooperative and
preemptive task switches.
A cooperative task switch is performed by the task itself. As its name indicates, it
requires the cooperation of the task: it suspends itself by calling a blocking
RTOS function, e.g. OS_TASK_Delay() or OS_TASKEVENT_GetBlocked().
A preemptive task switch, on the other hand, is a task switch that is caused externally. For example,
a task of higher priority becomes ready for execution and, as a result, the scheduler suspends the
current task in favor of that task.
Cooperative multitasking requires all tasks to cooperate by using blocking functions.
A task switch can only take place if the running task blocks itself by calling a blocking
function such as OS_TASK_Delay() or OS_MAILBOX_GetBlocked(). If tasks do not cooperate, the system
“hangs”, which means that other tasks have no chance of being executed by the CPU
while the first task is being carried out. This is illustrated in the diagram below. Even
if an ISR makes a higher-priority task ready to run, the interrupted task will be
resumed and complete before the task switch is made.
A pure cooperative multi-tasking system has the disadvantage of longer reaction
times when high priority tasks become ready for execution. This makes their usage in
embedded real-time systems uncommon.
Real-time operating systems like embOS operate with preemptive multitasking. The
highest-priority task in the READY state always executes as long as the task is not
suspended by a call of any blocking operating system function. A high-priority task waiting for
an event is signaled READY as soon as the event occurs. The event can be set by an
interrupt handler, which then activates the task immediately. Other tasks with lower
priority are suspended (preempted) for as long as the high-priority task is executing.
Usually, real-time operating systems such as embOS utilize a timer interrupt that interrupts
tasks at periodic intervals and thereby allows to perform task switches whenever timed task
switches are necessary.
Preemptive multitasking may be switched off in sections of a program where task switches are
prohibited, known as critical regions. embOS itself will also temporarily disable preemptive
task switches during critical operations, which might be performed during the execution of
some embOS API functions.
There are different algorithms that determine which task to execute, called
schedulers. All schedulers have one thing in common: they distinguish between tasks
that are ready to be executed (in the READY state) and other tasks that are suspended
for some reason (delay, waiting for mailbox, waiting for semaphore, waiting
for event, etc). The scheduler selects one of the tasks in the READY state and
activates it (executes the body of this task). The task which is currently executing is
referred to as the running task. The main difference between schedulers is the way
they distribute computation time between tasks in the READY state.
Round-robin scheduling algorithm
With round-robin scheduling, the scheduler has a list of tasks and, when deactivating
the running task, activates the next task that is in the READY state. Round-robin can
be used with either preemptive or cooperative multitasking. It works well if you do
not need to guarantee response time. Round-robin scheduling can be illustrated as
All tasks share the same priority; the possession of the CPU changes periodically
after a predefined execution time. This time is called a time slice and may be
defined individually for each task.
Priority-controlled scheduling algorithm
In real-world applications, different tasks require different response times. 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 the display. E.g., even while the display
is being updated, the motor needs to be controlled. This renders preemptive multitasking
essential. Round-robin might work, but as it cannot guarantee any specific reaction time, a
more suitable algorithm should be used.
In priority-controlled scheduling, every task is assigned a priority. Depending on
these priorities, a task is chosen for execution according to one simple rule:
The scheduler activates the task that has the highest priority of all tasks and is ready for execution.
This means that every time a task with a priority higher than the running task
becomes ready, it becomes the running task, and the previous task gets
preempted. However, the scheduler can be switched off in sections of a program
where task switches are prohibited, known as critical regions.
embOS uses a priority-controlled scheduling algorithm with round-robin between
tasks of identical priority. One hint at this point: round-robin scheduling is a nice feature
because you do not need to decide whether one task is more important than
another. Tasks with identical priority cannot block each other for longer periods than their
time slices. But round-robin scheduling also costs time if two or more tasks of identical
priority are ready and no task of higher priority is, because execution constantly
switches between the identical-priority tasks. It usually is more efficient to assign
distinct priority to each task, thereby avoiding unnecessary task switches.
Priority inversion / priority inheritance
The rule the scheduler obeys is:
Activate the task that has the highest priority of all tasks in the READY state.
But what happens if the highest-priority task is blocked because it is waiting for a
resource owned by a lower-priority task? According to the above rule, it would wait
until the low-priority task is resumed and releases the resource. Up to this point,
everything works as expected. Problems arise when a task with medium priority becomes ready
during the execution of the higher prioritized task.
When the higher priority task is suspended waiting for the resource, the task with the
medium priority will run until it finishes its work, because it has a higher priority than
the low-priority task. In this scenario, a task with medium priority runs in place of the
task with high priority. This is known as priority inversion.