Semaphore in FreeRTOS : Learn what a semaphore is in FreeRTOS with beginner-friendly examples. Understand binary semaphores, counting semaphores, and mutexes with code, FAQs, and real-world use cases.
When you start learning FreeRTOS, one of the first synchronization tools you will come across is the semaphore. If you are confused about what a semaphore is and how it works, don’t worry — this guide will explain everything step by step in simple language.
What is a Semaphore in FreeRTOS?
Think of a semaphore as a kind of signal or token that tasks use to communicate or control access to resources in FreeRTOS.
It is commonly used for:
- Synchronization – making one task wait until another task or interrupt gives a signal.
- Resource Management – controlling access to something that only one task should use at a time (like a printer, sensor, or shared variable).
Simple Real-Life Example of Semaphore in FreeRTOS
Imagine there is one bathroom (shared resource) in a house with three people (tasks).
- The bathroom has a key (semaphore).
- If the bathroom is free, the key is available.
- A person (task) must take the key before entering.
- While someone is inside, no one else can enter (they must wait until the key is returned).
- Once done, the key is placed back, and another person can take it.
👉 The key is the semaphore, ensuring only one task uses the bathroom at a time.
Types of Semaphores in FreeRTOS
- Binary Semaphore
- Has only two states: taken (0) or available (1).
- Example: Used to signal from an interrupt to a task (like “Button pressed → Task wakes up”).
- Counting Semaphore
- Has a count value (0, 1, 2, … N).
- Example: Useful when multiple identical resources are available (like 3 parking spots).
- Mutex (Mutual Exclusion Semaphore)
- Special type of binary semaphore used for protecting shared resources (like global variables or hardware).
- Has extra features like priority inheritance (helps prevent priority inversion problems).
How Semaphore work in FreeRTOS
- xSemaphoreTake() → A task tries to take the semaphore (waits if not available).
- xSemaphoreGive() → A task (or ISR) releases the semaphore (signals it’s free).
Code Example (Binary Semaphore)
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xBinarySemaphore;
void ISR_ButtonPress(void) {
// Give semaphore when button pressed
xSemaphoreGiveFromISR(xBinarySemaphore, NULL);
}
void Task_WaitForButton(void *pvParameters) {
for(;;) {
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY)) {
// Runs when button is pressed
printf("Button Pressed! Task Running...\n");
}
}
}
int main(void) {
xBinarySemaphore = xSemaphoreCreateBinary();
xTaskCreate(Task_WaitForButton, "ButtonTask", 1000, NULL, 1, NULL);
vTaskStartScheduler();
}
👉 In this example:
- The ISR gives the semaphore when the button is pressed.
- The task waits until the semaphore is available, then runs its code.
In short:
A semaphore in FreeRTOS is like a key or signal that tasks and interrupts use to coordinate actions and control shared resources safely.
1) Core idea (mental model) of Semaphore in FreeRTOS
A semaphore is a small counter inside the kernel that tasks (and sometimes ISRs) use to signal events and/or control access to something.
- When a task takes it and the counter is > 0, the counter decrements and the task continues.
- If the counter is 0, the task blocks (waits) until somebody gives it (increments the counter or “posts a signal”).
Under the hood in FreeRTOS, semaphores are implemented using the queue mechanism. So every semaphore is really a special queue with length 1 (binary/mutex) or >1 (counting).
2) The three flavors and when to use each of Semaphore in FreeRTOS
A) Binary semaphore — signal between contexts
- Counter can be 0 or 1.
- Perfect for task ↔ interrupt or task ↔ task signaling like “button pressed”, “DMA complete”, “sensor ready”.
- No ownership and no priority inheritance. Any task/ISR can
give
, any task cantake
.
B) Counting semaphore — N identical things / event counting
- Counter ranges 0..N.
- Use it either to:
- represent N identical resources (e.g., 3 UART channels, 4 buffers), or
- count events (e.g., an ISR “gives” once per pulse; a task consumes pulses by “taking”).
- No ownership, no priority inheritance.
Let’s understand both with simple examples 👇
1. Binary Semaphore
- Value: Can be either
0
or1
(just like a light switch: ON/OFF). - Use: Allows only one task to access a resource at a time.
- Once a task takes it, others must wait until it’s released.
💡 Example:
- Imagine a toilet with a lock 🚻
- If it’s free (1) → someone can go inside.
- Once someone enters, it becomes locked (0) → others must wait outside.
- When the person comes out, they unlock it (back to 1) → next person can enter.
👉 This is why it’s often used for mutual exclusion (mutex-like behavior).
2. Counting Semaphore
- Value: Can be any non-negative number (not just 0 or 1).
- Use: Allows multiple tasks to access a limited number of resources.
💡 Example:
- Imagine a parking lot with 5 spaces 🚗🚗🚗🚗🚗
- Semaphore starts at
5
. - Each car that parks → reduces count by
1
. - If count reaches
0
→ no parking space left, cars must wait. - When a car leaves → count increases by
1
(a space is free again).
- Semaphore starts at
👉 This is used when several instances of a resource are available.
C) Mutex (Mutual Exclusion Semaphore) — protect a shared resource
- Behavior looks like a binary semaphore but it has ownership and priority inheritance.
- Use for critical sections around shared state or drivers (e.g., I²C bus, shared log buffer).
- Only tasks may use mutexes (not ISRs). Mutex “owner” must be the one to release it.
The word “Mutual Exclusion” (often shortened to Mutex) means:
👉 Only one task or process can use a shared resource at a time, and others must wait until it’s free.
Example in Real Life
Imagine:
- You and your friend want to use the same pen ✏️.
- If both of you grab it at the same time, the pen might break or neither can write properly.
- So, you agree on a rule:
- Whoever takes the pen first → uses it alone.
- The other person must wait.
- Once the first person is done, they give back the pen.
👉 This “rule” is mutual exclusion — making sure only one person uses the shared resource at a time.
Priority inheritance: If a high-priority task waits on a mutex held by a lower-priority task, the kernel temporarily “boosts” the lower-priority task so it can finish and release the mutex quickly. This minimizes priority inversion.
3) API quick tour (most-used) of Semaphore in FreeRTOS
- Create:
xSemaphoreCreateBinary()
/xSemaphoreCreateBinaryStatic()
xSemaphoreCreateCounting(UBaseType_t max, UBaseType_t initial)
xSemaphoreCreateMutex()
/xSemaphoreCreateRecursiveMutex()
- Take (block up to
xTicksToWait
):xSemaphoreTake(SemaphoreHandle_t, TickType_t xTicksToWait)
xSemaphoreTakeRecursive()
(for recursive mutexes)
- Give:
xSemaphoreGive()
(tasks only)xSemaphoreGiveFromISR()
(for binary/counting semaphores from ISRs)xSemaphoreGiveRecursive()
(for recursive mutex)
Timeouts: pass
pdMS_TO_TICKS(ms)
for readability.portMAX_DELAY
means “wait forever” (if configured to allow that).
4) Blocking, timeouts, and wake-up order of Semaphore in FreeRTOS
- If a task calls
xSemaphoreTake()
and the semaphore is unavailable, it blocks for up toxTicksToWait
. - When the semaphore becomes available, the highest-priority waiting task gets it.
- If several waiting tasks have the same priority, the one waiting longest runs first (FIFO among equals).
5) ISR rules (super important) of Semaphore in FreeRTOS
- You may not block in an ISR. So never call
xSemaphoreTake()
in an ISR. - To signal from an ISR, use
xSemaphoreGiveFromISR()
(binary/counting only). - Capture the “woken” flag and request a context switch:
void ISR_Handler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBinSem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // or portEND_SWITCHING_ISR(...)
}
- Never use mutexes in ISRs.
6) Creation: initial state & static vs dynamic of Semaphore in FreeRTOS
xSemaphoreCreateBinary()
returns a semaphore with count = 0.- If you want the first
xSemaphoreTake()
to succeed immediately, give it once after creation.
- If you want the first
- Counting semaphores: choose
maxCount
andinitialCount
. - Prefer static creation on tiny MCUs to avoid heap use:
xSemaphoreCreateBinaryStatic(StaticSemaphore_t *buffer)
etc.
7) Common patterns (and code) of Semaphore in FreeRTOSd
Pattern 1: ISR → Task signal (binary semaphore)
Use when an interrupt should wake a task to do the heavy work.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
static SemaphoreHandle_t xBtnSem;
void EXTI_ButtonISR(void) // called on rising edge, for example
{
BaseType_t hpTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(xBtnSem, &hpTaskWoken);
portYIELD_FROM_ISR(hpTaskWoken);
}
static void vButtonTask(void *arg)
{
for (;;)
{
// Wait indefinitely until ISR signals
if (xSemaphoreTake(xBtnSem, portMAX_DELAY) == pdTRUE)
{
// Debounce / handle press
// ...
}
}
}
void app_init(void)
{
xBtnSem = xSemaphoreCreateBinary();
configASSERT(xBtnSem != NULL);
// Optional: if you want the first take to pass immediately
// xSemaphoreGive(xBtnSem);
xTaskCreate(vButtonTask, "Btn", 512, NULL, tskIDLE_PRIORITY + 2, NULL);
vTaskStartScheduler();
}
Pattern 2: N identical resources (counting semaphore)
Let up to 3 tasks use a resource simultaneously.
static SemaphoreHandle_t xSlots; // 3 slots
void app_init(void)
{
xSlots = xSemaphoreCreateCounting(3, 3); // max=3, initial=3 (all free)
configASSERT(xSlots);
}
void vWorker(void *arg)
{
for (;;)
{
if (xSemaphoreTake(xSlots, pdMS_TO_TICKS(1000)) == pdTRUE)
{
// Use one slot
// ... do work ...
xSemaphoreGive(xSlots); // release slot
}
else
{
// Timeout handling (no slot available)
}
}
}
Pattern 3: Protect a shared driver or data (mutex with PI)
static SemaphoreHandle_t xI2CMutex;
void app_init(void)
{
xI2CMutex = xSemaphoreCreateMutex();
configASSERT(xI2CMutex);
}
void vSensorTask(void *arg)
{
for (;;)
{
if (xSemaphoreTake(xI2CMutex, pdMS_TO_TICKS(50)) == pdTRUE)
{
// Exclusive access to I2C
// i2c_start(); i2c_txrx(); ...
xSemaphoreGive(xI2CMutex);
}
else
{
// Couldn’t get the bus in time
}
}
}
Pattern 4: Recursive mutex (same task re-enters)
Only use if a function may need to lock the same mutex multiple times from the same task (e.g., library calls that call back into you).
static SemaphoreHandle_t xCfgMutex;
void app_init(void)
{
xCfgMutex = xSemaphoreCreateRecursiveMutex();
configASSERT(xCfgMutex);
}
void config_update_A(void)
{
if (xSemaphoreTakeRecursive(xCfgMutex, pdMS_TO_TICKS(100)))
{
// ... modify part A ...
// Maybe call another function that also takes the same mutex:
config_update_B();
xSemaphoreGiveRecursive(xCfgMutex);
}
}
8) Choosing the right tool (cheatsheet) of Semaphore in FreeRTOS
- Just wake a task from an ISR?
- Use binary semaphore, or even better: Direct-to-Task Notifications (lighter & faster).
- Pool of identical resources / event bursts?
- Counting semaphore.
- Protect a shared resource in task context?
- Mutex (gets you priority inheritance).
- Set/clear multiple condition bits?
- Consider Event Groups.
- Pass data bytes/structs?
- Use a Queue (semaphores don’t carry data).
Direct-to-Task Notification vs Binary Semaphore
Task notifications are a per-task lightweight counter/bitfield. They are faster and use no extra RAM. Prefer them when a semaphore is only used to wake one specific task.
9) Pitfalls and how to avoid them of Semaphore in FreeRTOS
- Using a mutex from an ISR: not allowed. Use binary/counting sem +
xSemaphoreGiveFromISR
. - Priority inversion with a binary semaphore: binary semaphores lack priority inheritance; if you need PI, use a mutex.
- Deadlocks (two tasks take A then B in different orders): always acquire multiple mutexes in a fixed global order.
- Starvation: a low-priority task might rarely get the mutex if higher-priority tasks hold/retake it often. Keep critical sections short.
- Lost first event:
xSemaphoreCreateBinary()
starts at 0; if your task waits immediately, ensure the producer gives after creation (or you give once during init). - Overrun with counting semaphores: if the ISR “gives” faster than the consumer “takes”, the count tops out at
maxCount
; further events are dropped. Pick a suitablemaxCount
or drain faster. - Blocking forever by mistake: consider timeouts and fallback logic unless “must wait” is intended.
10) Timing & configuration tips of Semaphore in FreeRTOS
- Convert milliseconds to ticks with
pdMS_TO_TICKS(ms)
. portMAX_DELAY
means “wait forever” only if configured (depends onINCLUDE_vTaskSuspend
/ tickless).- In tiny systems, prefer static creation to avoid heap fragmentation.
- Keep critical sections as short as possible to maintain system responsiveness.
11) Quick decision flow of Semaphore in FreeRTOS
- Do I need to pass data? → Queue.
- Just need to wake a task? → Task notification (or binary sem if multiple producers).
- Need N identical tokens? → Counting sem.
- Need to protect shared resource in tasks + avoid priority inversion? → Mutex (or recursive mutex if re-entrancy is required).
Frequently Asked Questions (FAQ) on Semaphores in FreeRTOS
1. What is a semaphore in FreeRTOS?
A semaphore in FreeRTOS is a synchronization tool used by tasks and interrupts to signal events or control access to shared resources. It works like a token that tasks must take before using a resource and give back after finishing.
2. What are the types of semaphores in FreeRTOS?
FreeRTOS provides three types of semaphores:
- Binary Semaphore → Used for simple signaling (available or not).
- Counting Semaphore → Used when multiple resources are available or for event counting.
- Mutex (Mutual Exclusion) → Used to protect shared resources and prevent priority inversion.
3. What is the difference between a binary semaphore and a mutex?
- Binary Semaphore → Can be given and taken by any task or ISR, mainly used for signaling.
- Mutex → Has ownership (only the task that takes it can release it) and includes priority inheritance, making it ideal for protecting shared resources.
4. Can semaphores be used inside an ISR in FreeRTOS?
- Yes, but only binary and counting semaphores can be used inside an ISR using
xSemaphoreGiveFromISR()
. - Mutexes cannot be used inside ISRs because they require task ownership and priority inheritance.
5. What is the difference between a counting semaphore and a binary semaphore?
- Binary Semaphore → Can only have values 0 or 1 (like a simple flag).
- Counting Semaphore → Can count multiple resources or events (0 to N).
6. What happens if a task cannot take a semaphore immediately?
If a semaphore is not available, the task will either:
- Block (wait) for a specified time (
xTicksToWait
), or - Timeout if the wait period expires without success.
7. When should I use a semaphore vs a queue in FreeRTOS?
- Use a semaphore when you just need signaling or mutual exclusion.
- Use a queue when you need to send actual data between tasks or from ISR to a task.
8. Are semaphores and task notifications the same in FreeRTOS?
Not exactly.
- Semaphores are flexible and can be shared between tasks.
- Task notifications are lighter and faster but tied to a specific task.
9. What is priority inheritance in mutexes?
If a high-priority task is waiting for a mutex held by a low-priority task, FreeRTOS temporarily boosts the low-priority task’s priority. This helps it release the mutex quickly, preventing priority inversion.
10. Which semaphore should I use for protecting shared variables in FreeRTOS?
Use a mutex because it provides mutual exclusion and prevents priority inversion.
You can also Visit other tutorials of Embedded Prep
- Multithreading in C++
- Multithreading Interview Questions
- Multithreading in Operating System
- Multithreading in Java
- POSIX Threads pthread Beginner’s Guide in C/C++
- Speed Up Code using Multithreading
- Limitations of Multithreading
- Common Issues in Multithreading
- Multithreading Program with One Thread for Addition and One for Multiplication
- Advantage of Multithreading
- Disadvantages of Multithreading
- Applications of Multithreading: How Multithreading Makes Modern Software Faster and Smarter”
- Master CAN Bus Interview Questions 2025
- What Does CAN Stand For in CAN Bus?
- CAN Bus Message Filtering Explained
- CAN Bus Communication Between Nodes With Different Bit Rates
- How Does CAN Bus Handle Message Collisions
- Message Priority Using Identifiers in CAN Protocol

Mr. Raj Kumar is a highly experienced Technical Content Engineer with 7 years of dedicated expertise in the intricate field of embedded systems. At Embedded Prep, Raj is at the forefront of creating and curating high-quality technical content designed to educate and empower aspiring and seasoned professionals in the embedded domain.
Throughout his career, Raj has honed a unique skill set that bridges the gap between deep technical understanding and effective communication. His work encompasses a wide range of educational materials, including in-depth tutorials, practical guides, course modules, and insightful articles focused on embedded hardware and software solutions. He possesses a strong grasp of embedded architectures, microcontrollers, real-time operating systems (RTOS), firmware development, and various communication protocols relevant to the embedded industry.
Raj is adept at collaborating closely with subject matter experts, engineers, and instructional designers to ensure the accuracy, completeness, and pedagogical effectiveness of the content. His meticulous attention to detail and commitment to clarity are instrumental in transforming complex embedded concepts into easily digestible and engaging learning experiences. At Embedded Prep, he plays a crucial role in building a robust knowledge base that helps learners master the complexities of embedded technologies.
Leave a Reply