About
Recently, I was using FreeRTOS on an ESP32, so I thought I’d make a post documenting it and showing you how to use it. FreeRTOS is an open-source real-time operating system used for embedded systems.
Using an OS on a microcontroller becomes very useful when you need to run multiple tasks at once, and or are dealing with a file system, networking, etc. For example, one task is reading from a sensor while another is sending/receiving data over WiFi. Each task will get its own stack and priority. The FreeRTOS scheduler context switches between them and makes sure the higher-priority tasks run first. It also provides concepts like queues, semaphores, and mutexes for safe communication and synchronization between tasks.
I’m going to skip explaining some stuff like what tasks/threads, async vs parallelism, deadlocks, task/thread synchronization(1, 2, 3), memory segmentation, as I already did that in other posts(while it was about .NET and C#, the concepts/patterns are the same).
Additionally, you can check out my 8-bit computer build, as once you understand the inner workings of the CPU(registers, program counter, ALU, …), stuff like this explanation of context switching becomes much more intuitive and easier to understand.
Note: If you are interested in the inner workings of FreeRTOS, check out the documentation here.
Note: FreeRTOS actually comes preinstalled(gets included when you upload code from the Arduino IDE as it’s part of the ESP SDK) on the ESP32, so you don’t need to install anything.
void MyTask(void *parameters) {
pinMode(2, OUTPUT); // onboard LED
while (true) {
digitalWrite(2, HIGH);
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay 1 second, divide by portTICK_PERIOD_MS constant to account for the set tick rate.
digitalWrite(2, LOW);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup() {
//Create a task always running on core 1.
xTaskCreatePinnedToCore(
MyTask, //function to call
"MyTask", //name
1000, //stack size in bytes
NULL, //function parameters(if any eles NULL)
1, //priority
NULL, //task handle
1 //core (0 or 1)
);
}
void loop() {
} #include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
QueueHandle_t sensorQueue;
SemaphoreHandle_t serialLock;
void SensorTask(void *pvParameters) {
int value = 0;
//Continuously read from the sensor.
while(true) {
//Get value from ADC.
value = analogRead(34);
//Enqueue it.
xQueueSend(sensorQueue, &value, portMAX_DELAY);
//Aquire lock on serial and print the value then release the lock.
if (xSemaphoreTake(serialLock, portMAX_DELAY) == pdTRUE) {
Serial.println("[Sensor]: Value sent");
xSemaphoreGive(serialLock);
}
//Delay this task for 500ms and hand over the execution to any other tasks.
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void ProcessingTask(void *pvParameters) {
int receivedValue;
while(true) {
//Wait/check for item in queue, if precent the velue
if (xQueueReceive(sensorQueue, &receivedValue, portMAX_DELAY) == pdTRUE) {
//Do something with the value.
int value = receivedValue * 2 + 5;
if (xSemaphoreTake(serialLock, portMAX_DELAY) == pdTRUE) {
Serial.print("[Processing]: Raw: ");
Serial.print(receivedValue);
Serial.print(", Processed: ");
Serial.println(value);
xSemaphoreGive(serialLock);
}
}
}
}
void LogTask(void *pvParameters) {
while(true) {
//Aquire lock on serial and print the value then release the lock.
if (xSemaphoreTake(serialLock, portMAX_DELAY) == pdTRUE) {
Serial.println("[Logger]: System running...");
xSemaphoreGive(serialLock);
}
//Delay this task for 2000ms and hand over the execution to any other tasks.
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void setup() {
//Init. serial.
Serial.begin(9600);
//Make a queue.
sensorQueue = xQueueCreate(10, sizeof(int));
//Make a mutex.
serialLock = xSemaphoreCreateMutex();
//Create tasks and pin them to a core.
xTaskCreatePinnedToCore(SensorTask, "SensorTask", 2048, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(ProcessingTask, "ProcessingTask", 2048, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(LogTask, "LoggerTask", 2048, NULL, 1, NULL, 1);
}
void loop() {
}
#include <Arduino.h>
void TaskCounter1(void *pvParameters) {
int counter = 0;
while (true) {
Serial.print("[Task1] Counter: ");
Serial.println(counter++);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void TaskCounter2(void *pvParameters) {
int counter = 0;
while (true) {
Serial.print("[Task2] Counter: ");
Serial.println(counter++);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void TaskCounter3(void *pvParameters) {
int counter = 0;
while (true) {
Serial.print("[Task3] Counter: ");
Serial.println(counter++);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
delay(1000);
xTaskCreate(TaskCounter1, "Counter1", 2048, NULL, 1, NULL);
xTaskCreate(TaskCounter2, "Counter2", 2048, NULL, 1, NULL);
xTaskCreate(TaskCounter3, "Counter3", 2048, NULL, 1, NULL);
}
void loop() {
}
#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
//Defin struct/object.
struct SensorData {
int id;
float value;
char name[20];
};
void Task(void *pvParameters) {
int objectCount = 0;
const int maxObjects = 10;
//Array of object pointers.
SensorData *objects[maxObjects];
while (true) {
if (objectCount < maxObjects) {
//Allocate memory the size of SensorData.
objects[objectCount] = (SensorData *)pvPortMalloc(sizeof(SensorData));
if (objects[objectCount] != NULL) {
objects[objectCount]->id = objectCount;
objects[objectCount]->value = analogRead(34);
snprintf(objects[objectCount]->name, sizeof(objects[objectCount]->name), "Sensor_%d", objectCount);
Serial.print("New object ID:");
Serial.print(objects[objectCount]->id);
Serial.print(" Name: ");
Serial.print(objects[objectCount]->name);
Serial.print(" Value: ");
Serial.println(objects[objectCount]->value, 3);
objectCount++;
} else {
Serial.println("Memory allocation failed!");
}
} else {
Serial.println("Freeing memory...");
//Iterate over all objects and free the memory allocated to them.
for (int i = 0; i < maxObjects; i++) {
if (objects[i] != NULL) {
vPortFree(objects[i]);
objects[i] = NULL;
}
}
Serial.println("All objects memory freed.");
objectCount = 0;
}
//Print heap size.
Serial.print("Free heap: ");
Serial.println(xPortGetFreeHeapSize());
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
delay(1000);
xTaskCreate(Task, "Task", 4096, NULL, 1, NULL);
}
void loop() {
}





