# 5.6 Synchronization Mechanisms: A Comparative Guide

<span class="ng-star-inserted">FreeRTOS provides three primary mechanisms for safely managing shared resources between tasks and ISRs.</span>

### <span class="ng-star-inserted">Critical Sections: The "Big Hammer" for Protection</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">critical section</span>**<span class="ng-star-inserted"> is a section of code that is guaranteed to run to completion without being preempted by an interrupt or another task. It is the most direct and forceful way to prevent a race condition.</span>

**<span class="ng-star-inserted">How it Works:</span>**<span class="ng-star-inserted"> It works by temporarily disabling all interrupts system-wide.</span>

- <span class="ng-star-inserted">In a task, you wrap the critical code with </span>`<span class="inline-code ng-star-inserted">taskENTER_CRITICAL()</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">taskEXIT_CRITICAL()</span>`<span class="ng-star-inserted">.</span>
- <span class="ng-star-inserted">In an ISR, you use </span>`<span class="inline-code ng-star-inserted">portENTER_CRITICAL_ISR()</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">portEXIT_CRITICAL_ISR()</span>`<span class="ng-star-inserted">.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
volatile int counter = 0;

void IRAM_ATTR onTimer() {
    portENTER_CRITICAL_ISR(&timerMux);
    counter++; // This is now safe
    portEXIT_CRITICAL_ISR(&timerMux);
}

void printValues(void * parameter) {
    while (true) {
        taskENTER_CRITICAL();
        counter--; // This is now safe
        Serial.println(counter);
        taskEXIT_CRITICAL();
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> Only for protecting very short, fast operations on shared variables where other mechanisms are too slow or complex.</span>
- **<span class="ng-star-inserted">Risk:</span>**<span class="ng-star-inserted"> While a critical section is active, all interrupts are disabled. This can severely impact the real-time responsiveness of the system. </span>**<span class="ng-star-inserted">Keep critical sections as short as humanly possible.</span>**

### <span class="ng-star-inserted">Semaphores: The Best Tool for Pure Signaling</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">semaphore</span>**<span class="ng-star-inserted"> is a signaling mechanism. It does not transfer data. It is used to signal that an event has occurred or to control access to a resource. For ISR-to-task communication, a </span>**<span class="ng-star-inserted">binary semaphore</span>**<span class="ng-star-inserted"> is typically used.</span>

**<span class="ng-star-inserted">How it Works:</span>**<span class="ng-star-inserted"> Think of it as a flag.</span>

1. <span class="ng-star-inserted">A task tries to "take" the semaphore using </span>`xSemaphoreTake()`<span class="ng-star-inserted">. If the semaphore is not available, the task enters the Blocked state, consuming no CPU time.</span>
2. <span class="ng-star-inserted">An ISR, responding to a hardware event, "gives" the semaphore using </span>`<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`<span class="ng-star-inserted">.</span>
3. <span class="ng-star-inserted">Giving the semaphore unblocks the waiting task, moving it to the Ready state. The scheduler will then run the task when it is its turn.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
SemaphoreHandle_t binSemaphore = NULL;

void IRAM_ATTR onTimer() {
    xSemaphoreGiveFromISR(binSemaphore, NULL);
}

void processValues(void * parameter) {
    while (true) {
        // Wait here until the ISR gives the semaphore
        if (xSemaphoreTake(binSemaphore, portMAX_DELAY) == pdTRUE) {
            // The event occurred. Process the data.
            Serial.println("Processing data now...");
        }
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> When an ISR needs to notify a task that an event has happened (e.g., "ADC conversion is complete, the data is ready to be read"). It's a pure synchronization primitive.</span>

### <span class="ng-star-inserted">Queues: The Best Tool for Transferring Data</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">queue</span>**<span class="ng-star-inserted"> is the most powerful and often the best mechanism for ISR-to-task communication. It provides a thread-safe First-In, First-Out (FIFO) buffer to not only signal an event but to also safely transfer data from the ISR to the task.</span>

**<span class="ng-star-inserted">How it Works:</span>**

1. <span class="ng-star-inserted">A task waits to "receive" from a queue using </span>`<span class="inline-code ng-star-inserted">xQueueReceive()</span>`<span class="ng-star-inserted">. If the queue is empty, the task enters the Blocked state.</span>
2. <span class="ng-star-inserted">An ISR generates some data (e.g., reads a sensor). It then "sends" this data to the queue using </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`<span class="ng-star-inserted">. This action copies the data into the queue's buffer.</span>
3. <span class="ng-star-inserted">The act of sending data to the queue unblocks the waiting task. The task then receives the copy of the data from the queue for safe processing.</span>

<span class="ng-star-inserted">This approach is superior because the ISR and the task never access the same variable directly. They only interact via the RTOS-managed queue, which eliminates race conditions by design.</span>

**<span class="ng-star-inserted">Example:</span>**

```c
QueueHandle_t sensorQueue = NULL;

void IRAM_ATTR onTimer() {
    // Read sensor and package data into a struct
    sensorData_t data;
    data.adcValue1 = analogRead(34);
    
    // Send a COPY of the data to the queue
    xQueueSendFromISR(sensorQueue, &data, NULL);
}

void processValues(void * parameter) {
    sensorData_t receivedData;
    while (true) {
        // Wait here until data arrives in the queue
        if (xQueueReceive(sensorQueue, &receivedData, portMAX_DELAY) == pdTRUE) {
            // Safely process the received data
            Serial.println(receivedData.adcValue1);
        }
    }
}
```

- **<span class="ng-star-inserted">When to Use:</span>**<span class="ng-star-inserted"> Whenever an ISR needs to pass data to a task for processing. This is the preferred method in almost all data-generating ISR scenarios.</span>