Search code examples
arduinoesp32freertosarduino-c++platformio

ESP32 FreeRTOS pin interrupt ISR handler core 0 panic (C++)


Currently I am trying to attach a pin interrupt whose ISR is to call xTaskResumeFromISR or xQueueSendFromISR. The ISR gets called correctly, but the code to execute results in a core 0 panic.

Here are the implementation details.

PlatformIO: platform = espressif32 @ 6.0.1, framework = arduino, board = esp32dev

Header file (Worker.h)

#pragma once
#include <Arduino.h>

class Worker {
  public:
    Worker(uint8_t pinExtInterrupt);
    bool startTask(void);
  protected:
    // static wrapper for task creation
    static void  staticRun(void *arg) {
      reinterpret_cast<Worker *>(arg)->run();
    }

    // actual task's logic
    void run(void);

    // static interrupt handler
    static void staticIsrHandler(void* arg);

    // actual interrupt handler
    void isrHandler();
    
    TaskHandle_t _taskHandle = nullptr;
    uint8_t _pinExtInterrupt;
};

Source file (Worker.cpp)

#include "Worker.h"

Worker::Worker(uint8_t pinExtInterrupt) {
  _pinExtInterrupt = pinExtInterrupt;
  pinMode(pinExtInterrupt, INPUT);
}

bool Worker::startTask(void) {
  BaseType_t xReturned = xTaskCreate(staticRun, "Worker", 2048, this, 5, &_taskHandle);
  gpio_set_intr_type(static_cast<gpio_num_t>(_pinExtInterrupt), GPIO_INTR_NEGEDGE);
  gpio_install_isr_service(0);
  gpio_isr_handler_add(static_cast<gpio_num_t>(_pinExtInterrupt), staticIsrHandler, NULL);
  return true;
}

void Worker::run(void) {
  for (;;) {
    vTaskSuspend(NULL);
    // LOGIC
  }
}

void IRAM_ATTR Worker::staticIsrHandler(void* arg) {
  reinterpret_cast<Worker*>(arg)->isrHandler();
}

void IRAM_ATTR Worker::isrHandler() {
  xTaskResumeFromISR(_taskHandle); // ###### THIS LINE THROWS THE EXCEPTION ######
}

Error
Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
0x400d1d00:0x3ffbeaac in Worker::isrHandler() at ...

But what works is if you replace xTaskResumeFromISR e.g. with digitalWrite(..).

Need to fix the problem above.


Solution

  • Your call here:

    gpio_isr_handler_add(static_cast<gpio_num_t>(_pinExtInterrupt), staticIsrHandler, NULL);
    

    assigns a null pointer for the ISR handler's context data. As a result, when your static ISR is called:

    void IRAM_ATTR Worker::staticIsrHandler(void* arg) {
      reinterpret_cast<Worker*>(arg)->isrHandler();
    }
    

    arg is a null pointer, causing the access to _taskHandle to fail here:

    void IRAM_ATTR Worker::isrHandler() {
      xTaskResumeFromISR(_taskHandle); 
    }
    

    If you replace your gpio_isr_handler_add call with the following:

    gpio_isr_handler_add(static_cast<gpio_num_t>(_pinExtInterrupt), staticIsrHandler, this);
    

    everything should work.