RTOS on an MCU
The range of situations where an RTOS offers benefits keeps expanding—even into the MCU space. Stuart reviews the basics of the technology and shares the details of his project to run a simple RTOS application on an MCU.
First, let’s examine the question: What is an RTOS (real-time operating system)? A computer operating system (OS) such as Windows or Linux or Android provides a basic infrastructure on a PC, laptop or smartphone. It has a standardized API (application programming interface) for applications and hardware drivers. It will have a user interface, usually a GUI on modern operating systems. It provides a standard set of functions that allow applications to read and write storage (hard drive or flash memory), to capture mouse or keyboard actions and to display text or graphics on the display. The OS manages memory, storage access, focusing on different windows in the display and all the other routine things that you use every day on your PC.
A real-time operating system is optimized for real-time systems, typically a microcontroller (MCU). In most cases, an RTOS is used for a single purpose, such as a medical instrument or an avionics product, or the electronic ignition module in your car. An RTOS used in such an application does not need all the capabilities of a full OS. The user doesn’t install programs; all the programs it will use are embedded in the device when it is manufactured. The user interface, if there is one, is usually customized for the product. The electronic ignition module in your car might not communicate directly to the driver at all. Another device might just have a simple LCD display. A dishwasher might just have some LEDs and buttons on the front panel.
An RTOS does manage memory, control execution of applications and provide a standard API for applications. But the applications are written by the product developers and integrated into the MCU, along with the RTOS itself, usually in nonvolatile memory such as flash. The RTOS will often provide an API for the peripherals, but the RTOS must be configured for the specific hardware in use, since not all MCUs have the same internal peripherals. For example, some have a USB interface, some do not.
An RTOS usually has to operate with much less memory—both RAM and nonvolatile storage—than an OS on a PC. HDD or flash storage on a PC is typically measured in gigabytes or terabytes. Flash and RAM in an embedded MCU application are typically measured in kilobytes or megabytes.
A computer OS is intended to be a user-friendly general-purpose mechanism to install, control, and use programs on your PC. An RTOS is optimized to provide the functionality needed for a dedicated appliance that does one and only one thing. An RTOS is optimized for a small memory footprint, predictable timing and features applicable to an embedded application.
WHAT IS REAL TIME?
Generally, real time means that the system needs to meet hard timing deadlines. For example, if your device has a UART that is receiving continuous serial data from some external source at 115 kbaud, then a new byte is received every 86μs (10/115,200; asynchronous serial data takes a minimum of 10 bits to transmit a byte). So, your application has to receive and process a byte every 86μs. If it doesn’t, data bytes will be lost. An RTOS is intended to ensure that such timing requirements are met.
In some cases, MCU hardware can ease the processing burden of real-time requirements. For example, many MCUs have FIFO buffers for incoming UART data. The Texas Instruments (TI) TM4C1233D5 MCU  in the example here (more in the next section) has 16-byte FIFO buffers. So as long as the UART servicing function empties the incoming buffer at least once every 1.4ms (16 × 86μs), no data will be lost. This is an example of a soft, real-time requirement. The UART has to be serviced an average of every 86μs, but individual reads can occur less frequently, as long as the buffers aren’t allowed to overflow.
The circuit shown in Figure 1, used here as an example, is a TI TM4C1233D5 MCU with two LEDs, a JTAG connector and a serial programming connector. The actual board used for this has more hardware, but I deleted from the schematic everything that isn’t relevant to this example.
UART0 of the MCU is connected to a PC for downloading and debugging the firmware, using the technique described in my article “Debugging Embedded Systems with Minimal Resources,” (Circuit Cellar 312, July 2016) . Figure 2 shows the schematic of the serial port adapter used for FW download from a PC.
Let’s say that we want to blink the green heartbeat LED once per second. A simple way to do that would be with the pseudocode shown in Listing 1. That would work, but it would be very inefficient, because the MCU would spend most of the time in the 500ms delay and wouldn’t be doing anything else.
Listing 1 Pseudocode to blink the green heartbeat LED once per second Turn on heartbeat LED by driving Port F bit 4 low. Delay for 500 ms Turn off the heartbeat LED by driving port F bit 4 high. Delay for 500 ms Repeat forever
Another way to do it is to use the systick timer in the TM4C1233D5 MCU. The source code for this is included in the firmware files on the Circuit Cellar article code and files download webpage. The TM4C devices have a systick counter that is specifically for RTOS applications. It generates a periodic interrupt based on the divisor value programmed into it, as shown in Listing 2.
Listing 2 A systick counter that is specifically for RTOS applications; it generates a periodic interrupt based on the divisor value programmed into it, as shown here. Create a tick counter and set it to zero Enable systick timer and configure for 1 ms tick Each time that 1 ms tick occurs: Increment tick counter If tick counter = 500, turn on LED (drive Port F bit 4 low). If tick counter = 1000, turn off LED (drive Port F bit 4 high), reset tick counter.
This is considerably more efficient, because the 1ms timing is handled in hardware, specifically the systick counter. However, the code that processes the 1ms tick event must still check the tick count and reset it after 1 second. In the case of blinking an LED, this is a trivial amount of software overhead, but in a more complex function, it could be something more time-consuming. This approach allows the MCU to do other things and update the LED tick counter and the LED drive pin only when the systick event occurs. With an RTOS, you could do this as shown in Listing 3. (The source code is in the firmware files on Circuit Cellar’s article code and files download webpage.)
Listing 3 This code performs the same function as in Listing 2, but this time using an RTOS. Create a variable (call it HBLEDstate) that is 0 with the LED is off and 1 when the LED is on. Configure systick for 1 ms tick Create a task, call it HBLEDtask, that does the following: If HBLEDstate = 0, set HBLEDstate = 1 and turn on LED else set HBLEDstate = 0 and turn off LED sleep for 500ms Then in the system startup code, start the RTOS scheduler.
Now, every time the systick occurs, the RTOS will process the interrupt. Every 500 ticks (500ms), it will execute the LEDtask to toggle the LED on or off. It is worth noting that there still has to be a counter that counts off 500 ticks. In the RTOS version of the code, that is handled in the RTOS. When HBLEDtask goes to sleep, it exits via the RTOS with an instruction to “wake me up in 500ms.” The RTOS schedules HBLEDtask to run again in 500ms. The RTOS doesn’t know what HBLEDtask is doing—it just knows that it’s been told to wake up that task in half a second. The RTOS has a generic scheduler that can be told to execute any task after a specific number of milliseconds.
For blinking an LED, the RTOS is actually going to be less efficient than the version of code that counts systick interrupts. In the RTOS version, there is still a need to process the systick event, code that schedules tasks, switches control to the LED task, and then switches control back to the RTOS idle loop. This involves quite a bit of overhead, but a practical RTOS application would be doing many things in addition to blinking an LED.
Although I’ve worked on RTOS-based systems, most of the MCU applications that I’ve developed have not needed an RTOS. So where would you use an RTOS? The most common reason is probably when you need preemptive scheduling.
Preemptive scheduling is one of the things that makes an RTOS really shine. In preemptive scheduling, each task is given a priority. An RTOS task can be in one of three states: executing, blocked and ready to execute. A task that is executing has control of the CPU and is running. A task that is blocked is not executing and is waiting for some event to activate it.
If you look at the C code for the RTOS LED blinker, you will see a place in the LED task that sleeps for 500ms. During that time, the LED task is not executing; it is waiting to be called by the RTOS scheduler, and it is blocked. The scheduler won’t even call the LED task until the 500ms delay elapses, after which time, the LED task will go from blocked to ready.
A task that is ready to execute has everything it needs to run. If it is a UART task that reads bytes from a UART, it may be ready to execute as soon as a byte is received, or perhaps as soon as the receive FIFO is 50% full. Once the conditions are met for the task to be ready to execute, then the RTOS scheduler can execute the task. The task can then run until it is finished. However, if the next system tick or another interrupt occurs before the task completes, then control will return to the scheduler.
The scheduler will determine if the UART task is still the highest-priority task, and if so, the UART task will continue to execute. If, however, a higher-priority task is now ready to execute, it will be activated and the UART task will have to wait until it is again the highest priority. In other words, when the scheduler processes an interrupt, it may not hand control back to the task that was interrupted, but rather to some other higher-priority task.
Referring to Figure 3, let’s say we are using the UART on the example board to communicate with a PC. There is also a USB interface that communicates with another PC, since the TM4C1233D5 has a USB interface on two of the Port D pins. The USB interface task, which has the most stringent response requirements, has the highest priority. The UART task is next, then the LED blinker task is lowest priority.
Typically, in a case like this, the UART or USB interrupt will cause the respective interrupt handler to activate. Normally you would want the interrupt handler to be very fast, because it blocks other tasks while it is executing, effectively becoming the highest priority until it exits.
To allow the RTOS to manage priorities, unless the interrupt has to be fully serviced immediately or the handler code is very short, the handler will just set a semaphore or some other signaling notification and return through the RTOS, which will then execute the interrupt processing code once it is the highest-priority task. Say that the UART task is executing and then an interrupt occurs for the USB interface. The scheduler will execute the USB task, taking control away from the UART task, because the USB task is higher priority than the UART task.
As shown in Figure 3, while the USB task is executing, a tick interrupt occurs and the 500ms LED sleep time expires. The LED task is now ready. However, the LED task is the lowest priority, and the USB task is still ready to execute. The tick interrupt will be processed by the scheduler, and then the scheduler will return to the USB task to keep it executing to completion. Then, since the UART task is still ready to execute (it never finished), control returns to the UART task. Only after the UART task is completed will the LED task get control and be able to change the LED state. So, the LED task has to wait for both the USB and the UART tasks to complete before it can execute. This could be some time after the actual 500ms sleep time expires.
This ability to prioritize tasks is key to making an RTOS efficient and to making sure that hard real-time deadlines are met. Although it might appear that the LED task gets starved for CPU time, that’s actually a good thing. If the LED task switches the LED state one or two ticks late, nobody looking at that LED will even notice. And unless the system design just isn’t fast enough to keep up, the UART task should be able to complete before the input buffer overflows. Thus, giving priority to the task that requires the fastest response (USB in this case) ensures that the hard deadlines are met.
Note that the UART task is stopped in mid-execution by the scheduler when the USB interrupt occurs, going from execute state to ready state. Once the USB task is completed, the UART task again becomes the highest priority and is reactivated by the scheduler, going back to execute state. From the viewpoint of the UART task, execution continues exactly as it would have without the interrupt, picking up where it left off, and the only noticeable change is that it took a little longer to run.
In Figure 3, you can see that the LED task goes ready, but can’t run until it is the highest priority ready task. That’s the general rule for preemption: the highest-priority ready task will execute. This also means that if the system is overloaded so that high-priority tasks are always ready, a low-priority task such as the LED task may never execute. That is one problem with preemptive scheduling, though some RTOS flavors have workarounds such as making a low-priority task a higher priority, if it hasn’t run for a specified length of time. But generally, if there isn’t time to execute all the tasks, the system is overloaded—the MCU isn’t fast enough or the software is inefficient.
Note that Figure 3 implies that the task switching time in the scheduler is zero. In reality, the task priority check and task switch do take time and nothing else is happening during that time. But since that scheduling is a key component of the RTOS, it is optimized to be fast.
Preemption is difficult to implement without some kind of RTOS-like mechanism. You can sort of do it by manipulating interrupts, allowing higher-priority tasks to disable interrupts that would trigger lower-priority tasks, or by using other workarounds. But if you have enough tasks to make that necessary, an RTOS is likely an easier solution. The RTOS also handles the job of saving the context when switching tasks, so the interrupted task can be resumed where it left off.
OTHER RTOS FEATURES
Drivers: An RTOS can provide drivers, such as a TCP/IP stack. This can simplify development of a product. It avoids the development and debug time required to create your own peripheral functionality. In addition, using an RTOS driver usually means that a lot of other people have used it first, which means it’s been thoroughly tested. Sometimes an off-the-shelf driver is enough reason to use an RTOS.
Messaging: An RTOS also provides messaging or signaling methods between tasks. The message buffers and other signaling mechanisms (such as semaphores) are managed by the RTOS internally, simplifying and providing a standard protocol for inter-task communication. Like the driver example, the availability of built-in and tested messaging mechanisms may be adequate reason to use an RTOS in your design.
Other Types of Scheduling: There are other RTOS scheduling methods, the main one being round robin, in which each task gets an equal-sized slice of time to run. Round-robin scheduling prevents the problem where some low-priority task never runs, but it is harder to ensure that hard, real-time deadlines are met.
RTOS IN THIS PROJECT
For this project, I used FreeRTOS , which was developed by Richard Barry and is currently provided by Amazon Web Services. FreeRTOS is royalty-free and is distributed under the MIT open-source license. Another RTOS option would be TI-RTOS, distributed by TI for their MCUs. Wikipedia has an article, “Comparison of real-time operating systems,”  that lists the major RTOS options. I used TI’s Code Composer Studio version 10.2 for development. If choosing an RTOS, you have to be sure that it was ported to the MCU family you are using, because not every RTOS works with every MCU.
For both FreeRTOS and TI-RTOS, TI recommends starting a project by using an existing demo project and modifying it. I wanted to understand how to start from scratch, so with a lot of Internet searching and some trial and error, I identified the steps necessary to make that happen for this project. I’ve documented those in the comments section of the C source file so they can be duplicated. Essentially the steps are those shown in Listing 4.
Listing 4 For both FreeRTOS and TI-RTOS, it makes sense to use an existing demo project and modify it. Shown here are the steps needed to do this, starting from scratch. Configure a new project using the GNU compiler, select type of device. Create variables to point to the TI code paths Link the FreeRTOS resources (there are several) Select memory management model Add exception handlers to the startup C file Create or modify FreeRTOSConfig.h
In comparing the RTOS to the non-RTOS code, I found that both use about 5% of flash memory. (Blinking an LED is a small application in either method.) However, the non-RTOS code only uses 4% of the SRAM, whereas the RTOS code uses 41% of SRAM. The TM4C1233D5 doesn’t have a huge amount of memory and might not be suitable for a complex RTOS application. But you can see that, for this simple example, the RTOS version needed 10 times as much RAM as the non-RTOS version. There is a price to pay for the scheduling and other capabilities of an RTOS. And to be fair, this is a very simple application, so the RTOS overhead is going to be a big percentage of the amount of memory used.
The overhead of an RTOS also means that it won’t necessarily be faster than a non-RTOS implementation. I didn’t try to measure it, but it is all but certain that the time from systick to actual LED state change is shorter in the non-RTOS code than in the RTOS code. What you are getting with the RTOS is preemption and its assurance that the most critical real-time requirements are met.
An RTOS isn’t the best solution for every MCU-based project, and probably not even for the majority of them. But the features provided by an RTOS solve some problems that are a lot of work to solve any other way. This article can only scratch the surface of RTOS usage, but RTOS is a tool to keep in your toolbox for those cases in which you need preemption or its other capabilities.
For detailed article references and additional resources go to:
References  through  as marked in the article can be found there.
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • APRIL 2022 #381 – Get a PDF of the issueSponsor this Article