embOS-Ultra
Real-Time Operating System User Guide & Reference Manual
Document: UM01076
Software Version: 5.16.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
microcontrollers.
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.
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, software timers and all other synchronization and communication primitives like event objects, semaphores, mutexes, mailboxes and queues.
(limited only by the amount of available memory).
- Size and number of messages can be freely defined when initializing mailboxes.
- Up to 32-bit events for every task.
- 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.
Differences between embOS and embOS-Ultra
The main difference between embOS and embOS-Ultra is that the latter requires no periodic system tick.
Instead, with embOS-Ultra, system tick interrupts occur only when the scheduler needs to perform some time-based action.
embOS with periodic system tick
embOS uses a hardware timer to generate periodic system tick interrupts which are utilized as a time base.
In most applications the system tick occurs each millisecond, but can also be changed to occur with any other period.
Since the period might differ, all timeouts and periods are specified in system tick instead of, for example, milliseconds.
Even if there is only one task that is executed for several consecutive system ticks (which means the scheduler will not be executed during this time),
the system tick interrupt will still occur periodically and thereby “waste” computation time.
Furthermore, time-based functionality like task delays or timeouts are always aligned to the system tick interrupt.
A task delay cannot expire between two system tick interrupts, but with the next system tick interrupt only which then triggers the scheduler.
Therefore tasks that shall delay for a period that is shorter than a system tick, can only accomplish this by actively waiting until the desired period has elapsed.
embOS-Ultra with flexible system tick
embOS-Ultra does not rely on a periodic system tick, but uses a flexible system tick that is specifically configured by the operating system to occur whenever a time-based action is required.
This avoids unnecessary system tick interrupts and also allows delays and timeouts to expire at arbitrary points in time (limited by the frequency of the used hardware timer only).
As there are no periodic tick interrupts, however, the system time can no longer be held in system ticks, but is held in counter cycles instead.
For the same reason, timed embOS-Ultra API functions use milliseconds instead of system ticks unless explicitly stated otherwise (in which case microseconds or counter cycles are used instead).
Hardware timer
While embOS requires the target hardware to provide a hardware timer, embOS-Ultra requires the target hardware to provide a hardware timer and a continuously running counter (although the latter may also be part of the former).
With embOS-Ultra, the hardware timer is used to generate the system tick interrupt while the continuously running counter provides a time base to calculate the current system time in counter cycles.
For example, applications could use a hardware timer that generates interrupts when its continuously running counter matches a specific value.
In that case, the counter would serve for long-term stability while the compare register is used to generate interrupts when required.
Alternatively, it also is possible to use any hardware timer for generating interrupts and an additional continuously running counter for long-term stability.
In both cases the continuously running counter should never be stopped by the application since it is essential to long-term stability.
The frequencies of the used timer and counter may differ, specifically when using different timers/counters. In this case, the embOS system time matches counter cycles.
Unless explicitly stated otherwise, the embOS-Ultra manual always refers to counter cycles when it mentions “cycles”.
The maximal period of the hardware timer is of no relevance to embOS-Ultra:
If the next time-based action lies further in the future than the maximal period of the used hardware timer, the operating system will simply set up the timer multiple times until the desired point in time is reached.
For more information on how to implement the hardware timer routines, please refer to Board Support Packages.
embOS ports
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 evaluation boards.
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.
Additional documentation
Some embOS aspects are core and compiler specific and explained in a separate embOS manual which is shipped in the according embOS port shipment.
Example Cover of embOS Cortex-M ES Manual
Naming convention
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.
Generic embOS
Each release of the generic embOS sources has a unique version number:
V<Major>.<Minor>.<Patch>
For example:
V5.10.1
Major: 5
Minor: 10
Patch: 1
Major and minor values are used for new features.
The patch value is used for bug fixes only.
embOS Ports
An updated embOS port has the same version number as the used generic embOS sources, plus an additional revision for the port.
This is because an embOS port may be updated for changes in the CPU/compiler specific part, while still using the same generic embOS sources.
The complete version number for a specific embOS port is defined as:
V<Major>.<Minor>.<Patch>.<Revision>
For example:
V5.10.1.0
Major: 5
Minor: 10
Patch: 1
Revision: 0
Tasks
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
Advantages
- Simple structure (for small applications)
- Low stack usage (only one stack required)
Disadvantages
- 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.
Multitasking systems
In a multitasking system, there are different ways to distribute CPU time among different tasks.
This process is called scheduling.
Task switches
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
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().
This is illustrated in the diagram below.
If tasks in a pure cooperative multi-tasking system do not cooperate, the system “hangs”.
This means that other tasks have no chance of being executed by the CPU while the first task is being carried out.
Even if an ISR makes a higher-priority task ready to run, the interrupted task will be resumed and completes 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.
Preemptive multitasking
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 utilize a timer interrupt that interrupts tasks 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.
Scheduling
There are different algorithms used by schedulers to determine which task to execute.
But 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.
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:
Note
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.
Round-robin scheduling algorithm
With round-robin scheduling, the scheduler has a list of tasks and, when deactivating
the running task, it 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
follows:
The possession of the CPU changes periodically after a predefined execution time among all tasks with the same priority.
This time is specified in time slices and may be defined individually for each task.
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.