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?

shurup
  • 300
  • 1
  • 8
  • 21

1 Answers1

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().

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81