Index
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
- Binary Semaphore: A binary semaphore can have only two states: “available” (1) or “unavailable” (0). It is typically used for mutual exclusion.
- 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
- Semaphore Declaration:
- A binary semaphore handle
binarySemaphore
is declared.
- A binary semaphore handle
- Shared Resource:
- A shared counter variable
sharedCounter
is defined asvolatile
to prevent optimization issues, allowing both tasks to safely modify it.
- A shared counter variable
- 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.
- This task increments the
- 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.
- This task decrements the
- 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()
.
- The binary semaphore is created using
Step 4: Build and Flash the Project
- 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.