Semaphores

<span><span>binary semaphores</span></span> allows deferring tasks (tasks with high processing load and time consumption) in an interrupt to be completed in a processing (synchronization) task, ensuring that ISR() executes quickly.If the task in the interrupt is very urgent, it can be set to the highest priority, allowing the CPU to execute this processing task first after the interrupt ends, thus achieving the expectation of completing this processing in the interrupt -> low latency.

In this case, the binary semaphore acts as a messenger, being given in the interrupt with V(S), and taken in the task processing function with P(S) (this removal will not be returned), allowing the task to unblock, equivalent to using flag bits in bare-metal development for front and back-end response.
The binary semaphore functionally resembles a global flag in bare-metal systems, but it has two advantages in implementation: —1 CPU Sleep: Using a global flag, the task needs to spin in while(flag == 0), wasting CPU resources. In contrast, xSemaphoreTake() allows the task to enter a blocked state while waiting, completely yielding the CPU, achieving true low power consumption and high efficiency. – — 2 Atomicity and Thread Safety: The operation flag = 1 on a global flag may be interrupted on some non-atomic instruction CPUs. In contrast, FreeRTOS semaphore operations are kernel-level atomic operations, ensuring absolute thread safety in multi-tasking or interrupt environments.
SemaphoreHandle_t mySemaphore; // Define a semaphore handle variable
vSemaphoreCreateBinary( mySemaphore ); // Pass the variable itself, not the address

Create a queue with a depth of 1.
Except for<span>mutex semaphores (Recursive Semaphore</span>, all types of semaphores can call the function<span>xSemaphoreTake()</span>. The function xSemaphoreGiveFromISR() is a special form of xSemaphoreGive(), specifically used in interrupt service routines.
Only API functions or macros ending with “FromISR” or “FROM_ISR” can be used in interrupt service routines.
Similarly, for the API that transmits data in interrupts, herepxHigherPriorityTaskWoken serves the same purpose.

The binary semaphore can only store two states: 0 and 1. Each time a delayed processing task acquires the semaphore, it empties the queue and then executes the delayed processing task.
During this process, if another interrupt occurs, this binary semaphore can latch this state, allowing it to find the semaphore again after the delayed processing task is completed, seamlessly continuing to execute a new round of delayed processing tasks.
If multiple interrupts occur during the delayed task processing, the binary semaphore cannot distinguish the number of occurrences, and multiple interrupts will be ignored, leading to the introduction of counting semaphores.
Counting Semaphores
1. Event Counting: Each time an event occurs, the ISR will “give” (increment the count by 1). The delayed processing task will “take” once for each task processed (decrement the count by 1), and its count is initialized to 0 when created.2. Resource Management: The count of the semaphore is used to represent the number of available resources. A task must first obtain the semaphore (decrement the count by 1) to gain control of the resource. When the count reaches 0, it indicates that no resources are available. After the task completes its work using the resource, it will give (return) the semaphore (increment the count by 1), and its count is initialized to the total number of available resources when created.
“Difference from Mutex: The resources managed by counting semaphores are countable and non-specific, such as ‘3 available buffers’, without caring which specific one the task takes, only whether any are left. In contrast, a mutex manages a specific, unique resource, e.g., the usage rights of the I2C bus.”
When handlingmultiple different sources of interrupts, there are two basic issues:
- 1. Notification: Inform the task that “something has happened”.
- 2. Distinction: Inform the task “what has happened”.
Semaphores are purely notifiers and cannot distinguish interrupt sources, whilemessage queues are a more advanced “mailbox”.
- • Its information capacity: It can transmit astructured data packet (message), indicating the source of the event.
When the system needs to handle multiple different low to medium frequency interrupt events, the best architectural choice is not to set a semaphore for each event (or share a semaphore), but to use a unified message queue that transmits structured messages.
- • Semaphores are suitable for simple synchronization scenarios with a single event source, such as serial port reception using DMA, where data is placed in a buffer, and when the buffer is full, the counting semaphore is incremented by 1.
- • Message queues are suitable for complex asynchronous processing scenarios that require distinction and decoupling from multiple event sources.
For the usage rights of a unique resource, amutex semaphore should be used (mainly for resource management, which will be detailed in the next section).
Mutex Semaphores
Mutex semaphores are suitable for applications that require mutual exclusion access.
In mutual exclusion access, a mutex semaphore is like a key; when a task wants to use a resource, it must first obtain the key, and after using the resource, it must return the key so that other tasks can use the resource.
Mutex semaphores use the same API operation functions as binary semaphores.
The difference is that mutex semaphores have the property of priority inheritance.
Priority Inheritance: When a mutex semaphore is being used by a low-priority task, and a high-priority task attempts to acquire this mutex semaphore, it will be blocked. At this point, the high-priority task will elevate the priority of the low-priority task to match its own, which is the process of priority inheritance. Priority inheritance minimizes the time that high-priority tasks are in a blocked state and reduces the impact of “priority inversion” that has already occurred, making it the best strategy in system design.
Binary corresponds to counting, mutex corresponds to recursive mutex.
Recursive Mutex Semaphores
A task that has already acquired a mutex semaphore cannot acquire it again, while a task that has already acquired a recursive mutex semaphore can acquire it again, with no limit on the number of times.For example, when multiple functions of a module need to share the same internal resource.
A task using<span>xSemaphoreTakeRecursive()</span> successfully acquires the recursive mutex semaphore as many times as it does, it must use<span>xSemaphoreGiveRecursive()</span><span> to release the same number of times, e.g., releasing the recursive semaphore 5 times.</span>
AI-generated article flowchart, quite complex 😟
