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