Extra 5% OFF Use Code: OL05
Free Shipping over ₹999

Semaphores in FreeRTOS

Semaphores are synchronization primitives used in FreeRTOS to manage access to shared resources and to coordinate the execution of tasks. They are commonly used for mutual exclusion (ensuring that only one task accesses a resource at a time) and for signaling between tasks.

Types of Semaphores

  1. Binary Semaphore: A binary semaphore can have only two states: “available” (1) or “unavailable” (0). It is typically used for mutual exclusion.
  2. Counting Semaphore: A counting semaphore can take on a range of values, which allows it to count multiple resources. It is useful for managing access to a limited number of identical resources.

Key Concepts

  • Mutual Exclusion: Prevents multiple tasks from accessing a shared resource simultaneously.
  • Task Synchronization: Allows tasks to wait for a certain condition or event before proceeding.

Example: Using Semaphores in FreeRTOS

In this example, we will create a binary semaphore to manage mutual exclusion while two tasks access a shared resource. We will simulate a shared resource by incrementing a shared counter.

Step 1: Set Up the Environment

Make sure you have ESP-IDF installed and your environment set up:

. $HOME/esp/esp-idf/export.sh

Step 2: Create a New Project

Create a new folder for your project and initialize it.

Step 3: Write the Code

Here’s a simple FreeRTOS application using a binary semaphore:

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

#define TASK_COUNT 5

// Declare a binary semaphore handle
SemaphoreHandle_t binarySemaphore;

// Shared resource (counter)
volatile int sharedCounter = 0;

// Task that increments the shared counter
void increment_task(void *pvParameter) {
    for (int i = 0; i < TASK_COUNT; i++) {
        // Wait for the semaphore to become available
        if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {
            // Critical section: Increment the shared counter
            sharedCounter++;
            printf("Incremented Counter: %d\n", sharedCounter);
            // Release the semaphore
            xSemaphoreGive(binarySemaphore);
        }
        // Simulate work
        vTaskDelay(100 / portTICK_PERIOD_MS);
    }
}

// Task that decrements the shared counter
void decrement_task(void *pvParameter) {
    for (int i = 0; i < TASK_COUNT; i++) {
        // Wait for the semaphore to become available
        if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {
            // Critical section: Decrement the shared counter
            sharedCounter--;
            printf("Decremented Counter: %d\n", sharedCounter);
            // Release the semaphore
            xSemaphoreGive(binarySemaphore);
        }
        // Simulate work
        vTaskDelay(150 / portTICK_PERIOD_MS);
    }
}

// Main application entry point
void app_main(void) {
    // Create the binary semaphore
    binarySemaphore = xSemaphoreCreateBinary();

    // Give the semaphore initially to allow the first task to run
    xSemaphoreGive(binarySemaphore);

    // Create the increment and decrement tasks
    xTaskCreate(increment_task, "Increment Task", 2048, NULL, 1, NULL);
    xTaskCreate(decrement_task, "Decrement Task", 2048, NULL, 1, NULL);
}

Explanation of the Code

  1. Semaphore Declaration:
    • A binary semaphore handle binarySemaphore is declared.
  2. Shared Resource:
    • A shared counter variable sharedCounter is defined as volatile to prevent optimization issues, allowing both tasks to safely modify it.
  3. Increment Task (increment_task):
    • This task increments the sharedCounter.
    • It waits for the semaphore to become available using xSemaphoreTake(). If successful, it enters the critical section, increments the counter, and prints the new value.
    • After finishing the critical section, it releases the semaphore with xSemaphoreGive().
    • A delay simulates some work.
  4. Decrement Task (decrement_task):
    • This task decrements the sharedCounter.
    • Similar to the increment task, it waits for the semaphore, enters the critical section, decrements the counter, prints the value, and releases the semaphore.
    • A delay simulates some work.
  5. Main Function (app_main):
    • The binary semaphore is created using xSemaphoreCreateBinary().
    • The semaphore is initially given to allow the first task to start.
    • The increment and decrement tasks are created using xTaskCreate().

Step 4: Build and Flash the Project

  1. Build the project:
idf.py build

2. Flash the project to the ESP32:

idf.py -p /dev/ttyUSB0 flash

Output Explanation

When you run the project, you will see output similar to this:

Incremented Counter: 1
Decremented Counter: 0
Incremented Counter: 1
Decremented Counter: 0
Incremented Counter: 1
Decremented Counter: 0
...

The output will show that the increment and decrement tasks are operating correctly, updating the sharedCounter safely using the binary semaphore. The use of xSemaphoreTake() ensures that only one task accesses the shared resource at a time, preventing race conditions.

Summary

In this example, we demonstrated how to use semaphores in FreeRTOS for mutual exclusion and task synchronization. The binary semaphore ensured that only one task could access the shared counter at any time, preventing potential data corruption. Understanding semaphores is essential for developing robust and safe multitasking applications in embedded systems.

    Leave a Reply

    Your email address will not be published.

    Need Help?