Handling Multiple Interrupts on ESP32

Hi, I’m a beginner in ESP32 coding and I’m working on a project that’s been quite challenging. I hope someone can help me.

Let me explain my project:
I’m using 4 ultrasonic receiver sensors, which I’ll call Rx1, Rx2, Rx3, and Rx4. These sensors are connected to a custom PCB that I made. Each sensor has its own receiver circuit on the board.

There is also one ultrasonic transmitter sensor which I’ll call Tx1, which is placed in front of the receiver sensors and points directly at them.

I want to use the ESP32 to detect the first rising edge from each receiver sensor (Rx1, Rx2, Rx3, and Rx4). When the ESP32 detects this rising edge, it should record the exact time in microseconds.

This is the main goal of my project.

Now, let me explain the problem I’m facing:
The ESP32 is recording wrong timestamps for when the rising edge happens.

Below, I’ll show you the results I’m getting from the ESP32 and compare them to the correct times I should be getting based on theory and calculations.

The result I am getting from the ESP32:

13:15:06.265 -> Waiting for signals...
13:15:06.758 -> Waiting for signals...
13:15:07.276 -> Tx1 was the sender.
13:15:07.276 -> Rx1 was Received First at 0.00 µs
13:15:07.276 -> Rx3 was Received Second at 24941.00 µs
13:15:07.276 -> Rx2 was Received Third at 38334.00 µs
13:15:07.276 -> Rx4 was Received Last at 40562.00 µs
13:15:07.276 -> Time Difference Of Arrival:
13:15:07.276 -> Between Rx1 and Rx3 is 24941.00 µs.
13:15:07.276 -> Between Rx1 and Rx2 is 38334.00 µs.
13:15:07.276 -> Between Rx1 and Rx4 is 40562.00 µs.
13:15:07.323 -> ---------End Of This Cycle----------

The results that I must be getting based on Theoretical calculations:

13:15:05.759 -> Waiting for signals...
13:15:06.265 -> Waiting for signals...
13:15:06.758 -> Waiting for signals...
13:15:07.276 -> Tx1 was the sender.
13:15:07.276 -> Rx1 was Received First at 600.23 µs
13:15:07.276 -> Rx3 was Received Second at 617.52 µs
13:15:07.276 -> Rx2 was Received Third at 617.88 µs
13:15:07.276 -> Rx4 was Received Last at 650.25 µs
13:15:07.276 -> Time Difference Of Arrival:
13:15:07.276 -> Between Rx1 and Rx3 is 17.19 µs.
13:15:07.276 -> Between Rx1 and Rx2 is 17.65 µs.
13:15:07.276 -> Between Rx1 and Rx4 is 50.02 µs.
13:15:07.323 -> ---------End Of This Cycle----------

Note that based on my Theoretical calculations the Time Difference Of Arrival Between Rx1 and Rx3 & Between Rx1 and Rx2 must be about 17 µs.

Below I will add the code that I am using:

#include <Arduino.h>

// Function prototypes
void IRAM_ATTR ISR_Rx1_Receive();
void IRAM_ATTR ISR_Rx2_Receive();
void IRAM_ATTR ISR_Rx3_Receive();
void IRAM_ATTR ISR_Rx4_Receive();

// Shared variables
volatile uint32_t TOA_Rx1 = 0;
volatile uint32_t TOA_Rx2 = 0;
volatile uint32_t TOA_Rx3 = 0;
volatile uint32_t TOA_Rx4 = 0;
volatile uint8_t  Rx1State = LOW;
volatile uint8_t  Rx2State = LOW;
volatile uint8_t  Rx3State = LOW;
volatile uint8_t  Rx4State = LOW;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

// Pin assignments
const int Rx1Pin = 34;
const int Rx2Pin = 35;
const int Rx3Pin = 25;
const int Rx4Pin = 26;

void setup() {
  Serial.begin(115200);

  pinMode(Rx1Pin, INPUT);
  pinMode(Rx2Pin, INPUT);
  pinMode(Rx3Pin, INPUT);
  pinMode(Rx4Pin, INPUT);

  attachInterrupt(digitalPinToInterrupt(Rx1Pin), ISR_Rx1_Receive, RISING);
  attachInterrupt(digitalPinToInterrupt(Rx2Pin), ISR_Rx2_Receive, RISING);
  attachInterrupt(digitalPinToInterrupt(Rx3Pin), ISR_Rx3_Receive, RISING);
  attachInterrupt(digitalPinToInterrupt(Rx4Pin), ISR_Rx4_Receive, RISING);
}

void loop() {
  uint8_t  s1, s2, s3, s4;
  uint32_t t1, t2, t3, t4;

  portENTER_CRITICAL(&mux);
    s1 = Rx1State;  t1 = TOA_Rx1;
    s2 = Rx2State;  t2 = TOA_Rx2;
    s3 = Rx3State;  t3 = TOA_Rx3;
    s4 = Rx4State;  t4 = TOA_Rx4;
  portEXIT_CRITICAL(&mux);

  if (s1==HIGH || s2==HIGH || s3==HIGH || s4==HIGH) {
    struct { uint8_t idx; uint32_t t; } arr[4] = {
      {1, t1}, {2, t2}, {3, t3}, {4, t4}
    };

    for (int i = 0; i < 3; i++) {
      for (int j = 0; j < 3 - i; j++) {
        if (arr[j].t > arr[j+1].t) {
          auto tmp = arr[j];
          arr[j] = arr[j+1];
          arr[j+1] = tmp;
        }
      }
    }

    Serial.print("Tx");
    Serial.print(arr[0].idx);
    Serial.println(" was the sender.");

    for (int i = 0; i < 4; i++) {
      float us = arr[i].t - arr[0].t;  
      Serial.print("Rx");
      Serial.print(arr[i].idx);
      Serial.print(" was Received ");
      switch(i){
        case 0: Serial.print("First");  break;
        case 1: Serial.print("Second"); break;
        case 2: Serial.print("Third");  break;
        case 3: Serial.print("Last");   break;
      }
      Serial.print(" at ");
      Serial.print(us, 2);
      Serial.println(" µs");
    }

    Serial.println("Time Difference Of Arrival:");
    for (int i = 1; i < 4; i++) {
      float tdoa = arr[i].t - arr[0].t;
      Serial.print("Between Rx");
      Serial.print(arr[0].idx);
      Serial.print(" and Rx");
      Serial.print(arr[i].idx);
      Serial.print(" is ");
      Serial.print(tdoa, 2);
      Serial.println(" µs.");
    }

    Serial.println("---------End Of This Cycle----------\n");

    portENTER_CRITICAL(&mux);
      Rx1State = Rx2State = Rx3State = Rx4State = LOW;
      TOA_Rx1 = TOA_Rx2 = TOA_Rx3 = TOA_Rx4 = 0;
    portEXIT_CRITICAL(&mux);

    delay(1000); // delay to detect only the first rising edge (debounce)

    attachInterrupt(digitalPinToInterrupt(Rx1Pin), ISR_Rx1_Receive, RISING);
    attachInterrupt(digitalPinToInterrupt(Rx2Pin), ISR_Rx2_Receive, RISING);
    attachInterrupt(digitalPinToInterrupt(Rx3Pin), ISR_Rx3_Receive, RISING);
    attachInterrupt(digitalPinToInterrupt(Rx4Pin), ISR_Rx4_Receive, RISING);
  }
  else {
    Serial.println("Waiting for signals...");
    delay(500);
  }
}

// ISR Implementations using micros()
void IRAM_ATTR ISR_Rx1_Receive() {
  detachInterrupt(digitalPinToInterrupt(Rx1Pin));
  portENTER_CRITICAL_ISR(&mux);
    Rx1State = HIGH;
    TOA_Rx1 = micros();
  portEXIT_CRITICAL_ISR(&mux);
}

void IRAM_ATTR ISR_Rx2_Receive() {
  detachInterrupt(digitalPinToInterrupt(Rx2Pin));
  portENTER_CRITICAL_ISR(&mux);
    Rx2State = HIGH;
    TOA_Rx2 = micros();
  portEXIT_CRITICAL_ISR(&mux);
}

void IRAM_ATTR ISR_Rx3_Receive() {
  detachInterrupt(digitalPinToInterrupt(Rx3Pin));
  portENTER_CRITICAL_ISR(&mux);
    Rx3State = HIGH;
    TOA_Rx3 = micros();
  portEXIT_CRITICAL_ISR(&mux);
}

void IRAM_ATTR ISR_Rx4_Receive() {
  detachInterrupt(digitalPinToInterrupt(Rx4Pin));
  portENTER_CRITICAL_ISR(&mux);
    Rx4State = HIGH;
    TOA_Rx4 = micros();
  portEXIT_CRITICAL_ISR(&mux);
}

Zero value for first reflection means, to me, something did not happen.

is the first sensor defective, mis-wired?
do all sensor need to be configured as INPUT_PULLUP?

Zero value for Rx1 was because I wanted to set the arriving time of the first arrived signal to 0 us.

This is implemented in the code in this part:

    for (int i = 0; i < 4; i++) {
      float us = arr[i].t - arr[0].t;  
      Serial.print("Rx");
      Serial.print(arr[i].idx);
      Serial.print(" was Received ");
      switch(i){
        case 0: Serial.print("First");  break;
        case 1: Serial.print("Second"); break;
        case 2: Serial.print("Third");  break;
        case 3: Serial.print("Last");   break;
      }
      Serial.print(" at ");
      Serial.print(us, 2);
      Serial.println(" µs");
    }

if I set float us = arr[i].t; then Rx1 will have a reading but however this reading is incorrect

The first sensor is not defective or mis-wired. I wanted to set the arriving time of the first arrived signal to 0 us thats why Rx1 value was zero.

This is implemented in the code in this part:

for (int i = 0; i < 4; i++) {
      float us = arr[i].t - arr[0].t;  
      Serial.print("Rx");
      Serial.print(arr[i].idx);
      Serial.print(" was Received ");
      switch(i){
        case 0: Serial.print("First");  break;
        case 1: Serial.print("Second"); break;
        case 2: Serial.print("Third");  break;
        case 3: Serial.print("Last");   break;
      }
      Serial.print(" at ");
      Serial.print(us, 2);
      Serial.println(" µs");
    }

if I set float us = arr[i].t; then Rx1 will have a reading but however this reading is incorrect.

I tired configuring the sensors as INPUT_PULLUP but I am still getting the same incorrect readings.

This is your first post, you say you are a beginner, and yet you are using code elements that I, as a 50+ year veteran, didn't discover for many months. Did you, in fact, write this code? The reason I ask is that if the code is not from your experience and studies, then it is virtually impossible to explain what needs to be done.

the code looks excessive.

might be best test test with just one sensor

Why not simply

// Pin assignments
const int Rx1Pin = 34;
const int Rx2Pin = 35;
const int Rx3Pin = 25;
const int Rx4Pin = 26;

// Shared variables
volatile uint32_t t1 = 1000;
volatile uint32_t t2 = 2022;
volatile uint32_t t3 = 3033;
volatile uint32_t t4 = 4044;

volatile uint8_t  int1 = LOW;
volatile uint8_t  int2 = LOW;
volatile uint8_t  int3 = LOW;
volatile uint8_t  int4 = LOW;

char s [90];

// -----------------------------------------------------------------------------
// ISR Implementations using micros ()
void ISR_Rx1_Receive () {
    int1 = HIGH;
    t1 = micros ();
}

void ISR_Rx2_Receive ()
{
    int2 = HIGH;
    t2 = micros ();
}

void ISR_Rx3_Receive () {
    int3 = HIGH;
    t3 = micros ();
}

void ISR_Rx4_Receive () {
    int4 = HIGH;
    t4 = micros ();
}

// -----------------------------------------------------------------------------
void loop ()
{
    if (int1 == HIGH || int2 == HIGH || int3 == HIGH || int4 == HIGH) {
        sprintf (s, " %d %8lu",      1, t1);          Serial.println (s);
        sprintf (s, " %d %8lu %8lu", 2, t2, t2-t1);   Serial.println (s);
        sprintf (s, " %d %8lu %8lu", 3, t3, t3-t1);   Serial.println (s);
        sprintf (s, " %d %8lu %8lu", 4, t4, t4-t1);   Serial.println (s);

        int1 = int2 = int3 = int4 = LOW;
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (115200);
    delay (1000);
    Serial.println ("ready");

    pinMode (Rx1Pin, INPUT_PULLUP);
    pinMode (Rx2Pin, INPUT_PULLUP);
    pinMode (Rx3Pin, INPUT_PULLUP);
    pinMode (Rx4Pin, INPUT_PULLUP);

    attachInterrupt (digitalPinToInterrupt(Rx1Pin), ISR_Rx1_Receive, RISING);
    attachInterrupt (digitalPinToInterrupt(Rx2Pin), ISR_Rx2_Receive, RISING);
    attachInterrupt (digitalPinToInterrupt(Rx3Pin), ISR_Rx3_Receive, RISING);
    attachInterrupt (digitalPinToInterrupt(Rx4Pin), ISR_Rx4_Receive, RISING);
}

as you are using an ESP32 any reason not to use the 64bit ESP32 timer API ?

e.g. have a look at post manchester-decoding-200hz