Getting Started with FreeRTOS Tasks Using the ESP-WROVER-KIT

Introduction

FreeRTOS is one of the most popular real-time operating systems for microcontrollers. It brings multitasking capabilities to embedded systems by providing essential features like task scheduling, inter-task communication, synchronization, and time management.

The ESP-WROVER-KIT, powered by the ESP32 SoC, is a powerful development board ideal for FreeRTOS experimentation. It supports dual-core processing, ample RAM, and comes preloaded with FreeRTOS as part of the ESP-IDF (Espressif IoT Development Framework).

This guide focuses on understanding and using FreeRTOS Tasks—the building blocks of concurrent applications.


What Are FreeRTOS Tasks?

A task in FreeRTOS is a function that executes independently and can be scheduled alongside other tasks. It is equivalent to a thread in general operating system terminology.

Tasks allow you to divide your embedded application into concurrent modules—e.g., one for handling sensors, another for communication, and yet another for user interface or data logging.


ESP-WROVER-KIT and ESP-IDF

Before diving into tasks, let’s set up a minimal development environment.

Hardware Required

  • ESP-WROVER-KIT (ESP32)
  • USB-C cable
  • A computer with ESP-IDF installed

Software Prerequisites

  • ESP-IDF version 5.x or later
  • VS Code with Espressif plugin (optional but recommended)
  • Python 3.x

Once ESP-IDF is installed and configured, we can start creating FreeRTOS tasks.


Anatomy of a FreeRTOS Task

Here’s what you need to know about a task:

  1. A task is a function of type void with a void *pvParameters parameter.
  2. It is created using xTaskCreate() or xTaskCreatePinnedToCore().
  3. It must include an infinite loop (while (1)), unless designed to self-delete.

Here is a generic task function:

void myTask(void *pvParameters) {
    while (1) {
        // Task code here
        vTaskDelay(pdMS_TO_TICKS(1000));  // Delay for 1 second
    }
}

Example 1: Blinking an LED with a Task

Let’s write a simple task that blinks an LED connected to GPIO2.

Code

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#define LED_PIN GPIO_NUM_2

void blinkTask(void *pvParameters) {
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);

    while (1) {
        gpio_set_level(LED_PIN, 1);
        vTaskDelay(pdMS_TO_TICKS(500));
        gpio_set_level(LED_PIN, 0);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void app_main(void) {
    xTaskCreate(blinkTask, "Blink LED", 2048, NULL, 5, NULL);
}

Explanation

  • gpio_set_level() sets the GPIO high/low.
  • vTaskDelay() pauses the task for a period.
  • xTaskCreate() creates and schedules the task.

Task Parameters

Tasks can receive input parameters via the pvParameters argument. This is useful for creating reusable task functions.

Example: Multiple Blinking LEDs

typedef struct {
    gpio_num_t pin;
    int delay_ms;
} led_config_t;

void genericBlinkTask(void *pvParameters) {
    led_config_t *cfg = (led_config_t *)pvParameters;
    gpio_set_direction(cfg->pin, GPIO_MODE_OUTPUT);

    while (1) {
        gpio_set_level(cfg->pin, 1);
        vTaskDelay(pdMS_TO_TICKS(cfg->delay_ms));
        gpio_set_level(cfg->pin, 0);
        vTaskDelay(pdMS_TO_TICKS(cfg->delay_ms));
    }
}

void app_main(void) {
    static led_config_t led1 = { GPIO_NUM_2, 500 };
    static led_config_t led2 = { GPIO_NUM_4, 1000 };

    xTaskCreate(genericBlinkTask, "Blink LED1", 2048, &led1, 5, NULL);
    xTaskCreate(genericBlinkTask, "Blink LED2", 2048, &led2, 5, NULL);
}

Task Priorities

FreeRTOS allows assigning priorities from 0 (lowest) to configMAX_PRIORITIES-1.

A higher number means a higher priority. Tasks with the same priority are scheduled using round-robin.

You can adjust priority like this:

xTaskCreate(myTask, "MyTask", 2048, NULL, 10, NULL);  // Higher priority

Starvation Warning

Tasks with high priority can starve lower-priority tasks if they never yield or delay. Always use vTaskDelay() or taskYIELD() to allow context switching.


Task Handles and Deletion

You can manage tasks using a TaskHandle_t.

TaskHandle_t taskHandle = NULL;

xTaskCreate(myTask, "ManagedTask", 2048, NULL, 5, &taskHandle);

vTaskDelete(taskHandle);  // Delete from another task
vTaskDelete(NULL);        // Delete self

Task Lifecycle

Tasks can be:

  • Created: with xTaskCreate
  • Running: when scheduled
  • Blocked: using vTaskDelay, xQueueReceive, etc.
  • Suspended: using vTaskSuspend
  • Deleted: using vTaskDelete

FreeRTOS uses preemptive multitasking, so higher-priority tasks interrupt lower ones.


Debugging Tips with ESP-WROVER-KIT

The ESP-WROVER-KIT supports JTAG debugging. Here are a few suggestions to debug tasks:

  1. Use vTaskList() to list all tasks and their states.
  2. Use logging with ESP_LOGI, ESP_LOGW, etc.
  3. Monitor heap usage with xPortGetFreeHeapSize().

Example:

#include "esp_log.h"

ESP_LOGI("Heap", "Free heap: %d bytes", xPortGetFreeHeapSize());

Example 2: Temperature Sensor Task + Logging Task

Let’s assume you have a temperature sensor and want to read it periodically while another task logs the readings.

Sensor Task (Simulated)

static float temperature = 0;

void temperatureTask(void *pvParameters) {
    while (1) {
        temperature = 20.0 + (rand() % 1000) / 100.0;  // Fake value
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

Logger Task

void loggerTask(void *pvParameters) {
    while (1) {
        ESP_LOGI("Logger", "Temperature: %.2f C", temperature);
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

app_main

void app_main(void) {
    xTaskCreate(temperatureTask, "TempTask", 2048, NULL, 5, NULL);
    xTaskCreate(loggerTask, "LoggerTask", 2048, NULL, 4, NULL);
}

Advanced Features

FreeRTOS offers more advanced task management:

Task Notifications

Lightweight inter-task signaling:

xTaskNotifyGive(taskHandle); // From ISR or other task
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for notification

CPU Core Pinning (ESP32 Only)

You can bind tasks to a specific core using xTaskCreatePinnedToCore.

xTaskCreatePinnedToCore(taskFunc, "Task", 2048, NULL, 5, NULL, 0);  // Core 0

Useful for balancing load or isolating timing-critical tasks.


Summary

FreeRTOS tasks are fundamental to writing efficient, organized, and responsive embedded applications. With the ESP-WROVER-KIT and ESP-IDF, you can build real-time systems that take full advantage of FreeRTOS features.

We covered:

  • Creating tasks with xTaskCreate
  • Managing task priorities
  • Passing parameters to tasks
  • Deleting tasks and using task handles
  • Debugging and logging tasks
  • Using real-world examples like blinking LEDs and simulated sensor data

Sample Project Ideas

To solidify your understanding, try these sample projects using tasks:

  1. Multicolor LED Controller – Use three tasks to control RGB LEDs with different blinking patterns.
  2. UART Logger – One task reads from a sensor, another writes logs to UART.
  3. Wi-Fi Scanner – A periodic task scans available networks and logs them.
  4. Data Logger – Read ADC values and save them to SPIFFS using separate tasks.
  5. Weather Station – Combine temperature, humidity, and pressure tasks that share data with a display task.

Conclusion

FreeRTOS tasks are powerful tools to organize complex embedded applications into manageable, concurrent processes. By mastering task creation, scheduling, and synchronization, you’re well on your way to building responsive and scalable firmware for any real-time application.

Start small, experiment with task priorities and delays, and gradually layer in real-world complexity. The ESP-WROVER-KIT, combined with FreeRTOS, is an excellent platform to begin your real-time programming journey.

Happy coding!