What is RTOS
A Real-Time Operating System (RTOS) is a lightweight OS used to ease multitasking and task integration in resource and time constrained designs, which is normally the case in embedded systems. Besides, the term “real-time” indicates predictability/determinism in execution time rather than raw speed, hence an RTOS can usually be proven to satisfy hard real-time requirements due to its determinism.
Key concepts of RTOS are:
Task
Tasks (could also be called processes/threads) are independent functions running in infinite loops, usually each responsible for one feature. Tasks are running independently in their own time (temporal isolation) and memory stack (spatial isolation). Spatial isolation between tasks can be guaranteed with the use of a hardware memory protection unit (MPU), which restricts accessible memory region and triggers fault exceptions on access violation. Normally, internal peripherals are memory-mapped, so an MPU can be used to restrict access to peripherals as well.
Tasks can be in different states:
- Blocked – task is waiting for an event (e.g. delay timeout, availability of data/resources)
- Ready – task is ready to run on CPU but not running because CPU is in use by another task
- Running – task is assigned to be running on CPU
Scheduler
Schedulers in RTOS control which task to run on the CPU, and different scheduling algorithms are available. Normally they are:
- Pre-emptive – task execution can be interrupted if another task with higher priority is ready
- Co-operative – task switch will only happen if the current running task yields itself
Pre-emptive scheduling allows higher priority tasks to interrupt a lower task in order to fulfill real-time constraints, but it comes in the cost of more overhead in context switching.
Inter-task communication (ITC)
Multiple tasks will normally need to share information or events with each other. The simplest way to share is to directly read/write shared global variables in RAM, but this is undesirable due to risk of data corruption caused by a race condition. A better way is to read/write file-scoped static variables accessible by setter and getter functions, and race conditions can be prevented by disabling interrupts or using a mutual exclusion object (mutex) inside the setter/getter function. The cleaner way is using thread-safe RTOS objects like message queue to pass information between tasks.
Besides sharing of information, RTOS objects are also able to synchronize task execution because tasks can be blocked to wait for availability of RTOS objects. Most RTOS have objects such as:
- Message queue
- First-in-first-out (FIFO) queue to pass data
- Data can be sent by copy or by reference (pointer)
- Used to send data between tasks or between interrupt and task
- Semaphore
- Can be treated as a reference counter to record availability of a particular resource
- Can be a binary or counting semaphore
- Used to guard usage of resources or synchronize task execution
- Mutex
- Similar to binary semaphore, generally used to guard usage of a single resource (MUTual EXclusion)
- FreeRTOS mutex comes with a priority inheritance mechanism to avoid priority inversion (condition when high priority task ends up waiting for lower priority task) problem.
- Mailbox
- Simple storage location to share a single variable
- Can be considered as a single element queue
- Event Group
- Group of conditions (availability of semaphore, queue, event flag, etc.)
- Task can be blocked and can wait for a specific combination condition to be fulfilled
- Available in Zephyr as a Polling API, in FreeRTOS as QueueSets
System Tick
RTOS need a time base to measure time, normally in the form of a system tick counter variable incremented in a periodic hardware timer interrupt. With system tick, an application can maintain more than time-based services (task executing interval, wait timeout, time slicing) using just a single hardware timer. However, a higher tick rate will only increase the RTOS time base resolution, it will not make the software run faster.
Why use RTOS
Organization
Applications can always be written in a bare metal way, but as the code complexity increases, having some kind of structure will help in managing different parts of the application, keeping them separated. Moreover, with a structured way of development and familiar design language, a new team member can understand the code and start contributing faster. RFCOM Technologies has developed applications using different microcontrollers like Texas Instruments’ Hercules, Renesas’ RL78 and RX, and STMicroelectronics’ STM32 on a different RTOS. Similar design patterns allow us to develop applications on different microcontrollers and even a different RTOS.
Modularity
Divide and conquer. By separating features in different tasks, new features can be added easily without breaking other features; provided that the new feature does not overload shared resources like the CPU and peripherals. Development without RTOS will normally be in a big infinite loop where all features are part of the loop. A change to any feature within the loop will have an impact on other features, making the software hard to modify and maintain.
Communication stacks and drivers
Many extra drivers or stacks like TCP/IP, USB, BLE stacks, and graphics libraries are developed/ported for/to existing RTOSs. An application developer can focus on an application layer of the software and reduce time to market significantly.
Tips
Static allocation
Use of static allocation of memory for Real-Time Operating System objects means reserving memory stack in RAM for each RTOS object during compile time. An example of a static allocation function in freeRTOS is xTaskCreateStatic(). This ensures that a RTOS object can be created successfully, saving the hassle of handling a possible failed allocation and making the application more deterministic.
In terms of deciding stack size needed for a task, the task can be run with a bigger (more than enough) stack size and then the stack usage can be checked in runtime to determine the high water mark. There is static stack analysis tool available as well.
Operating system abstraction layer (OSAL) and meaningful abstraction
Just like Hardware Abstraction Layer (HAL), use of the Real-Time Operating System abstraction layer allows application software to migrate easily to other RTOSs. Features of RTOSs are quite similar, so creating OSAL should not be too complicated. For example:
Using the freeRTOS API directly:
if( xSemaphoreTake( spiMutex, ( TickType_t ) 10 ) == pdTRUE ) { //dosomething }
Wrapping the RTOS API into OSAL:
if( osalSemTake( spiMutex, 10 ) == true) { //dosomething }
using the abstraction layer on inter-task communication to make code more readable and minimize the scope of an RTOS object:
if( isSpiReadyWithinMs( 10 ) ) { //doSomething }
Additionally, the abstraction also allows a programmer to change the RTOS object used underneath (e.g., from mutex to counting semaphore) if there is more than one SPI module available. OSAL and other abstraction layers help in software testing as well by simplifying mock function insertion during unit testing.
Tick Interval selection
Ideally, a lower tick rate is better because of less overhead. To select a suitable tick rate, the developer can list down timing constraints of modules in an application (repeating interval, timeout duration, etc.). If there are some outlier modules needing a small interval, having a dedicated timer interrupt can be considered for the outlier modules rather than increasing Real-Time Operating System tick rate. If the high frequency function is very short (e.g., write to register to turn an LED on/off), it can be done inside an Interrupt Service Routine (ISR), otherwise deferred interrupt handling can be used. Deferred interrupt handling is a technique of deferring interrupt computation into an RTOS task, the ISR will only generate an event through the Real-Time Operating System object, then the RTOS task will be unblocked by the event and do the computation.
Tick suppression for low power application
Tickless idle disables tick interrupt when the system is going idle for a longer time. One significant way for embedded firmware to reduce power consumption is to put the system in low power mode for as long as possible. Tickless idle is implemented by disabling the periodic tick interrupt and then setting up a countdown timer to interrupt when a blocked task is due to execute. If there is no task waiting for a timeout, the tick interrupt can be disabled indefinitely until another interrupt occurs (e.g., button pressed). For example, in the case of a Bluetooth Low Energy (BLE) beacon, the MCU can be put into deep sleep between the advertising interval. As shown in Figure 1, the beacon is put into deep sleep mode for most of the time, consuming power in tens of µA.
Figure 1: Current consumption of a BLE beacon (Image source: RFCOM)
Conclusion
An Real-Time Operating System provides features like scheduler, tasks, and inter-task communication RTOS objects, as well as communication stacks and drivers. It allows developers to focus on the application layer of the embedded software, and design multitasking software with ease and speed. However, just like any other tools, it has to be used properly in order to bring out more value. To create safe, secure and efficient embedded software, developers should know when to use RTOS features and also how to configure RTOS.