ESP32 FreeRTOS Tutorial

ESP32 FreeRTOS Tutorial
Share:

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 systemnetworking, 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 queuessemaphores, and mutexes for safe communication and synchronization between tasks.

I’m going to skip explaining some stuff like what tasks/threadsasync vs parallelismdeadlocks, task/thread synchronization(123), 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(registersprogram counterALU, …), 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. 

This is how you can create a simple task running the blink sketch pinned to a single core:
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() {

}
The result is the blue LED is blinking. There is no difference compared to doing it with “bare-metal” code, but we are now successfully using the FreeRTOS tasks. In the next example, we’ll see how we can use multiple tasks to do multitasking.
This example shows how to do multitasking while using queues and semaphores:
#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() {
}
Result in the serial monitor:
This example shows how to run multiple tasks and let the OS automatically decide the core they will run on:
#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() {

}
Result:
This example demonstrates how to allocate, free and monitor memory in FreeRTOS. If you are doing bare metal programming, you use malloc() and free(), while in FreeRTOS, you have to use pvPortMalloc() for allocation and vPortFree() for deallocation.
#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() {

}
Result:
Share:

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.