Learning FreeRTOS: Software Timers

Scan to FollowLearn Embedded Together, learn and grow together

Learning FreeRTOS: Software Timers

This is a series of introductory articles on FreeRTOS. While organizing my knowledge, I hope to help beginners quickly get started and master the basic principles and usage of FreeRTOS.

Quick Start with FreeRTOS – Exploring the System

The official Chinese version of the FreeRTOS website is now online!

FreeRTOS Coding Standards and Data Types

Quick Start with FreeRTOS – Task Management

Learning FreeRTOS: Detailed Explanation of Message Queues

Learning FreeRTOS: Counting Semaphores

Learning FreeRTOS: Mutex Semaphores

Learning FreeRTOS: Event Groups

Learning FreeRTOS: Task Notifications

This article introduces the timer functionality of FreeRTOS.

FreeRTOS provides two types of timer functionalities, offering flexible time management solutions for embedded real-time systems:

  1. Software Timers: Timers implemented purely in software managed by the FreeRTOS kernel
  2. Task Delays: Task-level delay functionality based on system ticks

Timers play a critical role in embedded systems and are commonly used for:

  • Periodic task scheduling
  • Timeout detection
  • Delayed execution
  • Watchdog functionality
  • Timed event handling in protocol stacks

Working Principle

Basic Concepts

FreeRTOS software timers are a high-level abstraction built on the basis of system ticks, with the following characteristics:

  • Pure Software Implementation: Does not rely on hardware timer peripherals
  • Callback Mechanism: Calls a predefined callback function when expired
  • Dynamic Creation: Can be dynamically created and deleted at runtime
  • Two Modes:
    • One-shot Timer: Triggers only once
    • Auto-reload Timer: Repeatedly triggers periodically

Implementation Mechanism

FreeRTOS implements software timers in the following way:

  1. Timer Daemon Task: A dedicated FreeRTOS task that handles timer callbacks
  2. Timer Command Queue: Used to send commands to the daemon task in a thread-safe manner
  3. Timer List: A list of created timers maintained by the kernel

When a system tick interrupt occurs, the FreeRTOS kernel checks the timer list, sends expired timer commands to the timer command queue, and the daemon task processes these commands and executes the corresponding callback functions.

Configuration and Usage

Configuring FreeRTOS to Use Software Timers

Add the following configuration in FreeRTOSConfig.h:

#define configUSE_TIMERS             1   // Enable software timer functionality
#define configTIMER_TASK_PRIORITY    (configMAX_PRIORITIES-1) // Daemon task priority
#define configTIMER_QUEUE_LENGTH     10  // Timer command queue length
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) // Daemon task stack size

Creating a Timer

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

TimerHandle_t xTimerCreate(
    const char * const pcTimerName,      // Timer name (for debugging)
    const TickType_t xTimerPeriodInTicks,// Timer period (in ticks)
    const UBaseType_t uxAutoReload,      // Auto-reload flag (pdTRUE for auto-reload)
    void * const pvTimerID,             // Timer ID
    TimerCallbackFunction_t pxCallbackFunction // Callback function
);

Example:

// Define callback function
void vTimerCallback(TimerHandle_t xTimer) 
{
    uint32_t *pulParameter = (uint32_t *)pvTimerGetTimerID(xTimer);
    // Handle timer event
}

// Create an auto-reload timer (period 1000 ticks)
uint32_t ulParameter = 123;
TimerHandle_t xAutoReloadTimer = xTimerCreate(
    "AutoReloadTimer",
    pdMS_TO_TICKS(1000),  // 1000ms
    pdTRUE,               // Auto-reload
    (void *)&ulParameter, // Parameter passed to callback function
    vTimerCallback        // Callback function
);

Starting a Timer

BaseType_t xTimerStart(TimerHandle_t xTimer, TickType_t xTicksToWait);

Parameters:

  • <span>xTimer</span>: The timer handle to start
  • <span>xTicksToWait</span>: Maximum ticks to wait if the timer command queue is full

Example:

if(xTimerStart(xAutoReloadTimer, 0) != pdPASS) 
{
    // Handle timer start failure
}

Stopping a Timer

BaseType_t xTimerStop(TimerHandle_t xTimer, TickType_t xTicksToWait);

Changing Timer Period

BaseType_t xTimerChangePeriod(
    TimerHandle_t xTimer,
    TickType_t xNewPeriod,
    TickType_t xTicksToWait
);

Deleting a Timer

BaseType_t xTimerDelete(TimerHandle_t xTimer, TickType_t xTicksToWait);

Task Delay Functionality

In addition to software timers, FreeRTOS also provides task-based delay functionality:

Simple Delay

void vTaskDelay(const TickType_t xTicksToDelay);

Example: Delay 500ms

vTaskDelay(pdMS_TO_TICKS(500));

Absolute Time Delay

void vTaskDelayUntil(TickType_t * const pxPreviousWakeTime, 
                    const TickType_t xTimeIncrement);

Example: Precise periodic task

void vPeriodicTask(void *pvParameters)
{
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xFrequency = pdMS_TO_TICKS(100); // 100ms period
    
    for(;;) 
    {
        // Perform periodic work
        
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}

Usage Recommendations

  1. Callback Function Design Principles:
  • Keep it short, avoid long blocking
  • Do not call APIs that may cause blocking (e.g., vTaskDelay)
  • Consider using queues or event groups for task communication instead of directly handling complex logic in the callback
  • Priority Considerations:
    • The priority of the timer daemon task should be higher than that of the tasks using the timer
    • However, it should not be set too high to avoid affecting higher-priority real-time tasks
  • Resource Management:
    • Delete timers that are no longer in use in a timely manner
    • Pay attention to the configuration of the timer command queue length
  • Time Precision:
    • The precision of software timers is affected by system ticks
    • For high precision requirements, consider using hardware timers
  • Debugging Tips:
    • Assign meaningful names to timers
    • Use uxTimerGetTimerNumber() to assist in debugging

    Common Issues and Solutions

    1. Timer Callback Not Executing:
    • Check if xTimerStart() was called
    • Ensure the daemon task has enough stack space
    • Verify if the timer command queue is overflowing
  • Inaccurate Timing:
    • Consider whether the system tick frequency is appropriate
    • Check if higher-priority tasks are occupying the CPU for extended periods
  • Insufficient Memory:
    • Check heap space when timer creation fails
    • Consider statically allocating timer memory
  • Timer Competition in Multi-tasking Environment:
    • Use appropriate synchronization mechanisms
    • Consider using timer IDs to distinguish different contexts

    Advanced Examples

    Hardware Watchdog with Software Timer

    TimerHandle_t xWatchdogTimer;
    
    void vWatchdogCallback(TimerHandle_t xTimer) 
    {
        // Timer expired, system may hang
        LOG_ERROR("Watchdog timeout!");
        NVIC_SystemReset();
    }
    
    void vInitWatchdog(void) 
    {
        // Create watchdog timer (one-shot mode, 5 seconds timeout)
        xWatchdogTimer = xTimerCreate(
            "Watchdog",
            pdMS_TO_TICKS(5000),
            pdFALSE,
            NULL,
            vWatchdogCallback
        );
        xTimerStart(xWatchdogTimer, 0);
    }
    
    void vPetWatchdog(void) 
    {
        // Reset watchdog timer
        xTimerReset(xWatchdogTimer, 0);
    }
    

    Managing Multiple Timers

    typedef enum 
    {
        TIMER_ID_SENSOR_READ,
        TIMER_ID_NETWORK_UPDATE,
        TIMER_ID_UI_REFRESH
    } timer_id_t;
    
    void vTimerCallback(TimerHandle_t xTimer) 
    {
        timer_id_t id = (timer_id_t)pvTimerGetTimerID(xTimer);
        
        switch(id) 
        {
            case TIMER_ID_SENSOR_READ:
                // Handle sensor reading
                break;
            case TIMER_ID_NETWORK_UPDATE:
                // Handle network update
                break;
            case TIMER_ID_UI_REFRESH:
                // Handle UI refresh
                break;
        }
    }
    
    void vInitApplicationTimers(void) 
    {
        // Create multiple timers, using IDs to distinguish
        TimerHandle_t xSensorTimer = xTimerCreate("Sensor", pdMS_TO_TICKS(100), pdTRUE, 
                                               (void *)TIMER_ID_SENSOR_READ, vTimerCallback);
        TimerHandle_t xNetworkTimer = xTimerCreate("Network", pdMS_TO_TICKS(1000), pdTRUE, 
                                                 (void *)TIMER_ID_NETWORK_UPDATE, vTimerCallback);
        TimerHandle_t xUiTimer = xTimerCreate("UI", pdMS_TO_TICKS(50), pdTRUE, 
                                            (void *)TIMER_ID_UI_REFRESH, vTimerCallback);
        
        xTimerStart(xSensorTimer, 0);
        xTimerStart(xNetworkTimer, 0);
        xTimerStart(xUiTimer, 0);
    }
    

    Performance Considerations and Optimization

    1. System Tick Frequency Selection:

    • A higher frequency provides finer timing resolution
    • But increases CPU overhead (there is processing overhead for each tick interrupt)
    • Typical values: 100Hz (10ms) to 1000Hz (1ms)
  • Timer Count Limitations:

    • Each timer requires about 40 bytes of memory (depending on architecture)
    • Active timers increase the processing burden on the daemon task
  • Low Power Design:

    • Allow the CPU to enter low power mode when idle
    • Consider using tickless mode (configure configUSE_TICKLESS_IDLE)
  • Static Memory Allocation:

    StaticTimer_t xTimerBuffer;
    TimerHandle_t xTimer = xTimerCreateStatic(..., &xTimerBuffer);
    
    • For systems with high determinism requirements, static allocated timers can be used:

    In Conclusion

    By properly using software timers and task delay functionalities, developers can build efficient and reliable real-time applications.

    The key is to choose the appropriate timing strategy based on specific application scenarios and follow best practices to ensure system stability and responsiveness.

    In actual projects, it is recommended to:

    1. Thoroughly test the behavior of timers under various load conditions
    2. Monitor the stack usage of the timer daemon task
    3. For time-critical operations, consider using hardware timers in conjunction
    4. Document the design and expected behavior of timers for easier maintenance

    Learning FreeRTOS: Software Timers

    Follow 【Learn Embedded Together】 to become better together..

    If you find this article helpful, click “Share”, “Like”, or “Recommend”!

    Leave a Comment