Skip to content

Instantly share code, notes, and snippets.

@wolph
Created December 30, 2018 18:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save wolph/0094b278a54f8f472c76b7eb0d0a8f89 to your computer and use it in GitHub Desktop.
Save wolph/0094b278a54f8f472c76b7eb0d0a8f89 to your computer and use it in GitHub Desktop.
Simple arduino HC-SR04 (HCSR04) distance detection using interrupts for low latency measurements
#include <Arduino.h>
// Uses https://github.com/PaulStoffregen/TimerOne for sending on a regular interval
#include <TimerOne.h>
// ECHO pin, needs to be a pin that supports interrupts!
#define ULTRASONIC_PIN_INPUT 2
// TRIG pin, can be any output pin
#define ULTRASONIC_PIN_OUTPUT 3
// update interval, make sure to keep it above 20ms
#define ULTRASONIC_TIMER_US 50000
// the time the pulse was sent
volatile long ultrasonic_echo_start = 0;
// the distance once it's been measured
volatile long ultrasonic_distance = 0;
void ultrasonicPulse(){
// Sets the trigger on HIGH state for 10 micro seconds to send a series of pulses
digitalWrite(ULTRASONIC_PIN_OUTPUT, HIGH);
// blocks 10 microseconds from the interrupt, I think we'll live :)
delayMicroseconds(10);
// disable the sending again so we can wait for a response
digitalWrite(ULTRASONIC_PIN_OUTPUT, LOW);
// record the send time
ultrasonic_echo_start = micros();
}
void ultrasonicEcho(){
// don't do anything if no pulse has been sent
if(ultrasonic_echo_start != 0){
// calculate the distance by measuring how long it took to return the sound
// The speed of sound is 343 m/s and we need half the time it took (since
// the sound has to travel towards the object _AND_ back). So a single echo does
// 1/(343/2) = 0.005831 seconds per meter
ultrasonic_distance = (micros() - ultrasonic_echo_start) / 58;
ultrasonic_echo_start = 0;
}
}
void setup(){
Serial.begin(115200);
// set the echo pin to receive interrupts, on an arduino uno only pin 2 and 3 can link to interrupts
pinMode(ULTRASONIC_PIN_INPUT, INPUT_PULLUP);
pinMode(ULTRASONIC_PIN_OUTPUT, OUTPUT);
// set the update interval for sending the trigger
Timer1.initialize(ULTRASONIC_TIMER_US);
// link the trigger function to the timer
Timer1.attachInterrupt(ultrasonicPulse);
// link the echo function to the echo pin
attachInterrupt(digitalPinToInterrupt(ULTRASONIC_PIN_INPUT), ultrasonicEcho, RISING);
}
void loop() {
Serial.print("Distance: ");
Serial.println(ultrasonic_distance);
delay(ULTRASONIC_TIMER_US / 1000);
}
@wolph
Copy link
Author

wolph commented Nov 30, 2020

I've tried it with this thing and it seemed to work fine: https://wiki.keyestudio.com/Ks0192_keyestudio_4WD_Bluetooth_Multi-functional_Car

I'm not ruling out that there are bugs though... but it seemed to work perfect for a distance range between ~20cm and 1.5m if I remember correctly.

@dcmid
Copy link

dcmid commented Dec 3, 2020

Strangely, I get a constant 8cm distance when using this code. I ended up on this page because I had written my own version and was getting the 8cm issue. Our programs are nearly identical, so I copied yours to see if it worked better. Everything works perfectly fine if I don't use the pin interrupt and instead just use pulseIn during the timer interrupt, but of course that needlessly ties things up. I'll do some more digging tomorrow. Just figured I would mention it in case anybody has a similar problem in the future.

@greenonline
Copy link

For good practice, add these three lines to the start of ultrasonicPulse() to clear the trigger pin.

    // Clears the trigger Pin
    digitalWrite(ULTRASONIC_PIN_OUTPUT, LOW);
    delayMicroseconds(2);

like so

void ultrasonicPulse(){
    // Clears the trigger Pin
    digitalWrite(ULTRASONIC_PIN_OUTPUT, LOW);
    delayMicroseconds(2);
    // Sets the trigger on HIGH state for 10 micro seconds to send a series of pulses
    digitalWrite(ULTRASONIC_PIN_OUTPUT, HIGH);
    // blocks 10 microseconds from the interrupt, I think we'll live :)
    delayMicroseconds(10);
    // disable the sending again so we can wait for a response
    digitalWrite(ULTRASONIC_PIN_OUTPUT, LOW);
    // record the send time
    ultrasonic_echo_start = micros();
}

Also, the echo is a falling edge (usually), unless the signal has been inverted, so

    attachInterrupt(digitalPinToInterrupt(ULTRASONIC_PIN_INPUT), ultrasonicEcho, RISING);

should be

    attachInterrupt(digitalPinToInterrupt(ULTRASONIC_PIN_INPUT), ultrasonicEcho, FALLING);

One other thing is that there is an assumption made in your code that the echo pin has risen HIGH, when you execute this line:

    ultrasonic_echo_start = micros();

Logically, it should have but it might not be the case.

@ingtommi
Copy link

Aiming to code the same functionality I found this conversation and I thank you for the examples.

I'd like to share my version of the code, where I don't use a timer for the trigger (maybe should be a good pratice...) but I try to improve the accuracy considering only the time between the two echo edges (anyway, the same goal could have been reached by compensating the offset).

#include <TimerInterrupt_Generic.h>

const int trigPin = 4;
const int echoPin = 3; //3x 3.3kOhm used to divide voltage
volatile bool echoFallen = false;
volatile unsigned long echoStart, echoEnd;
unsigned long duration;
unsigned int distance;

void setup(){
    Serial.begin(9600);
    pinMode(echoPin, INPUT);
    pinMode(trigPin, OUTPUT);
    attachInterrupt(digitalPinToInterrupt(echoPin), ultrasonicEcho, CHANGE);
}

void loop() {
  //send trigger
  ultrasonicTrig();
  //wait for falling edge
  if(echoFallen){
    duration = echoEnd - echoStart;
    //s = v*t, sound speed is 343 m/s, wave reaches object and comes back
    distance = .0343*(duration/2);
    //unset falling edge flag
    echoFallen = false;
  }
  //output
  Serial.print("Distance (cm): ");
  Serial.println(distance);
  delay(1000);
}

//trigger
void ultrasonicTrig(){
    //clear pin
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    //send pulse
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
}

//ISR
void ultrasonicEcho(){
  switch(digitalRead(echoPin)){
    case HIGH:
      echoStart = micros();
      break;
    case LOW:
      echoEnd = micros();
      //set falling edge flag
      echoFallen = true;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment