A counting semaphore is a semaphore that can store multiple permits or pending events instead of being limited to 0 or 1.
We’ve seen Binary Semaphores (a simple flag: free/busy) and Mutexes (a single key with an owner). Both have something in common: they are binary. Their internal value can only be 0 or 1.
Things change when we don’t have one single resource, but several.
Imagine a web server that limits concurrent work to 3 clients, a parking lot with 10 spaces, or an interrupt that generates several events before a task can process them.
Here, 0 and 1 are not enough. We need to count. Today we’ll add to our arsenal the Counting Semaphores.
What is a Counting Semaphore?
We can visualize a Counting Semaphore as a basket with several keys (tokens) that are identical.
- If the semaphore has a value of
3, it means there are 3 keys available. - When a task performs a
Take, it grabs a key. 2 remain. - If the counter reaches
0, the basket is empty. The next task that tries toTakewill block until someone returns a key (Give).
FreeRTOS uses these semaphores for two main scenarios:
- Resource Management (Resource Pool): Controlling access to a limited number of identical objects.
- Event Counting: Recording how many times something has happened, to process it later without losing count.
Semaphore Comparison
At this point, we have three very similar tools but with different uses. Let’s summarize to avoid confusion:
| Type | Value Range | Ownership (Owner) | Main Use | Create function |
|---|---|---|---|---|
| Binary | 0 - 1 | No | Simple synchronization (1 event) | xSemaphoreCreateBinary |
| Mutex | 0 - 1 | Yes | Resource protection (Mutual Exclusion) | xSemaphoreCreateMutex |
| Counter | 0 - N | No | Resource pool or Event counting | xSemaphoreCreateCounting |
The xSemaphoreCreateCounting API
Creation is slightly different from the previous ones, as we need to define the limits.
SemaphoreHandle_t xSemCounter;
void setup() {
// Prototype:
// xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );
// Example: A parking lot with 5 spaces that starts empty
xSemCounter = xSemaphoreCreateCounting(5, 0);
}uxMaxCount: The maximum value it can reach (the size of the basket).uxInitialCount: How many keys the semaphore starts with.
The uxInitialCount parameter is key. Depending on whether we set it to 0 or to the maximum, the semaphore’s behavior changes completely (Events vs Resources).
Managing a resource pool
Imagine we have 3 connections available. If a fourth task tries to use one, it must wait for one to be released.
In this case:
- Max Count: 3 (Total resources).
- Initial Count: 3 (At startup, all resources are free).
The logic is:
- Task wants resource
xSemaphoreTake. (Subtracts 1). - Task uses resource.
- Task finishes
xSemaphoreGive. (Adds 1).
Practical example: the parking lot
Let’s simulate a parking lot with 3 spaces and 5 cars (tasks) trying to enter.
SemaphoreHandle_t semParking;
void CarTask(void *pvParameters) {
int carId = (int)(intptr_t)pvParameters;
for(;;) {
Serial.printf("Car %d: Trying to enter...\n", carId);
// Try to park. If full, wait.
if(xSemaphoreTake(semParking, portMAX_DELAY) == pdTRUE) {
// --- INSIDE THE PARKING LOT ---
// Read how many free spaces remain (informational only)
UBaseType_t freeSpaces = uxSemaphoreGetCount(semParking);
Serial.printf(">>> Car %d: PARKED. (%d spaces remaining)\n", carId, freeSpaces);
// Simulate parking time (random between 1 and 3 seconds)
vTaskDelay(pdMS_TO_TICKS(1000 + random(2000)));
Serial.printf("<<< Car %d: LEAVING.\n", carId);
// Return the space
xSemaphoreGive(semParking);
// Drive around before trying again
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
}
void setup() {
Serial.begin(115200);
// Create semaphore: Max 3 spaces, Initially 3 (all free)
semParking = xSemaphoreCreateCounting(3, 3);
// Create 5 cars
for(int i=0; i<5; i++) {
char name[10];
sprintf(name, "Car%d", i);
// Pass the ID as a parameter (casting trick to void*)
xTaskCreate(CarTask, name, 2048, (void*)(intptr_t)i, 1, NULL);
}
}
void loop() {}Result: You will see 3 cars enter and the 4th and 5th wait until one of the first ones leaves. The system manages the waiting automatically.
Counting events in a burst
Remember the limit of the binary semaphore: if an interrupt occurs 3 times before the task can process it, only one event is recorded.
With a Counting Semaphore, we can record those bursts.
In this case:
- Max Count: A high number (e.g., 10 or 100).
- Initial Count: 0 (At startup, no event has occurred).
The logic is:
- Interrupt
xSemaphoreGive. (Increments count: 0 -> 1 -> 2…). - Deferred task
xSemaphoreTake. (Processes one and decrements: 2 -> 1). - The task loops processing while the semaphore has charge.
Example: Fast pulses
Imagine a sensor that sends very fast pulses.
SemaphoreHandle_t semEvents;
void IRAM_ATTR isrSensor() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Each time it triggers, ADD 1 to the counter
xSemaphoreGiveFromISR(semEvents, &xHigherPriorityTaskWoken);
if(xHigherPriorityTaskWoken) portYIELD_FROM_ISR();
}
void ProcessingTask(void *param) {
for(;;) {
// Block until there is AT LEAST 1 event
if(xSemaphoreTake(semEvents, portMAX_DELAY) == pdTRUE) {
Serial.print("Processing event... ");
// Simulate some processing time
vTaskDelay(pdMS_TO_TICKS(100));
// Check if more work has accumulated
UBaseType_t pending = uxSemaphoreGetCount(semEvents);
Serial.printf("Done. (Pending in queue: %d)\n", pending);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(0, INPUT_PULLUP);
// Semaphore for events: Starts at 0. Maximum 10 accumulated.
semEvents = xSemaphoreCreateCounting(10, 0);
if (semEvents == NULL) {
Serial.println("Could not create semaphore");
return;
}
xTaskCreate(ProcessingTask, "Proc", 2048, NULL, 1, NULL);
attachInterrupt(digitalPinToInterrupt(0), isrSensor, FALLING);
}If you generate several rapid pulses, you will see the pending counter rise. The task processes them one by one until the semaphore is drained. If the counter reaches the maximum of 10, subsequent Give calls will fail, so you must size it appropriately and check the result when losing events is critical.