FreeRTOS is a real-time operating system (RTOS) for embedded systems. It is based on a microkernel architecture (explained below) and has been ported to various microcontrollers. FreeRTOS is an open-source RTOS designed for embedded systems and IoT devices. Richard Barry initiated its development in 2003, and since then, it has garnered significant traction in various industries due to its flexibility, portability, and robustness.
A microkernel, also known as a μ kernel or microkernel, is an operating system kernel that, unlike a monolithic kernel, performs only basic functions – usually memory and process management, as well as basic functions for synchronization and communication. All other functions are implemented as separate processes (servers) that communicate with the requesting programs (client), or as a program library that is integrated by the requesting programs in user mode.
The original FreeRTOS (can be referred to as Vanilla FreeRTOS) is supported on numerous single-core MCUs and SoCs. To support dual-core ESP targets, such as ESP32, ESP32-S3, and ESP32-P4, Espressif developed an implementation of FreeRTOS with dual-core symmetric multiprocessing (SMP) capabilities (can be referred to as IDF FreeRTOS). We program via ESP-IDF for IDF FreeRTOS, not the Vanilla FreeRTOS.
---
In this article, we are talking about the theories related to original FreeRTOS (Vanilla FreeRTOS). Implementation, use cases and examples are of various modified versions. IDF FreeRTOS is one of such modified versions. This is the official website of FreeRTOS. This is the official GitHub repository:
1 | https://github.com/FreeRTOS/FreeRTOS |
The thing to keep in mind is that microprocessors have become more powerful. As the power is increasing, the number of applications that require hardcoded real-time scheduling is shrinking.
Key Features of FreeRTOS
To ensure good maintainability, FreeRTOS is developed largely in C, only a few functions are implemented in assembler. The scheduler is configurable, so that pre-emptive and cooperative operation is possible. The operating system supports two different task classes from version 4 onwards. “Real” processes and coroutines that have little memory available. “Event Flags” are offered under the name “Binary Semaphore”. Mutexes are present in newer versions.
The package for download includes ready-made configurations for various architectures and various compiler environments. Furthermore, various demos (e.g. an IP stack) are included. On FreeRTOS.org there is extensive documentation on FreeRTOS, tutorials as well as documentation on the structure of an RTOS and a comparison of the implementations on different microcontrollers.
The provided examples will work with the 8051/8052 microcontroller. 8051/8052 is the original thing to start learning to program microcontrollers, that is still commonly used, followed by STM32 (STM32F1 is not costly), and ARM Cortex M. There are boards such as STM32 Nucleo-64 for the serious learners.
If you are using ESP line microcontrollers, then should read their documentation as well:
1 | https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/freertos_idf.html |
FreeRTOS allows developers to create multiple tasks, each running independently and executing its specific function. Tasks can have different priorities, enabling the system to prioritize critical tasks over less important ones.
From Espressif’s documentation, task deletion in Vanilla FreeRTOS is called via vTaskDelete(). IDF FreeRTOS provides the same vTaskDelete() function. However, due to the dual-core, there are some behavioral differences when calling vTaskDelete() in IDF FreeRTOS. Please read documentation to avoid unpredictable behavior.
FreeRTOS employs a preemptive scheduling algorithm, enabling tasks with higher priority to preempt lower-priority tasks. This ensures that time-critical operations are executed without delay, making it suitable for real-time applications.
Example code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include "FreeRTOS.h" #include "task.h" #include "stdio.h" // Task function prototypes void vTask1(void *pvParameters); void vTask2(void *pvParameters); int main(void) { // Create Task 1 xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Create Task 2 xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); // Should not reach here for(;;); } // Task 1 function void vTask1(void *pvParameters) { (void) pvParameters; // Unused parameter for (;;) { printf("Task 1 is running...\n"); vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for 1 second } } // Task 2 function void vTask2(void *pvParameters) { (void) pvParameters; // Unused parameter for (;;) { printf("Task 2 is running...\n"); vTaskDelay(pdMS_TO_TICKS(2000)); // Delay for 2 seconds } } |
In the main() function, we create two tasks (vTask1 and vTask2) using xTaskCreate(). We assign different priorities to each task (1 for Task 1 and 2 for Task 2). The stack size is set to configMINIMAL_STACK_SIZE, which is a FreeRTOS configuration constant defining the minimum stack size for tasks.
We start the FreeRTOS scheduler with vTaskStartScheduler(). Once the scheduler starts, it will take over the execution of tasks, and the main() function will not return.
Each task function (vTask1 and vTask2) contains an infinite loop where it prints a message every second and every two seconds, respectively, using printf().
The vTaskDelay() function is used to introduce delays between successive iterations of the loop. It takes the delay time in ticks, which is converted from milliseconds using pdMS_TO_TICKS() macro.
FreeRTOS provides memory allocation and deallocation mechanisms tailored for embedded systems. It offers dynamic memory allocation schemes like heap_1, heap_2, and heap_4, allowing developers to choose the most suitable option based on their memory requirements and constraints. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include "FreeRTOS.h" #include "task.h" #include "stdio.h" // Task function prototypes void vTask1(void *pvParameters); int main(void) { // Create Task 1 xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); // Should not reach here for(;;); } // Task 1 function void vTask1(void *pvParameters) { (void) pvParameters; // Unused parameter // Allocate memory dynamically char *ptr = (char *)pvPortMalloc(10 * sizeof(char)); if (ptr != NULL) { sprintf(ptr, "Hello"); printf("Dynamic memory allocation: %s\n", ptr); // Free the allocated memory vPortFree(ptr); printf("Memory freed successfully.\n"); } else { printf("Memory allocation failed!\n"); } // Delete the task vTaskDelete(NULL); } |
In the vTask1() function:
We allocate memory dynamically using the FreeRTOS memory allocation function pvPortMalloc(). We allocate memory for storing 10 characters. If the memory allocation is successful, we write the string “Hello” to the allocated memory using sprintf() and print it using printf().
We free the allocated memory using the FreeRTOS memory deallocation function vPortFree(). We delete the task itself using vTaskDelete(NULL).
Efficient interrupt handling is crucial for real-time systems. FreeRTOS provides mechanisms to handle interrupts gracefully, ensuring minimal latency and deterministic response times. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | #include "FreeRTOS.h" #include "task.h" #include "stdio.h" // Simulated interrupt handler void vInterruptHandler(void); // Task function prototypes void vTask1(void *pvParameters); int main(void) { // Initialize FreeRTOS kernel // Simulated interrupt initialization // For the sake of this example, assume an interrupt is generated every 1 second // In a real application, this would be a hardware-specific setup // For demonstration purposes, we'll call the interrupt handler directly in a loop // Create Task 1 xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); // Should not reach here for(;;); } // Task 1 function void vTask1(void *pvParameters) { (void) pvParameters; // Unused parameter for (;;) { // Task 1 operation printf("Task 1 is running...\n"); // Delay for 500ms vTaskDelay(pdMS_TO_TICKS(500)); } } // Simulated interrupt handler void vInterruptHandler(void) { // Handle the interrupt printf("Interrupt occurred!\n"); // If needed, notify a task from the interrupt handler using a semaphore or other synchronization mechanism } |
The vInterruptHandler() function simulates an interrupt handler. In a real application, this function would handle the actual interrupt events from hardware peripherals. Inside the interrupt handler, you can perform time-critical operations or notify tasks using synchronization mechanisms like semaphores or event flags.
Timers in FreeRTOS allow developers to schedule tasks or execute callback functions periodically or after a specified delay. This feature is invaluable for implementing time-critical operations and managing tasks with precision. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "stdio.h" // Timer callback function prototype void vTimerCallback(TimerHandle_t xTimer); // Task function prototypes void vTask1(void *pvParameters); // Timer handle TimerHandle_t xTimer = NULL; int main(void) { // Create Timer xTimer = xTimerCreate("Timer", // Timer name pdMS_TO_TICKS(1000),// Timer period in ticks (1000ms) pdTRUE, // Auto-reload 0, // Timer ID vTimerCallback); // Timer callback function // Check if timer creation was successful if (xTimer != NULL) { // Start the Timer xTimerStart(xTimer, 0); } else { printf("Failed to create timer!\n"); } // Create Task 1 xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); // Should not reach here for(;;); } // Task 1 function void vTask1(void *pvParameters) { (void) pvParameters; // Unused parameter for (;;) { // Task 1 operation printf("Task 1 is running...\n"); // Delay for 500ms vTaskDelay(pdMS_TO_TICKS(500)); } } // Timer callback function void vTimerCallback(TimerHandle_t xTimer) { // Timer callback operation printf("Timer callback: Timer expired!\n"); } |
The vTimerCallback() function serves as the callback for the software timer. In this function, we perform the desired operation when the timer expires, such as printing a message indicating that the timer has expired.
FreeRTOS facilitates communication between tasks through various mechanisms such as queues, semaphores, mutexes, and event groups. These synchronization primitives enable seamless data exchange and coordination among tasks. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "stdio.h" // Queue handle QueueHandle_t xQueue = NULL; // Task function prototypes void vTask1(void *pvParameters); void vTask2(void *pvParameters); int main(void) { // Create Queue xQueue = xQueueCreate(5, sizeof(int)); // Queue size: 5, item size: sizeof(int) // Check if queue creation was successful if (xQueue != NULL) { // Create Task 1 xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Create Task 2 xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // Start the FreeRTOS scheduler vTaskStartScheduler(); } else { printf("Failed to create queue!\n"); } // Should not reach here for(;;); } // Task 1 function void vTask1(void *pvParameters) { (void) pvParameters; // Unused parameter int txData = 0; for (;;) { // Send data to the queue if (xQueueSend(xQueue, &txData, portMAX_DELAY) == pdPASS) { printf("Task 1: Sent data to the queue: %d\n", txData); txData++; // Increment data for next transmission } else { printf("Task 1: Failed to send data to the queue!\n"); } // Delay for 1 second vTaskDelay(pdMS_TO_TICKS(1000)); } } // Task 2 function void vTask2(void *pvParameters) { (void) pvParameters; // Unused parameter int rxData; for (;;) { // Receive data from the queue if (xQueueReceive(xQueue, &rxData, portMAX_DELAY) == pdPASS) { printf("Task 2: Received data from the queue: %d\n", rxData); } else { printf("Task 2: Failed to receive data from the queue!\n"); } } } |
Architecture of FreeRTOS
FreeRTOS follows a modular architecture, comprising several core components. The kernel forms the heart of FreeRTOS and provides essential RTOS services such as task management, scheduling, and synchronization.
It employs a priority-based preemptive scheduler that ensures tasks with higher priority execute before lower-priority tasks. It supports both co-operative and preemptive scheduling modes.
FreeRTOS includes memory management components responsible for dynamic memory allocation and deallocation, catering to the memory requirements of tasks and system components. FreeRTOS offers a rich set of inter-task communication mechanisms like queues, semaphores, mutexes, and event groups, facilitating data exchange and synchronization between tasks. The Hardware Abstraction Layer (HAL) layer abstracts hardware-specific functionalities, enabling FreeRTOS to run on various microcontrollers and architectures with minimal modifications.

Image credit: FreeRTOS.org
Application of FreeRTOS
FreeRTOS finds applications in a wide range of industries and domains. It is used in automotive systems for tasks such as engine control, infotainment systems, and vehicle-to-vehicle communication. In industrial automation, FreeRTOS is employed in programmable logic controllers (PLCs), distributed control systems (DCS), and supervisory control and data acquisition (SCADA) systems for real-time control and monitoring.
FreeRTOS powers numerous consumer electronics devices such as smartwatches, digital cameras, home automation systems, and IoT devices, providing real-time responsiveness and efficient task management. FreeRTOS is also utilized in medical devices and healthcare systems for tasks like patient monitoring, diagnostic equipment control, and drug delivery systems, where real-time operation is critical.
Benefits of FreeRTOS
Being open-source, FreeRTOS offers full access to its source code, allowing developers to customize and tailor the operating system to meet their specific requirements.
It is highly portable and can run on various microcontrollers and architectures, making it suitable for a wide range of embedded systems and IoT devices.
It has a small memory footprint, making it suitable for resource-constrained embedded systems where memory and processing power are limited. FreeRTOS is scalable, allowing developers to configure the kernel based on the requirements of their application, thereby optimizing resource utilization and performance. Also, it is easy to add power management.
RTOSs make it easy to add middleware components such as TCP/IP stack, USB stacks, File System, Graphical User Interface etc.
Drawbacks of FreeRTOS
While FreeRTOS offers a plethora of advantages and is widely used in embedded systems and IoT devices, it’s important to acknowledge that no software is without its drawbacks. While FreeRTOS has extensive documentation, including reference manuals, API guides, and demo applications, some users find it insufficient, especially for complex scenarios or advanced features. This lack of comprehensive documentation can pose challenges for developers, particularly those new to the system.
Real-time programming and operating systems concepts can be challenging to grasp for developers transitioning from traditional programming paradigms. FreeRTOS, with its emphasis on real-time responsiveness and concurrent task management, may have a steeper learning curve for inexperienced developers compared to simpler operating systems.
While FreeRTOS is known for its small footprint and efficiency, there is still some overhead associated with task management, scheduling, and inter-task communication. In resource-constrained embedded systems, this overhead can impact overall performance and resource utilization, requiring careful optimization and configuration.
Unlike some commercial RTOS offerings that come with integrated development environments (IDEs) and debugging tools, FreeRTOS primarily provides the kernel and basic services. Developers may need to rely on third-party tools or integrate FreeRTOS with their preferred development environment, which can add complexity to the development process. While FreeRTOS provides various synchronization primitives such as queues, semaphores, mutexes, and event groups, handling complex synchronization scenarios involving multiple tasks and shared resources can be challenging. Developers may need to implement custom synchronization mechanisms or carefully design their application logic to avoid deadlocks and race conditions.
Real-time systems, including those built with FreeRTOS, often pose challenges for debugging and testing due to their concurrent and time-critical nature. Identifying and diagnosing issues related to task scheduling, timing constraints, and resource contention can be complex and time-consuming, requiring specialized debugging techniques and tools. While FreeRTOS has a large user base and active community forums, the level of support and availability of resources may vary depending on the specific platform or architecture. Developers working on less common or niche hardware may find it challenging to access relevant community support and resources.
Despite these drawbacks, FreeRTOS remains a popular choice for embedded system developers due to its flexibility, portability, and real-time capabilities.