# Module 5 - Software Timer

# 5.1 Introduction to Real-Time Multitasking

### <span class="ng-star-inserted">What is an RTOS? Tasks and Scheduling</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">Real-Time Operating System (RTOS)</span>**<span class="ng-star-inserted"> is a specialized operating system designed for embedded systems that must process data and events within a strict, predictable timeframe. Unlike a desktop OS (like Windows or macOS) which prioritizes throughput and fairness, an RTOS prioritizes </span>**<span class="ng-star-inserted">determinism, </span>**<span class="ng-star-inserted">the ability to guarantee that a task will be completed within a specific deadline.</span>

<span class="ng-star-inserted">The fundamental unit of execution in an RTOS is a </span>**<span class="ng-star-inserted">Task</span>**<span class="ng-star-inserted">. You can think of a task as an independent function that runs in its own context, with its own stack and priority. For example, in a smart device, you might have one task for managing the Wi-Fi connection, another for reading sensor data, and a third for updating a display.</span>

<span class="ng-star-inserted">The core component of the RTOS is the </span>**<span class="ng-star-inserted">Scheduler</span>**<span class="ng-star-inserted">. Its job is to manage which task gets to use the CPU at any given moment. By rapidly switching between tasks (a process called a "context switch"), the scheduler creates the illusion that all tasks are running simultaneously, a concept known as </span>**<span class="ng-star-inserted">multitasking</span>**<span class="ng-star-inserted">. In a priority-based scheduler like the one in FreeRTOS, the scheduler will always ensure that if multiple tasks are ready to run, the one with the highest priority is the one that executes.</span>

### <span class="ng-star-inserted">The Problem with </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">: Blocking vs. Non-Blocking Operations</span>

<span class="ng-star-inserted">In simple microcontroller programming (like a basic Arduino sketch), it is common to use a </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted"> function to control timing. For example, to blink an LED every second, you might write:</span>

```c++
void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(1000); // Wait for 1 second
  digitalWrite(LED_PIN, LOW);
  delay(1000); // Wait for 1 second
}
```

<span class="ng-star-inserted">This works, but it is extremely inefficient. During the </span>`<span class="inline-code ng-star-inserted">delay(1000)</span>`<span class="ng-star-inserted"> call, the CPU is completely occupied doing nothing, it is stuck in a busy-wait loop, unable to perform any other work. This is known as a </span>**<span class="ng-star-inserted">blocking</span>**<span class="ng-star-inserted"> operation. A blocking function halts the execution of its thread or task until a specific event occurs (in this case, the passage of time).</span>

<span class="ng-star-inserted">In a multitasking RTOS environment, blocking is the enemy of responsiveness. If one task calls </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">, it effectively puts the entire system on hold (unless a higher-priority task preempts it), preventing other, potentially critical, tasks from running. The goal in an RTOS is to use </span>**<span class="ng-star-inserted">non-blocking</span>**<span class="ng-star-inserted"> operations. A task should perform its work and, if it needs to wait, it should inform the scheduler so that a lower-priority task can use the CPU in the meantime.</span>

### <span class="ng-star-inserted">The Need for Asynchronous Events</span>

<span class="ng-star-inserted">The solution to the blocking problem is to design systems around </span>**<span class="ng-star-inserted">asynchronous events</span>**<span class="ng-star-inserted">. An asynchronous event is one that occurs independently of the main program flow, allowing the system to react to it without having to constantly wait and check for it.</span>

<span class="ng-star-inserted">An RTOS provides two primary mechanisms for handling asynchronous events:</span>

1. **<span class="ng-star-inserted">Hardware Interrupts:</span>**<span class="ng-star-inserted"> These are signals sent directly from hardware peripherals to the CPU, demanding immediate attention. When an interrupt occurs, the CPU immediately pauses whatever it is doing, executes a special function called an </span>**<span class="ng-star-inserted">Interrupt Service Routine (ISR)</span>**<span class="ng-star-inserted">, and then resumes its previous work. This is ideal for high-priority, time-critical events triggered by the outside world, such as a button being pressed, data arriving on a communication bus, or a hardware timer reaching zero.</span>
2. **<span class="ng-star-inserted">Software Timers:</span>**<span class="ng-star-inserted"> These are timers managed by the RTOS itself. You can ask the RTOS to execute a specific function (a "callback") at some point in the future, either once or repeatedly. This allows you to schedule application-level events without blocking. Instead of calling </span><span class="inline-code ng-star-inserted">delay()</span><span class="ng-star-inserted">, a task can start a software timer and then continue with other work or yield control of the CPU to other tasks. The RTOS will ensure the callback function is executed at the correct time.</span>

<span class="ng-star-inserted">By using timers and interrupts, you can build complex, responsive applications where tasks spend almost no time waiting and are instead driven by the occurrence of events.</span>

# 5.2 An Overview of Asynchronous Tools in FreeRTOS

### <span class="ng-star-inserted">Software Timers: For Application-Scheduled Events</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">FreeRTOS Software Timer</span>**<span class="ng-star-inserted"> is a tool used to schedule the execution of a function at a future time. It's like setting an alarm clock within your software. When the timer expires, the RTOS automatically calls a predefined function, known as a </span>**<span class="ng-star-inserted">callback function</span>**<span class="ng-star-inserted">.</span>

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

- **<span class="ng-star-inserted">Managed by the RTOS:</span>**<span class="ng-star-inserted"> Software timers are managed by a dedicated RTOS task (the "timer daemon"). This means they do not consume CPU time while they are waiting to expire.</span>
- **<span class="ng-star-inserted">Tied to the System Tick:</span>**<span class="ng-star-inserted"> The resolution of a software timer is determined by the FreeRTOS system tick rate (</span>`<span class="inline-code ng-star-inserted">configTICK_RATE_HZ</span>`<span class="ng-star-inserted">). You cannot schedule a timer for a period shorter than one tick.</span>
- **<span class="ng-star-inserted">Use Case:</span>**<span class="ng-star-inserted"> Ideal for repetitive, low-priority, or application-level timing. For example, you might use a software timer to:</span>
    
    
    - <span class="ng-star-inserted">Read a temperature sensor every five seconds.</span>
    - <span class="ng-star-inserted">Update a clock display once per minute.</span>
    - <span class="ng-star-inserted">Turn off an LED 500ms after it was turned on.</span>

**<span class="ng-star-inserted">Types of Software Timers:</span>**

1. **<span class="ng-star-inserted">One-Shot Timer:</span>**<span class="ng-star-inserted"> Executes its callback function only once after it is started.</span>
2. **<span class="ng-star-inserted">Auto-Reload Timer:</span>**<span class="ng-star-inserted"> Executes its callback function repeatedly at a fixed interval until it is explicitly stopped.</span>

### <span class="ng-star-inserted">Hardware Interrupts: For Hardware-Triggered Events</span>

<span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">Hardware Interrupt</span>**<span class="ng-star-inserted"> is a mechanism for a hardware peripheral to signal the CPU that it needs immediate attention. Unlike a software timer, which is scheduled by your application, an interrupt is triggered by an external, physical event.</span>

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

- **<span class="ng-star-inserted">High Priority:</span>**<span class="ng-star-inserted"> An interrupt will immediately preempt the currently running code, regardless of the task's priority. The CPU will save its current state and jump to execute the ISR.</span>
- **<span class="ng-star-inserted">Hardware-Driven:</span>**<span class="ng-star-inserted"> They are generated by peripherals like GPIO pins (e.g., a button press), hardware timers (for precise timing), or communication interfaces like UART/SPI (e.g., data has arrived).</span>
- **<span class="ng-star-inserted">Use Case:</span>**<span class="ng-star-inserted"> Essential for time-critical operations and reacting to external events with minimal latency. For example, you would use a hardware interrupt to:</span>
    
    
    - <span class="ng-star-inserted">Count pulses from a motor encoder to measure its speed.</span>
    - <span class="ng-star-inserted">Immediately stop a machine when a safety limit switch is triggered.</span>
    - <span class="ng-star-inserted">Capture incoming data from a high-speed sensor before it is overwritten.</span>

<span class="ng-star-inserted">In summary, the choice between them is driven by the source of the event:</span>

- <span class="ng-star-inserted">Use a </span>**<span class="ng-star-inserted">Software Timer</span>**<span class="ng-star-inserted"> when the event is driven by the logic of your application ("</span><span class="ng-star-inserted">I need to do X in 500 milliseconds</span><span class="ng-star-inserted">").</span>
- <span class="ng-star-inserted">Use a </span>**<span class="ng-star-inserted">Hardware Interrupt</span>**<span class="ng-star-inserted"> when the event is driven by an external hardware signal that requires an immediate response ("</span><span class="ng-star-inserted">The hardware needs attention NOW</span><span class="ng-star-inserted">").</span>

# 5.3 Deep Dive: FreeRTOS Software Timers

### <span class="ng-star-inserted">Creating, Starting, and Stopping Timers</span>

<span class="ng-star-inserted">Interacting with FreeRTOS software timers is done through a standard set of API functions. The core steps are to create a timer, start it, and, if needed, stop, reset, or delete it.</span>

#### <span class="ng-star-inserted">Creating a Timer</span>

<span class="ng-star-inserted">A software timer is created using the </span>`<span class="inline-code ng-star-inserted">xTimerCreate()</span>`<span class="ng-star-inserted"> function. This function does not start the timer; it only allocates the necessary resources and returns a handle that you will use to reference the timer in other API calls.</span>

<span class="ng-star-inserted">The function signature is:</span>

```c
TimerHandle_t xTimerCreate( const char * const pcTimerName,
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );
```

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

1. `<span class="inline-code ng-star-inserted">pcTimerName</span>`<span class="ng-star-inserted">: A descriptive name for the timer, used mainly for debugging.</span>
2. `<span class="inline-code ng-star-inserted">xTimerPeriodInTicks</span>`<span class="ng-star-inserted">: The timer's period in system ticks. You can use the </span>`<span class="inline-code ng-star-inserted">pdMS_TO_TICKS()</span>`<span class="ng-star-inserted"> macro to easily convert milliseconds to ticks.</span>
3. `<span class="inline-code ng-star-inserted">uxAutoReload</span>`<span class="ng-star-inserted">: Set to </span>`<span class="inline-code ng-star-inserted">pdTRUE</span>`<span class="ng-star-inserted"> for an auto-reload timer or </span>`<span class="inline-code ng-star-inserted">pdFALSE</span>`<span class="ng-star-inserted"> for a one-shot timer.</span>
4. `<span class="inline-code ng-star-inserted">pvTimerID</span>`<span class="ng-star-inserted">: A unique identifier for the timer. This is an application-defined value that can be used within the callback function to determine which timer has expired.</span>
5. `<span class="inline-code ng-star-inserted">pxCallbackFunction</span>`<span class="ng-star-inserted">: A pointer to the function that will be executed when the timer expires.</span>

<span class="ng-star-inserted">It is crucial to </span>**<span class="ng-star-inserted">always check the return value</span>**<span class="ng-star-inserted"> of </span>`<span class="inline-code ng-star-inserted">xTimerCreate()</span>`<span class="ng-star-inserted">. If it returns </span>`<span class="inline-code ng-star-inserted">NULL</span>`<span class="ng-star-inserted">, the timer could not be created, most likely due to insufficient FreeRTOS heap memory.</span>

#### <span class="ng-star-inserted">Controlling a Timer</span>

<span class="ng-star-inserted">Once you have a valid timer handle, you can control it with the following functions:</span>

- **<span class="ng-star-inserted">To start or restart a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerStart(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This places the timer into the active state. If the timer was already running, it will be reset to its initial period.</span>
- **<span class="ng-star-inserted">To stop a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerStop(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This stops the timer from running.</span>
- **<span class="ng-star-inserted">To reset a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerReset(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This is equivalent to calling </span><span class="inline-code ng-star-inserted">xTimerStart()</span><span class="ng-star-inserted"> on a running timer. It resets the timer's period back to its starting value.</span>
- **<span class="ng-star-inserted">To delete a timer:</span>**  
    `<span class="inline-code ng-star-inserted">xTimerDelete(TimerHandle_t xTimer, TickType_t xBlockTime)</span>`  
    <span class="ng-star-inserted">This frees the memory allocated when the timer was created. Once deleted, the handle is no longer valid.</span>

<span class="ng-star-inserted">The </span><span class="inline-code ng-star-inserted">xBlockTime</span><span class="ng-star-inserted"> parameter in these functions specifies how long the calling task should wait if the command cannot be sent to the timer daemon task immediately (because its command queue is full). Using </span>`<span class="inline-code ng-star-inserted">portMAX_DELAY</span>`<span class="ng-star-inserted"> will cause the task to wait indefinitely, which is a safe option in most cases.</span>

### <span class="ng-star-inserted">3.2 One-Shot vs. Auto-Reload Timers</span>

<span class="ng-star-inserted">FreeRTOS offers two types of software timers, defined at creation time by the </span><span class="inline-code ng-star-inserted">uxAutoReload</span><span class="ng-star-inserted"> parameter.</span>

#### <span class="ng-star-inserted">One-Shot Timer (</span><span class="inline-code ng-star-inserted">`uxAutoReload` = `pdFALSE`</span><span class="ng-star-inserted">)</span>

<span class="ng-star-inserted">A one-shot timer will execute its callback function </span>**<span class="ng-star-inserted">only once</span>**<span class="ng-star-inserted"> after its period expires. It is useful for performing a single, delayed action.</span>

- **<span class="ng-star-inserted">Example Use Case:</span>**<span class="ng-star-inserted"> You want to turn off a motor 10 seconds after it has been started.</span>

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

```c
TimerHandle_t xOneShotTimer;

void vOneShotCallback(TimerHandle_t xTimer); // Forward declaration

void setup() {
  xOneShotTimer = xTimerCreate(
      "OneShot",                // Timer name
      pdMS_TO_TICKS(2000),      // 2000ms period
      pdFALSE,                  // Set as a one-shot timer
      (void *) 0,               // Timer ID = 0
      vOneShotCallback          // Callback function
  );

  if (xOneShotTimer != NULL) {
    xTimerStart(xOneShotTimer, 0);
  }
}
```

#### <span class="ng-star-inserted">Auto-Reload Timer (</span><span class="inline-code ng-star-inserted">`uxAutoReload `= `pdTRUE`</span><span class="ng-star-inserted">)</span>

<span class="ng-star-inserted">An auto-reload timer will execute its callback function </span>**<span class="ng-star-inserted">repeatedly</span>**<span class="ng-star-inserted"> at a fixed interval. After the callback is executed, the timer automatically resets and starts counting down again.</span>

- **<span class="ng-star-inserted">Example Use Case:</span>**<span class="ng-star-inserted"> You need to read a sensor and print its value every 1000 milliseconds.</span>

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

```c
TimerHandle_t xAutoReloadTimer;

void vAutoReloadCallback(TimerHandle_t xTimer); // Forward declaration

void setup() {
  xAutoReloadTimer = xTimerCreate(
      "AutoReload",             // Timer name
      pdMS_TO_TICKS(1000),      // 1000ms period
      pdTRUE,                   // Set as an auto-reload timer
      (void *) 1,               // Timer ID = 1
      vAutoReloadCallback       // Callback function
  );

  if (xAutoReloadTimer != NULL) {
    xTimerStart(xAutoReloadTimer, 0);
  }
}
```

### <span class="ng-star-inserted">3.3 Writing Effective Timer Callback Functions</span>

<span class="ng-star-inserted">The callback function is the heart of the software timer. It's the code that runs when the timer expires. To ensure system stability, it must be written carefully.</span>

<span class="ng-star-inserted">The function must have the following signature:</span>

```c
void YourCallbackName(TimerHandle_t xTimer);
```

<span class="ng-star-inserted">The single parameter, </span>`<span class="inline-code ng-star-inserted">xTimer</span>`<span class="ng-star-inserted">, is the handle of the timer that just expired. This is very useful when a single callback function is used for multiple timers. You can retrieve the Timer ID you assigned during creation to identify which timer it was.</span>

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

```c
void vTimerCallback(TimerHandle_t xTimer) {
  // Get the ID of the timer that expired
  uint32_t ulTimerID = (uint32_t) pvTimerGetTimerID(xTimer);

  // Check which timer it was and perform an action
  if (ulTimerID == 0) {
    // This was the one-shot timer
    Serial.println("One-shot timer expired.");
  } else if (ulTimerID == 1) {
    // This was the auto-reload timer
    Serial.println("Auto-reload timer expired.");
  }
}
```

#### <span class="ng-star-inserted">Rules for Writing Callback Functions</span>

<span class="ng-star-inserted">Timer callbacks execute in the context of the FreeRTOS timer daemon task, not an ISR. However, they share similar restrictions because multiple callbacks may need to execute in sequence.</span>

1. **<span class="ng-star-inserted">Keep them short and fast.</span>**<span class="ng-star-inserted"> A long-running callback will delay the execution of other pending timer callbacks.</span>
2. **<span class="ng-star-inserted">Never block.</span>**<span class="ng-star-inserted"> Do not call any function that could block, such as </span>`<span class="inline-code ng-star-inserted">vTaskDelay()</span>`<span class="ng-star-inserted"> or waiting on a semaphore or queue with a long timeout. Doing so will halt the timer daemon task, preventing any other software timers in the system from running.</span>

# 5.4 Deep Dive: ESP32 Hardware Interrupts

### <span class="ng-star-inserted">Configuring Hardware Timers on the ESP32</span>

<span class="ng-star-inserted">The ESP32 microcontroller comes with four general-purpose 64-bit hardware timers. These timers are highly precise and can be used to generate interrupts at specific intervals, independent of the RTOS scheduler.</span>

<span class="ng-star-inserted">The configuration involves four main steps:</span>

1. **<span class="ng-star-inserted">Initialize the Timer:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerBegin(uint8_t num, uint16_t prescaler, bool countUp)</span>`
    
    
    - `<span class="inline-code ng-star-inserted">num</span>`<span class="ng-star-inserted">: The timer you want to use (0 to 3).</span>
    - `<span class="inline-code ng-star-inserted">prescaler</span>`<span class="ng-star-inserted">: A value used to divide the base clock (usually 80 MHz). A prescaler of 80 will make the timer count up every 1 microsecond (80,000,000 Hz / 80 = 1,000,000 Hz).</span>
    - `<span class="inline-code ng-star-inserted">countUp</span>`<span class="ng-star-inserted">: </span><span class="inline-code ng-star-inserted">true</span><span class="ng-star-inserted"> for counting up, </span><span class="inline-code ng-star-inserted">false</span><span class="ng-star-inserted"> for counting down.</span>
2. **<span class="ng-star-inserted">Attach the ISR:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge)</span>`
    
    
    - <span class="ng-star-inserted">This function links your ISR function to the hardware timer.</span>
3. **<span class="ng-star-inserted">Set the Alarm Value:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload)</span>`
    
    
    - <span class="ng-star-inserted">This sets the counter value at which the interrupt will be generated. For a 1 MHz timer clock, an </span><span class="inline-code ng-star-inserted">alarm\_value</span><span class="ng-star-inserted"> of 1,000,000 will trigger an interrupt every second.</span>
    - <span class="ng-star-inserted">If </span><span class="inline-code ng-star-inserted">autoreload</span><span class="ng-star-inserted"> is </span><span class="inline-code ng-star-inserted">true</span><span class="ng-star-inserted">, the timer will automatically restart after the interrupt, making it periodic.</span>
4. **<span class="ng-star-inserted">Enable the Alarm:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">timerAlarmEnable(hw_timer_t *timer)</span>`
    
    
    - <span class="ng-star-inserted">This starts the timer and enables the interrupt generation.</span>

### <span class="ng-star-inserted">Writing an Interrupt Service Routine (ISR)</span>

<span class="ng-star-inserted">An ISR is a special function that the CPU executes in response to a hardware interrupt.</span>

<span class="ng-star-inserted">On the ESP32, it is critical to use the </span>`<span class="inline-code ng-star-inserted">IRAM_ATTR</span>` <span class="ng-star-inserted">attribute in the function definition:</span>

```c
void IRAM_ATTR onTimer() {
  // Your interrupt code here...
}
```

`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">IRAM_ATTR</span></strong>`<span class="ng-star-inserted"> tells the compiler to place the ISR code into the ESP32's Internal RAM (IRAM). This is essential for performance and reliability. If an ISR is in flash memory, the CPU may have to wait for the flash to be read, which can introduce unacceptable latency and jitter into the interrupt response time.</span>

**<span class="ng-star-inserted">Example of a complete hardware timer setup:</span>**

```c
// Timer handle
hw_timer_t *timer = NULL;

// The ISR function to be called
void IRAM_ATTR onTimer() {
  // Toggle an LED or perform a quick action
  digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}

void setup() {
  pinMode(LED_PIN, OUTPUT);

  // 1. Initialize timer 0 with a prescaler of 80
  timer = timerBegin(0, 80, true);

  // 2. Attach the ISR to our timer
  timerAttachInterrupt(timer, &onTimer, true);

  // 3. Set the alarm to trigger every 1,000,000 counts (1 second)
  timerAlarmWrite(timer, 1000000, true);

  // 4. Enable the alarm
  timerAlarmEnable(timer);
}
```

### <span class="ng-star-inserted">The Golden Rules of ISRs</span>

<span class="ng-star-inserted">Interrupt Service Routines are powerful but dangerous if used incorrectly. Because they can interrupt any part of your code at any time, they must follow strict rules to avoid crashing the system.</span>

1. **<span class="ng-star-inserted">Keep it Fast:</span>**<span class="ng-star-inserted"> An ISR must execute as quickly as possible. The longer an ISR runs, the more it delays the main program and other interrupts. The best ISRs do the absolute minimum work required, such as setting a flag or sending data to a queue, and then exit. Defer all complex data processing to a regular FreeRTOS task.</span>
2. **<span class="ng-star-inserted">Never Block:</span>**<span class="ng-star-inserted"> An ISR </span>**<span class="ng-star-inserted">must never, ever block</span>**<span class="ng-star-inserted">. This means you cannot call functions like </span>`<span class="inline-code ng-star-inserted">vTaskDelay()</span>`<span class="ng-star-inserted">, </span>`<span class="inline-code ng-star-inserted">delay()</span>`<span class="ng-star-inserted">, or wait for a semaphore, mutex, or queue. The system is in a special interrupt context, and attempting to block will lead to a system crash.</span>
3. **<span class="ng-star-inserted">Use ISR-Safe API Functions:</span>**<span class="ng-star-inserted"> When you need to interact with FreeRTOS from an ISR (for example, to signal a task), you </span>**<span class="ng-star-inserted">must</span>**<span class="ng-star-inserted"> use the special ISR-safe version of the API function. These functions are specially designed to be non-blocking and safe to call from an interrupt context. They are easily recognizable as they all end with the suffix </span>`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">...FromISR()</span></strong>`<span class="ng-star-inserted">.</span>
    
    
    - **<span class="ng-star-inserted">Correct:</span>** `<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`
    - **<span class="ng-star-inserted">Incorrect:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xSemaphoreGive()</span>`
    - **<span class="ng-star-inserted">Correct:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`
    - **<span class="ng-star-inserted">Incorrect:</span>**<span class="ng-star-inserted"> </span>`<span class="inline-code ng-star-inserted">xQueueSend()</span>`
4. **<span class="ng-star-inserted">Be Careful with Global Variables:</span>**<span class="ng-star-inserted"> If an ISR modifies a global variable that is also accessed by a task, you must protect that variable to prevent data corruption (a "race condition"). The primary method for this is using a </span>**<span class="ng-star-inserted">critical section</span>**<span class="ng-star-inserted">, which will be discussed in the next part.</span>

# 5.5 The Core Challenge: ISRs and Tasks Synchronization

### <span class="ng-star-inserted">Understanding the Problem: Shared Data and Race Conditions</span>

<span class="ng-star-inserted">When a hardware interrupt occurs, the CPU immediately stops executing the current task and jumps to the ISR. This can happen at any time, even in the middle of a single line of C code that takes multiple machine instructions to execute. If the ISR and the task both access the same global variable, the system is vulnerable to a </span>**<span class="ng-star-inserted">race condition</span>**<span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">A race condition is an undesirable situation that occurs when the outcome of a process depends on the uncontrollable sequence of events. In our case, it's a bug that occurs when the timing of the interrupt corrupts shared data.</span>

**<span class="ng-star-inserted">A Classic Example of a Race Condition:</span>**  
<span class="ng-star-inserted">Imagine a global variable </span>`<span class="inline-code ng-star-inserted">volatile int counter = 0;</span>`<span class="ng-star-inserted">.</span>

- <span class="ng-star-inserted">An ISR, triggered by a timer, is programmed to increment the counter: </span><span class="inline-code ng-star-inserted">`counter++`;</span><span class="ng-star-inserted">.</span>
- <span class="ng-star-inserted">A task in the main application is programmed to decrement it: </span><span class="inline-code ng-star-inserted">`counter--`;</span><span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">Let's trace a potential failure scenario. Assume </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> is currently </span>`<span class="inline-code ng-star-inserted">10</span>`<span class="ng-star-inserted">.</span>

1. **<span class="ng-star-inserted">Task Executes:</span>**<span class="ng-star-inserted"> The task reads the value of </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> (10) into a CPU register.</span>
2. **<span class="ng-star-inserted">Task Calculates:</span>**<span class="ng-star-inserted"> The CPU calculates the new value, </span>`<span class="inline-code ng-star-inserted">10 - 1 = 9</span>`<span class="ng-star-inserted">.</span>
3. **<span class="ng-star-inserted">CONTEXT SWITCH (INTERRUPT):</span>**<span class="ng-star-inserted"> Before the task can write the value </span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted"> back to the </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> variable in memory, a hardware interrupt occurs!</span>
4. **<span class="ng-star-inserted">ISR Executes:</span>**<span class="ng-star-inserted"> The ISR runs. It reads the value of </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> from memory (which is still </span><span class="inline-code ng-star-inserted">10</span><span class="ng-star-inserted">).</span>
5. **<span class="ng-star-inserted">ISR Calculates:</span>**<span class="ng-star-inserted"> The ISR calculates </span>`<span class="inline-code ng-star-inserted">10 + 1 = 11</span>`<span class="ng-star-inserted">.</span>
6. **<span class="ng-star-inserted">ISR Writes:</span>**<span class="ng-star-inserted"> The ISR writes the value </span>`<span class="inline-code ng-star-inserted">11</span>`<span class="ng-star-inserted"> back to the </span><span class="inline-code ng-star-inserted">counter</span><span class="ng-star-inserted"> variable in memory.</span>
7. **<span class="ng-star-inserted">ISR Finishes:</span>**<span class="ng-star-inserted"> The interrupt is complete, and the CPU returns control to the task, restoring its state exactly where it left off.</span>
8. **<span class="ng-star-inserted">Task Resumes:</span>**<span class="ng-star-inserted"> The task is completely unaware that it was interrupted. Its next step is to write its calculated value (</span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted">) back to the </span>`<span class="inline-code ng-star-inserted">counter</span>`<span class="ng-star-inserted"> variable.</span>
9. **<span class="ng-star-inserted">Corruption:</span>**<span class="ng-star-inserted"> The value </span>`<span class="inline-code ng-star-inserted">11</span>`<span class="ng-star-inserted"> that the ISR correctly calculated is now overwritten with </span>`<span class="inline-code ng-star-inserted">9</span>`<span class="ng-star-inserted">. The increment operation has been completely lost.</span>

<span class="ng-star-inserted">This is the fundamental problem of concurrency: protecting shared resources from uncontrolled, simultaneous access.</span>

### <span class="ng-star-inserted">The ISR Context and </span>`<span class="inline-code ng-star-inserted">...FromISR()</span>`<span class="ng-star-inserted"> Functions</span>

<span class="ng-star-inserted">To solve the synchronization problem, we need tools to manage access to shared data. However, as we learned in Part 2, ISRs operate in a special </span>**<span class="ng-star-inserted">interrupt context</span>**<span class="ng-star-inserted">. They are not tasks and are not managed by the RTOS scheduler. This leads to a critical rule: </span>**<span class="ng-star-inserted">an ISR can never block</span>**<span class="ng-star-inserted">.</span>

<span class="ng-star-inserted">If an ISR tried to wait for a resource (like calling </span>`<span class="inline-code ng-star-inserted">xQueueSend()</span>`<span class="ng-star-inserted"> and the queue was full), it would effectively block. But since the ISR is not a task, the scheduler has no other context to switch to. The entire system would freeze, leading to a crash.</span>

<span class="ng-star-inserted">To solve this, FreeRTOS provides a special set of ISR-safe functions that are non-blocking. You can recognize them by their` `</span>`<strong class="ng-star-inserted"><span class="inline-code ng-star-inserted">...FromISR()</span></strong>`<span class="ng-star-inserted"> suffix.</span>

- `<span class="inline-code ng-star-inserted">xQueueSend()</span>`<span class="ng-star-inserted"> -&gt; </span>`<span class="inline-code ng-star-inserted">xQueueSendFromISR()</span>`
- `<span class="inline-code ng-star-inserted">xSemaphoreGive()</span>`<span class="ng-star-inserted"> -&gt; </span>`<span class="inline-code ng-star-inserted">xSemaphoreGiveFromISR()</span>`

<span class="ng-star-inserted">These functions include an optional parameter, </span>`<span class="inline-code ng-star-inserted">pxHigherPriorityTaskWoken</span>`<span class="ng-star-inserted">. An ISR uses this parameter to inform the RTOS if its action (e.g., giving a semaphore) has unblocked a task that has a higher priority than the task that was originally interrupted. If so, the RTOS can perform an immediate context switch to the higher-priority task as soon as the ISR finishes, ensuring the system remains as responsive as possible.</span>

# 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>

# 5.7 Choosing the Right Tool: A Practical Comparison

<span class="ng-star-inserted">Deciding which synchronization mechanism to use is a key skill in embedded programming. Use the following table and questions as a guide.</span>

<table border="1" id="bkmrk-mechanism-purpose-tr" style="border-collapse: collapse; width: 100%; height: 303.953px;"><colgroup><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col><col style="width: 20.0238%;"></col></colgroup><tbody><tr style="height: 29.7969px;"><td class="ng-star-inserted" style="height: 29.7969px;">**<span class="ng-star-inserted">Mechanism</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Purpose</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Transfers Data?</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">When to Use</span>**</td><td class="ng-star-inserted align-center" style="height: 29.7969px;">**<span class="ng-star-inserted">Primary Risk</span>**</td></tr><tr style="height: 96.9844px;"><td class="ng-star-inserted" style="height: 96.9844px;">**<span class="ng-star-inserted">Critical Section</span>**</td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Mutual Exclusion</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">No</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Protecting a few lines of code that modify a shared variable. Must be extremely fast.</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Halts system responsiveness by disabling all interrupts. Can easily break real-time deadlines.</span></td></tr><tr style="height: 80.1875px;"><td class="ng-star-inserted" style="height: 80.1875px;">**<span class="ng-star-inserted">Semaphore</span>**</td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Signaling</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">No</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Notifying a task that a specific event has occurred. Deferring ISR work to a task.</span></td><td class="ng-star-inserted align-center" style="height: 80.1875px;"><span class="ng-star-inserted">Does not help with transferring the actual data associated with the event.</span></td></tr><tr style="height: 96.9844px;"><td class="ng-star-inserted" style="height: 96.9844px;">**<span class="ng-star-inserted">Queue</span>**</td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Data Transfer</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Yes</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Sending data of any type from an ISR to a task for processing.</span></td><td class="ng-star-inserted align-center" style="height: 96.9844px;"><span class="ng-star-inserted">Minor overhead for copying data into the queue. May not be suitable for very large data structures.</span></td></tr></tbody></table>

<div _ngcontent-ng-c4139270029="" class="table-container ng-star-inserted" id="bkmrk-"></div>### <span class="ng-star-inserted">Decision-Making Guide</span>

<span class="ng-star-inserted">When designing an interaction between an ISR and a task, ask yourself these questions:</span>

1. **<span class="ng-star-inserted">Do I need to pass data from the ISR to the task?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> Use a </span>**<span class="ng-star-inserted">Queue</span>**<span class="ng-star-inserted">. This is the safest and most robust solution for transferring data.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Go to question 2.</span>
2. **<span class="ng-star-inserted">Is my goal simply to wake up a task to do some work when an interrupt occurs?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> Use a </span>**<span class="ng-star-inserted">Semaphore</span>**<span class="ng-star-inserted">. It is a lightweight and highly efficient signaling mechanism.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Go to question 3.</span>
3. **<span class="ng-star-inserted">Do I only need to protect a single, simple variable (like a counter or flag) during a very quick read-modify-write operation?</span>**
    
    
    - **<span class="ng-star-inserted">Yes:</span>**<span class="ng-star-inserted"> A </span>**<span class="ng-star-inserted">Critical Section</span>**<span class="ng-star-inserted"> is an option, but only if the operation is genuinely just a few lines of code. Be aware of the impact on system latency.</span>
    - **<span class="ng-star-inserted">No:</span>**<span class="ng-star-inserted"> Re-evaluate your design. You likely need a semaphore or a queue.</span>

<span class="ng-star-inserted">In modern RTOS development, </span>**<span class="ng-star-inserted">Queues and Semaphores are almost always preferred over Critical Sections</span>**<span class="ng-star-inserted"> for managing ISR-task interactions. They provide cleaner, safer, and more scalable solutions that have less impact on the overall real-time performance of your system.</span>

# 5.8 Advanced Project: A Multi-Sensor Data Logger

<span class="ng-star-inserted">In this chapter, we will build a complete data logging application that utilizes all the core concepts we have learned: hardware interrupts for precise data acquisition, queues for safe data transfer, multiple tasks with different priorities for processing and logging, and a software timer for periodic status checks.</span>

### <span class="ng-star-inserted">Project Goal</span>

<span class="ng-star-inserted">We will create a system that performs the following actions:</span>

1. <span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">hardware timer</span>**<span class="ng-star-inserted"> will generate an interrupt every 200 milliseconds.</span>
2. <span class="ng-star-inserted">The </span>**<span class="ng-star-inserted">Interrupt Service Routine (ISR)</span>**<span class="ng-star-inserted"> will simulate reading data from two sensors (e.g., temperature and humidity) and will send this data package to a queue.</span>
3. <span class="ng-star-inserted">A high-priority </span>**<span class="ng-star-inserted">"Processing Task"</span>**<span class="ng-star-inserted"> will wait for data to arrive in the queue. When it does, it will perform a simple calculation (e.g., calculate an average) and place the result into a second queue.</span>
4. <span class="ng-star-inserted">A low-priority </span>**<span class="ng-star-inserted">"Logging Task"</span>**<span class="ng-star-inserted"> will wait for results to arrive in the second queue and print them to the Serial Monitor.</span>
5. <span class="ng-star-inserted">A </span>**<span class="ng-star-inserted">software timer</span>**<span class="ng-star-inserted"> will fire every 5 seconds to print a "System OK" status message, demonstrating a non-critical, periodic background action.</span>

<span class="ng-star-inserted">This architecture is a common and robust pattern in embedded systems. It decouples the time-critical data acquisition (in the ISR) from the less critical data processing and logging (in the tasks), ensuring the system remains responsive.</span>

### <span class="ng-star-inserted">You Will Need</span>

- <span class="ng-star-inserted">An ESP32 development board.</span>
- <span class="ng-star-inserted">The Arduino IDE with the ESP32 board package installed.</span>

```c++
#include <Arduino.h>

// Define task priorities
#define PROCESSING_TASK_PRIORITY 2
#define LOGGING_TASK_PRIORITY    1

// Define handles for RTOS objects
QueueHandle_t sensorDataQueue;
QueueHandle_t resultQueue;
TimerHandle_t systemHealthTimer;
hw_timer_t *hardwareTimer = NULL;

// Data structure to hold raw sensor readings
typedef struct {
  int temperature;
  int humidity;
} SensorData;

// Data structure for the processed result
typedef struct {
  float averageValue;
} ProcessedResult;

// --- Interrupt Service Routine ---
// This function runs every time the hardware timer fires.
// It must be fast and non-blocking.
void IRAM_ATTR onTimer() {
  // Simulate reading sensor data
  SensorData data;
  data.temperature = random(20, 30); // Simulate temp between 20-29°C
  data.humidity = random(40, 60);    // Simulate humidity between 40-59%

  // Send a COPY of the data to the queue.
  // Use the ISR-safe version of the function.
  xQueueSendFromISR(sensorDataQueue, &data, NULL);
}

// --- Software Timer Callback ---
// This function runs every time the software timer expires.
void systemHealthCallback(TimerHandle_t xTimer) {
  Serial.println("[HEALTH] System OK");
}

// --- High-Priority Task: Processing ---
void processingTask(void *parameter) {
  SensorData receivedData;
  ProcessedResult result;

  while (true) {
    // Wait indefinitely until an item arrives in the sensorDataQueue
    if (xQueueReceive(sensorDataQueue, &receivedData, portMAX_DELAY) == pdPASS) {
      // We have data, now process it.
      Serial.println("[PROCESS] Data received. Calculating average...");
      
      result.averageValue = (receivedData.temperature + receivedData.humidity) / 2.0;

      // Send the result to the logging task via the resultQueue
      xQueueSend(resultQueue, &result, portMAX_DELAY);
    }
  }
}

// --- Low-Priority Task: Logging ---
void loggingTask(void *parameter) {
  ProcessedResult receivedResult;

  while (true) {
    // Wait indefinitely until a result arrives in the resultQueue
    if (xQueueReceive(resultQueue, &receivedResult, portMAX_DELAY) == pdPASS) {
      // We have a result, now log it to the console.
      Serial.print("[LOG] Processed Average: ");
      Serial.println(receivedResult.averageValue);
      Serial.println("--------------------");
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("--- Multi-Sensor Data Logger ---");

  // 1. Create the queues
  // Queue to hold 10 SensorData structs
  sensorDataQueue = xQueueCreate(10, sizeof(SensorData));
  // Queue to hold 5 ProcessedResult structs
  resultQueue = xQueueCreate(5, sizeof(ProcessedResult));

  // 2. Create the software timer for system health checks
  systemHealthTimer = xTimerCreate(
      "HealthTimer",          // Name
      pdMS_TO_TICKS(5000),    // 5000ms period
      pdTRUE,                 // Auto-reload
      (void *) 0,             // Timer ID
      systemHealthCallback    // Callback function
  );

  // 3. Create the tasks
  xTaskCreate(
      processingTask,         // Function to implement the task
      "Processing Task",      // Name of the task
      2048,                   // Stack size in words
      NULL,                   // Task input parameter
      PROCESSING_TASK_PRIORITY, // Priority of the task
      NULL                    // Task handle
  );

  xTaskCreate(
      loggingTask,
      "Logging Task",
      2048,
      NULL,
      LOGGING_TASK_PRIORITY,
      NULL
  );

  // 4. Configure the hardware timer
  // Use a prescaler of 80 to get a 1MHz clock (80MHz / 80)
  hardwareTimer = timerBegin(0, 80, true);
  timerAttachInterrupt(hardwareTimer, &onTimer, true);
  // Set alarm for 200,000 counts (200ms at 1MHz)
  timerAlarmWrite(hardwareTimer, 200000, true); 
  timerAlarmEnable(hardwareTimer);

  // 5. Start the software timer
  if (systemHealthTimer != NULL) {
    xTimerStart(systemHealthTimer, 0);
  }
  
  Serial.println("System initialized. Starting data acquisition...");
}

void loop() {
  // The main loop is empty. All work is done by RTOS tasks and timers.
  vTaskDelete(NULL); // Delete the loop task to save resources
}
```

### <span class="ng-star-inserted">Code Walkthrough</span>

1. **<span class="ng-star-inserted">Data Structures:</span>**<span class="ng-star-inserted"> We define two </span>`<span class="inline-code ng-star-inserted">structs</span>`<span class="ng-star-inserted">, </span>`<span class="inline-code ng-star-inserted">SensorData</span>`<span class="ng-star-inserted"> and </span>`<span class="inline-code ng-star-inserted">ProcessedResult</span>`<span class="ng-star-inserted">, to create organized data packages that can be sent to queues. This is much cleaner than passing raw variables.</span>
2. **<span class="ng-star-inserted">Hardware Interrupt (</span>`<span class="inline-code ng-star-inserted">onTimer</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This ISR is the "producer." It runs at a precise interval, generates data, and immediately sends it to the </span>`<span class="inline-code ng-star-inserted">sensorDataQueue</span>`<span class="ng-star-inserted">. It does no processing and exits quickly, as a good ISR should.</span>
3. **<span class="ng-star-inserted">Processing Task (</span>`<span class="inline-code ng-star-inserted">processingTask</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This task is a "consumer-producer." It has a higher priority because we want to process data as soon as it's available. It waits on </span>`<span class="inline-code ng-star-inserted">sensorDataQueue</span>`<span class="ng-star-inserted">. When data arrives, it performs a quick calculation and sends the result to </span>`<span class="inline-code ng-star-inserted">resultQueue</span>`<span class="ng-star-inserted">.</span>
4. **<span class="ng-star-inserted">Logging Task (</span>`<span class="inline-code ng-star-inserted">loggingTask</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This is the final "consumer." It has a lower priority because logging is generally not a time-critical operation. It waits for fully processed data on </span>`<span class="inline-code ng-star-inserted">resultQueue</span>`<span class="ng-star-inserted"> and prints it. Because of its lower priority, it will only run when the </span>`<span class="inline-code ng-star-inserted">processingTask</span>`<span class="ng-star-inserted"> is blocked (waiting for data).</span>
5. **<span class="ng-star-inserted">Software Timer (</span>`<span class="inline-code ng-star-inserted">systemHealthCallback</span>`<span class="ng-star-inserted">)</span>**<span class="ng-star-inserted">: This runs completely independently of the main data flow. Every 5 seconds, it prints a status message, demonstrating how you can easily add periodic, non-critical background functions to your application without interfering with the main logic.</span>
6. **`<span class="inline-code ng-star-inserted">setup()</span>`<span class="ng-star-inserted"> Function</span>**<span class="ng-star-inserted">: This is where we initialize all our RTOS objects. We create the queues, create the tasks, configure the hardware timer, and start the software timer.</span>
7. **`<span class="inline-code ng-star-inserted">loop()</span>`<span class="ng-star-inserted"> Function</span>**<span class="ng-star-inserted">: In a complex RTOS application, the main </span>`<span class="inline-code ng-star-inserted">loop()</span>`<span class="ng-star-inserted"> function is often no longer needed. All continuous work is handled by tasks. We can delete the loop task with </span>`<span class="inline-code ng-star-inserted">vTaskDelete(NULL)</span>`<span class="ng-star-inserted"> to free up its stack memory.</span>

### <span class="ng-star-inserted">How to Test It</span>

1. <span class="ng-star-inserted">Upload the code to your ESP32.</span>
2. <span class="ng-star-inserted">Open the Arduino Serial Monitor at 115200 baud.</span>
3. <span class="ng-star-inserted">You should see the following pattern:</span>
    
    
    - <span class="ng-star-inserted">Every 200 milliseconds, the "\[PROCESS\]" message will appear, indicating the high-priority task has received data from the ISR.</span>
    - <span class="ng-star-inserted">Immediately after, the "\[LOG\]" message will print the calculated average, followed by a separator.</span>
    - <span class="ng-star-inserted">Every 5 seconds, a "\[HEALTH\] System OK" message will appear, independently of the other messages.</span>