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?
1 Answer 1
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()
.
-
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– shurup2016年09月06日 00:50:33 +00:00Commented Sep 6, 2016 at 0:50
-
@Edgar Is there any close example for us to try the input capture ?Shahabaz– Shahabaz2020年01月03日 05:31:13 +00:00Commented 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".Edgar Bonet– Edgar Bonet2020年01月03日 15:09:30 +00:00Commented 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– Shahabaz2020年01月04日 08:33:45 +00:00Commented Jan 4, 2020 at 8:33
Explore related questions
See similar questions with these tags.
delay()
from your code. The whole purpose of all this is to make the thing non-blocking, and you are completely defeating this withdelay()
: 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.