HomeESP32 TutorialsESP32 FreeRTOSFreeRTOS on ESP32 (PART 5): Software Timers and Task Notifications Explained

FreeRTOS Software Timers and Task Notifications on ESP32

FreeRTOS is one of the most powerful features of the ESP32. It allows us to run multiple tasks, manage timing, and communicate between them easily. In our previous tutorials, we learned how to create tasks, set priorities, and send data between them.

In this tutorial, we will explore FreeRTOS Software Timers and Task Notifications in ESP-IDF. Both of these are extremely useful when you want to perform actions at fixed intervals or send quick signals between tasks without using queues or semaphores.

You will learn how to create software timers, handle timer callbacks, and use task notifications for lightweight communication. By the end of this tutorial, you’ll be able to build responsive and efficient ESP32 applications using these FreeRTOS features.

FreeRTOS Software Timers and Task Notifications on ESP32

Introduction to FreeRTOS Timers and Task Notifications

In FreeRTOS, timing and synchronization play a major role in task management. Sometimes we need a function to run at regular intervals — like blinking an LED, reading a sensor, or sending data to a server every few seconds. Instead of creating a separate task with vTaskDelay(), we can use FreeRTOS Software Timers.

Similarly, when two tasks need to communicate quickly and efficiently, Task Notifications offer a lightweight alternative to queues or semaphores. In this section, we’ll understand what these features are and why they are so useful in ESP32 FreeRTOS applications.

Why Timers are Used in FreeRTOS

Timers are used whenever we want to perform an action periodically or after a specific time delay. For example:

  • Turning an LED ON and OFF every second
  • Checking sensor data every 100 ms
  • Sending heartbeat messages to a server

In traditional tasks, we could use vTaskDelay() or vTaskDelayUntil(), but that keeps the task blocked during the delay. Software timers, on the other hand, free up the CPU. The timer runs in the background and calls a callback function only when the time expires.

This means less CPU usage, better timing precision, and cleaner code, especially for recurring operations.


Difference Between Hardware and Software Timers

It’s important to understand the difference between hardware and software timers on the ESP32.

FeatureHardware TimerSoftware Timer
Runs OnMicrocontroller’s internal hardware counterFreeRTOS kernel (Timer Service Task)
AccuracyVery highDepends on system tick rate
Interrupt BasedYesNo, handled in software
FlexibilityLimited (fixed count)Highly flexible
Callback ContextISR (Interrupt)Normal task context
CPU UsageMinimalUses CPU time (via kernel)
  • Hardware timers are best for precise, low-latency timing (like PWM or sensor sampling).
  • Software timers are best for general-purpose periodic tasks where timing precision in milliseconds is acceptable.

ESP32 provides both options, and FreeRTOS software timers are ideal for most scheduling and control operations.


What are Task Notifications in FreeRTOS

Task Notifications are a fast and lightweight way for tasks to signal each other. Each task in FreeRTOS has a built-in notification value that can be updated by another task or an ISR (Interrupt Service Routine).

You can think of a task notification as a direct message to a specific task, without needing to use a queue or semaphore.

Task notifications can:

  • Wake up a blocked task
  • Send simple data (like a counter or flag)
  • Replace binary or counting semaphores for efficiency

For example, one task can notify another when a timer expires, when data is ready, or when an event occurs.

They are faster and use less memory than queues or semaphores, which makes them perfect for real-time, resource-constrained ESP32 applications.

Understanding FreeRTOS Software Timers

FreeRTOS Software Timers allow you to execute a specific function automatically after a set time interval — without creating a separate task for it.
These timers are managed entirely by the FreeRTOS kernel, making them very efficient and easy to use on the ESP32.

In this section, we’ll look at how these timers work, how to create and handle their callback functions, and what happens behind the scenes when you start one.

How Software Timers Work in ESP32 FreeRTOS

A software timer in FreeRTOS works by counting the system ticks generated by the kernel. When the timer expires, FreeRTOS automatically calls a callback function that we define.

Here’s a simple explanation of what happens:

  1. We create a timer using xTimerCreate().
  2. Start the timer using xTimerStart().
  3. FreeRTOS adds the timer to a special timer list.
  4. When the timer reaches its timeout value, the kernel schedules the callback function.
  5. The callback runs in a background task, not an interrupt.

Unlike hardware timers, software timers don’t run in real time — they depend on the tick rate configured in your FreeRTOS setup (usually 1000 Hz = 1 ms tick).
That’s accurate enough for most general-purpose tasks like LED blinking, sensor reading, or timed events.


Timer Callback Function

Every software timer in FreeRTOS must have a callback function. This is the function that gets executed when the timer expires.

The function is defined as:

void vTimerCallback( TimerHandle_t xTimer );

Inside this function, you can:

  • Toggle an LED
  • Send a notification to a task
  • Restart or stop the timer
  • Perform any other small operation

Example:

void myTimerCallback(TimerHandle_t xTimer)
{
    printf("Timer expired! Performing action...\n");
}

When you create the timer, you attach this callback:

TimerHandle_t myTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, myTimerCallback);

This means your callback will run every 1000 ms (1 second) when the timer expires.

Note: The callback runs in the context of the Timer Service Task, so avoid long or blocking operations here.


One-Shot and Auto-Reload Timers

FreeRTOS supports two types of software timers:

TypeDescriptionExample Use
One-Shot TimerRuns only once when started. You must restart it manually.Delayed startup action or timeout event
Auto-Reload TimerAutomatically restarts after each expiration.Periodic LED blinking or sensor reading

You can define the type while creating the timer using this parameter:

xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, myTimerCallback);
  • Use pdTRUE for Auto-Reload Timer
  • Use pdFALSE for One-Shot Timer

This makes it very flexible — the same callback can behave differently based on the timer type.


Timer Service Task

All software timers in FreeRTOS are managed by a special Timer Service Task (also called the Timer Daemon Task).
This task runs automatically in the background and handles:

  • Timer creation and deletion
  • Starting and stopping timers
  • Executing timer callbacks

When you start the FreeRTOS scheduler, this service task is created automatically. Its priority and stack size are configured by two macros in FreeRTOSConfig.h:

#define configTIMER_TASK_PRIORITY        (configMAX_PRIORITIES - 1)
#define configTIMER_TASK_STACK_DEPTH     (configMINIMAL_STACK_SIZE * 2)

You normally don’t need to modify these settings unless your timer callbacks use a lot of stack space.

`Note: Since all timer callbacks run in this single service task, avoid writing time-consuming code inside them — it may delay other timers.

Creating and Managing Software Timers in ESP-IDF

FreeRTOS provides ready-to-use APIs for creating, starting, stopping, and resetting timers. In this section, we’ll see how to create a timer, attach a callback, control its operation, and verify that it works correctly on your ESP32 board.

Steps to Create a Software Timer

Creating a software timer in FreeRTOS involves four main steps:

  1. Define the callback function
    This function runs automatically when the timer expires.
    Example: void myTimerCallback(TimerHandle_t xTimer)
  2. Create the timer handle
    You use xTimerCreate() to create a timer and assign it to a handle.
TimerHandle_t myTimer; 
myTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, (void *)0, myTimerCallback);

Parameters explained:

  • Name: Optional name for debugging.
  • Period: Time interval in ticks.
  • Auto-reload: pdTRUE for repeating timer, pdFALSE for one-shot.
  • Timer ID: Used to identify the timer (can be NULL or any pointer).
  • Callback: Function to run when the timer expires.
  1. Check if the timer was created successfully
if (myTimer == NULL) printf("Timer creation failed!\n"); else printf("Timer created successfully!\n");
  1. Start the timer
    Once created, the timer doesn’t start automatically.
    You must start it with: xTimerStart(myTimer, 0);

That’s it! Your software timer is now active and will call the callback at the interval you set.


Starting, Stopping, and Resetting Timers

FreeRTOS gives you several functions to control timers dynamically:

FunctionDescription
xTimerStart()Starts the timer from stopped state
xTimerStop()Stops a running timer
xTimerReset()Resets the timer’s count back to zero
xTimerChangePeriod()Changes the timer period during runtime
xTimerDelete()Deletes the timer completely

Example usage:

xTimerStart(myTimer, 0);     // Start timer
vTaskDelay(pdMS_TO_TICKS(5000));
xTimerStop(myTimer, 0);      // Stop timer after 5s
xTimerReset(myTimer, 0);     // Restart timer count
xTimerChangePeriod(myTimer, pdMS_TO_TICKS(2000), 0); // Change to 2s period

These functions can be called from both tasks and ISRs, but use the FromISR versions (like xTimerStartFromISR()) inside interrupt routines.


Example Code for Software Timer in ESP32

Here’s a complete working example using ESP-IDF and FreeRTOS:

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

TimerHandle_t myTimer;
#define LED_PIN 2
bool ledLevel = false;

void myTimerCallback(TimerHandle_t xTimer)
{
    printf("Timer expired! Callback executed.\n");
	ledLevel = !ledLevel;
	gpio_set_level(LED_PIN, ledLevel);
}

void app_main(void)
{
	gpio_reset_pin(LED_PIN);
	gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);

    myTimer = xTimerCreate("MyTimer", pdMS_TO_TICKS(1000), pdTRUE, 0, myTimerCallback);

    if (myTimer == NULL)
    {
        printf("Timer creation failed!\n");
    }
    else
    {
        printf("Starting timer...\n");
        xTimerStart(myTimer, 0);
    }
}

This code demonstrates how to toggle an LED using a FreeRTOS software timer on an ESP32.

It first configures the GPIO pin as an output for the LED. Then, a periodic FreeRTOS timer is created with a 1-second interval. Each time the timer expires, the callback function runs automatically, inverting the LED state and printing a message to the console.


Demonstration: ESP32 LED Blinking Using Software Timer

The gif below shows the output of the above code.

The FreeRTOS software timer expires every 1 second. The logs are printed on the console and the LED connected to ESP32 blinks every second.

As shown above, the timer expires every 1 second, triggering the callback function. Each time the callback runs, a message is printed to the console, and the LED toggles at the same interval.


Debugging and Testing Timers

If your software timer is not working as expected, check the following:

  1. Scheduler not started:
    Timers work only after vTaskStartScheduler() is called.
    In ESP-IDF, the scheduler starts automatically when app_main() runs.
  2. Callback not printing anything:
    Make sure the timer period is correctly converted using pdMS_TO_TICKS().
  3. Timer not created:
    Always check if the handle returned by xTimerCreate() is not NULL.
  4. Stack overflow or crash:
    If your callback uses heavy operations or prints too much, increase configTIMER_TASK_STACK_DEPTH in FreeRTOSConfig.h.
  5. Multiple timers delay each other:
    Remember that all callbacks share a single Timer Service Task, so one slow callback can block others.

Tip: Use vTaskGetRunTimeStats() to see CPU usage of the timer service task if you suspect timing issues.

FreeRTOS Task Notifications

Task Notifications are one of the most powerful and lightweight features of FreeRTOS. They allow tasks to send signals or small data values directly to each other without using queues, semaphores, or other complex IPC (inter-process communication) mechanisms.

In this section, we’ll learn what task notifications are, why they’re better in many cases than queues and semaphores, and how to use them effectively in ESP32 FreeRTOS.

What are Task Notifications

In FreeRTOS, every task has an internal notification value (like a built-in variable). This value can be set, incremented, or used as a flag by other tasks or even by interrupt service routines (ISRs).

You can think of it as a private mailbox for each task.
When another task or an ISR sends a notification, the target task can:

  • Wake up if it was waiting for a notification
  • Read the notification value
  • Reset it automatically

FreeRTOS provides different APIs for notifications, such as:

xTaskNotifyGive()
xTaskNotify()
xTaskNotifyFromISR()
ulTaskNotifyTake()
xTaskNotifyWait()

These allow sending signals or small integers very efficiently — often faster and lighter than queues.


Advantages Over Queues and Semaphores

While queues and semaphores are extremely useful, task notifications are even more efficient for simple signaling or counting events.

Here’s why task notifications are often preferred:

FeatureQueueSemaphoreTask Notification
Memory UsageHigh (needs buffer)MediumVery low (no buffer)
SpeedSlowerModerateFastest
Data TypeAny (struct, bytes, etc.)Binary or count32-bit integer
Use CasePassing dataSynchronizationSignaling and counters

If you just need to notify a task that an event occurred, using a task notification is much simpler and more efficient.

Example use cases:

  • Notify a task when a timer expires
  • Signal data ready from an ISR
  • Count button presses or events

Sending and Receiving Task Notifications

Let’s see how to send and receive notifications in FreeRTOS using ESP-IDF.

Sending a Notification

From a task:

xTaskNotifyGive(taskHandle);

From an ISR:

xTaskNotifyGiveFromISR(taskHandle, NULL);

From another task with custom value:

xTaskNotify(taskHandle, 0x01, eSetValueWithOverwrite);

Receiving a Notification

In the receiving task:

ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
  • The first parameter (pdTRUE) clears the notification value after receiving.
  • The second parameter is the timeout (how long to wait).

Or you can use:

xTaskNotifyWait(0x00, 0x00, notiificationValue, portMAX_DELAY);

This waits for a notification and stores the received value in notificationValue.


Example Code for Task Notification

Here’s a simple ESP32 FreeRTOS example where one task notifies another:

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

TaskHandle_t Task1Handle;
TaskHandle_t Task2Handle;

void Task1(void *pvParameters)
{
    while (1)
    {
        printf("Task1: Sending notification to Task2\n");
        xTaskNotifyGive(Task2Handle);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void Task2(void *pvParameters)
{
    while (1)
    {
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
        printf("Task2: Notification received!\n");
    }
}

void app_main(void)
{
	  xTaskCreate(Task2, "Task2", 2048, NULL, 2, &Task2Handle);
    xTaskCreate(Task1, "Task1", 2048, NULL, 2, &Task1Handle);
}

This code demonstrates inter-task communication using FreeRTOS task notifications on the ESP32. Task1 periodically sends a notification to Task2 every second. Task2 waits for the notification and prints a message when it is received. This approach allows tasks to synchronize or signal events efficiently without using queues or semaphores.

Note: When creating tasks, ensure that the task receiving the notification is created before the task sending it. If the sender runs first, it will attempt to notify a task that doesn’t exist yet, causing errors. Alternatively, you can delay the execution of the sender task to give the receiver time to start.


Task Notification in Action

The gif below shows the output of the above code for Task Notification.

FreeRTOS Task2 only runs when the notification is sent by the Task1. The logs are printed on the ESP32 serial console.

You can see that the Task2, despite having no delay at all, runs only when the Notification is sent by the Task1. This means we can use Task Notification to synchronize the Tasks - no queue, no delay, just clean and fast communication.

Combining Timers and Task Notifications

So far, we have learned how to use FreeRTOS Software Timers and Task Notifications individually. Now, let’s see how we can combine both features to make our ESP32 projects even more efficient. Timers are great for generating periodic events, and task notifications are perfect for signaling tasks. When we combine them, we can easily trigger a specific task at regular intervals without using any delay or queue.

Using a Timer to Notify a Task

In many FreeRTOS applications, you may want a timer to notify a task whenever it expires.
For example:

  • A timer can notify a task to read a sensor every second.
  • A timer can signal a task to send data over UART or Wi-Fi.
  • A timer can toggle an LED by notifying the control task.

Here’s the basic idea:

  1. A software timer runs periodically.
  2. Inside its callback function, we use xTaskNotifyGive() or xTaskNotify() to signal another task.
  3. The task waits using ulTaskNotifyTake() until it receives the notification.
  4. Once notified, it performs the desired action.

This method is non-blocking, lightweight, and perfect for real-time systems.


Real Example – LED Blinking Using Timer and Notification

Let’s write a simple example where:

  • A timer triggers every 1 second.
  • It notifies a task.
  • The task toggles an LED each time it receives a notification.

Example Code

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

#define LED_PIN 2  
bool ledLevel = false;


TaskHandle_t ledTaskHandle;
TimerHandle_t timerHandle;

void timerCallback(TimerHandle_t xTimer)
{
    // Notify the LED task
    xTaskNotifyGive(ledTaskHandle);
}

void ledTask(void *pvParameters)
{
    gpio_reset_pin(LED_PIN);
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);

    while (1)
    {
        // Wait until timer notifies
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        // Toggle LED
        ledLevel = !ledLevel;
        gpio_set_level(LED_PIN, ledLevel);

        printf("LED toggled. State: %d\n", ledLevel);
    }
}

void app_main(void)
{
    printf("FreeRTOS Timer + Task Notification Example\n");

    // Create LED Task
    xTaskCreate(ledTask, "LED_Task", 2048, NULL, 2, &ledTaskHandle);

    // Create Timer (1 second period, auto-reload)
    timerHandle = xTimerCreate("LED_Timer", pdMS_TO_TICKS(1000), pdTRUE, NULL, timerCallback);

    if (timerHandle != NULL)
    {
        xTimerStart(timerHandle, 0);
        printf("Timer started successfully.\n");
    }
    else
    {
        printf("Timer creation failed!\n");
    }
}

This code demonstrates how to toggle an LED using a FreeRTOS software timer and task notifications on the ESP32.

  • The timer is created with a 1-second auto-reload period.
  • The callback function (timerCallback) doesn’t do any heavy work — it just notifies the LED task.
  • The LED task stays blocked in ulTaskNotifyTake(), consuming no CPU until notified.
  • When notified, it toggles the LED and prints the new state.

This is the preferred way to perform periodic actions in FreeRTOS — using timers and notifications instead of delays. It keeps your application responsive, power-efficient, and real-time safe.


Output of Timer + Task Notification Example

The gif below shows the output of the above code for Timer + Task Notification.

The LED connected to ESP32 is blinking every 1 second. Also the logs from the ledTask are printing on the console.

As you can see above, Every second, the timer triggers and sends a notification to the LED Task. The task wakes up immediately, toggles the LED, and then waits again for the next notification.

No delay functions are used — it’s purely event-driven, making it highly efficient.

Common Issues and Troubleshooting

While working with FreeRTOS Software Timers and Task Notifications on the ESP32, you might run into some common problems. These usually happen because of incorrect configuration, missing function calls, or misunderstandings of how the FreeRTOS kernel works.

In this section, we’ll go through the most frequent issues, why they happen, and how to fix them easily.

Timer Not Triggering Callback

If your software timer is not calling the callback function, check the following:

  1. Scheduler Not Started
    FreeRTOS timers run only when the scheduler is active.
    In ESP-IDF, vTaskStartScheduler() is called automatically when app_main() starts, but if you’re testing in plain FreeRTOS, make sure it’s started manually.
  2. Timer Not Started
    Creating a timer using xTimerCreate() does not start it automatically.
    You must call xTimerStart() or xTimerStartFromISR() after creation. xTimerStart(myTimer, 0);
  3. Wrong Period Setting
    Always convert milliseconds using pdMS_TO_TICKS().
    For example: pdMS_TO_TICKS(1000) // Converts 1000 ms to ticks
  4. Low Tick Rate
    If configTICK_RATE_HZ is too low (e.g., 100), your timer may appear delayed.
    Increase it to 1000 for millisecond accuracy.
  5. Timer Deleted or Overflowed
    Make sure you didn’t delete the timer by mistake or reset it incorrectly in the callback.

Tip: Add a printf() in your callback to confirm it’s being called.


Task Not Receiving Notification

If your task never receives notifications, the problem is likely in the signaling logic.
Check the following points:

  1. Incorrect Task Handle
    Ensure that the task handle used in xTaskNotifyGive() or xTaskNotify() is the same one that was returned by xTaskCreate(). xTaskCreate(taskFunc, "Task", 2048, NULL, 2, &taskHandle); // Later: xTaskNotifyGive(taskHandle);
  2. Wrong Waiting Function
    The task must use one of the following functions to wait for notifications:
    • ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    • xTaskNotifyWait(0, 0, &value, portMAX_DELAY);
    If you forget to use one of these, the task will never unblock.
  3. Notification Value Already Consumed
    If you call ulTaskNotifyTake() multiple times before a new notification is sent, it won’t receive anything.
    Make sure each notification corresponds to one Take().
  4. Notification Sent Before Task Starts Waiting
    If the timer or another task sends a notification before the receiving task starts waiting, the notification may be lost.
    You can use synchronization techniques like event groups or start timers only after all tasks are ready.

Stack Size and Timing Errors

Another common source of problems in FreeRTOS is insufficient stack size or timing configuration errors.

  1. Stack Overflow
    If your callback or task uses heavy functions like printf(), it may cause a stack overflow.
    Increase stack size when creating tasks or modify timer task configuration in FreeRTOSConfig.h. #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2)
  2. Blocking Calls Inside Callback
    Never use functions like vTaskDelay() or long loops inside timer callbacks. The Timer Service Task will freeze, blocking all other timers.
  3. Multiple Timers Interfering
    Remember that all software timers share one service task. If one callback takes too long, other timers will be delayed.
  4. Incorrect Tick Configuration
    The FreeRTOS kernel uses ticks for time measurement.
    Verify that configTICK_RATE_HZ in FreeRTOSConfig.h is properly set, usually: #define configTICK_RATE_HZ 1000
  5. Watchdog Timer (WDT) Resets
    If your callback runs too long without yielding, the ESP32’s Watchdog Timer might trigger a reset. Keep callbacks short and efficient.

Tip: Use the ESP-IDF monitor or menuconfig to adjust FreeRTOS parameters when debugging timing issues.

Conclusion

We’ve now reached the end of our ESP32 FreeRTOS Software Timers and Task Notifications tutorial. In this post, you learned how to create periodic events using software timers and use task notifications for efficient inter-task communication — without the need for queues or semaphores. These techniques make your applications more lightweight, responsive, and ideal for real-time performance on the ESP32.

Up next, we’ll explore Task Control: Suspending, Resuming, and Deleting Tasks. This tutorial will show you how to manage the execution flow of tasks dynamically — pausing them when not needed, resuming them when required, and safely deleting them to free system resources. Stay tuned as we continue to build your FreeRTOS expertise with deeper control and optimization techniques.

Browse More ESP32 FreeRTOS Tutorials

ESP32 FreeRTOS Project Download

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

FAQs – FreeRTOS Timer & Task Notification

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments