Learning FreeRTOS: Counting Semaphores

Scan to followLearn Embedded Together, learn and grow together

Learning FreeRTOS: Counting Semaphores

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 – Initial Exploration of 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

Detailed Explanation of FreeRTOS Message Queues

This article introduces the related content of counting semaphores in FreeRTOS.

A counting semaphore is an important synchronization mechanism in FreeRTOS that allows for event notification and resource management between tasks or between tasks and interrupt service routines (ISRs).

Counting semaphores have the following characteristics:

  • It maintains a count value that represents the number of available resources or the number of events that have occurred.
  • When a task takes the semaphore, the count value decreases; when the semaphore is given back, the count value increases.
  • The count value can be greater than 1, which is different from binary semaphores.
  • When the count value is 0, tasks attempting to take the semaphore may be blocked (depending on the calling method).

Working Principle

The core of a counting semaphore is a counter and a task waiting list:

  1. On creation: specify the initial count value
  2. Taking the semaphore:
  • If the count value > 0, the count value decreases by 1 and returns success immediately.
  • If the count value = 0, it may block or return failure immediately depending on the calling method.
  • Giving the semaphore:
    • If there are tasks waiting, wake up the highest priority task.
    • If no tasks are waiting, increase the count value by 1.

    Main Application Scenarios

    Counting semaphores have two main uses in embedded systems:

    Event Counting

    • Record the number of occurrences of events.
    • Allow a task to unblock when an event occurs while tracking the number of events that have occurred but have not yet been processed.
    • Commonly used for ISRs to send event notifications to tasks.

    Resource Management

    • Manage a limited number of identical resources.
    • The count value represents the number of currently available resources.
    • Tasks take the semaphore before using the resource and give it back after use.

    Detailed Explanation of FreeRTOS Counting Semaphore API

    Creating a Counting Semaphore

    SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, 
                                              UBaseType_t uxInitialCount );
    
    • Parameters:
      • <span>uxMaxCount</span>: The maximum count value the semaphore can reach.
      • <span>uxInitialCount</span>: The initial count value of the semaphore.
    • Return Value:
      • Success: Semaphore handle.
      • Failure: NULL (usually due to insufficient memory).

    Example:

    // Create a counting semaphore with a maximum count of 10 and an initial value of 0
    SemaphoreHandle_t xCountingSemaphore = xSemaphoreCreateCounting(10, 0);
    

    Taking the Semaphore

    FreeRTOS provides several ways to take a semaphore:

    Blocking Method

    BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
    
    • Parameters:
      • <span>xSemaphore</span>: Semaphore handle.
      • <span>xTicksToWait</span>: Maximum blocking time (in ticks), use<span>portMAX_DELAY</span><span> for infinite wait.</span>
    • Return Value:
      • pdTRUE: Successfully took the semaphore.
      • pdFALSE: Timeout without taking the semaphore.

    Non-blocking Method

    BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, 
                                     BaseType_t *pxHigherPriorityTaskWoken );
    
    • Used in interrupt service routines (ISRs).
    • Parameters: <span>pxHigherPriorityTaskWoken</span> indicates whether a higher priority task has been woken up.

    Giving the Semaphore

    Normal Give

    BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
    
    • Parameter: Semaphore handle.
    • Return Value:
      • pdTRUE: Successfully gave the semaphore.
      • pdFALSE: The semaphore count has reached the maximum value.

    Give in ISR

    BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, 
                                     BaseType_t *pxHigherPriorityTaskWoken );
    
    • Used in interrupt service routines.
    • Parameters: <span>pxHigherPriorityTaskWoken</span> indicates whether a higher priority task has been woken up.

    Example

    Event Counting Example

    // Semaphore handle declaration
    SemaphoreHandle_t xEventSemaphore;
    
    // Task function: Event handling
    void vEventHandlerTask(void *pvParameters) {
        while(1) {
            // Wait for an event to occur
            if(xSemaphoreTake(xEventSemaphore, portMAX_DELAY) == pdTRUE) {
                // Process the event
                printf("Event processed. Remaining events: %u\n", 
                       uxSemaphoreGetCount(xEventSemaphore));
            }
        }
    }
    
    // Interrupt service routine: Generate an event
    void vInterruptHandler(void) {
        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        
        // Give the semaphore (notify that an event has occurred)
        xSemaphoreGiveFromISR(xEventSemaphore, &amp;xHigherPriorityTaskWoken);
        
        // If needed, perform a context switch
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
    
    // Main function
    int main(void) {
        // Create a counting semaphore, initial value 0, maximum value 10
        xEventSemaphore = xSemaphoreCreateCounting(10, 0);
        
        // Create event handling task
        xTaskCreate(vEventHandlerTask, "EventHandler", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
        
        // Start the scheduler
        vTaskStartScheduler();
        
        while(1);
    }
    

    Resource Management Example

    // Assume we have 3 identical hardware resources (e.g., UART interfaces)
    #define MAX_UART_RESOURCES 3
    
    // Semaphore handle
    SemaphoreHandle_t xUARTSemaphore;
    
    // Task function: Using UART resource
    void vUARTUserTask(void *pvParameters) {
        while(1) {
            // Try to take the UART resource
            if(xSemaphoreTake(xUARTSemaphore, pdMS_TO_TICKS(100)) == pdTRUE) {
                // Successfully took the resource
                printf("Task %s got UART resource\n", pcTaskGetName(NULL));
                
                // Simulate using the UART resource
                vTaskDelay(pdMS_TO_TICKS(500));
                
                // Give back the resource
                xSemaphoreGive(xUARTSemaphore);
                printf("Task %s released UART resource\n", pcTaskGetName(NULL));
            } else {
                // Timeout while trying to get the resource
                printf("Task %s failed to get UART resource\n", pcTaskGetName(NULL));
            }
            
            vTaskDelay(pdMS_TO_TICKS(200));
        }
    }
    
    // Main function
    int main(void) {
        // Create a counting semaphore, initial value = maximum value = 3 (indicating 3 available resources)
        xUARTSemaphore = xSemaphoreCreateCounting(MAX_UART_RESOURCES, MAX_UART_RESOURCES);
        
        // Create multiple tasks competing for the UART resource
        xTaskCreate(vUARTUserTask, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
        xTaskCreate(vUARTUserTask, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
        xTaskCreate(vUARTUserTask, "Task3", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
        xTaskCreate(vUARTUserTask, "Task4", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
        
        // Start the scheduler
        vTaskStartScheduler();
        
        while(1);
    }
    

    Advanced

    Getting the Current Count Value

    UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
    
    • Returns the current count value of the semaphore.
    • For a semaphore that has been taken, it returns the number of currently available resources, not the number of waiting tasks.

    Semaphore Overflow Handling

    When the semaphore count reaches the maximum value, further giving the semaphore will fail. In practical applications, consider:

    1. Set the maximum count value reasonably.
    2. Check the return value of <span>xSemaphoreGive</span>.
    3. If necessary, use a larger data type (e.g., set <span>configUSE_16_BIT_TICKS</span> to 0 to use 32-bit count values).

    Comparison with Binary Semaphores

    Feature Counting Semaphore Binary Semaphore
    Count Value Range 0 to uxMaxCount 0 or 1
    Initial Value Can be specified Usually 0 (not available) or 1 (available)
    Application Scenario Multi-resource management/event counting Single resource/event notification
    Implementation Mechanism Based on counting Can be viewed as a counting semaphore with a maximum value of 1

    Considerations

    1. Set the maximum count value reasonably: Should be set based on the actual number of resources or expected maximum number of events.
    2. Avoid priority inversion: High-priority tasks waiting for a long time for low-priority tasks to release the semaphore.
    3. Usage in ISR:
    • Must use the <span>FromISR</span> version of the API.
    • Blocking calls cannot be used in ISR.
  • Deadlock Prevention:
    • Avoid tasks taking multiple semaphores in reverse order.
    • Set reasonable timeout values.
  • Resource Leak Checking:
    • Ensure each <span>Take</span> has a corresponding <span>Give</span>.
    • Pay special attention to resource release on error paths.
  • Performance Considerations:
    • Semaphore operations are relatively time-consuming; avoid using them in high-frequency code paths.
    • For high-frequency events, consider using direct task notifications instead.

    Problems and Solutions

    Semaphore Count Value Does Not Match Expectations

    Possible Reasons:

    • Some paths did not correctly release the semaphore.
    • Semaphore released in an interrupt but context switching was not handled correctly.

    Solutions:

    • Check all code paths to ensure the semaphore is released.
    • Use <span>uxSemaphoreGetCount</span> to debug the current count value.
    • Check the return value of <span>xSemaphoreGive</span>.

    System Hangs Due to Semaphore Operations

    Possible Reasons:

    • Multiple tasks waiting for each other to hold the semaphore (deadlock).
    • High-priority tasks continuously occupying the CPU, preventing low-priority tasks holding the semaphore from running (priority inversion).

    Solutions:

    • Use the same order to take multiple semaphores.
    • Consider using priority inheritance mutexes (if applicable).
    • Use timeout mechanisms to avoid infinite waiting.

    Semaphore Operations in ISR Are Ineffective

    Possible Reasons:

    • Used the non-ISR version of the API.
    • Did not handle the <span>pxHigherPriorityTaskWoken</span> flag correctly.

    Solutions:

    • Ensure to use the <span>FromISR</span> version of the API in ISR.
    • Correctly check and handle the <span>pxHigherPriorityTaskWoken</span> flag.

    Conclusion

    By using counting semaphores appropriately, efficient and reliable embedded multitasking systems can be built. Key points include:

    1. Select the appropriate semaphore type (counting or binary) based on the scenario.
    2. Correctly initialize and clean up semaphore resources.
    3. Use the correct API version in tasks and ISRs.
    4. Pay attention to thread safety and deadlock prevention.
    5. Set reasonable timeouts and error handling mechanisms.

    Learning FreeRTOS: Counting Semaphores

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

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

    Leave a Comment