3

I have an HC-SR04 sensor and I need to get the distance from it. I use this code:

digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
duration=pulseIn(echo, HIGH, 24000L);
cm=duration/2/29.1;

Pulsein takes too much time and I would like to use interrupts to prevent code from being blocked. From the beginning, I had no idea how to do it. I did not want to ask this question without trying on my own so here is my miserable attempt:

 while(digitalRead(echo)==LOW){
 }
if(digitalRead(echo)==HIGH){
      lowTimer=millis();
}
while(digitalRead(echo)==HIGH){

}

if(digitalRead(echo)==LOW){
   duration=millis()-lowTimer;
}

Obviously, this does not work. I don't know if I am on the right track, but I do know that even if this worked, it would still block my code for a long time. Can someone help me, point me in the right direction? Please don't just write it for me - I want to learn. I would like someone to point me to the right direction till I get it. Thank you!

EDIT:

I tried on of the suggestions:

unsigned long duration;
int cm;
const int echo = 24;
const int trig = 22;
bool doPing=true;

void setup()
{
  pinMode(echo, INPUT);
  pinMode(trig, OUTPUT);
  Serial.begin(9600);
}

void loop()
{
    if(doPing){
        ping();
        doPing=false;
    }
    read_pulse();
    if(duration!=0){
        cm=duration/29.1/2;
        Serial.println("cm: "+cm);
        doPing=true;
    }
    delay(2);
}
void ping(){
  digitalWrite(trig, LOW);
  delayMicroseconds(2);
  digitalWrite(trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(trig, LOW);
}

void read_pulse()
{
    static unsigned long rising_time;  // time of the rising edge
    static int last_state;             // previous pin state
    int state = digitalRead(echo);      // current pin state
    duration = 0; // default return value

    // On rising edge: record current time.
    if (last_state == LOW && state == HIGH) {
        rising_time = micros();
    }

    // On falling edge: report pulse length.
    if (last_state == HIGH && state == LOW) {
        unsigned long falling_time = micros();
        duration = falling_time - rising_time;
    }

    last_state = state;
}

This does not print anything to my serial monitor. Is there something I am missing?

5
  • This page explains Arduino interrupts really well: Interrupts by Nick Gammon also see this: answer on StackOverflow
    – Chris
    Aug 29, 2016 at 2:13
  • Start by studying NewPing; bitbucket.org/teckel12/arduino-new-ping/downloads. Aug 29, 2016 at 8:30
  • Note that you can remove both if-statements. Since the while loop before it will only exit, if the condition inside the if-statement is true. Also work on proper indentation. It makes your code a lot easier to read.
    – Gerben
    Aug 29, 2016 at 14:26
  • 1
    First, remove the delay() from your code. The whole purpose of all this is to make the thing non-blocking, and you are completely defeating this with delay(): Not only will you have a terrible resolution of only 2 ms, you may miss the pulse altogether. Second, your pin assignments look dubious: you tagged the question “arduino-uno”, but there are no pins 22 nor 24 on the Uno. Sep 6, 2016 at 7:48
  • @EdgarBonet I removed the delay and it works! I don't think it makes a difference on which board I tried it. I changed the tag know to arduino mega. Since the speed of sound is 340m/s, if an object is one meter away, it will take 2.9ms for the pulse to arrive. Therefore 2ms delay was indeed a terrible idea.
    – shurup
    Sep 7, 2016 at 0:09

1 Answer 1

3

You do not want the reading of the incoming pulse to block your program execution. This is the very key requirement: you want a non-blocking pulse reading. You can write a non-blocking replacement for pulseIn(), but you will have to use it differently. You cannot expect a non-blocking pulseIn() to return the pulse length whenever you call it: instead, it would return that value only when you call it right after the pulse falling edge. On all other circumstances it will return a dummy value meaning “the pulse is not finished yet, thus I cannot give you its length right now, please call me later”.

Below is an example of a function implementing this approach. You have to call it on every loop iteration: the more often you call it, the better resolution you will have. This function uses 0 as the dummy value meaning “no answer available right now”:

/*
 * Non-blocking pulseIn(): returns the pulse length in microseconds
 * when the falling edge is detected. Otherwise returns 0.
 */
unsigned long read_pulse(int pin)
{
    static unsigned long rising_time;  // time of the rising edge
    static int last_state;             // previous pin state
    int state = digitalRead(pin);      // current pin state
    unsigned long pulse_length = 0;    // default return value

    // On rising edge: record current time.
    if (last_state == LOW && state == HIGH) {
        rising_time = micros();
    }

    // On falling edge: report pulse length.
    if (last_state == HIGH && state == LOW) {
        unsigned long falling_time = micros();
        pulse_length = falling_time - rising_time;
    }

    last_state = state;
    return pulse_length;
}

A better approach, but harder to implement for a novice, is to use interrupts. Chris gave you a pointer to Nick Gammon’s tutorial on interrupts. This is definitely a good reading. But you will also have to dig into the data sheet of the ATmega328P microcontroller. It's a little bit daunting, but a must read if you go into low-level programming. If you follow this path, I suggest you look at the “input capture” capability of Timer 1 (chapter 20, section 20.9). This is a feature of the timer that can have it automatically save a timestamp whenever some external pin changes state. You then use the input capture interrupt to alert the program when an edge has been detected, and your interrupt service routine can read the automatically saved timestamp. Using this approach, you can have extremely accurate pulse length measurements, independent on how fast your program goes through its loop().

4
  • Sorry for not checking out your answer in a while. I tried using the function you wrote in my code and the sensor gave back no values. I posted the code above in my question. Can you please take a look at it? Am I calling it at a wrong time?
    – shurup
    Sep 6, 2016 at 0:50
  • @Edgar Is there any close example for us to try the input capture ?
    – Shahabaz
    Jan 3, 2020 at 5:31
  • @Shahabaz: I have no example at hand, but you can do a search for “input capture Arduino” or “input capture AVR”. Jan 3, 2020 at 15:09
  • I posted a question and got answer as well. If incase anybody is looking for it arduino.stackexchange.com/questions/71434/…
    – Shahabaz
    Jan 4, 2020 at 8:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.