An RTOS is an operating system designed to respond to events within known time frames, which is fundamental when programming embedded systems that cannot be left “thinking” indefinitely.
We are starting a new course on the website, and this is one of the ones I was most eager to bring. We are going to talk about FreeRTOS and real-time operating systems.
If you come from the Arduino world, you are most likely used to programming within setup() and loop(). It is the standard, simple way and works perfectly for blinking an LED or reading a sensor.
However, as our projects grow, we start encountering problems. We want to read a sensor every 100ms, control a stepper motor, handle WiFi requests, and update a display, all “at the same time”.
At that point, classic sequential programming starts to fall short, and an RTOS becomes a very useful tool.
In this course we will primarily use the ESP32, as it uses FreeRTOS natively in its SDK, although the concepts are applicable to any compatible microcontroller.
The “Super Loop” Paradigm
Before understanding the solution, let’s analyze the problem. The classic architecture of simple firmware is the Infinite Loop or “Super Loop”.
void setup() {
// Initialization
}
void loop() {
readSensors();
processData();
updateDisplay();
moveMotors();
}This is sequential programming. The processor executes the first instruction, then the second, and so on.
The Blocking Problem
The problem arises when one of these functions takes a long time. For example, imagine we use the infamous delay() function:
void loop() {
digitalWrite(LED, HIGH);
delay(1000); // <--- The processor stays HERE blocked
digitalWrite(LED, LOW);
delay(1000);
readButton(); // <--- If you press the button during the delay, the system does NOT notice
}While the processor is counting milliseconds in the delay(), it cannot do anything else. If data arrives via the serial port, if the user presses a button, or if there is an emergency, the microcontroller is “deaf.”
We can improve this by using millis() and state machines (as we have seen many times on the blog), but the code becomes complex, difficult to read, and hard to maintain as we add features.
What is an RTOS?
An RTOS (Real-Time Operating System) is an operating system designed to manage hardware resources (CPU and memory) with predictable and bounded response times.
Unlike general-purpose operating systems (such as Windows or Linux), where the goal is global performance (throughput), in an RTOS the primary objective is determinism and latency.
This allows us to design the system so that, if a critical event occurs, it reacts within a known timeframe. But beware: using an RTOS does not guarantee by itself that we will meet that deadline. We also need to assign priorities properly, avoid long blockages, and analyze the worst-case scenario.
Multitasking and the Scheduler
The beauty of an RTOS lies in its ability to simulate multitasking.
Even if we have a single processor core, the RTOS allows us to divide our code into small, independent units called Tasks.
The component in charge of managing this is the Scheduler. The Scheduler stops one task, saves its state, loads the state of the next task, and executes it.
This happens so fast (hundreds or thousands of times per second) that it gives us the illusion that everything is running simultaneously.
Preemptive Multitasking
FreeRTOS typically uses a preemptive multitasking model. This means the Scheduler can interrupt (preempt) the currently running task at any time to give way to another higher priority task.
Imagine this scenario:
- We have a task
DisplayTaskdrawing graphics (low priority). - An interrupt from a critical sensor arrives and unblocks
SensorTaskvia an ISR-safe API. - The Scheduler immediately pauses the
DisplayTask. - It executes the
SensorTask(high priority). - When finished, it resumes
DisplayTaskexactly where it left off.
This is something that is extremely difficult to achieve cleanly with a “Super Loop.”
Differences: Super Loop vs RTOS
To visualize it better, let’s look at a direct comparison:
| Feature | Super Loop (Bare Metal) | RTOS (FreeRTOS) |
|---|---|---|
| Structure | One giant while(1) loop | Multiple independent Tasks |
| Time Management | Waits that stop the flow or manual logic with millis() | Waits that block only the current task |
| Complexity | Coordination gets tangled as it grows | Responsibilities are separated into tasks |
| Response | Depends on the loop duration | Depends on priorities, blockages, and interrupts |
| CPU Usage | Can waste CPU with active waiting | CPU can enter Idle if there is no work |
Why Use FreeRTOS?
At this point, you might think: “Well, I get by fine with millis()”. And that is true for small projects. But using an RTOS offers significant advantages:
- Modularity: Each part of the program (WiFi, sensors, motor) can have its own task and a clear responsibility.
- Maintainability: It is much easier to add a new feature without breaking previous ones. You simply create a new Task.
- Time Management: We can say “execute this every exact 100ms,” and the RTOS handles it.
- Efficient Driver Usage: In the case of the ESP32, WiFi and Bluetooth run on top of FreeRTOS. If you use an ESP32, you are already using FreeRTOS even if you don’t know it!
Disadvantages (nothing is free)
As in all engineering, there is a trade-off.
- Resource Consumption: The RTOS needs RAM memory to manage tasks (each task needs its own stack) and consumes CPU cycles for context switching.
- Debugging Complexity: New types of errors arise, such as Race Conditions, Deadlocks, or Stack Overflows.
But don’t worry, because precisely to learn how to avoid these problems we have created this course.