Efficient LED blinking using RTOS software timers and basic state machine
Firmware
BY Edwin Mwiti 19-08-2025
Share:

The problem

In one of my current projects, I had the task of developing a fully functional user interface using a couple of LEDs and a single buzzer. One of the minor sub-tasks was to control the blink rate of the LED based on the system state. The requirement is that the code has to be extremely resource-efficient in terms of CPU and peripheral utilization. 

This means that I have to find a way to implement LED UI to meet the above constraints. The system is written as a hierarchical state machine(HSM). For modularization, and since I did not want to inject repeatable code in the state functions, I figured that I can use a hybrid approach where I offload some UI functions to their own RTOS tasks. In this article I am going to show you a part of this approach in terms of controlling the LED blink time (frequency) based on the current system state. 

 

We all know the “hello world” of hardware/embedded  is blinking an LED. There are a myriad of ways to blink and LED depending on the system you are using and am not going to discuss them. However, let’s say you are designing a performant system and you need your system to save every CPU cycle it can get: The best way to approach this is to use non-blocking delays during blinking. The other way is to use hardware timers with interrupts. This method is very efficient for MCUs that have inbuilt hardware timer peripherals (which is almost all of them). 

In addition however, when using FreeRTOS and you want to escape the hassle of configuring hardware timers and interrupts, FreeRTOS provides you with a very efficient implementation of software timers. 

 

FreeRTOS software timers

From the documentation of FreeRTOS, software timers are used to schedule the execution of a function at a set time in the future, or periodically with fixed frequency. The function executed by the software timer is called the software timer callback. 

The main advantage of using a software timer is that they do not at all use any processing time unless a software callback function is actually executing.

 

Software timer handle creation


Note:Am using PlatformIO here

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
TimerHandle_t blink_timer_handle;

 

In this code, we create a timer handle using the TimerHandle_t typedef.

In the setup, we create the actual timer as shown below:

/*
 * create an auto-reload timer with a period
 * of 500ms
 */
 blink_timer_handle = xTimerCreate(
            "blink_500ms_timer",
            pdMS_TO_TICKS(500),
            pdTRUE,
            NULL,
            blink_led_callback
 );

The arguments to the xTimerCreate are:

  1. “Blink_500_ms_timer” -> Timer name to use for debugging
  2. pdMS_TO_TICKS(500) -> timer period in ticks. Note that I am converting from MS to TICKS using the inbuilt pdMS_TO_TICKS macro. After the period is over, the callback function is called.
  3. pdTRUE -> set the timer to auto-reload mode. Use pdFALSE to set it to a one-shot timer.
  4. NULL -> Timer ID. Can be 0 or any other void* const parameter. Used when using the same callback across multiple timers 
  5. blink_led_callback -> the callback function to be called after the timer period elapses.

We are going to create a simple callback to toggle the state of the LED.

bool led_state = false;

/**
 * @brief Auto reload timer callback function
 */
 static void blink_led_callback(TimerHandle_t xTimer) {
     led_state = !led_state;
     digitalWrite(LED_BUILTIN, led_state);
 }

This callback simply toggles the led_state bool variable and writes the state to the LED_BUILTIN pin.

Finally in the setup function, we start the timer:

/* start 500ms timer */
if(xTimerStart(blink_timer_handle, 0) == pdPASS) {
    Serial.println("500ms Timer started OK");
} else {
    Serial.println("500ms Timer Failed to start");
}

Full code

/**
 * @file main.cpp
 * @author Edwin Mwiti
 *
 * @brief This file implements a blink using auto-reload software timer
 */


#include <Arduino.h>
#include "freertos/FreeRTOS.h"
TimerHandle_t blink_timer_handle;
bool led_state = false;


/**
 * @brief Auto reload timer callback function
 */
 static void blink_led_callback(TimerHandle_t xTimer) {
     led_state = !led_state;
     digitalWrite(LED_BUILTIN, led_state);
 }


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


    /*
     * create an auto-reload timer with a period
     * of 500ms
     */
    blink_timer_handle = xTimerCreate(
            "blink_500ms_timer",
            pdMS_TO_TICKS(500),
            pdTRUE,
            NULL,
            blink_led_callback


            );
/* start 500ms timer */
    if(xTimerStart(blink_timer_handle, 0) == pdPASS) {
        Serial.println("500ms Timer started OK");
    } else {
        Serial.println("500ms Timer Failed to start");
    }
}


void loop() {


    vTaskDelay(pdMS_TO_TICKS(1));


}

This implementation is bound to generate a 1Hz LED blink using the least processor time in FreeRTOS. 

 

Extending with a basic state machine

Let’s say you want to change the blink rate based on the current state. FreeRTOS provides a kernel API called xTimerChangePeriod(timer_handle, new_blink_period, wait_time).

 

This code below shows how I have used this API call. The idea is simple: Create a RTOS queue that stores the current state of the system. Using a queue here is advisable so that later if you create another task needing to use the system state, you can just create a separate queue and send the system state to that new queue. 

Next, you create a task that receives the current system state and checks against the possible states. 

Finally for each state, call the  xTimerChangePeriod() with the new timer period and voila!. It should work. 

/**
 * @file main.cpp
 * @author Edwin Mwiti
 *
 * @brief This file implements a blink using auto-reload software timer
 */


#include <Arduino.h>
#include "freertos/FreeRTOS.h"
TimerHandle_t blink_timer_handle;
QueueHandle_t  state_queue;


uint8_t state = 0; /* to store the current system state*/
bool led_state = false;


/**
 * @brief Auto reload timer callback function
 */
 static void blink_led_callback(TimerHandle_t xTimer) {
     led_state = !led_state;
     digitalWrite(LED_BUILTIN, led_state);
 }

 /**
  * @brief This task changes the timer period based on the received state
  */
 void led_state_check(void* arg) {
     uint8_t recvd_state;
     uint16_t blink_time;


     for (;;) {
         xQueueReceive(state_queue, &recvd_state, 0);


         /* simulate state machine function */
         switch (state) {
             case 0:
                 blink_time = 200;
                 break;


             case 1:
                 blink_time = 400;
                 break;


             case 2:
                 blink_time = 600;
                 break;


             case 3:
                 blink_time = 800;
                 break;


             case 4:
                 blink_time = 1000;
                 break;


             case 5:
                blink_time = 1200;
                 break;


             case 6:
                blink_time = 1400;
                 break;


             default:
                 break;
         }


xTimerChangePeriod(blink_timer_handle, pdMS_TO_TICKS(blink_time), 0);
     }
 }

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


    /*
     * create an auto-reload timer with a period
     * of 500ms
     */
    blink_timer_handle = xTimerCreate(
            "blink_500ms_timer",
            pdMS_TO_TICKS(500),
            pdTRUE,
            NULL,
            blink_led_callback


            );
/* start 500ms timer */
    if(xTimerStart(blink_timer_handle, 0) == pdPASS) {
        Serial.println("500ms Timer started OK");
    } else {
        Serial.println("500ms Timer Failed to start");
    }


	/**
     * create queue for state machine
     *
     */
    state_queue = xQueueCreate(5, sizeof(uint8_t ) );


    xTaskCreate(led_state_check,
                "led_task_code",
                1024,
                NULL,
                tskIDLE_PRIORITY + 1,
                NULL
                );


/**
     * create task to monitor the LED blink rate
     */
    BaseType_t status = xTaskCreate(
        led_state_check,
        "led_ui_task",
        1024,
        NULL,
        tskIDLE_PRIORITY + 1,
        NULL
    );


    if(status == pdPASS) {
        Serial.println("LED task created Okay");
    } else {
        Serial.println("LED task failed to create");
    }
}


void loop() {

    state++; /* simulate state change */


    /* wrap around the states */
     if (state == 7) {
         state = 0;
     }

    vTaskDelay(pdMS_TO_TICKS(1000));


}

This code can be extended to fit a lot of possible real world use cases

 

Resources 

Mastering FREE-RTOS Reference Manual

 

© 2023