Trying to record microseconds on an ESP32S3 using interrupts

Hello everyone, I am still new to coding ESP32 so please be kind on my mistakes. I am trying to calculate the angle of arrival of a sonar signal using the time difference of when two receivers receive the signal. Below is a diagram and a picture of my set up. The distance between A and B is 13cm. A is connected to pin 4, B is connected to pin 5. I am using the raw signal from the OPAMP of the HCSR04 into a comparator (which also converts it into 3.3v) into the ESP32

and this is the circuit (each receiver)

Now here is the problem. At 13 centimeters, the maximum time difference i should get is 379us. But when the transmitter is parallel to the receivers, i get anywhere from 420-600us. And when i hold it and the middle of the receivers, the time difference is ±1us which is expected. This leads me to believe the circuitry is good but my code is faulty. Here is the code in question.

volatile unsigned long timestamp1 = 0;  // Timestamp for pin 4
volatile unsigned long timestamp2 = 0;  // Timestamp for pin 5
volatile unsigned long lastInterruptTime1 = 0;  // Last interrupt time for pin 4
volatile unsigned long lastInterruptTime2 = 0;  // Last interrupt time for pin 5
int ignoreTime = 10000;  // how long to ingnore subsequent pings in microseconds
volatile bool flag1 = false;  // Flag for pin 4
volatile bool flag2 = false;  // Flag for pin 5

// ISR for pin 4
void IRAM_ATTR reciever1() {
    unsigned long currentTime = esp_timer_get_time();
    if (currentTime - lastInterruptTime1 >= ignoreTime) {
        timestamp1 = currentTime;
        flag1 = true;
        lastInterruptTime1 = currentTime;
    }
}

// ISR for pin 5
void IRAM_ATTR reciever2() {
    unsigned long currentTime = esp_timer_get_time();
    if (currentTime - lastInterruptTime2 >= ignoreTime) {
        timestamp2 = currentTime;
        flag2 = true;
        lastInterruptTime2 = currentTime;
    }
}

void setup() {
    Serial.begin(115200);  // Start serial communication
    pinMode(4, INPUT_PULLUP);  // Set pin 4 as input with internal pull-up resistor
    pinMode(5, INPUT_PULLUP);  // Set pin 5 as input with internal pull-up resistor
    attachInterrupt(digitalPinToInterrupt(4), reciever1, RISING);  // Attach interrupt on pin 4
    attachInterrupt(digitalPinToInterrupt(5), reciever2, RISING);  // Attach interrupt on pin 5
}

void loop() {
    if (flag1 && flag2) {  // If both interrupts have triggered
        long timeDifference = timestamp2 - timestamp1;
        float timeDiffSec = timeDifference / 1e6;  // Convert to seconds
        float ratio = (343 * timeDiffSec) / 0.13;
        ratio = constrain(ratio, -1.0, 1.0);  // Ensure it stays between -1 and 1
        float angleArrival = asin(ratio) * (180.0 / PI);  // Convert to degrees

        Serial.print("Time difference: ");
        Serial.print(timeDifference);
        Serial.print(" us  Angle: ");
        Serial.print(angleArrival);
        Serial.println("°");

        flag1 = false;
        flag2 = false;
    }
}

If anyone has any advice on this, please share. I dont know what im doing wrong.

Using the data from the interrupts inside the loop would require a critical section

may be something like this

volatile unsigned long timestamp1 = 0;
volatile unsigned long timestamp2 = 0;
volatile bool flag1 = false;
volatile bool flag2 = false;
constexpr unsigned long ignoreTime = 10000;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR receiver1() {
  static unsigned long lastInterruptTime = 0;
  unsigned long currentTime = micros();
  if (currentTime - lastInterruptTime >= ignoreTime) {
    portENTER_CRITICAL_ISR(&mux);
    timestamp1 = currentTime;
    flag1 = true;
    lastInterruptTime = currentTime;
    portEXIT_CRITICAL_ISR(&mux);
  }
}

void IRAM_ATTR receiver2() {
  static unsigned long lastInterruptTime = 0;
  unsigned long currentTime = micros();
  if (currentTime - lastInterruptTime >= ignoreTime) {
    portENTER_CRITICAL_ISR(&mux);
    timestamp2 = currentTime;
    flag2 = true;
    lastInterruptTime = currentTime;
    portEXIT_CRITICAL_ISR(&mux);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(4), receiver1, RISING);
  attachInterrupt(digitalPinToInterrupt(5), receiver2, RISING);
}

void loop() {

  unsigned long t1, t2;
  bool f1, f2;

  portENTER_CRITICAL(&mux);
  f1 = flag1;
  f2 = flag2;
  portEXIT_CRITICAL(&mux);

  if (f1 && f2) {
    portENTER_CRITICAL(&mux);
    t1 = timestamp1;
    t2 = timestamp2;
    flag1 = false;
    flag2 = false;
    portEXIT_CRITICAL(&mux);
    long timeDifference = t2 - t1;
    float timeDiffSec = timeDifference / 1e6;
    float ratio = (343 * timeDiffSec) / 0.13;
    ratio = constrain(ratio, -1.0, 1.0);
    float angleArrival = asin(ratio) * (180.0 / PI);


    Serial.print("Time difference: ");
    Serial.print(timeDifference);
    Serial.print(" us  Angle: ");
    Serial.print(angleArrival);
    Serial.println("°");
  }

}

micros() is faster than calling esp_timer_get_time (32 bits instead of 64 bits) and would not matter much since you only handle a difference.

Also t2 - t1 suggests that t2 is always larger than t1 which might not be the case depending which sensor got the signal first and in that case you'll get a huge number from the subtraction . You might need to take that into account

Indeed, the t2-t1 thing did cause large negative numbers often but i figured ill fix this problem first. so thanks for that. But the problem still remains. This is the code i used

volatile unsigned long timestamp1 = 0;
volatile unsigned long timestamp2 = 0;
volatile bool flag1 = false;
volatile bool flag2 = false;
constexpr unsigned long ignoreTime = 10000;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR receiver1() {
  static unsigned long lastInterruptTime = 0;
  unsigned long currentTime = micros();
  if (currentTime - lastInterruptTime >= ignoreTime) {
    portENTER_CRITICAL_ISR(&mux);
    timestamp1 = currentTime;
    flag1 = true;
    lastInterruptTime = currentTime;
    portEXIT_CRITICAL_ISR(&mux);
  }
}

void IRAM_ATTR receiver2() {
  static unsigned long lastInterruptTime = 0;
  unsigned long currentTime = micros();
  if (currentTime - lastInterruptTime >= ignoreTime) {
    portENTER_CRITICAL_ISR(&mux);
    timestamp2 = currentTime;
    flag2 = true;
    lastInterruptTime = currentTime;
    portEXIT_CRITICAL_ISR(&mux);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(4), receiver1, RISING);
  attachInterrupt(digitalPinToInterrupt(5), receiver2, RISING);
}

void loop() {

  unsigned long t1, t2;
  long timeDifference;
  bool f1, f2;

  portENTER_CRITICAL(&mux);
  f1 = flag1;
  f2 = flag2;
  portEXIT_CRITICAL(&mux);

  if (f1 && f2) {
    portENTER_CRITICAL(&mux);
    t1 = timestamp1;
    t2 = timestamp2;
    flag1 = false;
    flag2 = false;
    portEXIT_CRITICAL(&mux);
    if (t1 > t2) {
      timeDifference = t1 - t2;
    }
    if (t2 > t1){
      timeDifference = t2 - t1;
    }
    float timeDiffSec = timeDifference / 1e6;
    float ratio = (343 * timeDiffSec) / 0.13;
    ratio = constrain(ratio, -1.0, 1.0);
    float angleArrival = asin(ratio) * (180.0 / PI);


    Serial.print("Time difference: ");
    Serial.print(timeDifference);
    Serial.print(" us  Angle: ");
    Serial.print(angleArrival);
    Serial.println("°");
  }

}

Im still getting differences larger than the theoretical after 70 degrees or so

The HC-SR04 sensor works best between 2cm – 400 cm (1" - 13ft) within a 30 degree cone, and is accurate to the nearest 0.3cm.

From

I.e., 15 degrees each side of a straight line perp to the board

1 Like

What is the purpose of this op-amp?

If you are attempting to convert the 5V pulse from the sensor to 3.3V, I do not think it will work. The op-amp is powered with 5V and will output 5V and could damage the ESP. The pull-up resistor won't fix that. Anyway, no op-amp is necessary to convert to a 3.3V signal. Just a voltage divider.

As pointed out above, the sensors have narrow 30° beams, so anything more than 15° away from the centre line is unlikely to be reliably detected. Is it possible what is being picked up is an echo from some other object which is further away? That could explain why the difference is more than expected.


The pulse you are talking about here is the yellow trace. The reason i cant use the echo signal from the sensor is because it only sends out one after the sensor has fired. That means a signal is out in the air, that will interfere. Thus i solder a wire directly to the sensors opamp. And that means i deal with the bottom trace now

The opamp signal goes into a comparator to digitize and also set the voltage level to 3.3v (see the pullup resistor.

On the Atmega328P, you would use the input capture register to capture the current timer count when a certain pin changes state. That state change could also trigger an interrupt which would save that value so the next transition could then be captured. But you might need some logic chips to use the same pin for two sources.

Does the ESP32 have such a function? Google says:

Yes, the ESP32 does have an input capture register functionality, which is accessible through the "MCPWM" peripheral, allowing it to detect edge transitions on a signal and measure the time between them, effectively acting like an input capture mechanism.

As far as i understand from reading the datasheet for this transducer (TCT-40 T/R). This only counts for the transmitter. So I would need to ensure both the receivers fall under the 30 degree cone? Ill test this in a bit and will let you all know

Interesting. Again, sorry for my ignorance about ESP32 hardware but I will try to implement this and let you know

:confused_face:

Please explain.

The echo pin cannot be used for passive listening like i am here. It is meant to give out a signal proportional to how long the transmitted signal took to came back.

For my 'passive listening' i am instead using the 'raw signal' directly from the last stage of the TL074 chip on the sensor

Have you checked with your scope to see if the time difference between those two signals at the two digital inputs to the ESP32 corresponds to the values your code is producing? I'm just thinking that would indentify whether there's something wrong with your code, or something wrong (or at least unexpected) with your hardware.

Ok, I understand a little better now. The third sensor (not labelled, let's call it "C") is sending out the pulse, and sensors A and B detect it. Sensor C isn't used to receive anything. Sensors A and B don't get triggered by the Arduino, so don't send any pulses, but they receive the pulses from C.

This part I still don't get. I don't think it will do that. Does the op-amp used have an "open-collector" output?

Another thing that struck me. How are you allocating the 2x ISRs to the ESP32's 2 CPU cores? If they both run in the same core, they can't execute simultaneously as they would need to when the signal is directly ahead.

1 Like
  1. yes, the LM311 is indeed an open collector.

  2. That hadnt struck me actually. This explains why i could never get the difference to come out at 0, always plus-minus 1. This seems to be the reason why. Id like to use the other core for wifi and whatnot still so i think i can live with that 1 microsecond.

1 Like

If they are the TCT40-16t/r...

(from this)

...(which is unfortunately unitless) then you have about 30 to 40 degrees each side before the signal dramatically falls off.

micros() on esp32 calls esp_timer_get_time()...
You could try esp_cpu_get_cycle_count() for even higher resolution (don't forget to convert back to microseconds)

Interesting. Your own take on Loran? :slight_smile:

I meant that the maths later is done using 32 bit (ie native math) rather than 64 bits maths