Microcontrollers are limited to certain tasks and typically lack the ability for multithreading, which would allow for more than one user or task at a time without requiring multiple copies of a program or computer. Generally, microcontrollers cannot break larger or more complex applications into multiple threads. They handle straightforward sequences of code in real-time, with the exception of programming loops, specific conditions, and interrupts.
Interrupts are one way to work around time-critical tasks in embedded devices. However, the interrupts remain dependent on external circuits (in the case of hardware interrupts) or built-in peripherals (in the case of timer interrupts).
Bear in mind interrupts are meant to make microcontrollers responsive to real-time specific events and not strict deadlines. As a result, there’s no guarantee of timely execution of critical tasks because interrupts in microcontrollers are inherently prioritized. Additionally, despite the best coding practices, an interrupt service routine can be too long. It also suspends the main execution code, which might include other time-critical tasks.
The solution? A real-time operating system (RTOS) can be used to meet strict operating deadlines. There are several RTOS available for microcontrollers, and FreeRTOS is a popular one with commercial versions available.
Technically, FreeRTOS is not a full-fledged real-time operating system but a real-time scheduler designed for microcontrollers. With the support of FreeRTOS, it’s possible to prioritize embedded tasks so they’re executed within strict deadlines.
In this project, we’ll upload FreeRTOS on Arduino UNO to learn how it schedules embedded tasks. In this case, it will be to light up LEDs. This project can also be run on ESP8266, ESP32, or any other Arduino-compatible microcontroller board.
1. Arduino UNO x1
2. LED x4
3. Resistors 330Ω x4
5. Jumper wires / Connecting wires
How FreeRTOS works
Microcontrollers have a single core, meaning they can only run one task simultaneously. FreeRTOS allows for the scheduling of tasks, so it appears that multiple events are running simultaneously. This is called real-time scheduling. In reality, only one task is active at any time in single-core microcontrollers, and the others remain inactive.
FreeRTOS divides the inactive tasks into three states…
1. Ready state: The tasks in the ready state are available for scheduling via the FreeRTOS scheduler. These tasks are assigned memory and processor resources according to the scheduling algorithm. The algorithm sets the schedule for the tasks in the ready state based on the priority assigned to them. These tasks are neither blocked nor suspended. So, they’re not running but waiting to run.
2. Blocked state: The tasks in the blocked state are not in the scheduler’s queue. Rather, they’re blocked by the scheduler and remain inactive indefinitely. The blocked state depends on an external interrupt or a resource such as mutex, semaphore, or counting semaphore. Another reason for a blocked, inactive task could be its periodic nature — meaning it’s periodically delayed in the main loop.
3. Suspended state: Inactive tasks in a blocked state can only run when a respective interrupt is received or a requisite resource is used. But some inactive tasks are explicitly delayed by the developer and are then placed in a queue. Often these tasks are scheduled when specific conditions are met or after another task is executed. For example, a developer might suspend printing the value of a sensor reading to a display unit until it’s read and converted by the ADC.
FreeRTOS API offers a vTaskSuspend() function that explicitly suspends a task and a vTaskResume() function that resumes the task.
A task can transit through a blocked, ready, suspended, and running state several times during its multitasking lifetime, as shown in the flow chart below.
In Arduino, each task is defined as a block of code wrapped in its own function. FreeRTOS allows for the creation of tasks that call a respective function. Each task is assigned a priority. FreeRTOS automatically sets the scheduling algorithm for the tasks during run time, according to the assigned priorities and availability of processing resources.
FreeRTOS is available in the Arduino library. To incorporate FreeRTOS into an Arduino application, simply include the library in the sketch. But first, install it on Arduino IDE.
To do so, navigate to Tools->Manage Libraries and search for FreeRTOS. Scroll to the FreeRTOS library written by Phillip Steven. Install the library.
Now, you can easily include FreeRTOS in an Arduino application by importing the library into the sketch using this statement…
Creating and prioritizing tasks
FreeRTOS API provides a xTaskCreate() function to create tasks. The function has this prototype…
const char * const pcName,
It takes the following arguments…
- pvTaskCode: the pointer to the defined function of the task. The task is implemented as an infinite loop. The pointer is passed as an argument to the function. The task can also delete itself.
- pcName: the name of the callback function that executes the task. The function should not return a value or exit.
- usStackDepth: the size of the stack in the number of words that are allocated to the task. In a 32-bit microcontroller, each word is four bytes long. So, if a stack size is set to 100, then 400 bytes are allocated to the task.
- pvParameters: the value of the parameter, which is passed as an argument of the task function.
- uxPriority: the priority set to the task, which can be a positive integer or -1. If set to -1, the priority is set to undefined. The higher the integer (which is passed as an argument), the higher the priority of the task. For example, a task with a priority of 3 has a higher priority than a task with a priority of 2. The lowest priority is 0.
- pxCreatedTask: the handle of the function for the task. The handle can be used to delete the task.
Setting the task execution sequence
FreeRTOS runs the highest priority task first. The other tasks are run when resources are available or in periodic turns. Typically, it’s necessary to set the initial sequence of tasks. This applies to running first-time tasks. If the tasks should be run in a predetermined order, this can be set by using FreeRTOS API’s vTaskDelayUntil() function. This function allows setting a delay before the task is run the first time.
To demonstrate FreeRTOS as a task scheduler, we’ll light up LEDs on Arduino. For this project, we use four LEDs and interface them with Arduino’s GPIO8, GPIO9, GPIO10, and GPIO11.
The LEDs are interfaced so that they glow when the GPIO sends a LOW signal output.
After making the circuit connections, upload the following sketch to Arduino.
How it works
FreeRTOS is used to run four different tasks. Each task lights up one LED light and switches off the other LEDs. A message is printed to the serial monitor indicating the execution of the given task.
- The task that lights up the LED connected to GPIO8 has the highest priority of 3. It’s scheduled to run after 100 milliseconds when run the first time.
- The task that lights up the LED connected to GPIO9 has a priority of 2. It’s scheduled to run after 110 milliseconds when run the first time.
- The task that lights up the LED connected to GPIO10 has a priority of 1. It’s scheduled to run after 120 milliseconds when run the first time.
- The task that lights up the LED connected to GPIO11 has the lowest priority of 0. It’s scheduled to run on a delay of 50 milliseconds.
Initially, the tasks must run in this predefined order. Then, the execution of tasks run according to their priority and the availability of processing resources.
The sketch begins by importing the Arduino_FreeRTOS.h library. In the setup() function, the baud rate for communicating messages to the serial monitor is 9600 bps. The execution of the setup() function is indicated by printing a message to the serial monitor.
The LEDs are connected to Arduino pins that are set as digital outputs using the pinMode() function. The tasks are created with priorities 3, 2, 1, and 0 using FreeRTOS API’s xTaskCreate() function. All of the tasks are assigned a stack of 100 words. The tasks are bound to the callback functions MyTask1(), MyTask2(), MyTask3(), and IdleTask().
The loop() function is left empty as the embedded tasks will be run by the FreeRTOS scheduler. The first task is defined in MyTask1() function. It switches on the LED that’s connected to GPIO8, switching off all the other LEDs. Upon execution, ‘Task1’ is printed to the serial monitor. The task is scheduled to run after 100 milliseconds using the vTaskDelay() function when run the first time.
The second task is defined in the MyTask2() function. It switches on the LED that’s connected to GPIO9, switching off all the other LEDs. Upon execution, ‘Task2’ is printed to the serial monitor. The task is scheduled to run after 110 milliseconds using the vTaskDelay() function when run the first time.
The third task is defined in the MyTask3() function. It switches on the LED that’s connected to GPIO10, switching off all the other LEDs. Upon execution, ‘Task3’ is printed to the serial monitor. The task is scheduled to run after 120 milliseconds using the vTaskDelay() function when run the first time.
The last task is defined in the MyIdleTask() function. It switches on the LED that’s connected to GPIO11 (Yellow LED), switching off all other LEDs. Upon execution, ‘Idle State’ is printed to the serial monitor. The task is scheduled to run periodically after 50 milliseconds .
This task only runs when the microcontroller is not busy doing other tasks. Since, it is delayed by 50 milliseconds, it must run periodically.
The following log of the execution of the FreeRTOS tasks is presented on the serial monitor.
The LEDs are switched by the FreeRTOS scheduler.
[Link to demonstration video P35-DV]
The serial monitor’s log indicates that the tasks were initially executed in the predefined pattern set by the vTaskDelay() function. Afterward, the tasks are executed according to their priority and the availability of processing resources.
As the idle task runs periodically at 50 milliseconds, the LED connected at GPIO11 appears to remain on. The LED connected to GPIO8 is on the longest because the task responsible for switching it ON has the highest priority and runs for the longest. The LED connected to GPIO9 switches on intermittently because the task responsible for switching it ON does so whenever task 1 is not running. The LED connected to GPIO10 appears off because the task responsible for switching it ON rarely gets a chance.
You may also like:
Filed Under: Electronic Projects, Microcontrollers, Tutorials