ESP32 Multicore Programming Techniques: Interrupt Handling Mechanism

In the dual-core architecture of the ESP32, the interrupt handling mechanism is key to achieving efficient real-time response. The ESP32 supports multicore interrupt handling (Core 0 and Core 1), but it needs to be combined with FreeRTOS task scheduling and synchronization mechanisms to fully leverage its performance. Below are the core technologies, configuration methods, and practical suggestions for ESP32 multicore interrupt handling:

1. Overview of ESP32 Interrupt Mechanism

1. Classification of Interrupt Sources

  • Hardware Interrupts triggered by external events (such as GPIO level changes, timer overflows, sensor signals).
  • Software Interrupts triggered by internal CPU events (such as timer interrupts, watchdog interrupts).
  • Wi-Fi/BLE Interrupts are by default bound to Core 0 (managed by ESP-IDF).

2. Interrupt Priority

  • Each core of the ESP32 supports 32 interrupt priorities (0 being the lowest, 31 being the highest).
  • Default Priority GPIO interrupts have a lower priority (manual adjustment is needed to avoid conflicts).
  • Nested Interrupts higher priority interrupts can preempt lower priority interrupts.

3. Interrupt Handling Process

  1. Interrupt Trigger An external event (such as a button press) triggers a GPIO interrupt.
  2. Interrupt Service Routine (ISR) executes a brief interrupt handling logic (such as setting a flag).
  3. Task Wakeup wakes up user tasks to handle complex logic via queues or task notifications.
  4. Resume Main Program After the interrupt handling is complete, the CPU returns to execute the main program.

2. Core Strategies for Multicore Interrupt Handling

1. Interrupt Allocation Rules

  • Core 0 is responsible for handling Wi-Fi/BLE interrupts by default (fixed by ESP-IDF).
  • Core 1 can freely allocate GPIO/timer interrupts.
  • Interrupt Binding is configured through <span>gpio_install_isr_service()</span> and <span>gpio_isr_handler_add()</span> to set up the interrupt service routine.

2. Collaboration Between Interrupts and Tasks

  • Quick ISR Return ISRs should only perform minimal operations (like setting flags), with complex logic handled by tasks.
  • Task Wakeup Mechanism
    • Queues use <span>xQueueSendFromISR()</span> to pass data.
    • Task Notifications use <span>xTaskNotifyFromISR()</span> to wake up tasks.
    • Semaphores use <span>xSemaphoreGiveFromISR()</span> to release semaphores.

3. Multicore Interrupt Synchronization

  • Mutexes protect shared resources through <span>xSemaphoreTakeFromISR()</span>/<span>xSemaphoreGiveFromISR()</span>.
  • Global Variable Protection uses <span>portENTER_CRITICAL()</span>/<span>portEXIT_CRITICAL()</span> to enter critical sections.

3. Configuration Steps for Interrupt Handling

1. GPIO Interrupt Configuration (Arduino Environment)

// Define interrupt pin and handler function
const int interruptPin = 4;
volatile bool interruptFlag = false;

void IRAM_ATTR isrHandler() {
    interruptFlag = true; // Set flag
}

void setup() {
    pinMode(interruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(interruptPin), isrHandler, FALLING); // Bind interrupt
}

void loop() {
    if (interruptFlag) {
        // Handle interrupt event
        interruptFlag = false;
    }
}

2. GPIO Interrupt Configuration (ESP-IDF Environment)

#include "driver/gpio.h"
#include "freertos/queue.h"

// Interrupt service routine
void IRAM_ATTR gpio_isr_handler(void* arg) {
    uint32_t gpio_num = (uint32_t)arg;
    xQueueSendFromISR(queue, &gpio_num, NULL); // Send message to queue
}

void app_main() {
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << GPIO_NUM_4),
        .mode = GPIO_MODE_INPUT,
        .intr_type = GPIO_INTR_NEGEDGE, // Trigger on falling edge
        .pull_up_en = GPIO_PULLUP_ENABLE
    };
    gpio_config(&io_conf);

    gpio_install_isr_service(0); // Initialize interrupt service
    gpio_isr_handler_add(GPIO_NUM_4, gpio_isr_handler, (void*)GPIO_NUM_4); // Register interrupt handler

    QueueHandle_t queue = xQueueCreate(10, sizeof(uint32_t)); // Create queue
    // Read from queue in task
    while (1) {
        uint32_t gpio_num;
        if (xQueueReceive(queue, &gpio_num, portMAX_DELAY) == pdTRUE) {
            // Handle interrupt event
        }
    }
}

3. Multicore Interrupt Binding (ESP-IDF)

// Create task and bind to Core 1
xTaskCreatePinnedToCore(taskFunction, "Task", 2048, NULL, 1, NULL, 1);

// Interrupt handler bound to Core 0
gpio_isr_handler_add(GPIO_NUM_4, gpio_isr_handler, (void*)GPIO_NUM_4);

4. Considerations for Multicore Interrupt Handling

  1. ISR Execution Time Limit:

  • ISRs should complete within 10 microseconds, to avoid blocking other interrupts or tasks.
  • Do not call <span>delay()</span>, <span>Serial.print()</span>, or complex calculations within ISRs.
  • Separation of Interrupts and Tasks:

    • Move complex logic to tasks, passing data via queues or task notifications.
    • Example: Set a flag in ISR, and have the task poll the flag and perform actions.
  • Shared Resource Protection:

    • Use mutexes or critical sections to protect global variables.
    • Example: Use <span>portENTER_CRITICAL()</span> to lock in ISR.
  • Interrupt Priority Configuration:

    • Adjust interrupt priority using <span>gpio_set_intr_type()</span><code><span> to avoid conflicts.</span>
    • High priority interrupts can preempt low priority interrupts.
  • Debugging and Monitoring:

    • Use <span>vTaskList()</span> to view task status.
    • Enable Segger SystemView for visualizing interrupts and task scheduling.

    5. Typical Application Scenarios

    Scenario Handling Method
    Button Press Detection GPIO interrupt trigger → Set flag → Task handles event
    Sensor Data Acquisition Timer interrupt trigger → Trigger ADC acquisition → Queue passes data → Task handles
    Wi-Fi/BLE Communication Core 0 handles protocol stack interrupts → Core 1 handles user tasks
    Real-time Control (Motor Drive) GPIO interrupt trigger → Quickly adjust PWM signal → Task handles complex control logic

    6. Conclusion

    The multicore interrupt handling of the ESP32 requires the combination of hardware interrupt sources, FreeRTOS task scheduling, and synchronization mechanisms to achieve efficient response. The key points are:

    1. Reasonable allocation of interrupt sources (Core 0 handles protocol stack, Core 1 handles user logic).
    2. Optimize ISR execution efficiency (quick return + task collaboration).
    3. Protect shared resources (mutexes, critical sections).
    4. Debugging and monitoring (queues, task notifications, visualization tools).

    By following these strategies, efficient dual-core interrupt handling can be achieved on the ESP32, suitable for scenarios requiring high real-time performance such as IoT devices and industrial control.

    Leave a Comment