Esp32 attaching multiple hardware interrupts to specific cores

Hello I've been struggling to attach 2 HW interrupts to specific cores.
I mean, i want one interrupt in core 0 and one interrupt in core 1.

The doc from the esp32 (dual core) says that each core has an isr so it should be possible to attach an interrupt on each core.
This should be done configuring the config task on each core. I have an example that i thought should work but when i check wich core is the interrupt i have both on the same. Depending on wich task i pinned to a core first both are in core 0 or core 1. Here is my code test: <<

byte c0Pin=23;
byte c1Pin=32;

void IRAM_ATTR c0Interrupt() {
  ets_printf("Core ID of c0 ISR: %d\n",xPortGetCoreID());
}
void IRAM_ATTR c1Interrupt() {
  ets_printf("Core ID of c1 ISR: %d\n",xPortGetCoreID());
}

void c0Config(void * pvParameters){
   /* GPIO config procedure END*/
  pinMode(c0Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c0Pin), c0Interrupt, RISING);
  ets_printf("Core ID of c0 TASK: %d\n",xPortGetCoreID());

  while(1){
      vTaskDelay(1);
  }
}

void c1Config(void * pvParameters){
  pinMode(c1Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c1Pin), c1Interrupt, FALLING);
  ets_printf("Core ID of c1 TASK: %d\n",xPortGetCoreID());

  while(1){
    vTaskDelay(1);
  }
}

void setup() {
  Serial.begin(115200);
  vTaskDelay(1000/portTICK_PERIOD_MS);
  xTaskCreatePinnedToCore(c1Config,
                          "TASK_config_DRDY_INT_HANDLER",
                          2048, NULL, 1, NULL, 1);
  vTaskDelay(1000/portTICK_PERIOD_MS);
  xTaskCreatePinnedToCore(c0Config,
                          "TASK_config_DRDY_INT_HANDLER2",
                          2048, NULL, 1, NULL, 0);
  vTaskDelay(1000/portTICK_PERIOD_MS); 
}

void loop() {
  vTaskDelay(1);
}

Can someone get why it's not working?

Thanks

This is outdated, there is no longer a requirement to convert the ms to port ticks.

putting code in loop() stops the ESP32 from doing memory cleanup.

Uisng the Arduino IDE, the attachinterrupt resources will be allocated to core1.

What is the code supposed to do? What does it do instead?

fixed your code.

byte c0Pin=23;
byte c1Pin=32;

void IRAM_ATTR c0Interrupt() {
  ets_printf("Core ID of c0 ISR: %d\n",xPortGetCoreID());
}
void IRAM_ATTR c1Interrupt() {
  ets_printf("Core ID of c1 ISR: %d\n",xPortGetCoreID());
}

//void c0Config(void * pvParameters){
//  ets_printf("Core ID of c0 TASK: %d\n",xPortGetCoreID());

//  while(1){
//      vTaskDelay(1);
//  }
//}

//void c1Config(void * pvParameters){
//  ets_printf("Core ID of c1 TASK: %d\n",xPortGetCoreID());

//  while(1){
//    vTaskDelay(1);
//  }
//}

void setup() {
  Serial.begin(115200);
  pinMode(c1Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c1Pin), c1Interrupt, FALLING);
   /* GPIO config procedure END*/
  pinMode(c0Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c0Pin), c0Interrupt, RISING);
  
  
  
  //vTaskDelay(1000/portTICK_PERIOD_MS);
  //xTaskCreatePinnedToCore(c1Config,
  //                        "TASK_config_DRDY_INT_HANDLER",
  //                        2048, NULL, 1, NULL, 1);
  //vTaskDelay(1000/portTICK_PERIOD_MS);
  //xTaskCreatePinnedToCore(c0Config,
  //                        "TASK_config_DRDY_INT_HANDLER2",
  //                        2048, NULL, 1, NULL, 0);
  //vTaskDelay(1000/portTICK_PERIOD_MS); 
}
////
void loop() {}

That should work.

something else that should work.

Not tested or debuged.

#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
////
#define evtDoInt0 ( 1 << 0 )
#define evtDoInt1 ( 1 << 1 )
EventGroupHandle_t eg;


int c0Pin=23;
int c1Pin=32;



void IRAM_ATTR c0Interrupt() 
{
  BaseType_t xHigherPriorityTaskWoken; ////
  xEventGroupSetBitsFromISR(eg, evtDoInt0, &xHigherPriorityTaskWoken);
}
void IRAM_ATTR c1Interrupt() 
{ 
  BaseType_t xHigherPriorityTaskWoken; ////
  xEventGroupSetBitsFromISR(eg, evtDoInt1, &xHigherPriorityTaskWoken);
}

void c0Config(void * pvParameters){
  while(1)
  {
  xEventGroupWaitBits (eg, evtDoInt0, pdTRUE, pdTRUE, portMAX_DELAY );
  log_i("pinky");    
  }
  vTaskDelete( NULL );
}

void c1Config(void * pvParameters)
{
  while(1)
  {
  xEventGroupWaitBits (eg, evtDoInt1, pdTRUE, pdTRUE, portMAX_DELAY );
  log_i("ponk");    
  }
  vTaskDelete( NULL );
}

void setup() 
{
    //
  eg = xEventGroupCreate(); // get an event group handle
  //
  //Serial.begin(115200);
  pinMode(c1Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c1Pin), c1Interrupt, FALLING);
   /* GPIO config procedure END*/
  pinMode(c0Pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(c0Pin), c0Interrupt, RISING);
  xTaskCreatePinnedToCore(c1Config, "c1Config", 2048, NULL, 3, NULL, 1);
  xTaskCreatePinnedToCore(c0Config, "c0Config" , 2048, NULL, 3, NULL, 0);
}
////
void loop() {}

that will put both interrupts in the same core (1) wich is not what i'm looking for.

Then use the ESP-IDF.

So, it's impossible to attach an interrupt to each core in arduino right?

The things with interrupts in different cores:
one is way faster than the other and sometimes it will block the second interrupt to be analized.

One interrupt ticks the cadence (the fast tick one) and the second ticks the speed weel (slow).
I can get errors on speed due to the cadence ticking. (i see it on the real code).
I found a work around, since one of the core is only used for bluetooth comms. I just do a fast loop that checks the state of the pin (not using the interrupt) and since it's the speed one, everything looks ok. But is an ugly workaround....I was looking for clean code. :smiley:

Thanks anyway.

You are using bluetooth and putting code onto core0? That will end up being trouble.

on core 0 i only use blutooth and the fast check of the pin.

'''
while(true){
    if (digitalRead(vitessePin)==0){
      vitessecalc();  
    }
}
'''

(on the task bluetooth on core 0.)

I know that is not good, but using the cadence and speed interrupts on the same cores gives errors on the speed calc)

Have you tried to not use the Arduino ESP32 core and use the ESP32's API under the Arduino IDE?

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html

#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

void setup()
{
  gpio_config_t io_cfg = {}; // initialize the gpio configuration structure
  io_cfg.mode = GPIO_MODE_INPUT; // set gpio mode. GPIO_NUM_0 input from water level sensor
  io_cfg.pull_down_en = GPIO_PULLDOWN_ENABLE; // enable pull down
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_0) | (1ULL << GPIO_NUM_18) ); //bit mask of the pins to set, assign gpio number to be configured
  gpio_config(&io_cfg); // configure the gpio based upon the parameters as set in the configuration structure
  //
  io_cfg = {}; //set configuration structure back to default values
  io_cfg.mode = GPIO_MODE_OUTPUT;
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_4) | (1ULL << GPIO_NUM_5) | (1ULL << GPIO_NUM_19)  ); //bit mask of the pins to set, assign gpio number to be configured
  gpio_config(&io_cfg);
  gpio_set_level( GPIO_NUM_4, LOW); // deenergize relay module
  gpio_set_level( GPIO_NUM_5, LOW); // deenergize valve
  gpio_set_level( GPIO_NUM_19, LOW); // deenergize valve


}

the gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type) might be of interest.

The ESP32 has 3 memory spaces for the main processor. Memory space is broken down into memory for core0 memory for core1 and shared memory. It's that shared memory I want my variables going into whenever possible. In those cases using as much of the ESP32's API as possible becomes the programing method I chose to use. Which includes using freeRTOS.

I didn't try. Thanks for the tip. I will test it. thanks!

what method of data passing are you using?

Such as are you storing the count of whatever in a global variable or are you passing the info using task triggered by a queue arrival?

This task is triggered by an event group,

void fFindDewPointWithHumidity( void *pvParameters )
{
  float temperature = 0.0f;
  for ( ;; )
  {
    xEventGroupWaitBits (eg, evtDewPoint, pdTRUE, pdTRUE, portMAX_DELAY );

a queue receipt trigger.

  for (;;)
  {
    if ( xQueueReceive(xQ_WindChillDewPoint, &px_message, portMAX_DELAY) == pdTRUE )
    {

Thus, the issue could be how the data is being passed to the task.

I'm passing data to a global variable. this is the handler part:

void IRAM_ATTR vitesseInterrupt() {
    vitesse_passed = millis() - last_interrupt_vitesse_time;
    if (vitesse_passed > 40){
      vitesse = 2*pi*1000*wsize/vitesse_passed;
      //ets_printf("Core ID of vitesse: %d\n",xPortGetCoreID());
    }
    last_interrupt_vitesse_time = millis();
}

ALL global variables created under the Arduino IDE are stored on core1. The only thing you can do to change this is to use the ESP-IDF.

All variables made inside a task are stored on the core the task is assigned to.

Using a task on core0 when ble or wifi is in use will cause erratic behavior's.

don't use millis()! Its slow.


void fDoMoistureDetector1( void * parameter )
{
  //wait for a mqtt connection
  while ( !MQTTclient.connected() )
  {
    vTaskDelay( 250 );
  }
  int      TimeToPublish = 5000000; //5000000uS
  int      TimeForADreading = 100 * 1000; // 100mS
  uint64_t TimePastPublish = esp_timer_get_time(); // used by publish
  uint64_t TimeADreading   = esp_timer_get_time();
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t xFrequency = 10; //delay for 10mS
  float    RemainingMoisture = 100.0f; //prevents pump turn on during start up
  bool     pumpOn = false;
  uint64_t PumpOnTime = esp_timer_get_time();
  int      PumpRunTime = 11000000;
  uint64_t PumpOffWait = esp_timer_get_time();
  uint64_t PumpOffWaitFor = 60000000; //one minute
  float    lowMoisture = 23.0f;
  float    highMoisture = 40.0f;
  for (;;)
  {
    xSemaphoreTake( sema_WaterCactus, portMAX_DELAY );
    //read AD values every 100mS.
    if ( (esp_timer_get_time() - TimeADreading) >= TimeForADreading )
    {
      xEventGroupSetBits( eg, evtADCreading0 );
      TimeADreading = esp_timer_get_time();
    }
    xQueueReceive(xQ_RM, &RemainingMoisture, 0 ); //receive queue stuff no waiting
    //read gpio 0 is water level good. Yes: OK to run pump : no pump off.   remaining moisture good, denergize water pump otherwise energize water pump.
    if ( RemainingMoisture >= highMoisture )
    {
      WaterPump0_off();
    }
    if ( !pumpOn )
    {
      log_i( "not pump on ");
      if ( gpio_get_level( GPIO_NUM_0 ) )
      {
        if ( RemainingMoisture <= lowMoisture )
        {
          //has one minute passed since last pump energize, if so then allow motor to run
          if ( (esp_timer_get_time() - PumpOffWait) >= PumpOffWaitFor )
          {
            gpio_set_level( GPIO_NUM_5, HIGH); //open valve
            WaterPump0_on();
            log_i( "pump on " );
            pumpOn = !pumpOn;
            PumpOnTime = esp_timer_get_time();
          }
        }
        //xSemaphoreGive( sema_RemainingMoisture );
      } else {
        log_i( "water level bad " );
        WaterPump0_off();
        gpio_set_level( GPIO_NUM_5, LOW); //denergize/close valve
        PumpOffWait = esp_timer_get_time();
      }
    } else {
      /*
         pump goes on runs for X seconds then turn off, then wait PumpOffWaitTime before being allowed to energize again
      */
      if ( (esp_timer_get_time() - PumpOnTime) >= PumpRunTime )
      {
        log_i( "pump off " );
        WaterPump0_off(); // after 5 seconds turn pump off
        gpio_set_level( GPIO_NUM_5, LOW); //denergize/close valve
        pumpOn = !pumpOn;
        PumpOffWait = esp_timer_get_time();
      }
    }
    // publish to MQTT every 5000000uS
    if ( (esp_timer_get_time() - TimePastPublish) >= TimeToPublish )
    {
      xQueueOverwrite( xQ_RemainingMoistureMQTT, (void *) &RemainingMoisture );// data for mqtt publish
      TimePastPublish = esp_timer_get_time(); // get next publish time
    }
    xSemaphoreGive( sema_WaterCactus );
    xLastWakeTime = xTaskGetTickCount();
    vTaskDelayUntil( &xLastWakeTime, xFrequency );
  }
  vTaskDelete( NULL );
}// end fDoMoistureDetector1()

use esp_timer_get_time();. In the above task I use esp_timer_get_time(); as one would use millis(). A ESP32 millis() call is made to the esp_timer_get_time(); macro that cuts the return value down from a 64bit number to a 32 bit number. That cutting takes time. Also, esp_timer_get_time(); overflows in about 204+ years.

code snippets are poo! when trying to solve an issue like you present.

I also use for timing the xTaskGetTickCount(); which comes from the freeRTOS software timer. I know it may not be as accurate as a hardware timer but I do not need the accuracy and the xTaskGetTickCount(); is synced to freeRTOS operations.

thanks!

1 Like

trigger a timed task this way insures; the task will run on time over using vTaskDelay(x) and not require a hardware trigger.

IMPORTANT.
End all tasks this way

  } //for or while ending
  vTaskDelete( NULL );
}// end fDoMoist

when testing your tasks to get an idea of stack size to set, start with 10K. Then use this //log_i( "DoTheBME280Thing high watermark % d", uxTaskGetStackHighWaterMark( NULL ) ); to get an idea of how much the task is actually using. Then comment out the print. Oh, add 2000 more to the stack size that uxTaskGetStackHighWaterMark( NULL ) reports and you'll be fine.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.