Writing a Blinky Program with FreeRTOS on ESP32

If you're diving into embedded systems or real-time programming, FreeRTOS (Free Real-Time Operating System) is a must-learn tool. In this guide, we’ll explore key RTOS concepts and how to implement them using FreeRTOS on an ESP32 board through the Arduino IDE. Whether you're a beginner or looking to expand your ESP32 project with multitasking features, this tutorial has you covered.

Downloading and Setting Up FreeRTOS

To begin, head over to FreeRTOS.org and click Download FreeRTOS. This will give you access to the complete source code.

While it's downloading, navigate to Documentation > Kernel > Getting Started. The instructions will walk you through running demo applications. If your development board isn’t directly supported, visit the "Create your own FreeRTOS project" page.

Minimum Requirements to Include FreeRTOS in Your Project:

  • FreeRTOS kernel source files (.c and .h)

  • A heap memory management file (choose from the available options)

  • Assignment of a microcontroller timer for the RTOS tick

Also, it’s a good idea to download the "Mastering the FreeRTOS Real Time Kernel" and the FreeRTOS Reference Manual from the Books section for comprehensive help.

Understanding FreeRTOS vs FreeRTOS+ and the Demo Examples

Once downloaded, extract the FreeRTOS zip file and locate the /FreeRTOS directory. You’ll find two important libraries:

  • FreeRTOS Kernel – The task scheduler only.

  • FreeRTOS+ – Kernel with additional drivers (e.g., TCP/IP).

Look into the Demo folder for sample projects. For instance, GCC examples include a FreeRTOSConfig.h file, which defines critical system parameters. Be sure to review main.c to see how tasks are declared and the library is integrated.

ESP32 and FreeRTOS: What You Need to Know

The ESP32 microcontroller, commonly found on boards like the Adafruit Feather Huzzah32, uses a modified version of FreeRTOS inside ESP-IDF (Espressif IoT Development Framework).

Most ESP32 boards feature dual-core processors using Symmetric Multiprocessing (SMP)—two cores sharing memory and resources. Espressif’s version of FreeRTOS is customized to support this architecture.

🔥 Note: In this series, we’ll simplify by using only one core to avoid the complexities of multi-core programming in the early stages.

Installing ESP32 Support in Arduino IDE

  1. Open Arduino IDE.

  2. Go to File > Preferences.

  3. Add this to the Additional Board Manager URLs:

 https://dl.espressif.com/dl/package_esp32_index.json

    4. Go to Tools > Board > Board Manager.

    5. Search for ESP32 and install the latest Espressif Systems package.

Understanding FreeRTOSConfig.h for ESP32

The ESP32 board uses its own FreeRTOSConfig.h file, located under:

This file reveals system-wide RTOS settings. For example:

  • Maximum priorities = 25

  • Minimum task stack size = 768 bytes

Always check this config file to understand what options are available and how they affect your system.

Writing a Blinky Program with FreeRTOS on ESP32

Let’s get hands-on and write a blinking LED program using FreeRTOS. Note that you don't need the above libraries to compile the code here.

Key Concepts:

  • A task is a function managed by the RTOS scheduler.

  • Use vTaskDelay() instead of delay() for non-blocking waits.

  • The tick timer is a hardware interrupt that helps FreeRTOS manage timing and task switching. By default, 1 tick = 1 ms.

Code Solution:

#include <Arduino.h>
const int LED_PIN = 2; // or any GPIO pin you prefer

void TaskBlink(void *pvParameters) {
  pinMode(LED_PIN, OUTPUT);

  while (true) {
    digitalWrite(LED_PIN, HIGH);
    vTaskDelay(500 / portTICK_PERIOD_MS);
    digitalWrite(LED_PIN, LOW);
    vTaskDelay(500 / portTICK_PERIOD_MS);
  }
}

void setup() {
  // Create a task and pin it to Core 1
  xTaskCreatePinnedToCore(
    TaskBlink,         // Task function
    "Blink Task",      // Name of the task
    1024,              // Stack size in bytes
    NULL,              // Parameter to pass
    1,                 // Task priority (0-24)
    NULL,              // Task handle (optional)
    1                  // Core number (0 or 1)
  );
}

void loop() {
  // Leave empty or add future tasks
}

Video tutorial

Watch the following video tutorial to write led blink program for ESP32 using Free Real Time Operating system(RTOS).
 

Experiment Ideas

  • Try changing rate_1 and rate_2 to see how the blinking pattern changes.

  • Assign different priorities to the tasks and observe how it affects the LED behavior.

  • Add more tasks (e.g., reading a sensor or printing to Serial) to experiment with multitasking.

  • Set priority of the LED tasks to 0. If the LED stops blinking, think about why that happens (hint: priority conflict with loop() task).

Code Solution:

  // Use only core 1 for demo purposes
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif

// LED rates
static const int rate_1 = 500;  // ms
static const int rate_2 = 323;  // ms

// Pins
static const int led_pin = LED_BUILTIN;

// Our task: blink an LED at one rate
void toggleLED_1(void *parameter) {
  while(1) {
    digitalWrite(led_pin, HIGH);
    vTaskDelay(rate_1 / portTICK_PERIOD_MS);
    digitalWrite(led_pin, LOW);
    vTaskDelay(rate_1 / portTICK_PERIOD_MS);
  }
}

// Our task: blink an LED at another rate
void toggleLED_2(void *parameter) {
  while(1) {
    digitalWrite(led_pin, HIGH);
    vTaskDelay(rate_2 / portTICK_PERIOD_MS);
    digitalWrite(led_pin, LOW);
    vTaskDelay(rate_2 / portTICK_PERIOD_MS);
  }
}

void setup() {

  // Configure pin
  pinMode(led_pin, OUTPUT);

  // Task to run forever
  xTaskCreatePinnedToCore(  // Use xTaskCreate() in vanilla FreeRTOS
              toggleLED_1,  // Function to be called
              "Toggle 1",   // Name of task
              1024,         // Stack size (bytes in ESP32, words in FreeRTOS)
              NULL,         // Parameter to pass to function
              1,            // Task priority (0 to configMAX_PRIORITIES - 1)
              NULL,         // Task handle
              app_cpu);     // Run on one core for demo purposes (ESP32 only)

  // Task to run forever
  xTaskCreatePinnedToCore(  // Use xTaskCreate() in vanilla FreeRTOS
              toggleLED_2,  // Function to be called
              "Toggle 2",   // Name of task
              1024,         // Stack size (bytes in ESP32, words in FreeRTOS)
              NULL,         // Parameter to pass to function
              1,            // Task priority (0 to configMAX_PRIORITIES - 1)
              NULL,         // Task handle
              app_cpu);     // Run on one core for demo purposes (ESP32 only)

  // If this was vanilla FreeRTOS, you'd want to call vTaskStartScheduler() in
  // main after setting up your tasks.
}

void loop() {
  // Do nothing
  // setup() and loop() run in their own task with priority 1 in core 1
  // on ESP32
}

Processor Core Selection

#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
ESP32 has two cores. Here, we choose to run our tasks on core 1, unless the system is set up for single-core (UNICORE) mode, in which case we use core 0.
 

Blinking Rates and Pin Setup

static const int rate_1 = 500; // ms
static const int rate_2 = 323; // ms
static const int led_pin = LED_BUILTIN;
 
 We define two different blinking rates (rate_1 and rate_2) in milliseconds, and specify the LED pin (typically the built-in LED).

Task 1: Blink LED at rate_1

void toggleLED_2(void *parameter) {
while(1) {
digitalWrite(led_pin, HIGH);
vTaskDelay(rate_2 / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(rate_2 / portTICK_PERIOD_MS);
}
}
 This task is similar to toggleLED_1, but uses a different delay (rate_2), so the blinking is out of phase and creates a unique visual pattern.

Setup Function: Creating Tasks

void setup() {
pinMode(led_pin, OUTPUT); // Set the pin as output

xTaskCreatePinnedToCore(
toggleLED_1, // Task function
"Toggle 1", // Task name
1024, // Stack size in bytes
NULL, // Parameters to pass to the task
1, // Priority (0 is lowest)
NULL, // Task handle
app_cpu); // Core to pin this task on

xTaskCreatePinnedToCore(
toggleLED_2,
"Toggle 2",
1024,
NULL,
1,
NULL,
app_cpu);
}

Here we use xTaskCreatePinnedToCore() to create two tasks that run on the same core (app_cpu). Both tasks have the same priority (1) and run forever, blinking the same LED at their respective rates.

Note: If you’re using plain FreeRTOS on a non-ESP32 system, use xTaskCreate() instead. And don’t forget to call vTaskStartScheduler() in your main() function to start task execution.

loop() Function

void loop() {
// Nothing to do here
}
In the ESP32 Arduino framework, setup() and loop() run in their own FreeRTOS task. Since all tasks (including the default loop() task) are set to priority 1, the core's time is shared among them.

Key Differences: Vanilla FreeRTOS vs ESP-IDF

FeatureVanilla FreeRTOSESP32 FreeRTOS (ESP-IDF)
CoresSingle coreDual-core SMP supported
Task CreationxTaskCreate()xTaskCreatePinnedToCore()
StartupRequires vTaskStartScheduler()Called automatically
Stack SizeIn "words"In bytes
DelayvTaskDelay() (ticks)Same, but supports Arduino environment

Test Your Skills: Create Multiple Tasks

As a challenge, create a second FreeRTOS task that blinks another LED at a different interval. This will demonstrate task concurrency and time slicing in action.

Conclusion

Congratulations! You've just built your first FreeRTOS task on an ESP32 using Arduino IDE. While it's just a blinking LED, you've taken your first steps into the world of real-time systems, task scheduling, and multithreaded programming.

As you progress, consider exploring:

  • Task prioritization

  • Context switching

  • Inter-task communication

  • Semaphore and mutex management

📌 Stay tuned for the next tutorial where we'll dive into task prioritization and context switching in FreeRTOS.

Post a Comment

Previous Post Next Post