ESP32 with FreeRTOS (Arduino IDE) – Getting Started: Create Tasks

In this tutorial, we’ll introduce the basic concepts of FreeRTOS and show you how to use it with the ESP32 and the Arduino IDE. You’ll learn how to create single and multiple tasks, suspend and resume tasks, run code on the ESP32’s two cores, and calculate the appropriate stack size needed (memory) for each task.

FreeRTOS is a real-time operating system that allows the ESP32 to manage and run multiple tasks simultaneously in a smooth and efficient way. It’s built into the ESP32 and fully integrated with both the Arduino core and the Espressif IoT Development Framework (IDF).

ESP32 with FreeRTOS - Getting Started Guide Create Tasks

In this tutorial, we’ll cover the following topics:

Prerequisites

This tutorial focuses on programming the ESP32 using the Arduino core. Before proceeding, you should have the ESP32 Arduino core installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven’t already.


What is FreeRTOS?

FreeRTOS is a lightweight, open-source real-time operating system (RTOS). It provides a framework for running multiple tasks concurrently, each with its own priority and execution schedule. Instead of running code line by line, FreeRTOS lets you create independent tasks that the ESP32 can switch between quickly based on the task priority.

FreeRTOS Logo

It also provides tools like queues and semaphores so tasks can seamlessly communicate with each other.

This makes your code more organized and responsive, especially when your ESP32 is handling several tasks at the same time, like reading sensors, handling HTTP requests, and displaying information on a screen, while listening to interrupts.

Why is FreeRTOS useful with the ESP32?

The FreeRTOS real-time operating system is built into the ESP32 and integrated into the Espressif IDF and the Arduino core. It supports:

  • Task Management: create, suspend, resume, or delete tasks (covered in this tutorial).
  • Scheduling: allows you to give priority to your tasks, so they run in a specific order (covered in this tutorial).
  • Inter-Task Communication: using things like queues, semaphores, and mutexes, we can ensure seamless communication between tasks without crashing the ESP32.
  • Dual-Core Support: it allows you to run your tasks on either core 0 or core 1 of the ESP32 (also covered in this tutorial, but for a more in-depth guide about using dual-core with the ESP32, check this guide: How to use ESP32 Dual Core with Arduino IDE).

So, in summary…

FreeRTOS is useful with the ESP32 because it enables multitasking, allowing multiple tasks like sensor reading, Wi-Fi, and display updates to run without blocking each other.

It also allows you to take advantage of the ESP32 dual-core processor by letting you assign tasks to specific cores for better performance.

Additionally, with priority-based scheduling, time-critical tasks can run immediately, making it ideal for real-time applications when you need to react quickly to external events detected by the ESP32 GPIOs.

FreeRTOS Basic Concepts

Before diving into the practical examples, let’s cover some basic concepts related to FreeRTOS:

  • Tasks: tasks are independent functions running concurrently, each with its own stack (memory usage allocated) and priority. Tasks can be in states like Running, Ready, Blocked, or Suspended.
  • Scheduler: the scheduler decides which tasks to run based on their priorities. This is a preemptive scheduler, which means it can interrupt a lower-priority task at any time to run a higher-priority one, ensuring that critical tasks are executed as soon as they’re ready.
  • Priorities: higher numbers indicate higher priority (for example: 1 = low, 5 = high).

1) Creating Tasks

This is the simplest example in which we’ll show you how to create a FreeRTOS task to blink an LED every second. We’ll connect the LED to GPIO 2. Instead, you can skip the LED and check the results on the ESP32 built-in LED.

Parts Required

For this example, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Wiring the Circuit

Wire an LED to GPIO 2 as shown in the following schematic diagram.

ESP32 with an LED connected to GPIO2 via a 220Ohm resistor

ESP32: Creating FreeRTOS Tasks – Arduino Code

The following code creates a task that will blink an LED connected to GPIO 2 every second.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED_PIN 2

// Declare task handle
TaskHandle_t BlinkTaskHandle = NULL;

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void setup() {
  Serial.begin(115200);
  
  pinMode(LED_PIN, OUTPUT);

  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

View raw code

How Does the Code Work?

We start by defining the GPIO that will be connected to the LED.

#define LED_PIN 2

Task Handle

Then, declare the task handle. A TaskHandle_t is a variable that points to a FreeRTOS task, letting you control it, like resuming it, stopping it, or delete it. In this example, we won’t need the task handle, but we’re creating it nonetheless to show you how it’s done.

TaskHandle_t BlinkTaskHandle = NULL;

Task Function

Then, we create a task. A task is nothing more than a function that executes whatever commands you want. Here’s the BlinkTask function used in this example.

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

This function is a FreeRTOS task, which is a special kind of function that runs independently under the FreeRTOS scheduler, allowing multitasking on the ESP32.

FreeRTOS tasks must return void and must accept a single argument, which can be used to pass data to the function (not used in our case).

void BlinkTask(void *parameter) {

The for(;;) creates an infinite loop to ensure the task runs indefinitely until explicitly stopped. This is similar to the loop() function used in Arduino code.

for (;;) { // Infinite loop

Then, we use the digitalWrite() function to turn the LED on and off. Notice that instead of using the typical delay() function, we use vTaskDelay().

digitalWrite(LED_PIN, HIGH);
Serial.println("BlinkTask: LED ON");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
digitalWrite(LED_PIN, LOW);
Serial.println("BlinkTask: LED OFF");
vTaskDelay(1000 / portTICK_PERIOD_MS);

vTaskDelay()

vTaskDelay() is a FreeRTOS function that pauses a task for a specified number of ticks, allowing other tasks to run during that time. It doesn’t block your code like delay(). The vTaskDelay() function accepts ticks. On the ESP32, each tick is typically 1ms (defined by portTICK_PERIOD_MS), so vTaskDelay(1000 / portTICK_PERIOD_MS) pauses the task for 1000ms (1 second).

vTaskDelay(1000 / portTICK_PERIOD_MS);

Get Core IDE

For demonstration purposes, we also print in which core the task is running. We can get that information by calling the xPortGetCoreID() function.

Serial.print("BlinkTask running on core ");
Serial.println(xPortGetCoreID());

setup()

In the setup(), we initialize the Serial Monitor and set the LED as an output.

void setup() {
  Serial.begin(115200);
  
  pinMode(LED_PIN, OUTPUT);

Create a Task

Now, to actually create a FreeRTOS task and assign it to a specific core, we need to use the xTaskCreatePinnedToCore() function. This function also specifies the task function, name, stack size, parameters, priority, and task handle.

xTaskCreatePinnedToCore(
  BlinkTask,      // Task function
  "BlinkTask",   // Task name
  10000,           // Stack size (bytes)
  NULL,            // Parameters
  1,                   // Priority
  &BlinkTaskHandle,  // Task handle
  1                  // Core 1
);

The task function is BlinkTask that we defined earlier. We can also give a name to the task. In this case, “BlinkTask”.

BlinkTask,     // Task function
"BlinkTask",   // Task name

We set the task stack size to 10000 bytes. The task stack size is the amount of memory allocated for the task to store its variables, function calls, and temporary data while it runs, ensuring it has enough space to operate without crashing the ESP32. It is defined in bytes. In a later example, we’ll see how to get the stack size of a task.

10000,    // Stack size (bytes)

In this case, our task doesn’t have any parameters, so we set that parameter to NULL.

NULL,     // Parameters

We give the task priority 1. The higher the number, the higher the priority. In this case, it doesn’t matter much because we only have one task.

1,    // Priority

We also define the task handle for the task that we created at the beginning of the code.

&BlinkTaskHandle,  // Task handle

And finally, we define in which core we want to run the task. The ESP32 has two cores, designated core 0 and core 1. In this example, we’re using core 1.

1   // Core 1

loop()

The loop() is empty because the FreeRTOS scheduler will run the task. However, it is possible to add code to the loop() to run any other commands you want.

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

Demonstration

Upload the code to your ESP32 board. After uploading, open the Serial Monitor at a baud rate of 115200. You should get a similar result.

ESP32 running a single task using freertos - Serial Monitor demonstration

At the same time, the LED should be blinking every second.


2) Suspend and Resume Tasks

In this example, you’ll learn how to suspend and resume a FreeRTOS task on the ESP32. We’ll create a task that blinks an LED, and use a pushbutton to control it. When the button is pressed, the task will be suspended to stop the blinking, and pressing it again will resume the task.

Parts Required

For this example, you need the following parts:

Wiring the Circuit

Add a pushbutton to your previous circuit. We’re connecting the pushbutton to GPIO 23, but you can use any other suitable GPIO.

ESP32 connected to an LED on GPIO 2 and a pushbutton on GPIO 23

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

ESP32: Suspend and Resume FreeRTOS Tasks – Arduino Code

The following code listens to the press of a pushbutton to either suspend or resume the blinking FreeRTOS task.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define BUTTON_PIN 23

// Task handle
TaskHandle_t BlinkTaskHandle = NULL;

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

void IRAM_ATTR buttonISR() {
  // Debounce
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime < debounceDelay) {
    return;
  }
  lastInterruptTime = currentTime;

  // Toggle task state
  taskSuspended = !taskSuspended;
  if (taskSuspended) {
    vTaskSuspend(BlinkTaskHandle);
    Serial.println("BlinkTask Suspended");
  } else {
    vTaskResume(BlinkTaskHandle);
    Serial.println("BlinkTask Resumed");
  }
}

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED1_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);


  // Initialize pins
  pinMode(LED1_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Internal pull-up resistor

  // Attach interrupt to button
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  // Create task
  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

View raw code

How Does the Code Work?

We already covered how to create tasks in the previous example. So, we’ll just cover the relevant parts for this section.

We define the GPIOs for the LED and the pushbutton.

#define LED1_PIN 2
#define BUTTON_PIN 23

We create volatile variables that will be used in the ISR (interrupt service routine for the pushbutton). The taskSuspended variable is used to determine whether the task is suspended or not, and the lastInterruptTime and debounceDelay are needed to debounce the pushbutton.

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

To detect pushbutton presses, we’re using interrupts. When we use interrupts, we need to define an interrupt service routine (a function that runs on the ESP32 RAM). In this case, we create the buttonISR() function. We must add IRAM_ATTR to the function definition to run it on RAM.

void IRAM_ATTR buttonISR() {

Recommended reading: ESP32 with PIR Motion Sensor using Interrupts and Timers.

First, we debounce the pushbutton, and if a button press is detected, we toggle the state of the taskSuspended flag variable.

// Debounce
uint32_t currentTime = millis();
if (currentTime - lastInterruptTime < debounceDelay) {
  return;
}
lastInterruptTime = currentTime;

// Toggle task state
taskSuspended = !taskSuspended;

Suspend and Resume Tasks

Then, if the taskSuspended variable is true, we call the vTaskSuspend() function and pass the task handle as an argument. Here, you can see one of the uses of the Task Handle. It is a way to refer to the task to control it.

if (taskSuspended) {
  vTaskSuspend(BlinkTaskHandle);

If the taskSuspended variable is false, we call the vTaskResume() function to resume the execution of the task.

} else {
  vTaskResume(BlinkTaskHandle);

setup()

In the setup(), we must declare our pushbutton as an interrupt as follows:

attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

The rest of the code remains similar to the previous example.

Demonstration

Upload the code to your ESP32 board.

The LED will start blinking. If you press the pushbutton, the LED will stop blinking. Press again and the LED will start blinking again.

ESP32 with an LED blinking with a pushbutton to suspend and resume the task

You can check how it works by watching the following video.


At the same time, you should get all the information on the Serial Monitor.

ESP32 suspend and resume FreeRTOS tasks - serial monitor demonstration

3) Creating and Running Multiple Tasks

In this section, you’ll learn how to create and run multiple FreeRTOS tasks simultaneously. As an example, we’ll create two tasks to blink two different LEDs.

Parts Required

For this example, you need the following parts:

Wiring the Circuit

We’ll wire two LEDs to the ESP32. We’ll use GPIOs 2 and 4. You can use any other suitable GPIOs as long as you modify the code accordingly.

Two LEDs connected to the ESP32 - one LED connected to GPIO2 and another connected to GPIO4

ESP32: Creating and Running Multiple Tasks – Arduino Code

The following code creates two separate FreeRTOS tasks, each blinking an LED at a different rate. With FreeRTOS, it’s easy to run both tasks independently without blocking each other. This approach is much cleaner and more efficient than using delays or complex timing logic in traditional Arduino code.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    1                 // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

View raw code

How Does the Code Work?

Creating and running multiple tasks is as simple as creating and running a single task.

First, you need to define your tasks. In our case, we have two different tasks to blink two different LEDs at different rates. We call them Task1 and Task2.

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
  }
}

Then, in the setup(), we just need to create the tasks using the xTaskCreatePinnedToCore. In this example, we’re running both tasks on core 1 of the ESP32 and both have the same priority.

xTaskCreatePinnedToCore(
  Task1,             // Task function
  "Task1",           // Task name
  10000,             // Stack size (bytes)
  NULL,              // Parameters
  1,                 // Priority
  &Task1Handle,      // Task handle
  1                  // Core 1
);

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  1                 // Core 1
);

Demonstration

Upload the code to your board. After uploading, press the ESP32 RST button so that it starts running the code. You’ll have two different LEDs blinking at different rates.

ESP32 running two FreeRTOS tasks- blinking two LEDs at different rates

In the following video, you can better understand how it works.


As you can see, it is super simple to achieve this by creating FreeRTOS tasks instead of using delays or other complex timing calculations.


4) Creating and Running Multiple Tasks On Different Cores (ESP32 Dual-Core)

Most ESP32 models come with two cores, designated core 0 and core 1. By default, when we run code on the Arduino IDE, the code runs on core 1. In this section, we’ll show you how to run different tasks on different ESP32 cores.

Wiring the Circuit

Maintain the circuit from the previous example with two LEDs.

Tasks on Multiple Cores

The following code is similar to the previous example, but each task is running on a different core. Additionally, we also add a line to each task to double-check in which core it is running.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("Task 1 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.print("Task 2 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void setup() {
  Serial.begin(115200);

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    0                 // Core 0
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

View raw code

How Does the Code Work?

The only difference in this example is that we define Task2 to run on core 0 as shown below.

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  0                 // Core 0
);

Demonstration

If you upload the code to your ESP32, the result will be the same as the previous example. Two LEDs are blinking at different rates.

ESP32 running two FreeRTOS tasks- blinking two LEDs at different rates

In the Serial Monitor, you can actually check that each task is running in a different core.

ESP32 running tasks on two cores - Serial Monitor Demonstration

5) Tasks Memory Usage

In this section, we’ll cover how to measure stack and heap usage for your tasks so that you can optimize memory allocation.

Stack and heap are two types of memory used by FreeRTOS tasks on the ESP32, each serving a unique role in managing a program’s memory needs.

What exactly is the Stack Usage? The stack is a dedicated memory area for each FreeRTOS task, used to store temporary data like local variables, function call information, and task state during execution. Each task has its own stack, allocated when the task is created. You’ve seen that in previous examples, we’re allocating a 10000-byte stack to each task.

There is a function that you can call inside your task to determine the stack usage: the uxTaskGetStackHighWaterMark() function. That function determines the allocated stack size that is not being used.

What is the Heap Usage? The heap is a shared memory pool in the ESP32’s SRAM, used for dynamic memory allocation, including task stacks, buffers, and other runtime data allocated by FreeRTOS or the Arduino core. We can call the xPortGetFreeHeapSize() function in our code to determine the free heap.

Task Stack Size and Free Heap – Code

The following code is similar to the one in previous projects, with two functions to blink two different LEDs. We determine the free stack and free heap.

Upload the following code to your board.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.printf("Task1 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.printf("Task2 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.printf("Starting FreeRTOS: Memory Usage\nInitial Free Heap: %u bytes\n", xPortGetFreeHeapSize());

  xTaskCreatePinnedToCore(
    Task1,
    "Task1",
    10000,
    NULL,
    1,
    &Task1Handle,
    1
  );

  xTaskCreatePinnedToCore(
    Task2,
    "Task2",
    10000,
    NULL,
    1,
    &Task2Handle,
    1
  );
}

void loop() {
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > 5000) {
    Serial.printf("Free Heap: %u bytes\n", xPortGetFreeHeapSize());
    lastCheck = millis();
  }
}

View raw code

Demonstration

After uploading the code to your board, you should get something similar on your Serial Monitor.

ESP32 with FreeRTOS determine task stack high watermark

The uxTaskGetStackHighWaterMark reports 8556 bytes free for Task1 and 8552 bytes free for Task2, meaning each task uses 1444–1448 bytes of its 10000-byte stack at peak. So, we can greatly reduce the allocated stack size to each task.

A good stack size should cover the task’s peak usage (1444 bytes) plus a safety margin of 500–1000 bytes to handle unexpected increases

In the case of free heap, in my case, the xPortGetFreeHeapSize() reports 247616 bytes free (not shown in the screenshot), indicating the remaining heap after allocating stacks (20000 bytes for two tasks) and other system resources.

Wrapping Up

This tutorial was a detailed introduction to FreeRTOS with the ESP32. You learned how to create single and multiple tasks, assign a core to each task, suspend and resume tasks, and even calculate the task stack.

Using FreeRTOS with the ESP32 is a great choice because it allows you to perform multiple tasks simultaneously in a simple way, with priorities to run the most critical tasks first.

In future tutorials, we’ll cover communication between tasks using semaphores and queues.

We hope you’ve found this guide useful.

Learn more about the ESP32 with our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

2 thoughts on “ESP32 with FreeRTOS (Arduino IDE) – Getting Started: Create Tasks”

  1. Excellent!
    Today I was looking the ESP-IDF tutorials and they were awesome.
    Then I was writing some ESP32 stuff using the ESP-IDF and then I saw this post on my e-mail. After reading this tutorial, I solved what I was trying to solve in just few minutes and in a more friendly framework (theArduino IDE). I believe that the results and the performance of the project I just built will be similar to the results that I would see if I had continued in the ESP-IDF.

    Have a nice weekend guys!

    Reply
  2. Excellent!
    Today I was looking the ESP-IDF tutorials and they were awesome.
    Then I was writing some ESP32 stuff using the ESP-IDF and then I saw this post on my e-mail. After reading this tutorial, I solved what I was trying to solve in just few minutes and in a more friendly framework (theArduino IDE). I believe that the results and the performance of the project I just built will be similar to the results that I would see if I had continued in the ESP-IDF.

    Have a nice weekend guys!

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.