Home FreeRTOS Introduction to FreeRTOS Software Timers: Beginner’s Guide

Introduction to FreeRTOS Software Timers: Beginner’s Guide

by shedboy71

Introduction

FreeRTOS software timers are a useful way to schedule actions that need to happen later or happen repeatedly without creating a dedicated task for each timed job. Once you begin working with tasks, delays, and queues, the next question often becomes how to trigger an action after a certain period of time in a cleaner way. You could keep adding more tasks, or you could constantly check elapsed time inside existing tasks, but software timers often provide a more organized solution.

A software timer is a timer managed by the FreeRTOS kernel rather than by a specific hardware timer peripheral. You define a timer, give it a period, attach a callback function, and then start it. When the timer expires, FreeRTOS runs the callback. That makes software timers useful for periodic status updates, delayed actions, timeouts, retry logic, and similar timing-based behavior.

This article explains what FreeRTOS software timers are, how they work, how they differ from hardware timers and task delays, how to create them, and what beginner mistakes to avoid.

What a software timer is

A FreeRTOS software timer is a kernel-managed timing object. It does not directly use a dedicated task written by you, and it is not the same thing as a hardware timer interrupt. Instead, it is a higher-level mechanism provided by FreeRTOS for timing events.

You create a timer with a specified period. You also provide a callback function. When the timer reaches its expiration point, the callback function is executed.

This means a software timer is not something you poll manually. Once created and started, it becomes part of the RTOS-managed timing system.

A simple mental model is this:

A software timer is like telling FreeRTOS, “After this amount of time, run this function,” or “Every this many ticks, run this function again.”

That makes it especially useful for recurring housekeeping work or delayed actions that do not need a full task of their own.

Why software timers matter

Software timers matter because they help keep embedded applications cleaner.

Suppose you want to blink a status LED every second, retry a communication attempt after a delay, or detect when a sensor has not responded within a timeout. You could create dedicated tasks for those jobs, but that may be unnecessary if the timed action is small.

Software timers let you represent timing logic directly instead of wrapping everything inside extra task loops.

They are useful because they:

  • reduce the need for dedicated timing-only tasks
  • make periodic actions easier to organize
  • support delayed one-shot actions
  • fit naturally into event-driven designs
  • help separate timing logic from main task logic

This does not mean software timers replace tasks. They simply give you another tool for timing-related behavior.

Software timers versus task delays

Beginners often first learn timing in FreeRTOS with vTaskDelay().

Example:

void LedTask(void *pvParameters)
{
    (void) pvParameters;

    for (;;)
    {
        toggle_led();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

This works fine. The task performs an action, delays, then repeats.

So why use a software timer instead?

A software timer is useful when the timed action is small and does not justify a full task. Instead of dedicating a whole task just to waiting and performing a quick callback, you can define a timer and let the kernel handle the timing.

In other words:

  • a task with vTaskDelay() is a full execution context that happens to use delays
  • a software timer is a timing object that triggers a callback

If the job is lightweight and timing-based, a software timer may be cleaner.

Software timers versus hardware timers

It is also important to separate software timers from hardware timers.

A hardware timer is a peripheral inside the microcontroller. It can count based on clock signals, generate interrupts, produce PWM, measure pulse widths, and perform low-level timing operations.

A FreeRTOS software timer is not that. It is managed by the RTOS and depends on the kernel tick and internal timer service mechanisms.

So the difference is roughly this:

  • hardware timers are low-level microcontroller peripherals
  • software timers are RTOS-managed timing objects

If you need very precise timing, waveform generation, or low-level peripheral behavior, hardware timers are usually the right tool.

If you need a clean RTOS-level delayed or periodic callback, software timers are often the better choice.

One-shot timers and auto-reload timers

FreeRTOS software timers come in two common styles.

A one-shot timer expires once. After its callback runs, it stops.

An auto-reload timer expires repeatedly. After each expiration, it automatically starts counting again.

This is a very important distinction.

A one-shot timer is useful for things like:

  • communication timeouts
  • delayed retries
  • turning something off after a fixed delay
  • scheduled one-time actions

An auto-reload timer is useful for things like:

  • periodic LED updates
  • repeating status checks
  • heartbeat signals
  • regular housekeeping actions

Understanding this difference helps you choose the right timer type from the beginning.

The timer callback function

Every software timer uses a callback function. This is the function FreeRTOS runs when the timer expires.

A basic timer callback looks like this:

void TimerCallback(TimerHandle_t xTimer)
{
    toggle_led();
}

This function receives the timer handle as a parameter. That can be useful if the callback needs to know which timer triggered it.

The callback should usually be short and efficient. Software timer callbacks are not the same as ordinary long-running tasks. They are better suited to small actions rather than large processing loops.

A good beginner rule is this:

If the action is small and quick, it may fit well in a timer callback.

If the action is large, blocking, or complicated, it may be better for the timer callback to notify a task instead.

Creating a software timer

The basic API for creating a software timer is xTimerCreate().

A typical example looks like this:

TimerHandle_t ledTimer;

ledTimer = xTimerCreate(
    "LEDTimer",
    pdMS_TO_TICKS(1000),
    pdTRUE,
    NULL,
    TimerCallback
);

This creates a timer with several key properties.

The first argument is the timer name.

The second argument is the timer period in ticks.

The third argument chooses whether the timer auto-reloads. pdTRUE means it repeats automatically.

The fourth argument is an optional timer ID pointer.

The fifth argument is the callback function.

This creates a timer that expires every 1000 milliseconds and calls TimerCallback() each time.

Breaking down the timer creation parameters

It helps to understand each parameter clearly.

Timer name

"LEDTimer"

This is mainly useful for debugging and identification. It does not affect behavior.

Timer period

pdMS_TO_TICKS(1000)

This defines the timer interval. Using pdMS_TO_TICKS() makes the code easier to read and keeps the timing expression clear.

Auto-reload setting

pdTRUE

If this is pdTRUE, the timer reloads itself and repeats periodically.

If this is pdFALSE, the timer expires once and stops.

Timer ID

NULL

This is an optional pointer that can be used to associate user data with the timer.

Callback function

TimerCallback

This is the function that runs when the timer expires.

Starting a timer

Creating a timer does not automatically make it run. After creation, you usually start it with xTimerStart().

Example:

xTimerStart(ledTimer, 0);

The second parameter is the block time for the command to the timer service system. In many simple examples, 0 is used because the start request is expected to succeed immediately.

A common beginner mistake is creating a timer and forgetting to start it. If the timer is never started, its callback will never run.

A complete beginner example

Here is a simple example of a repeating software timer that toggles an LED every second.

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

TimerHandle_t ledTimer;

void LedTimerCallback(TimerHandle_t xTimer)
{
    (void) xTimer;
    toggle_led();
}

int main(void)
{
    hardware_init();

    ledTimer = xTimerCreate(
        "LEDTimer",
        pdMS_TO_TICKS(1000),
        pdTRUE,
        NULL,
        LedTimerCallback
    );

    if (ledTimer != NULL)
    {
        xTimerStart(ledTimer, 0);
    }

    vTaskStartScheduler();

    for (;;)
    {
    }
}

This is a good first example because the behavior is easy to observe. If the LED toggles once per second, the timer is clearly working.

It also shows that a software timer can exist even if the application does not define a custom task for that behavior.

A one-shot timer example

Here is a one-shot example that turns off an LED after a delay.

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

TimerHandle_t offTimer;

void OffTimerCallback(TimerHandle_t xTimer)
{
    (void) xTimer;
    led_off();
}

int main(void)
{
    hardware_init();

    led_on();

    offTimer = xTimerCreate(
        "OffTimer",
        pdMS_TO_TICKS(3000),
        pdFALSE,
        NULL,
        OffTimerCallback
    );

    if (offTimer != NULL)
    {
        xTimerStart(offTimer, 0);
    }

    vTaskStartScheduler();

    for (;;)
    {
    }
}

This example turns the LED on during setup, then creates a timer that fires once after 3 seconds and turns it off.

This is a nice demonstration of delayed one-time action.

Using the timer ID

The timer ID parameter can be useful when one callback function handles multiple timers or when you want a timer to carry associated data.

Example:

static int ledNumber = 1;

TimerHandle_t myTimer = xTimerCreate(
    "MyTimer",
    pdMS_TO_TICKS(500),
    pdTRUE,
    &ledNumber,
    TimerCallback
);

Inside the callback, you can access the ID and use it to decide what to do.

This is helpful in more advanced designs, though beginners can safely start with NULL until the core idea is clear.

How software timers actually fit into the system

A software timer does not run by magic. It is handled by the FreeRTOS timer service mechanism. That means timer callbacks are part of RTOS-managed behavior, not independent hardware interrupt routines and not separate full application tasks written by you.

This matters because software timers are best seen as lightweight timed events inside the RTOS environment.

A useful practical takeaway is this:

A timer callback should usually be short and simple.

If you need more work done, the callback can signal a task rather than doing everything itself.

That keeps the system cleaner and avoids turning timer callbacks into overloaded pseudo-tasks.

Restarting a one-shot timer

A one-shot timer can be started again after it expires.

For example, imagine a communication timeout system. Every time new data arrives, you might restart a one-shot timer so the timeout window begins again.

This is a common design pattern. Instead of constantly checking elapsed time manually, you let the timer represent the timeout logic.

That is one of the reasons software timers are so useful in communication and interface code.

Stopping a timer

If a timer is no longer needed, it can be stopped.

Example:

xTimerStop(myTimer, 0);

This is useful if a periodic action should be paused or if a timeout should be canceled because the expected event happened.

For example, if a device responds successfully before a timeout expires, the timeout timer can be stopped.

Changing the timer period

A timer’s period can also be changed if needed.

For example, a status LED might blink slowly under normal conditions and faster during an error condition. Instead of creating two separate timers, you might change the period of one existing timer.

This makes software timers flexible for adaptive timing behavior.

A practical example: heartbeat plus timeout

Imagine a system with two timing needs:

  • blink a heartbeat LED every second
  • detect if a communication response does not arrive within 2 seconds

A clean design could use:

  • one auto-reload timer for the heartbeat
  • one one-shot timer for the response timeout

That is a strong example because it shows two different timer types serving two different purposes without requiring extra task loops just for timing.

When a software timer is a good choice

A software timer is often a good choice when:

  • the action is small and periodic
  • the action is delayed and one-time
  • the timing behavior is part of application logic
  • a full task would be excessive
  • you want cleaner timeout or retry logic

Examples include:

  • status LED blinking
  • timeout detection
  • delayed retries
  • watchdog-style reminders inside the application
  • periodic housekeeping actions

When a task may be better than a timer

A software timer is not always the best choice.

A task may be better when:

  • the work is long or complex
  • the action may block
  • the logic is better expressed as a full loop
  • the code needs its own scheduling behavior
  • the action depends on larger communication patterns

For example, if a timed event should trigger a large sensor read, complex processing, and output update, it may be better for the timer callback to notify a task rather than doing all of that directly.

A good design habit is to keep timer callbacks small.

Common beginner mistakes

One common mistake is confusing software timers with hardware timers. They are not the same thing.

Another is creating a timer and forgetting to start it.

Another is using the wrong auto-reload setting, then being surprised that the timer runs only once or keeps repeating.

Another is trying to do too much work inside the timer callback.

Another is assuming a software timer is just “a task with a delay.” It is not. It is a different RTOS mechanism.

Another is forgetting that timer creation can fail if resources are insufficient.

Another is using a timer where a simple task delay would be clearer, or using a task where a timer would be cleaner. Good design means choosing the right mechanism for the job.

 

Conclusion

FreeRTOS software timers are a practical way to handle delayed and periodic actions without creating dedicated timing-only tasks. They are managed by the RTOS, use callback functions, and support both one-shot and repeating behavior.

The key ideas are simple. You create a timer with xTimerCreate(), choose a period, choose whether it auto-reloads, provide a callback, and then start it. Auto-reload timers are useful for repeated actions such as heartbeat LEDs. One-shot timers are useful for delayed actions and timeouts.

 

Share

You may also like