In embedded systems, there are several ways to implement task scheduling. For small systems with limited functionality, a simple infinite loop is often sufficient. As software design becomes larger and more complex, developers should consider using a real-time operating system (RTOS).

Key advantages of RTOS over bare-metal
Below are several advantages of using an RTOS compared with bare-metal programming:
1. Hard real-time responsiveness
Priority-based preemptive RTOS schedules tasks according to their real-time requirements. Tasks with strict timing constraints can be executed with higher priority, improving the application's response to time-critical events.
2. System performance maximization
For large, complex embedded applications, using an event-driven RTOS instead of a polling-based superloop can produce a more efficient design, reduce memory footprint, and provide applications with more processor time.
3. Reduced complexity
An RTOS allows an application to be divided into small, autonomous tasks. Each task runs in its own context without depending on other tasks or the scheduler.
4. Peak load management
An RTOS provides an effective way to manage peak system activity. Higher priority can be assigned to tasks that handle peak loads, ensuring they gain processor access within critical time windows while lower-priority tasks are delayed.
5. Tight integration of middleware
Modular RTOS design makes it easy to add middleware. Middleware components are added as tasks and drivers and use RTOS-provided resources to communicate with other tasks. They are scheduled by the RTOS based on relevant events.
6. Larger development teams
Each task can be treated as a separate project. Inputs and outputs can be defined using RTOS-provided resources (queues, semaphores, etc.). Defining the system as discrete tasks makes it easier to distribute work across more developers.
7. Easier debugging and verification
When the system is divided into tasks with clear responsibilities and minimal interdependence, each task can be debugged and verified individually before full system integration.
8. Code reuse
Modular design in RTOS-based systems encourages implementing software features as independent, validated tasks. Their independence simplifies reuse in other designs.
MCU resources are larger now than before, making RTOS suitable for many scenarios. Of course, some scenarios are still simple enough that bare-metal programming is sufficient.
01 Logical system models
Bare-metal systems are usually categorized into polling systems and foreground-background systems.
1. Polling system
A polling system initializes the relevant hardware and then lets the main program run in a continuous loop, sequentially performing various tasks. Example pseudocode:
intmain(void){/* Hardware-related initialization */HardWareInit();/* Endless loop */for(;;) { /* Handle task 1 */ DoSomething1(); /* Handle task 2 */ DoSomethingg2(); /* Handle task 3 */ DoSomethingg3();}}
The polling model is a very simple software structure, typically suitable for tasks that only require sequential code execution and do not depend on external events. For example, LED toggling, serial output, or LCD display tasks can be implemented well with a polling system. However, if external events such as button presses need to be detected to simulate emergency alarms, the system's real-time responsiveness is reduced.
Assume DoSomethingg3 performs button scanning. When a button press occurs, it is an alarm that needs immediate response and handling. If the program happens to be executing DoSomethingg1 at that moment and DoSomethingg1 takes a long time to complete—longer than the button press duration—then the event may be missed when execution returns to DoSomethingg3. This shows that polling systems are best for sequential functional code; real-time responsiveness degrades with external event-driven requirements.
2. Foreground-background system
Compared with polling, the foreground-background system adds interrupts to the polling model. External event responses occur in interrupts, while event handling returns to the polling loop. The interrupt routine is the foreground, and the infinite loop in main is the background. Example structure:
Global flags are used to signal events from ISRs to the background loop. If event handling is short it may be done inside the ISR; if it is longer, the ISR sets a flag and the background loop processes the event.
When the background code is running and an interrupt arrives, the interrupt service routine interrupts the background flow to mark the event. If the event handling is brief it may be done inside the ISR; if it is longer, processing is deferred to the background loop.
Although response and processing are separated, processing still occurs sequentially in the background. Compared with polling systems, foreground-background ensures events are not lost and, together with interrupt nesting, greatly improves real-time responsiveness. In many small to medium projects, a well-designed foreground-background system can provide OS-like behavior.
02 Multithreaded systems
Compared with the foreground-background model, event detection in multithreaded systems also occurs in interrupts, but event processing takes place in threads. Threads, like interrupts, have priorities; higher-priority threads are executed first.
When an urgent event is flagged in an ISR, if the corresponding thread has sufficiently high priority, it will be scheduled immediately. Compared with the foreground-background model, multithreaded systems further improve real-time behavior.
Example pseudocode for a multithreaded system:
intflag1 =0;intflag2 =0;intflag3 =0;intmain(void){/* Hardware-related initialization */HardWareInit();/* OS initialization */RTOSInit();/* OS start: begin multithreaded scheduling, does not return */RTOSStart();}voidISR1(void){/* Set flag */flag1 =1;}voidISR2(void){/* Set flag */flag2 =2;}voidISR3(void){/* Set flag */flag3 =1;}voidDoSomething1(void){/* Endless loop, must not return */for(;;) { /* Thread body */ if(flag1) { }}}voidDoSomething2(void){/* Endless loop, must not return */for(;;) { /* Thread body */ if(flag2) { }}}voidDoSomething3(void){/* Endless loop, must not return */for(;;) { /* Thread body */ if(flag3) { }}}
Compared with the sequential background execution in foreground-background systems, multithreaded systems divide the main program body into independent, non-returning, infinite-loop subprograms called threads.
Each thread is independent, non-interfering, and has its own priority managed by the OS. With an RTOS, developers do not need to carefully design the overall execution flow or worry about interference between functional modules.
Adding an RTOS simplifies programming at the cost of a small amount of FLASH and RAM used by the RTOS. Modern MCUs generally have ample FLASH and RAM to absorb this overhead.

ALLPCB